Merge "citrix" branch into "master".
authorBen Pfaff <blp@nicira.com>
Thu, 25 Mar 2010 19:30:05 +0000 (12:30 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 25 Mar 2010 19:30:05 +0000 (12:30 -0700)
This merge is long overdue, simply because I forgot that there were
outstanding changes on "citrix" that had not yet been merged.

The important fix here is the addition of mlockall.  This fixes some
bugs seen under stressful conditions in XenServer.

475 files changed:
.gitignore
COPYING
ChangeLog
CodingStyle
INSTALL.Linux
INSTALL.OpenFlow [new file with mode: 0644]
INSTALL.SSL
INSTALL.XenServer
INSTALL.bridge [new file with mode: 0644]
INSTALL.userspace [new file with mode: 0644]
Makefile.am
README
README-gcov [new file with mode: 0644]
REPORTING-BUGS [new file with mode: 0644]
SubmittingPatches [new file with mode: 0644]
acinclude.m4
build-aux/.gitignore [new file with mode: 0644]
build-aux/check-structs [new file with mode: 0755]
configure.ac
datapath/actions.c
datapath/brc_procfs.c
datapath/brcompat.c
datapath/datapath.c
datapath/datapath.h
datapath/dp_dev.c
datapath/dp_notify.c
datapath/dp_sysfs.h
datapath/dp_sysfs_dp.c
datapath/dp_sysfs_if.c
datapath/flow.c
datapath/flow.h
datapath/linux-2.6/.gitignore
datapath/linux-2.6/Makefile.main.in
datapath/linux-2.6/Modules.mk
datapath/linux-2.6/compat-2.6/addrconf_core-ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/dev-ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/dev-openvswitch.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/if.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/if_ether.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/in.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/kobject.h
datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
datapath/linux-2.6/compat-2.6/include/linux/skbuff.h
datapath/linux-2.6/compat-2.6/include/linux/stddef.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/types.h
datapath/linux-2.6/compat-2.6/include/net/dst.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/ip.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/ipip.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/net_namespace.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/netlink.h
datapath/linux-2.6/compat-2.6/include/net/netns/generic.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/route.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/ip_output-ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/veth.c
debian/automake.mk
debian/changelog
debian/commands/update [deleted file]
debian/control
debian/control.modules.in
debian/corekeeper.init
debian/openvswitch-common.install
debian/openvswitch-common.manpages
debian/openvswitch-controller.README.Debian
debian/openvswitch-controller.init
debian/openvswitch-datapath-source.install
debian/openvswitch-monitor.default
debian/openvswitch-monitor.init
debian/openvswitch-switch-config.templates
debian/openvswitch-switch.init
debian/openvswitch-switch.install
debian/openvswitch-switch.logrotate
debian/openvswitch-switch.manpages
debian/openvswitch-switch.postinst
debian/openvswitch-switch.postrm
debian/openvswitch-switch.template
debian/openvswitch-switchui.default
debian/openvswitch-switchui.init
debian/openvswitch-switchui.install
debian/openvswitch-wdt.init
debian/ovs-switch-setup.8
debian/po/templates.pot
debian/reconfigure [moved from debian/commands/reconfigure with 100% similarity]
debian/rules
debian/rules.modules [new file with mode: 0755]
extras/ezio/automake.mk
extras/ezio/ezio-term.c
extras/ezio/ovs-switchui.c
extras/ezio/terminal.c
extras/ezio/tty.c
include/openflow/.gitignore [new file with mode: 0644]
include/openflow/automake.mk
include/openflow/nicira-ext.h
include/openflow/openflow-mgmt.h [deleted file]
include/openflow/openflow.h
include/openvswitch/automake.mk
include/openvswitch/datapath-protocol.h
include/openvswitch/gre.h [new file with mode: 0644]
lib/.gitignore
lib/aes128.c [new file with mode: 0644]
lib/aes128.h [new file with mode: 0644]
lib/automake.mk
lib/backtrace.c
lib/bitmap.c
lib/bitmap.h
lib/byteq.c [moved from extras/ezio/byteq.c with 81% similarity]
lib/byteq.h [moved from extras/ezio/byteq.h with 83% similarity]
lib/cfg.c [deleted file]
lib/cfg.h [deleted file]
lib/classifier.c
lib/classifier.h
lib/command-line.c
lib/command-line.h
lib/common-syn.man [new file with mode: 0644]
lib/common.man
lib/compiler.h
lib/coverage.c
lib/csum.c
lib/daemon-syn.man [new file with mode: 0644]
lib/daemon.c
lib/daemon.h
lib/daemon.man
lib/dhcp-client.c
lib/dhcp.c
lib/dpif-linux.c [new file with mode: 0644]
lib/dpif-netdev.c [new file with mode: 0644]
lib/dpif-provider.h [new file with mode: 0644]
lib/dpif.c
lib/dpif.h
lib/dpif.man
lib/dynamic-string.c
lib/dynamic-string.h
lib/fatal-signal.c
lib/fatal-signal.h
lib/fault.c [deleted file]
lib/flow.c
lib/flow.h
lib/hash.c
lib/hash.h
lib/hmap.c
lib/hmap.h
lib/json.c [new file with mode: 0644]
lib/json.h [new file with mode: 0644]
lib/jsonrpc.c [new file with mode: 0644]
lib/jsonrpc.h [new file with mode: 0644]
lib/leak-checker.c
lib/learning-switch.c
lib/learning-switch.h
lib/list.h
lib/lockfile.c [new file with mode: 0644]
lib/lockfile.h [moved from vswitchd/mgmt.h with 64% similarity]
lib/mac-learning.c
lib/mac-learning.h
lib/netdev-linux.c [new file with mode: 0644]
lib/netdev-provider.h [new file with mode: 0644]
lib/netdev.c
lib/netdev.h
lib/odp-util.c
lib/ofp-print.c
lib/ofpbuf.c
lib/ofpbuf.h
lib/ovsdb-data.c [new file with mode: 0644]
lib/ovsdb-data.h [new file with mode: 0644]
lib/ovsdb-error.c [new file with mode: 0644]
lib/ovsdb-error.h [new file with mode: 0644]
lib/ovsdb-idl-provider.h [new file with mode: 0644]
lib/ovsdb-idl.c [new file with mode: 0644]
lib/ovsdb-idl.h [new file with mode: 0644]
lib/ovsdb-parser.c [new file with mode: 0644]
lib/ovsdb-parser.h [new file with mode: 0644]
lib/ovsdb-types.c [new file with mode: 0644]
lib/ovsdb-types.h [new file with mode: 0644]
lib/packets.c
lib/packets.h
lib/pcap.c
lib/poll-loop.c
lib/poll-loop.h
lib/process.c
lib/queue.c
lib/queue.h
lib/rconn.c
lib/rconn.h
lib/reconnect.c [new file with mode: 0644]
lib/reconnect.h [new file with mode: 0644]
lib/rtnetlink.c [new file with mode: 0644]
lib/rtnetlink.h [new file with mode: 0644]
lib/sflow.h [new file with mode: 0644]
lib/sflow_agent.c [new file with mode: 0644]
lib/sflow_api.h [new file with mode: 0644]
lib/sflow_poller.c [new file with mode: 0644]
lib/sflow_receiver.c [new file with mode: 0644]
lib/sflow_sampler.c [new file with mode: 0644]
lib/sha1.c
lib/sha1.h
lib/shash.c
lib/shash.h
lib/signals.c
lib/socket-util.c
lib/socket-util.h
lib/sort.c [new file with mode: 0644]
lib/sort.h [moved from vswitchd/port.h with 72% similarity]
lib/ssl-bootstrap-syn.man [new file with mode: 0644]
lib/ssl-bootstrap.man [new file with mode: 0644]
lib/ssl-peer-ca-cert.man [new file with mode: 0644]
lib/ssl-syn.man [new file with mode: 0644]
lib/ssl.man [new file with mode: 0644]
lib/stp.c
lib/stream-fd.c [new file with mode: 0644]
lib/stream-fd.h [moved from lib/vconn-stream.h with 58% similarity]
lib/stream-provider.h [new file with mode: 0644]
lib/stream-ssl.c [moved from lib/vconn-ssl.c with 70% similarity]
lib/stream-ssl.h [moved from lib/vconn-ssl.h with 54% similarity]
lib/stream-tcp.c [new file with mode: 0644]
lib/stream-unix.c [moved from lib/vconn-unix.c with 66% similarity]
lib/stream.c [new file with mode: 0644]
lib/stream.h [new file with mode: 0644]
lib/svec.c
lib/svec.h
lib/timeval.c
lib/timeval.h
lib/unicode.c [new file with mode: 0644]
lib/unicode.h [new file with mode: 0644]
lib/unixctl.c
lib/unixctl.h
lib/util.c
lib/util.h
lib/uuid.c [new file with mode: 0644]
lib/uuid.h [new file with mode: 0644]
lib/vconn-active.man [new file with mode: 0644]
lib/vconn-passive.man [new file with mode: 0644]
lib/vconn-provider.h
lib/vconn-stream.c
lib/vconn-tcp.c [deleted file]
lib/vconn.c
lib/vconn.h
lib/vlog-modules.def
lib/vlog-syn.man [new file with mode: 0644]
lib/vlog-unixctl.man
lib/vlog.c
lib/vlog.h
lib/vlog.man
m4/openvswitch.m4
ofproto/.gitignore [moved from secchan/.gitignore with 53% similarity]
ofproto/automake.mk [new file with mode: 0644]
ofproto/collectors.c [new file with mode: 0644]
ofproto/collectors.h [moved from vswitchd/ovs-vswitchd.h with 55% similarity]
ofproto/discovery.c [moved from secchan/discovery.c with 95% similarity]
ofproto/discovery.h [moved from secchan/discovery.h with 100% similarity]
ofproto/fail-open.c [moved from secchan/fail-open.c with 95% similarity]
ofproto/fail-open.h [moved from secchan/fail-open.h with 100% similarity]
ofproto/in-band.c [moved from secchan/in-band.c with 90% similarity]
ofproto/in-band.h [moved from secchan/in-band.h with 88% similarity]
ofproto/netflow.c [moved from secchan/netflow.c with 75% similarity]
ofproto/netflow.h [moved from secchan/netflow.h with 94% similarity]
ofproto/ofproto-sflow.c [new file with mode: 0644]
ofproto/ofproto-sflow.h [new file with mode: 0644]
ofproto/ofproto.c [moved from secchan/ofproto.c with 84% similarity]
ofproto/ofproto.h [moved from secchan/ofproto.h with 81% similarity]
ofproto/pinsched.c [moved from secchan/pinsched.c with 98% similarity]
ofproto/pinsched.h [moved from secchan/pinsched.h with 100% similarity]
ofproto/pktbuf.c [moved from secchan/pktbuf.c with 93% similarity]
ofproto/pktbuf.h [moved from secchan/pktbuf.h with 100% similarity]
ofproto/status.c [moved from secchan/status.c with 95% similarity]
ofproto/status.h [moved from secchan/status.h with 98% similarity]
ovsdb/.gitignore [new file with mode: 0644]
ovsdb/OVSDB.py [new file with mode: 0644]
ovsdb/SPECS [new file with mode: 0644]
ovsdb/automake.mk [new file with mode: 0644]
ovsdb/column.c [new file with mode: 0644]
ovsdb/column.h [new file with mode: 0644]
ovsdb/condition.c [new file with mode: 0644]
ovsdb/condition.h [new file with mode: 0644]
ovsdb/execution.c [new file with mode: 0644]
ovsdb/file.c [new file with mode: 0644]
ovsdb/file.h [new file with mode: 0644]
ovsdb/jsonrpc-server.c [new file with mode: 0644]
ovsdb/jsonrpc-server.h [new file with mode: 0644]
ovsdb/log.c [new file with mode: 0644]
ovsdb/log.h [new file with mode: 0644]
ovsdb/mutation.c [new file with mode: 0644]
ovsdb/mutation.h [new file with mode: 0644]
ovsdb/ovsdb-client.1.in [new file with mode: 0644]
ovsdb/ovsdb-client.c [new file with mode: 0644]
ovsdb/ovsdb-doc.in [new file with mode: 0755]
ovsdb/ovsdb-idlc.1 [new file with mode: 0644]
ovsdb/ovsdb-idlc.in [new file with mode: 0755]
ovsdb/ovsdb-server.1.in [new file with mode: 0644]
ovsdb/ovsdb-server.c [new file with mode: 0644]
ovsdb/ovsdb-tool.1.in [new file with mode: 0644]
ovsdb/ovsdb-tool.c [new file with mode: 0644]
ovsdb/ovsdb.c [new file with mode: 0644]
ovsdb/ovsdb.h [new file with mode: 0644]
ovsdb/query.c [new file with mode: 0644]
ovsdb/query.h [new file with mode: 0644]
ovsdb/remote-active.man [new file with mode: 0644]
ovsdb/remote-passive.man [new file with mode: 0644]
ovsdb/row.c [new file with mode: 0644]
ovsdb/row.h [new file with mode: 0644]
ovsdb/simplejson/__init__.py [new file with mode: 0644]
ovsdb/simplejson/_speedups.c [new file with mode: 0644]
ovsdb/simplejson/decoder.py [new file with mode: 0644]
ovsdb/simplejson/encoder.py [new file with mode: 0644]
ovsdb/simplejson/scanner.py [new file with mode: 0644]
ovsdb/simplejson/tests/__init__.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_check_circular.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_decode.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_default.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_dump.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_encode_basestring_ascii.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_fail.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_float.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_indent.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass1.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass2.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass3.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_recursion.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_scanstring.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_separators.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_unicode.py [new file with mode: 0644]
ovsdb/simplejson/tool.py [new file with mode: 0644]
ovsdb/table.c [new file with mode: 0644]
ovsdb/table.h [new file with mode: 0644]
ovsdb/transaction.c [new file with mode: 0644]
ovsdb/transaction.h [new file with mode: 0644]
ovsdb/trigger.c [new file with mode: 0644]
ovsdb/trigger.h [new file with mode: 0644]
secchan/automake.mk [deleted file]
secchan/commands/automake.mk [deleted file]
secchan/commands/reboot [deleted file]
secchan/executer.c [deleted file]
secchan/executer.h [deleted file]
tests/.gitignore
tests/aes128.at [new file with mode: 0644]
tests/atlocal.in [new file with mode: 0644]
tests/automake.mk
tests/check-structs.at [new file with mode: 0644]
tests/daemon.at [new file with mode: 0644]
tests/dir_name.at [new file with mode: 0644]
tests/flowgen.pl
tests/idltest.ann [new file with mode: 0644]
tests/idltest.ovsschema [new file with mode: 0644]
tests/interface-reconfigure.at [new file with mode: 0644]
tests/json.at [new file with mode: 0644]
tests/jsonrpc.at [new file with mode: 0644]
tests/lcov-wrapper.in [new file with mode: 0755]
tests/library.at [new file with mode: 0644]
tests/lockfile.at [new file with mode: 0644]
tests/openssl.supp [new file with mode: 0644]
tests/ovs-vsctl.at [new file with mode: 0644]
tests/ovsdb-column.at [new file with mode: 0644]
tests/ovsdb-condition.at [new file with mode: 0644]
tests/ovsdb-data.at [new file with mode: 0644]
tests/ovsdb-execution.at [new file with mode: 0644]
tests/ovsdb-idl.at [new file with mode: 0644]
tests/ovsdb-log.at [new file with mode: 0644]
tests/ovsdb-macros.at [new file with mode: 0644]
tests/ovsdb-monitor-sort.pl [new file with mode: 0755]
tests/ovsdb-monitor.at [new file with mode: 0644]
tests/ovsdb-mutation.at [new file with mode: 0644]
tests/ovsdb-query.at [new file with mode: 0644]
tests/ovsdb-row.at [new file with mode: 0644]
tests/ovsdb-schema.at [new file with mode: 0644]
tests/ovsdb-server.at [new file with mode: 0644]
tests/ovsdb-table.at [new file with mode: 0644]
tests/ovsdb-tool.at [new file with mode: 0644]
tests/ovsdb-transaction.at [new file with mode: 0644]
tests/ovsdb-trigger.at [new file with mode: 0644]
tests/ovsdb-types.at [new file with mode: 0644]
tests/ovsdb.at [new file with mode: 0644]
tests/reconnect.at [new file with mode: 0644]
tests/stp.at [new file with mode: 0644]
tests/test-aes128.c [new file with mode: 0644]
tests/test-classifier.c
tests/test-dhcp-client.c
tests/test-dir_name.c [moved from lib/fault.h with 67% similarity]
tests/test-flows.c
tests/test-flows.sh [deleted file]
tests/test-hmap.c
tests/test-json.c [new file with mode: 0644]
tests/test-jsonrpc.c [new file with mode: 0644]
tests/test-lockfile.c [new file with mode: 0644]
tests/test-ovsdb.c [new file with mode: 0644]
tests/test-reconnect.c [new file with mode: 0644]
tests/test-stp-ieee802.1d-1998 [deleted file]
tests/test-stp-ieee802.1d-2004-fig17.4 [deleted file]
tests/test-stp-ieee802.1d-2004-fig17.6 [deleted file]
tests/test-stp-ieee802.1d-2004-fig17.7 [deleted file]
tests/test-stp-iol-io-1.1 [deleted file]
tests/test-stp-iol-io-1.2 [deleted file]
tests/test-stp-iol-io-1.4 [deleted file]
tests/test-stp-iol-io-1.5 [deleted file]
tests/test-stp-iol-op-1.1 [deleted file]
tests/test-stp-iol-op-1.4 [deleted file]
tests/test-stp-iol-op-3.1 [deleted file]
tests/test-stp-iol-op-3.3 [deleted file]
tests/test-stp-iol-op-3.4 [deleted file]
tests/test-stp.c
tests/test-stp.sh [deleted file]
tests/test-strtok_r.c [new file with mode: 0644]
tests/test-timeval.c [new file with mode: 0644]
tests/test-uuid.c [new file with mode: 0644]
tests/test-vconn.c [new file with mode: 0644]
tests/testpki-cacert.pem [new file with mode: 0644]
tests/testpki-cert.pem [new file with mode: 0644]
tests/testpki-cert2.pem [new file with mode: 0644]
tests/testpki-privkey.pem [new file with mode: 0644]
tests/testpki-privkey2.pem [new file with mode: 0644]
tests/testpki-req.pem [new file with mode: 0644]
tests/testpki-req2.pem [new file with mode: 0644]
tests/testsuite.at [new file with mode: 0644]
tests/timeval.at [new file with mode: 0644]
tests/uuid.at [new file with mode: 0644]
tests/uuidfilt.pl [new file with mode: 0755]
tests/valgrind-wrapper.in [new file with mode: 0755]
tests/vconn.at [new file with mode: 0644]
utilities/.gitignore
utilities/automake.mk
utilities/nlmon.c
utilities/ovs-appctl.8.in
utilities/ovs-appctl.c
utilities/ovs-cfg-mod.8.in [deleted file]
utilities/ovs-cfg-mod.c [deleted file]
utilities/ovs-controller.8.in
utilities/ovs-controller.c
utilities/ovs-discover.8.in
utilities/ovs-discover.c
utilities/ovs-dpctl.8.in
utilities/ovs-dpctl.c
utilities/ovs-monitor
utilities/ovs-ofctl.8.in
utilities/ovs-ofctl.c
utilities/ovs-openflowd.8.in [moved from secchan/secchan.8.in with 61% similarity]
utilities/ovs-openflowd.c [moved from secchan/main.c with 79% similarity]
utilities/ovs-pki.8.in
utilities/ovs-pki.in
utilities/ovs-vsctl.8.in [new file with mode: 0644]
utilities/ovs-vsctl.c [new file with mode: 0644]
utilities/ovs-wdt.c
vswitchd/.gitignore
vswitchd/automake.mk
vswitchd/bridge.c
vswitchd/bridge.h
vswitchd/mgmt.c [deleted file]
vswitchd/ovs-brcompatd.8.in
vswitchd/ovs-brcompatd.c
vswitchd/ovs-vswitchd.8.in
vswitchd/ovs-vswitchd.c
vswitchd/ovs-vswitchd.conf.5.in [deleted file]
vswitchd/port.c [deleted file]
vswitchd/proc-net-compat.c
vswitchd/vswitch-idl.ann [new file with mode: 0644]
vswitchd/vswitch.ovsschema [new file with mode: 0644]
vswitchd/vswitch.xml [new file with mode: 0644]
xenserver/README
xenserver/automake.mk
xenserver/etc_init.d_vswitch
xenserver/etc_init.d_vswitch-xapi-update
xenserver/etc_logrotate.d_vswitch
xenserver/etc_profile.d_vswitch.sh
xenserver/etc_xapi.d_plugins_vswitch-cfg-update
xenserver/etc_xensource_scripts_vif
xenserver/opt_xensource_libexec_InterfaceReconfigure.py [new file with mode: 0644]
xenserver/opt_xensource_libexec_InterfaceReconfigureBridge.py [new file with mode: 0644]
xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py [new file with mode: 0644]
xenserver/opt_xensource_libexec_interface-reconfigure
xenserver/root_vswitch_scripts_dump-vif-details [deleted file]
xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py
xenserver/usr_sbin_brctl
xenserver/usr_sbin_xen-bugtool
xenserver/usr_share_vswitch_scripts_refresh-network-uuids [moved from xenserver/root_vswitch_scripts_refresh-xs-network-uuids with 100% similarity]
xenserver/usr_share_vswitch_scripts_sysconfig.template [moved from xenserver/root_vswitch_scripts_sysconfig.template with 65% similarity]
xenserver/vswitch-xen.spec

index 480615a..4e1fccb 100644 (file)
@@ -15,6 +15,7 @@
 .*.cmd
 .*.swp
 .deps
+.dirstamp
 .libs
 .tmp_versions
 /Makefile
@@ -22,7 +23,6 @@
 /aclocal.m4
 /autom4te.cache
 /build-arch-stamp
-/build-aux
 /build-indep-stamp
 /compile
 /config.guess
@@ -36,6 +36,7 @@
 /depcomp
 /install-sh
 /missing
+/package.m4
 /stamp-h1
 Module.symvers
 TAGS
diff --git a/COPYING b/COPYING
index 134f02b..39dd45c 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -22,3 +22,31 @@ Public License, version 2.
 Files under the xenserver directory are licensed on a file-by-file
 basis.  Some files are under an uncertain license that may not be
 DFSG-compliant or GPL-compatible.  Refer to each file for details.
+
+The files under ovsdb/simplejson are covered by the following license:
+
+    Copyright (c) 2006 Bob Ippolito
+
+    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.
+
+Files lib/sflow*.[ch] are licensed under the terms of the InMon sFlow
+licence that is available at:
+        http://www.inmon.com/technology/sflowlicense.txt
index 9cfa8b9..e9ba255 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+v0.99.2 - 18 Feb 2010
+---------------------
+    - Bug fixes
+
+v0.99.1 - 25 Jan 2010
+---------------------
+    - Add support for sFlow(R)
+    - Make headers compatible with C++
+    - Bug fixes
+
+v0.99.0 - 14 Jan 2010
+---------------------
+    - User-space forwarding engine
+    - Bug fixes
+       
 v0.90.7 - 29 Nov 2009
 ---------------------
     - Add support for NetFlow active timeouts
index 126b45a..f4765ad 100644 (file)
@@ -168,7 +168,7 @@ prototype:
   Omit parameter names from function prototypes when the names do not
 give useful information, e.g.:
 
-    int netdev_get_mtu(const struct netdev *);
+    int netdev_get_mtu(const struct netdev *, int *mtup);
 
 
 STATEMENTS
@@ -430,7 +430,9 @@ and
 precedence makes it necessary, or unless the operands are themselves
 expressions that use && and ||.  Thus:
 
-    if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2])) {
+    if (!isdigit((unsigned char)s[0])
+            || !isdigit((unsigned char)s[1])
+            || !isdigit((unsigned char)s[2])) {
         printf("string %s does not start with 3-digit code\n", s);
     }
 
index aadac73..263e261 100644 (file)
@@ -2,8 +2,8 @@
                  ====================================
 
 This document describes how to build and install Open vSwitch on a
-generic Linux host host.  If you want to install Open vSwitch on a
-Citrix XenServer 5.5.0, see INSTALL.XenServer instead.
+generic Linux host.  If you want to install Open vSwitch on a Citrix
+XenServer version 5.5.0, see INSTALL.XenServer instead.
 
 This version of Open vSwitch should be built manually with "configure"
 and "make".  Debian packaging for Open vSwitch is also included, but
@@ -27,8 +27,11 @@ you will need the following software:
       connections from an Open vSwitch to an OpenFlow controller.  To
       enable, configure with --enable-ssl=yes.
 
-To compile the kernel module (which is required for operation), you
-must also install the following:
+To compile the kernel module, you must also install the following.  If
+you cannot build or install the kernel module, you may use the
+userspace-only implementation, at a cost in performance.  The
+userspace implementation may also lack some features.  Refer to
+INSTALL.userspace for more information.
 
     - A supported Linux kernel version.  Please refer to README for a
       list of supported versions.
@@ -58,12 +61,14 @@ If you are working from a Git tree or snapshot (instead of from a
 distribution tarball), or if you modify the Open vSwitch build system,
 you will also need the following software:
 
-    - Autoconf version 2.60 or later.
+    - Autoconf version 2.64 or later.
 
     - Automake version 1.10 or later.
 
     - pkg-config.  We test with version 0.22.
 
+    - Python 2.x, for x >= 4.
+
 Installation Requirements
 -------------------------
 
@@ -150,27 +155,42 @@ Prerequisites section, follow the procedure below to build.
    To verify that the modules have been loaded, run "/sbin/lsmod" and
    check that openvswitch_mod is listed.
 
-Configuration
-=============
+7. Initialize the configuration database using ovsdb-tool, e.g.:
+
+      % ovsdb-tool create /usr/local/etc/ovs-vswitchd.conf.db vswitchd/vswitch.ovsschema
+
+Startup
+=======
+
+Before starting ovs-vswitchd itself, you need to start its
+configuration database, ovsdb-server.  Configure it to use the
+database you created during step 7 of installation, above, and to
+listen on a Unix domain socket, e.g.:
+
+      % ovsdb-server /usr/local/etc/ovs-vswitchd.conf.db --remote=punix:/usr/local/var/run/ovsdb-server
+
+Then initialize the database with "ovs-vsctl init".  This is only
+necessary the first time after you create the database with
+ovsdb-tool (but running it at any time is harmless):
+
+      % ovs-vsctl init
+
+Then start the main Open vSwitch daemon, telling it to connect to the
+same Unix domain socket:
 
-Open vSwitch is configured primarily through a configuration file,
-whose name is specified on the ovs-vswitchd command line.  Please
-refer to ovs-vswitchd(8) and ovs-vswitchd.conf(5) for information on
-how to start ovs-vswitchd and the syntax of its configuration file,
-respectively.
+      % ovs-vswitchd unix:/usr/local/var/run/ovsdb-server
 
-At runtime, you may make ovs-vswitchd reload its configuration file
-and update its configuration accordingly by sending it a SIGHUP
-signal.  The ovs-appctl utility can also be used to do this with a
-command such as:
+Now you may use ovs-vsctl to set up bridges and other Open vSwitch
+features.  For example, to create a bridge named br0 and add ports
+eth0 and vif1.0 to it:
 
-    % ovs-appctl -t <pid> -e vswitchd/reload
+      % ovs-vsctl add-br br0
+      % ovs-vsctl add-port br0 eth0
+      % ovs-vsctl add-port br0 vif1.0
 
-where <pid> is ovs-vswitchd's process ID.  In the latter case,
-ovs-appctl will not exit until the reload and reconfiguration is
-complete.
+Please refer to ovs-vsctl(8) for more details.
 
 Bug Reporting
 -------------
 
-Please report problems to ovs-bugs@openvswitch.org.
+Please report problems to bugs@openvswitch.org.
diff --git a/INSTALL.OpenFlow b/INSTALL.OpenFlow
new file mode 100644 (file)
index 0000000..7a4a7e6
--- /dev/null
@@ -0,0 +1,158 @@
+            Using Open vSwitch as a Simple OpenFlow Switch
+            ==============================================
+
+Open vSwitch uses OpenFlow as its preferred method of remote flow
+table configuration.  This is the simplest method of using it with an
+OpenFlow controller.  The ovs-vsctl "set-controller" command will set
+the controller for one or more bridges.  We recommend using OpenFlow
+in this manner.
+
+However, it is also possible to use Open vSwitch as a simple OpenFlow
+switch like that provided by the OpenFlow reference implementation
+[1].  The remainder of this file describes how to use it in that
+manner.
+
+What is OpenFlow?
+-----------------
+
+OpenFlow is a flow-based switch specification designed to enable
+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" that connects 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
+the controller using OpenFlow commands.  Example actions are:
+
+    - Forward this flow's packets to the given port(s)
+    - Drop this flow's packets
+    - Encapsulate and forward this flow's packets to the controller.
+
+The OpenFlow switch is defined in detail in the OpenFlow switch
+Specification [2].
+
+Installation Procedure
+----------------------
+
+The procedure below explains how to use the Open vSwitch as a simple
+OpenFlow switch. 
+
+1. Build and install the Open vSwitch kernel modules and userspace
+   programs as described in INSTALL.Linux.
+
+   It is important to run "make install", because some Open vSwitch
+   programs expect to find files in locations selected at installation
+   time.
+
+2. Load the openvswitch kernel module (which was built in step 1), e.g.:
+
+      % insmod datapath/linux-2.6/openvswitch_mod.ko
+
+   This kernel module cannot be loaded if the Linux bridge module is
+   already loaded.  Thus, you may need to remove any existing bridges
+   and unload the bridge module with "rmmod bridge" before you can do
+   this.
+
+3. Create a datapath instance.  The command below creates a datapath
+   identified as dp0 (see ovs-dpctl(8) for more detailed usage
+   information).
+
+      # ovs-dpctl add-dp dp0
+   
+   Creating datapath dp0 creates a new network device, also named dp0.
+   This network device, called the datapath's "local port", will be
+   bridged to the physical switch ports by ovs-openflowd(8).  It is
+   optionally used for in-band control as described in step 5.
+
+4. Use ovs-dpctl to attach the datapath to physical interfaces on the
+   machine.  Say, for example, you want to create a trivial 2-port
+   switch using interfaces eth1 and eth2, you would issue the following
+   commands:
+
+      # ovs-dpctl add-if dp0 eth1
+      # ovs-dpctl add-if dp0 eth2
+
+   You can verify that the interfaces were successfully added by asking
+   ovs-dpctl to print the current status of datapath dp0:
+
+      # ovs-dpctl show dp0
+
+5. Arrange so that the switch can reach the controller over the network.  
+   This can be done in two ways.  The switch may be configured for 
+   out-of-band control, which means it uses a network separate from the 
+   data traffic that it controls.  Alternatively, the switch may be 
+   configured to contact the controller over one of the network devices 
+   under its control.  In-band control is often more convenient than 
+   out-of-band, because it is not necessary to maintain two independent 
+   networks.
+
+      - If you are using out-of-band control, at this point make sure
+        that the switch machine can reach the controller over the
+        network.
+
+      - If you are using in-band control, then at this point you must
+        configure the dp0 network device created in step 3.  This
+        device is not yet bridged to any physical network (because
+        ovs-openflowd does that, and it is not yet running), so the next
+        step depends on whether connectivity is required to configure
+        the device's IP address:
+
+           * If the switch has a static IP address, you may configure
+             its IP address now, e.g.:
+
+                # ifconfig dp0 192.168.1.1
+
+           * If the switch does not have a static IP address, e.g. its
+             IP address is obtained dynamically via DHCP, then proceed
+             to the next step.  The DHCP client will not be able to 
+             contact the DHCP server until the secure channel has 
+             started.  The address will be obtained in step 7.
+
+      - If you are using in-band control with controller discovery, no
+        configuration is required at this point.  You may proceed to
+        the next step.
+
+6. Run ovs-openflowd to start the secure channel connecting the datapath to
+   a remote controller.  If the controller is running on host
+   192.168.1.2 port 6633 (the default port), the ovs-openflowd invocation
+   would look like this:
+
+      # ovs-openflowd dp0 tcp:192.168.1.2
+
+   - If you are using in-band control with controller discovery, omit
+     the second argument to the ovs-openflowd command.
+
+   - If you are using out-of-band control, add --out-of-band to the
+     command line.
+
+   Using the "tcp:<controller_ip>" argument causes the switch to connect
+   in an insecure manner.  Please see INSTALL.SSL for a description of
+   how to connect securely using SSL.
+
+7. If you are using in-band control with manual configuration, and the
+   switch obtains its IP address dynamically, then you may now obtain
+   the switch's IP address, e.g. by invoking a DHCP client.  The
+   secure channel will only be able to connect to the controller after
+   an IP address has been obtained.
+
+8. The secure channel should connect to the controller within a few
+   seconds.  It may take a little longer if controller discovery is in
+   use, because the switch must then also obtain its own IP address
+   and the controller's location via DHCP.
+
+References
+----------
+
+    [1] OpenFlow Reference Implementation.
+        <http://www.openflowswitch.org/wp/downloads/>
+
+    [2] OpenFlow Switch Specification.
+        <http://openflowswitch.org/documents/openflow-spec-latest.pdf>
index d672d62..4ba0925 100644 (file)
@@ -287,31 +287,32 @@ cacert.pem:
       OpenFlow controller by verifying a signature against this CA
       certificate.
 
-Once you have these files, configure ovs-vswitchd to use them by
-adding the following keys to your ovs-vswitchd.conf file:
+Once you have these files, configure ovs-vswitchd to use them using
+the ovs-vsctl "set-ssl" command, e.g.:
 
-    ssl.private-key=/etc/vswitch/sc-privkey.pem
-    ssl.certificate=/etc/vswitch/sc-cert.pem
-    ssl.ca-cert=/etc/vswitch/cacert.pem
+    ovs-vsctl set-ssl /etc/vswitch/sc-privkey.pem /etc/vswitch/sc-cert.pem /etc/vswitch/cacert.pem
 
 Substitute the correct file names, of course, if they differ from the
-ones used above.
+ones used above.  You should use absolute file names (ones that begin
+with "/"), because ovs-vswitchd's current directory is unrelated to
+the one from which you run ovs-vsctl.
 
 If you are using self-signed certificates (see "SSL Concepts for
 OpenFlow") and you did not copy controllerca/cacert.pem from the PKI
-machine to the Open vSwitch, then also add the following key:
+machine to the Open vSwitch, then add the --bootstrap option, e.g.:
 
-    ssl.bootstrap-ca-cert=true
+    ovs-vsctl -- --bootstrap set-ssl /etc/vswitch/sc-privkey.pem /etc/vswitch/sc-cert.pem /etc/vswitch/cacert.pem
 
 After you have added all of these configuration keys, you may specify
-"ssl:" connection methods elsewhere in ovs-vswitchd.conf, e.g.:
-
-    mgmt.controller=ssl:192.168.0.1
-
+"ssl:" connection methods elsewhere in the configuration database.
 "tcp:" connection methods are still allowed even after SSL has been
 configured, so for security you should use only "ssl:" connections.
 
+Unlike most Open vSwitch settings, the SSL settings are read only
+once, at ovs-vswitchd startup time.  For changes to take effect,
+ovs-vswitchd must be killed and restarted.
+
 Reporting Bugs
 --------------
 
-Please report problems to ovs-bugs@openvswitch.org.
+Please report problems to bugs@openvswitch.org.
index bdfc26a..5476633 100644 (file)
@@ -2,9 +2,12 @@
            ===============================================
 
 This document describes how to build and install Open vSwitch on a
-Citrix XenServer 5.5.0 host.  If you want to install Open vSwitch on a
+Citrix XenServer host.  If you want to install Open vSwitch on a
 generic Linux host, see INSTALL.Linux instead.
 
+These instructions have been tested with XenServer versions 5.5.0,
+5.5.0-24648p (Update 1), 5.5.0-25727p (Update 2), and 5.5.900.
+
 Building Open vSwitch for XenServer
 -----------------------------------
 
@@ -67,4 +70,4 @@ rebooted as soon as possible.
 Reporting Bugs
 --------------
 
-Please report problems to ovs-bugs@openvswitch.org.
+Please report problems to bugs@openvswitch.org.
diff --git a/INSTALL.bridge b/INSTALL.bridge
new file mode 100644 (file)
index 0000000..fc04a87
--- /dev/null
@@ -0,0 +1,62 @@
+              Replacing a Linux Bridge with Open vSwitch
+              ==========================================
+
+This file documents how Open vSwitch may be used as a drop-in
+replacement for a Linux kernel bridge in an environment that includes
+elements that are tightly tied to the Linux bridge tools
+(e.g. "brctl") and architecture.  We recommend directly using the
+management tools provided with Open vSwitch rather than these
+compatibility hooks for environments that are not tightly tied to the
+Linux bridging tools; they are more efficient and better reflect the
+actual operation and status.
+
+Installation Procedure
+----------------------
+
+The procedure below explains how to use the Open vSwitch bridge
+compatibility support.  This procedure is written from the perspective
+of a system administrator manually loading and starting Open vSwitch
+in bridge compatibility mode, but of course in practice one would want
+to update system scripts to follow these steps.  If you do edit your
+system configuration files to start Open vSwitch at boot time, make
+sure that it starts up before any bridge configuration (e.g. before
+any calls to "brctl" or "ifup" of any bridge interfaces), to ensure
+that the Open vSwitch kernel modules are loaded before the Linux
+kernel bridge module.
+
+1. Build, install, and start up the Open vSwitch kernel modules and
+   userspace programs as described in INSTALL.Linux.
+
+   It is important to run "make install", because some Open vSwitch
+   programs expect to find files in locations selected at installation
+   time.  The instructions below assume that files are installed in
+   their default locations, under /usr/local.
+
+2. Load the brcompat kernel module (which was built in step 1), e.g.:
+
+      % insmod datapath/linux-2.6/brcompat_mod.ko
+
+   (openvswitch_mod.ko should already have been loaded.)
+
+3. Start ovs-brcompatd:
+
+      % ovs-brcompatd --pidfile --detach -vANY:console:EMER \
+                unix:/usr/local/var/run/ovsdb-server
+
+   (ovsdb-server and ovs-vswitchd should already have been loaded.)
+
+4. Now you should be able to manage the Open vSwitch using brctl and
+   related tools.  For example, you can create an Open vSwitch bridge,
+   add interfaces to it, then print information about bridges with the
+   commands:
+
+      % brctl addbr br0
+      % brctl addif br0 eth0
+      % brctl addif br0 eth1
+      % brctl show
+
+   Each of these commands actually uses or modifies the Open vSwitch
+   configuration database, then notifies the ovs-vswitchd daemon of
+   the change.  For example, after executing the commands above
+   starting from an empty configuration file, "ovs-vsctl list-ports
+   br0" should show that bridge br0 contains two ports, eth0 and eth1.
diff --git a/INSTALL.userspace b/INSTALL.userspace
new file mode 100644 (file)
index 0000000..c13365a
--- /dev/null
@@ -0,0 +1,65 @@
+              Using Open vSwitch without kernel support
+              =========================================
+
+Open vSwitch can operate, at a cost in performance, entirely in
+userspace, without assistance from a kernel module.  This file
+explains how to install Open vSwitch in such a mode.
+
+The userspace-only mode of Open vSwitch is considered experimental.
+It has not been thoroughly tested.
+
+This version of Open vSwitch should be built manually with "configure"
+and "make".  Debian packaging for Open vSwitch is also included, but
+it has not been recently tested, and so Debian packages are not a
+recommended way to use this version of Open vSwitch.
+
+Building and Installing
+-----------------------
+
+The requirements and procedure for building, installing, and
+configuring Open vSwitch are the same as those given in INSTALL.Linux.
+You may omit configuring, building, and installing the kernel module,
+and the related requirements.
+
+On Linux, the userspace switch additionally requires the kernel
+TUN/TAP driver to be available, either built into the kernel or loaded
+as a module.  If you are not sure, check for a directory named
+/sys/class/misc/tun.  If it does not exist, then attempt to load the
+module with "modprobe tun".
+
+The tun device must also exist as /dev/net/tun.  If it does not exist,
+then create /dev/net (if necessary) with "mkdir /dev/net", then create
+/dev/net/tun with "mknod /dev/net/tun c 10 200".
+
+Using the Userspace Datapath with ovs-vswitchd
+----------------------------------------------
+
+To use ovs-vswitchd in userspace mode, create a bridge with datapath_type
+"netdev" in the configuration database.  For example:
+
+    ovs-vsctl add-br br0
+    ovs-vsctl set bridge br0 datapath_type=netdev
+    ovs-vsctl add-port br0 eth0
+    ovs-vsctl add-port br0 eth1
+    ovs-vsctl add-port br0 eth2
+
+ovs-vswitchd will create a TAP device as the bridge's local interface,
+named the same as the bridge, as well as for each configured internal
+interface.
+
+Using the Userspace Datapath with ovs-openflowd
+-----------------------------------------------
+
+To use ovs-openflowd in userspace mode, specify a datapath name that
+begins with "netdev@", and specify --ports with the names of the ports
+that should be included in the datapath as argument.  For example:
+
+    ovs-openflowd netdev@br0 --ports=eth0,eth1,eth2
+
+ovs-openflowd will create a TAP device as the bridge's local
+interface, named the same as the bridge minus the "netdev@" prefix.
+
+Bug Reporting
+-------------
+
+Please report problems to bugs@openvswitch.org.
index 0b6f00e..f8dfd16 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008, 2009 Nicira Networks, Inc.
+# Copyright (C) 2007, 2008, 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -16,6 +16,7 @@ AM_CPPFLAGS += -I $(top_srcdir)/include
 AM_CPPFLAGS += -I $(top_srcdir)/lib
 
 AM_CFLAGS = -Wstrict-prototypes
+AM_CFLAGS += $(WARNING_FLAGS)
 
 if NDEBUG
 AM_CPPFLAGS += -DNDEBUG
@@ -24,44 +25,65 @@ else
 AM_LDFLAGS = -export-dynamic
 endif
 
+BUILT_SOURCES =
 CLEANFILES =
 DISTCLEANFILES =
-EXTRA_DIST = INSTALL.Linux INSTALL.XenServer INSTALL.SSL
-TESTS =
-TESTS_ENVIRONMENT =
+EXTRA_DIST = INSTALL.bridge \
+       INSTALL.Linux \
+       INSTALL.userspace \
+       INSTALL.OpenFlow \
+       INSTALL.SSL \
+       INSTALL.XenServer \
+       README-gcov
 bin_PROGRAMS =
 sbin_PROGRAMS =
 bin_SCRIPTS =
-dist_commands_DATA =
 dist_man_MANS =
+dist_pkgdata_DATA =
 dist_pkgdata_SCRIPTS =
 dist_sbin_SCRIPTS =
 man_MANS =
+noinst_DATA =
 noinst_HEADERS =
 noinst_LIBRARIES =
 noinst_PROGRAMS =
 noinst_SCRIPTS =
+OVSIDL_BUILT =
+SUFFIXES =
 
 EXTRA_DIST += soexpand.pl
 
 ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
 
-SUFFIXES = .in
+SUFFIXES += .in
 .in:
        $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \
-           sed -e 's,[@]LOGDIR[@],$(LOGDIR),g' \
+           sed \
                -e 's,[@]PKIDIR[@],$(PKIDIR),g' \
+                -e 's,[@]LOGDIR[@],$(LOGDIR),g' \
+                -e 's,[@]PERL[@],$(PERL),g' \
+                -e 's,[@]PYTHON[@],$(PYTHON),g' \
                 -e 's,[@]RUNDIR[@],$(RUNDIR),g' \
+                -e 's,[@]VERSION[@],$(VERSION),g' \
+                -e 's,[@]localstatedir[@],$(localstatedir),g' \
                 -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
-                -e 's,[@]PERL[@],$(PERL),g' > $@
+                -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
+                -e 's,[@]abs_top_srcdir[@],$(abs_top_srcdir),g' \
+            > $@.tmp
+       @if head -n 1 $@.tmp | grep -q '#!'; then \
+           echo chmod +x $@.tmp; \
+           chmod +x $@.tmp; \
+       fi
+       mv $@.tmp $@
 
 include lib/automake.mk
-include secchan/automake.mk
+include ofproto/automake.mk
 include utilities/automake.mk
 include tests/automake.mk
 include include/automake.mk
 include third-party/automake.mk
 include debian/automake.mk
 include vswitchd/automake.mk
+include ovsdb/automake.mk
 include xenserver/automake.mk
 include extras/ezio/automake.mk
diff --git a/README b/README
index 7311292..5b80998 100644 (file)
--- a/README
+++ b/README
 What is Open vSwitch?
 ---------------------
 
-Open vSwitch is an Ethernet switch for virtual servers with the
-following features:
-
-       * NIC bonding with automatic fail-over and source MAC-based TX
-         load balancing ("SLB").
-
-       * 802.1Q VLAN support.
-
-       * Port mirroring, with optional VLAN tagging.
-
-       * NetFlow v5 flow logging.
-
-       * Connectivity to an external OpenFlow controller, such as
-          NOX.
-
-Open vSwitch supports Linux 2.6.15 and up, with testing focused on
-2.6.18 with Centos and Xen patches and version 2.6.26 from kernel.org.
-Open vSwitch also has special support for Citrix XenServer hosts.
+Open vSwitch is a multilayer software switch licensed under the open
+source Apache 2 license.  Our goal is to implement a production
+quality switch platform that supports standard management interfaces
+(e.g. NetFlow, sFlow(R), RSPAN, ERSPAN, IOS-like CLI), and opens the
+forwarding functions to programmatic extension and control.
+
+Open vSwitch is well suited to function as a virtual switch in VM
+environments.  In addition to exposing standard control and visibility
+interfaces to the virtual networking layer, it was designed to support
+distribution across multiple physical servers.  Open vSwitch supports
+multiple Linux-based virtualization technologies including
+Xen/XenServer, KVM, and VirtualBox.
+
+The bulk of the code is written in platform-independent C and is
+easily ported to other environments.  The current release of Open
+vSwitch supports the following features:
+
+    * Visibility into inter-VM communication via NetFlow, sFlow, SPAN,
+      and RSPAN
+    * Standard 802.1Q VLAN model with trunking
+    * Per VM policing
+    * NIC bonding with source-MAC load balancing
+    * Kernel-based forwarding
+    * Support for OpenFlow
+    * Compatibility layer for the Linux bridging code
+
+The included Linux kernel module supports Linux 2.6.15 and up, with
+testing focused on 2.6.18 with Centos and Xen patches and version
+2.6.26 from kernel.org.  Open vSwitch also has special support for
+Citrix XenServer hosts.
+
+Open vSwitch can also operate, at a cost in performance, entirely in
+userspace, without assistance from a kernel module.  This userspace
+implementation should be easier to port than the kernel-based switch.
+It is considered experimental.
 
 What's here?
 ------------
 
 The main components of this distribution are:
 
-        - ovs-vswitchd, a daemon that implements the virtual switch,
-          along with a companion Linux kernel module for flow-based
-          switching.
+    * ovs-vswitchd, a daemon that implements the switch, along with 
+      a companion Linux kernel module for flow-based switching.
+
+    * ovsdb-server, a lightweight database server that ovs-vswitchd
+      queries to obtain its configuration.
+
+    * ovs-brcompatd, a daemon that allows ovs-vswitchd to act as a
+      drop-in replacement for the Linux bridge in many environments, 
+      along with a companion Linux kernel module to intercept bridge 
+      ioctls.
 
-        - ovs-brcompatd, a daemon that allows ovs-vswitchd to act as a
-          drop-in replacement for the Linux bridge in many
-          environments, along with a companion Linux kernel module to
-          intercept bridge ioctls.
+    * ovs-dpctl, a tool for configuring the switch kernel module.
 
-       - ovs-dpctl, a tool for configuring the virtual switch kernel
-          module.
+    * Scripts and specs for building RPMs that allow Open vSwitch
+      to be installed on a Citrix XenServer host as a drop-in
+      replacement for its switch, with additional functionality.
 
-        - Scripts and specs for building RPMs that allow Open vSwitch
-          to be installed on a Citrix XenServer host as a drop-in
-          replacement for its virtual switch, with additional
-          functionality.
+    * ovs-vsctl, a utility for querying and updating the configuration
+      of ovs-vswitchd.
 
-       - vlog-appctl, a utility that can control Open vSwitch daemons,
-          adjusting their logging levels among other uses.
+    * ovs-appctl, a utility that sends commands to running Open
+      vSwitch daemons.
 
 Open vSwitch also provides an OpenFlow implementation and tools for
 those interested in OpenFlow but not additional Open vSwitch features:
 
-       - secchan, a program that implements a simple OpenFlow switch
-          (without the special features provided by ovs-vswitchd) using
-          the same kernel module as ovs-vswitchd.
+    * ovs-openflowd, a program that implements a simple OpenFlow
+      switch (without the special features provided by ovs-vswitchd)
+      using the same kernel module as ovs-vswitchd.
 
-       - ovs-controller, a simple OpenFlow controller.
+    * ovs-controller, a simple OpenFlow controller.
 
-       - ovs-ofctl, a utility for querying and controlling OpenFlow
-          switches and controllers.
+    * ovs-ofctl, a utility for querying and controlling OpenFlow
+      switches and controllers.
 
-       - ovs-pki, a utility for creating and managing the public-key
-          infrastructure for OpenFlow switches.
+    * ovs-pki, a utility for creating and managing the public-key
+      infrastructure for OpenFlow switches.
 
-       - A patch to tcpdump that enables it to parse OpenFlow
-          messages.
+    * A patch to tcpdump that enables it to parse OpenFlow messages.
 
 What other documentation is available?
 --------------------------------------
 
 To install Open vSwitch on a regular Linux machine, read INSTALL.Linux.
 
+To use Open vSwitch as a drop-in replacement for the Linux bridge,
+read INSTALL.bridge.
+
 To build RPMs for installing Open vSwitch on a Citrix XenServer host
 or resource pool, read INSTALL.XenServer.
 
+To install Open vSwitch without using a kernel module, read
+INSTALL.userspace.
+
 To learn set up SSL support for Open vSwitch, read INSTALL.SSL.
 
 Each Open vSwitch userspace program is accompanied by a manpage.  Many
@@ -83,5 +108,5 @@ the manpages.
 Contact 
 -------
 
-ovs-bugs@openvswitch.org
+bugs@openvswitch.org
 http://openvswitch.org/
diff --git a/README-gcov b/README-gcov
new file mode 100644 (file)
index 0000000..01c49cc
--- /dev/null
@@ -0,0 +1,24 @@
+Building with gcov support
+==========================
+
+The Open vSwitch "configure" script supports the following
+code-coverage related options:
+
+  --disable-coverage
+  --enable-coverage=no
+
+    Do not build with gcov code coverage support.
+
+    This is the default if no coverage option is passed to
+    "configure".
+
+  --enable-coverage
+  --enable-coverage=yes
+
+    Build with gcov code coverage support.
+
+    If you enable coverage and you have the "lcov" and "genhtml"
+    programs in PATH, then you may run "make check-lcov" to produce a
+    directory "tests/coverage.html" in the build directory with an
+    analysis of the test suite's coverage.  lcov is available at
+    http://ltp.sourceforge.net/coverage/lcov.php
diff --git a/REPORTING-BUGS b/REPORTING-BUGS
new file mode 100644 (file)
index 0000000..75da3d6
--- /dev/null
@@ -0,0 +1,53 @@
+Reporting Bugs in Open vSwitch
+==============================
+
+We are eager to hear from users about problems that they have
+encountered with Open vSwitch.  This file documents how best to report
+bugs so as to ensure that they can be fixed as quickly as possible.
+
+Please report bugs by sending email to bugs@openvswitch.org.  Include
+as much of the following information as you can in your report:
+
+        * The Open vSwitch version number (as output by "ovs-vswitchd
+          --version").
+
+        * The Git commit number (as output by "git rev-parse HEAD"),
+          if you built from a Git snapshot.
+
+        * Any local patches or changes you have applied (if any).
+
+        * The kernel version on which Open vSwitch is running (from
+          /proc/version) and the distribution and version number of
+          your OS (e.g. "Centos 5.0").
+
+        * The contents of the vswitchd configuration database (usually
+          /etc/ovs-vswitchd.conf.db).
+
+        * The output of "ovs-dpctl show".
+
+        * If you have Open vSwitch configured to connect to an
+          OpenFlow controller, the output of "ovs-ofctl show <bridge>"
+          for each <bridge> configured in the vswitchd configuration
+          file.
+
+        * A description of the problem, which should include:
+
+                - What you did that make the problem appear.
+
+                - What you expected to happen.
+
+                - What actually happened.
+
+        * A fix or workaround, if you have one.
+
+        * Any other information that you think might be relevant.
+
+bugs@openvswitch.org is a public mailing list, to which anyone can
+subscribe, so please do not include confidential information in your
+bug report.
+
+Contact 
+-------
+
+bugs@openvswitch.org
+http://openvswitch.org/
diff --git a/SubmittingPatches b/SubmittingPatches
new file mode 100644 (file)
index 0000000..280f11e
--- /dev/null
@@ -0,0 +1,220 @@
+How to Submit Patches for Open vSwitch
+======================================
+
+Send changes to Open vSwitch as patches to discuss@openvswitch.org.
+One patch per email, please.  More details are included below.
+
+If you are using Git, then "git format-patch" takes care of most of
+the mechanics described below for you.
+
+Before You Start
+----------------
+
+Before you send patches at all, make sure that each patch makes sense.
+In particular:
+
+        - A given patch should not break anything, even if later
+          patches fix the problems that it causes.  The source tree
+          should still build and work after each patch is applied.
+          (This enables "git bisect" to work best.)
+
+        - A patch should make one logical change.  Don't make
+          multiple, logically unconnected changes to disparate
+          subsystems in a single patch.
+
+        - A patch that adds or removes user-visible features should
+          also update the appropriate user documentation or manpages.
+
+Testing is also important:
+
+        - A patch that adds or deletes files should be tested with
+          "make distcheck" before submission.
+
+        - A patch that modifies Linux kernel code should be at least
+          build-tested on various Linux kernel versions before
+          submission.  I suggest versions 2.6.18, 2.6.27, and whatever
+          the current latest release version is at the time.
+
+        - A patch that modifies the ofproto or vswitchd code should be
+          tested in at least simple cases before submission.
+
+        - A patch that modifies xenserver code should be tested on
+          XenServer before submission.
+
+Email Subject
+-------------
+
+The subject line of your email should be in the following format:
+[PATCH <n>/<m>] <area>: <summary>
+
+        - [PATCH <n>/<m>] indicates that this is the nth of a series
+          of m patches.  It helps reviewers to read patches in the
+          correct order.  You may omit this prefix if you are sending
+          only one patch.
+
+        - <area>: indicates the area of the Open vSwitch to which the
+          change applies (often the name of a source file or a
+          directory).  You may omit it if the change crosses multiple
+          distinct pieces of code.
+
+        - <summary> briefly describes the change.
+
+The subject, minus the [PATCH <n>/<m>] prefix, becomes the first line
+of the commit's change log message.
+
+Description
+-----------
+
+The body of the email should start with a more thorough description of
+the change.  This becomes the body of the commit message, following
+the subject.  There is no need to duplicate the summary given in the
+subject.
+
+Please limit lines in the description to 79 characters in width.
+
+The description should include:
+
+        - The rationale for the change.
+
+        - Design description and rationale (but this might be better
+          added as code comments).
+
+        - Testing that you performed (or testing that should be done
+          but you could not for whatever reason).
+
+There is no need to describe what the patch actually changed, if the
+reader can see it for himself.
+
+If the patch refers to a commit already in the Open vSwitch
+repository, please include both the commit number and the subject of
+the patch, e.g. 'commit 632d136c "vswitch: Remove restriction on
+datapath names."'.
+
+If you, the person sending the patch, did not write the patch
+yourself, then the very first line of the body should take the form
+"From: <author name> <author email>", followed by a blank line.  This
+will automatically cause the named author to be credited with
+authorship in the repository.  If others contributed to the patch, but
+are not the main authors, then please credit them as part of the
+description (e.g. "Thanks to Bob J. User for reporting this bug.").
+
+Comments
+--------
+
+If you want to include any comments in your email that should not be
+part of the commit's change log message, put them after the
+description, separated by a line that contains just "---".  It may be
+helpful to include a diffstat here for changes that touch multiple
+files.
+
+Patch
+-----
+
+The patch should be in the body of the email following the descrition,
+separated by a blank line.
+
+Patches should be in "diff -up" format.  We recommend that you use Git
+to produce your patches, in which case you should use the -M -C
+options to "git diff" (or other Git tools) if your patch renames or
+copies files.  Quilt (http://savannah.nongnu.org/projects/quilt) might
+be useful if you do not want to use Git.
+
+Patches should be inline in the email message.  Some email clients
+corrupt white space or wrap lines in patches.  There are hints on how
+to configure many email clients to avoid this problem at:
+        http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob_plain;f=Documentation/email-clients.txt
+If you cannot convince your email client not to mangle patches, then
+sending the patch as an attachment is a second choice.
+
+Please follow the style used in the code that you are modifying.  The
+CodingStyle file describes the coding style used in most of Open
+vSwitch.  Use Linux kernel coding style for Linux kernel code.
+
+Example
+-------
+
+From 632d136c7b108cd3d39a2e64fe6230e23977caf8 Mon Sep 17 00:00:00 2001
+From: Ben Pfaff <blp@nicira.com>
+Date: Mon, 6 Jul 2009 10:17:54 -0700
+Subject: [PATCH] vswitch: Remove restriction on datapath names.
+
+Commit f4b96c92c "vswitch: Disallow bridges named "dpN" or "nl:N"" disabled
+naming bridges "dpN" because the vswitchd code made the bad assumption that
+the bridge's local port has the same name as the bridge, which was not
+true (at the time) for bridges named dpN.  Now that assumption has been
+eliminated, so this commit eliminates the restriction too.
+
+This change is also a cleanup in that it eliminates one form of the
+vswitch's dependence on specifics of the dpif implementation.
+---
+ vswitchd/bridge.c               |   23 +++++------------------
+ vswitchd/ovs-vswitchd.conf.5.in |    3 +--
+ 2 files changed, 6 insertions(+), 20 deletions(-)
+
+diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
+index 32647ea..00cffbc 100644
+--- a/vswitchd/bridge.c
++++ b/vswitchd/bridge.c
+@@ -351,32 +351,19 @@ bridge_configure_ssl(void)
+ void
+ bridge_reconfigure(void)
+ {
+-    struct svec old_br, new_br, raw_new_br;
++    struct svec old_br, new_br;
+     struct bridge *br, *next;
+     size_t i, j;
+     COVERAGE_INC(bridge_reconfigure);
+-    /* Collect old bridges. */
++    /* Collect old and new bridges. */
+     svec_init(&old_br);
++    svec_init(&new_br);
+     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
+         svec_add(&old_br, br->name);
+     }
+-
+-    /* Collect new bridges. */
+-    svec_init(&raw_new_br);
+-    cfg_get_subsections(&raw_new_br, "bridge");
+-    svec_init(&new_br);
+-    for (i = 0; i < raw_new_br.n; i++) {
+-        const char *name = raw_new_br.names[i];
+-        if (!strncmp(name, "dp", 2) && isdigit((unsigned char)name[2])) {
+-            VLOG_ERR("%s is not a valid bridge name (bridges may not be "
+-                     "named \"dp\" followed by a digit)", name);
+-        } else {
+-            svec_add(&new_br, name);
+-        }
+-    }
+-    svec_destroy(&raw_new_br);
++    cfg_get_subsections(&new_br, "bridge");
+     /* Get rid of deleted bridges and add new bridges. */
+     svec_sort(&old_br);
+@@ -793,7 +780,7 @@ bridge_create(const char *name)
+     br = xzalloc(sizeof *br);
+     error = dpif_create(name, &br->dpif);
+-    if (error == EEXIST) {
++    if (error == EEXIST || error == EBUSY) {
+         error = dpif_open(name, &br->dpif);
+         if (error) {
+             VLOG_ERR("datapath %s already exists but cannot be opened: %s",
+diff --git a/vswitchd/ovs-vswitchd.conf.5.in b/vswitchd/ovs-vswitchd.conf.5.in
+index 5483ad5..d82a08a 100644
+--- a/vswitchd/ovs-vswitchd.conf.5.in
++++ b/vswitchd/ovs-vswitchd.conf.5.in
+@@ -50,8 +50,7 @@ configure \fBovs\-vswitchd\fR.
+ .SS "Bridge Configuration"
+ A bridge (switch) with a given \fIname\fR is configured by specifying
+ the names of its network devices as values for key
+-\fBbridge.\fIname\fB.port\fR.  (The specified \fIname\fR may not begin
+-with \fBdp\fR followed by a digit.)
++\fBbridge.\fIname\fB.port\fR.
+ .PP
+ The names given on \fBbridge.\fIname\fB.port\fR must be the names of
+ existing network devices, except for ``internal ports.''  An internal
+-- 
+1.6.3.3
+
index 6ba647a..abbc57e 100644 (file)
@@ -1,6 +1,6 @@
 # -*- autoconf -*-
 
-# Copyright (c) 2008, 2009 Nicira Networks.
+# Copyright (c) 2008, 2009, 2010 Nicira Networks.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-dnl OVS_CHECK_LINUX(OPTION, VERSION, VARIABLE, CONDITIONAL)
+dnl OVS_CHECK_LINUX26
 dnl
 dnl Configure linux kernel source tree 
-AC_DEFUN([OVS_CHECK_LINUX], [
-  AC_ARG_WITH([$1],
-              [AC_HELP_STRING([--with-$1=/path/to/linux-$2],
-                              [Specify the linux $2 kernel sources])],
-              [path="$withval"], [path=])dnl
-  if test -n "$path"; then
-    path=`eval echo "$path"`
-
-    AC_MSG_CHECKING([for $path directory])
-    if test -d "$path"; then
-       AC_MSG_RESULT([yes])
-       $3=$path
-       AC_SUBST($3)
+AC_DEFUN([OVS_CHECK_LINUX26], [
+  AC_ARG_WITH([l26],
+              [AC_HELP_STRING([--with-l26=/path/to/linux-2.6],
+                              [Specify the linux 2.6 kernel sources])],
+              [KBUILD26="$withval"], [KBUILD26=])dnl
+  if test -n "$KBUILD26"; then
+    KBUILD26=`eval echo "$KBUILD26"`
+
+    # The build directory is what the user provided.
+    # Make sure that it exists.
+    AC_MSG_CHECKING([for Linux 2.6 build directory])
+    if test -d "$KBUILD26"; then
+       AC_MSG_RESULT([$KBUILD26])
+       AC_SUBST(KBUILD26)
     else
        AC_MSG_RESULT([no])
-       AC_ERROR([source dir $path doesn't exist])
+       AC_ERROR([source dir $KBUILD26 doesn't exist])
+    fi
+
+    # Debian breaks kernel headers into "source" header and "build" headers.
+    # We want the source headers, but $KBUILD26 gives us the "build" headers.
+    # Use heuristics to find the source headers.
+    AC_MSG_CHECKING([for Linux 2.6 source directory])
+    KSRC26=$KBUILD26
+    if test ! -e $KSRC26/include/linux/kernel.h; then
+      case `echo "$KBUILD26" | sed 's,/*$,,'` in # (
+        */build)
+          KSRC26=`echo "$KBUILD26" | sed 's,/build/*$,/source,'`
+          ;; # (
+        *)
+          KSRC26=`(cd $KBUILD26 && pwd -P) | sed 's,-[[^-]]*$,-common,'`
+          ;;
+      esac
+      if test ! -e $KSRC26/include/linux/kernel.h; then
+        AC_MSG_ERROR([cannot find source directory])
+      fi
     fi
+    AC_MSG_RESULT([$KSRC26])
 
-    AC_MSG_CHECKING([for $path kernel version])
-    patchlevel=`sed -n 's/^PATCHLEVEL = //p' "$path/Makefile"`
-    sublevel=`sed -n 's/^SUBLEVEL = //p' "$path/Makefile"`
+    AC_MSG_CHECKING([for kernel version])
+    patchlevel=`sed -n 's/^PATCHLEVEL = //p' "$KSRC26/Makefile"`
+    sublevel=`sed -n 's/^SUBLEVEL = //p' "$KSRC26/Makefile"`
+    if test -z "$patchlevel" || test -z "$sublevel"; then
+       AC_ERROR([cannot determine kernel version])
+    fi
     AC_MSG_RESULT([2.$patchlevel.$sublevel])
-    if test "2.$patchlevel" != '$2'; then
-       AC_ERROR([Linux kernel source in $path is not version $2])
+    if test "2.$patchlevel" != '2.6'; then
+       if test "$BUILD26" = "$KSRC26"; then
+         AC_ERROR([Linux kernel in $KBUILD26 is not version 2.6])
+       else
+         AC_ERROR([Linux kernel in build tree $KBUILD26 (source tree $KSRC26) is not version 2.6])
+       fi
     fi
-    if ! test -e "$path"/include/linux/version.h || \
-       ! test -e "$path"/include/linux/autoconf.h; then
-       AC_MSG_ERROR([Linux kernel source in $path is not configured])
+    if test ! -e "$KBUILD26"/include/linux/version.h || \
+       (test ! -e "$KBUILD26"/include/linux/autoconf.h && \
+        test ! -e "$KBUILD26"/include/generated/autoconf.h); then
+       AC_MSG_ERROR([Linux kernel source in $KBUILD26 is not configured])
     fi
-    m4_if($2, [2.6], [OVS_CHECK_LINUX26_COMPAT])
+    OVS_CHECK_LINUX26_COMPAT
   fi
-  AM_CONDITIONAL($4, test -n "$path")
+  AM_CONDITIONAL(L26_ENABLED, test -n "$KBUILD26")
 ])
 
 dnl OVS_GREP_IFELSE(FILE, REGEX, IF-MATCH, IF-NO-MATCH)
@@ -90,6 +119,16 @@ AC_DEFUN([OVS_CHECK_VETH], [
   fi
 ])
 
+AC_DEFUN([OVS_CHECK_GRE], [
+  AC_MSG_CHECKING([whether to build gre module])
+  if test "$sublevel" -ge 18; then
+    AC_MSG_RESULT([yes])
+    AC_SUBST([BUILD_GRE], 1)
+  else
+    AC_MSG_RESULT([no])
+  fi
+])
+
 AC_DEFUN([OVS_CHECK_LOG2_H], [
   AC_MSG_CHECKING([for $KSRC26/include/linux/log2.h])
   if test -e $KSRC26/include/linux/log2.h; then
@@ -103,11 +142,13 @@ AC_DEFUN([OVS_CHECK_LOG2_H], [
 dnl OVS_CHECK_LINUX26_COMPAT
 dnl
 dnl Runs various Autoconf checks on the Linux 2.6 kernel source in
-dnl the directory in $KSRC26.
+dnl the directory in $KBUILD26.
 AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
   rm -f datapath/linux-2.6/kcompat.h.new
   mkdir -p datapath/linux-2.6
   : > datapath/linux-2.6/kcompat.h.new
+  OVS_GREP_IFELSE([$KSRC26/include/linux/types.h], [bool],
+                  [OVS_DEFINE([HAVE_BOOL_TYPE])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_transport_header],
                   [OVS_DEFINE([HAVE_SKBUFF_HEADER_HELPERS])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [raw],
@@ -123,8 +164,20 @@ AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
                   [OVS_DEFINE([HAVE_CSUM_UNFOLD])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_cow],
                   [OVS_DEFINE([HAVE_SKB_COW])])
+  OVS_GREP_IFELSE([$KSRC26/include/net/netlink.h], [nla_get_be16],
+                  [OVS_DEFINE([HAVE_NLA_GET_BE16])])
+  OVS_GREP_IFELSE([$KSRC26/include/linux/in.h], [ipv4_is_multicast],
+                  [OVS_DEFINE([HAVE_IPV4_IS_MULTICAST])])
+  # Check for the proto_data_valid member in struct sk_buff.  The [^@]
+  # is necessary because some versions of this header remove the
+  # member but retain the kerneldoc comment that describes it (which
+  # starts with @).  The brackets must be doubled because of m4
+  # quoting rules.
+  OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [[[^@]]proto_data_valid],
+                  [OVS_DEFINE([HAVE_PROTO_DATA_VALID])])
   OVS_CHECK_LOG2_H
   OVS_CHECK_VETH
+  OVS_CHECK_GRE
   if cmp -s datapath/linux-2.6/kcompat.h.new \
             datapath/linux-2.6/kcompat.h >/dev/null 2>&1; then
     rm datapath/linux-2.6/kcompat.h.new
@@ -163,6 +216,7 @@ AC_DEFUN([OVS_CHECK_STRTOK_R],
                            char *token1, *token2;
                            token1 = strtok_r(string, ":", &save_ptr);
                            token2 = strtok_r(NULL, ":", &save_ptr);
+                           freopen ("/dev/null", "w", stdout);
                            printf ("%s %s\n", token1, token2);
                            return 0;
                           ]])],
@@ -194,7 +248,7 @@ AC_DEFUN([OVS_CHECK_CC_OPTION],
      AC_COMPILE_IFELSE([AC_LANG_PROGRAM(,)], [ovs_cv_name[]=yes], [ovs_cv_name[]=no])
      CFLAGS="$ovs_save_CFLAGS"])
   if test $ovs_cv_name = yes; then
-    m4_if([$2], [], [;], [$2])
+    m4_if([$2], [], [:], [$2])
   else
     m4_if([$3], [], [:], [$3])
   fi
@@ -202,8 +256,19 @@ AC_DEFUN([OVS_CHECK_CC_OPTION],
 
 dnl OVS_ENABLE_OPTION([OPTION])
 dnl Check whether the given C compiler OPTION is accepted.
-dnl If so, add it to CFLAGS.
+dnl If so, add it to WARNING_FLAGS.
 dnl Example: OVS_ENABLE_OPTION([-Wdeclaration-after-statement])
 AC_DEFUN([OVS_ENABLE_OPTION], 
-  [OVS_CHECK_CC_OPTION([$1], [CFLAGS="$CFLAGS $1"])])
+  [OVS_CHECK_CC_OPTION([$1], [WARNING_FLAGS="$WARNING_FLAGS $1"])
+   AC_SUBST([WARNING_FLAGS])])
+
+dnl OVS_CONDITIONAL_CC_OPTION([OPTION], [CONDITIONAL])
+dnl Check whether the given C compiler OPTION is accepted.
+dnl If so, enable the given Automake CONDITIONAL.
+
+dnl Example: OVS_CONDITIONAL_CC_OPTION([-Wno-unused], [HAVE_WNO_UNUSED])
+AC_DEFUN([OVS_CONDITIONAL_CC_OPTION],
+  [OVS_CHECK_CC_OPTION(
+    [$1], [ovs_have_cc_option=yes], [ovs_have_cc_option=no])
+   AM_CONDITIONAL([$2], [test $ovs_have_cc_option = yes])])
 dnl ----------------------------------------------------------------------
diff --git a/build-aux/.gitignore b/build-aux/.gitignore
new file mode 100644 (file)
index 0000000..999eae2
--- /dev/null
@@ -0,0 +1,4 @@
+/compile
+/depcomp
+/install-sh
+/missing
diff --git a/build-aux/check-structs b/build-aux/check-structs
new file mode 100755 (executable)
index 0000000..545c80a
--- /dev/null
@@ -0,0 +1,267 @@
+#! /usr/bin/python
+
+import sys
+import re
+
+macros = {}
+
+anyWarnings = False
+
+types = {}
+types['char'] = {"size": 1, "alignment": 1}
+types['uint8_t'] = {"size": 1, "alignment": 1}
+types['uint16_t'] = {"size": 2, "alignment": 2}
+types['uint32_t'] = {"size": 4, "alignment": 4}
+types['uint64_t'] = {"size": 8, "alignment": 8}
+
+token = None
+line = ""
+idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
+tokenRe = "#?" + idRe + "|[0-9]+|."
+inComment = False
+inDirective = False
+def getToken():
+    global token
+    global line
+    global inComment
+    global inDirective
+    while True:
+        line = line.lstrip()
+        if line != "":
+            if line.startswith("/*"):
+                inComment = True
+                line = line[2:]
+            elif inComment:
+                commentEnd = line.find("*/")
+                if commentEnd < 0:
+                    line = ""
+                else:
+                    inComment = False
+                    line = line[commentEnd + 2:]
+            else:
+                match = re.match(tokenRe, line)
+                token = match.group(0)
+                line = line[len(token):]
+                if token.startswith('#'):
+                    inDirective = True
+                elif token in macros and not inDirective:
+                    line = macros[token] + line
+                    continue
+                return True
+        elif inDirective:
+            token = "$"
+            inDirective = False
+            return True
+        else:
+            global lineNumber
+            line = inputFile.readline()
+            lineNumber += 1
+            while line.endswith("\\\n"):
+                line = line[:-2] + inputFile.readline()
+                lineNumber += 1
+            if line == "":
+                if token == None:
+                    fatal("unexpected end of input")
+                token = None
+                return False
+    
+def fatal(msg):
+    sys.stderr.write("%s:%d: error at \"%s\": %s\n" % (fileName, lineNumber, token, msg))
+    sys.exit(1)
+    
+def warn(msg):
+    global anyWarnings
+    anyWarnings = True
+    sys.stderr.write("%s:%d: warning: %s\n" % (fileName, lineNumber, msg))
+
+def skipDirective():
+    getToken()
+    while token != '$':
+        getToken()
+
+def isId(s):
+    return re.match(idRe + "$", s) != None
+
+def forceId():
+    if not isId(token):
+        fatal("identifier expected")
+
+def forceInteger():
+    if not re.match('[0-9]+$', token):
+        fatal("integer expected")
+
+def match(t):
+    if token == t:
+        getToken()
+        return True
+    else:
+        return False
+
+def forceMatch(t):
+    if not match(t):
+        fatal("%s expected" % t)
+
+def parseTaggedName():
+    assert token in ('struct', 'union')
+    name = token
+    getToken()
+    forceId()
+    name = "%s %s" % (name, token)
+    getToken()
+    return name
+
+def parseTypeName():
+    if token in ('struct', 'union'):
+        name = parseTaggedName()
+    elif isId(token):
+        name = token
+        getToken()
+    else:
+        fatal("type name expected")
+
+    if name in types:
+        return name
+    else:
+        fatal("unknown type \"%s\"" % name)
+
+def parseStruct():
+    isStruct = token == 'struct'
+    structName = parseTaggedName()
+    if token == ";":
+        return
+
+    ofs = size = 0
+    alignment = 4               # ARM has minimum 32-bit alignment
+    forceMatch('{')
+    while not match('}'):
+        typeName = parseTypeName()
+        typeSize = types[typeName]['size']
+        typeAlignment = types[typeName]['alignment']
+
+        forceId()
+        memberName = token
+        getToken()
+
+        if match('['):
+            if token == ']':
+                count = 0
+            else:
+                forceInteger()
+                count = int(token)
+                getToken()
+            forceMatch(']')
+        else:
+            count = 1
+
+        nBytes = typeSize * count
+        if isStruct:
+            if ofs % typeAlignment:
+                shortage = typeAlignment - (ofs % typeAlignment)
+                warn("%s member %s is %d bytes short of %d-byte alignment"
+                     % (structName, memberName, shortage, typeAlignment))
+                size += shortage
+                ofs += shortage
+            size += nBytes
+            ofs += nBytes
+        else:
+            if nBytes > size:
+                size = nBytes
+        if typeAlignment > alignment:
+            alignment = typeAlignment
+
+        forceMatch(';')
+    if size % alignment:
+        shortage = alignment - (size % alignment)
+        if (structName == "struct ofp_packet_in" and
+            shortage == 2 and
+            memberName == 'data' and
+            count == 0):
+            # This is intentional
+            pass
+        else:
+            warn("%s needs %d bytes of tail padding" % (structName, shortage))
+        size += shortage
+    types[structName] = {"size": size, "alignment": alignment}
+
+def checkStructs():
+    if len(sys.argv) < 2:
+        sys.stderr.write("at least one non-option argument required; "
+                         "use --help for help")
+        sys.exit(1)
+
+    if '--help' in sys.argv:
+        argv0 = sys.argv[0]
+        slash = argv0.rfind('/')
+        if slash:
+            argv0 = argv0[slash + 1:]
+        print '''\
+%(argv0)s, for checking struct and struct member alignment
+usage: %(argv0)s HEADER [HEADER]...
+
+This program reads the header files specified on the command line and
+verifies that all struct members are aligned on natural boundaries
+without any need for the compiler to add additional padding.  It also
+verifies that each struct's size is a multiple of 32 bits (because
+some ABIs for ARM require all structs to be a multiple of 32 bits), or
+64 bits if the struct has any 64-bit members, again without the
+compiler adding additional padding.  Finally, it checks struct size
+assertions using OFP_ASSERT.
+
+Header files are read in the order specified.  #include directives are
+not processed, so specify them in dependency order.
+
+This program is specialized for reading include/openflow/openflow.h
+and include/openflow/nicira-ext.h.  It will not work on arbitrary
+header files without extensions.''' % {"argv0": argv0}
+        sys.exit(0)
+
+    global fileName
+    for fileName in sys.argv[1:]:
+        global inputFile
+        global lineNumber
+        inputFile = open(fileName)
+        lineNumber = 0
+        while getToken():
+            if token in ("#ifdef", "#ifndef", "#include",
+                         "#endif", "#elif", "#else"):
+                skipDirective()
+            elif token == "#define":
+                getToken()
+                name = token
+                if line.startswith('('):
+                    skipDirective()
+                else:
+                    definition = ""
+                    getToken()
+                    while token != '$':
+                        definition += token
+                        getToken()
+                    macros[name] = definition
+            elif token == "enum":
+                while token != ';':
+                    getToken()
+            elif token in ('struct', 'union'):
+                parseStruct()
+            elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'):
+                forceMatch('(')
+                forceMatch('sizeof')
+                forceMatch('(')
+                typeName = parseTypeName()
+                forceMatch(')')
+                forceMatch('=')
+                forceMatch('=')
+                forceInteger()
+                size = int(token)
+                getToken()
+                forceMatch(')')
+                if types[typeName]['size'] != size:
+                    warn("%s is %d bytes long but declared as %d" % (
+                            typeName, types[typeName]['size'], size))
+            else:
+                fatal("parse error")
+        inputFile.close()
+    if anyWarnings:
+        sys.exit(1)
+
+if __name__ == '__main__':
+    checkStructs()
index 55df342..e8497e1 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2008, 2009 Nicira Networks
+# Copyright (c) 2008, 2009, 2010 Nicira Networks
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-AC_PREREQ(2.60)
-AC_INIT(openvswitch, 0.90.7, ovs-bugs@openvswitch.org)
+AC_PREREQ(2.64)
+AC_INIT(openvswitch, 0.99.2, ovs-bugs@openvswitch.org)
 NX_BUILDNR
 AC_CONFIG_SRCDIR([datapath/datapath.c])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_TESTDIR([tests])
 AM_INIT_AUTOMAKE
 
 AC_PROG_CC
@@ -37,6 +38,9 @@ AC_USE_SYSTEM_EXTENSIONS
 AC_C_BIGENDIAN
 AC_SYS_LARGEFILE
 
+AC_SEARCH_LIBS([pow], [m])
+
+OVS_CHECK_COVERAGE
 OVS_CHECK_NDEBUG
 OVS_CHECK_NETLINK
 OVS_CHECK_OPENSSL
@@ -44,8 +48,11 @@ OVS_CHECK_LOGDIR
 OVS_CHECK_CURSES
 OVS_CHECK_LINUX_VT_H
 OVS_CHECK_PCRE
+OVS_CHECK_PYTHON
 OVS_CHECK_IF_PACKET
 OVS_CHECK_STRTOK_R
+AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec],
+  [], [], [[#include <sys/stat.h>]])
 AC_CHECK_FUNCS([mlockall])
 
 OVS_CHECK_PKIDIR
@@ -54,7 +61,6 @@ OVS_CHECK_MALLOC_HOOKS
 OVS_CHECK_VALGRIND
 OVS_CHECK_TTY_LOCK_DIR
 OVS_CHECK_SOCKET_LIBS
-OVS_CHECK_FAULT_LIBS
 
 AC_CHECK_FUNCS([strsignal])
 
@@ -73,15 +79,20 @@ OVS_ENABLE_OPTION([-Wold-style-definition])
 OVS_ENABLE_OPTION([-Wmissing-prototypes])
 OVS_ENABLE_OPTION([-Wmissing-field-initializers])
 OVS_ENABLE_OPTION([-Wno-override-init])
+OVS_CONDITIONAL_CC_OPTION([-Wno-unused], [HAVE_WNO_UNUSED])
 
 AC_ARG_VAR(KARCH, [Kernel Architecture String])
 AC_SUBST(KARCH)
-OVS_CHECK_LINUX(l26, 2.6, KSRC26, L26_ENABLED)
+OVS_CHECK_LINUX26
 
 AC_CONFIG_FILES([Makefile 
 datapath/Makefile 
 datapath/linux-2.6/Kbuild
 datapath/linux-2.6/Makefile
-datapath/linux-2.6/Makefile.main])
+datapath/linux-2.6/Makefile.main
+tests/atlocal])
+
+dnl This makes sure that include/openflow gets created in the build directory.
+AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])
 
 AC_OUTPUT
index 1526328..bef7d10 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Distributed under the terms of the GNU GPL version 2.
- * Copyright (c) 2007, 2008, 2009 Nicira Networks.
+ * Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks.
  *
  * Significant portions of this file may be copied from parts of the Linux
  * kernel, by Linus Torvalds and others.
@@ -15,6 +15,7 @@
 #include <linux/udp.h>
 #include <linux/in6.h>
 #include <linux/if_vlan.h>
+#include <net/inet_ecn.h>
 #include <net/ip.h>
 #include <net/checksum.h>
 #include "datapath.h"
@@ -37,7 +38,7 @@ make_writable(struct sk_buff *skb, unsigned min_headroom, gfp_t gfp)
                        nskb->ip_summed = skb->ip_summed;
                        nskb->csum = skb->csum;
 #endif
-#if defined(CONFIG_XEN) && LINUX_VERSION_CODE == KERNEL_VERSION(2,6,18)
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
                        /* These fields are copied in skb_clone but not in
                         * skb_copy or related functions.  We need to manually
                         * copy them over here. */
@@ -64,11 +65,14 @@ vlan_pull_tag(struct sk_buff *skb)
        struct vlan_ethhdr *vh = vlan_eth_hdr(skb);
        struct ethhdr *eh;
 
-
        /* Verify we were given a vlan packet */
        if (vh->h_vlan_proto != htons(ETH_P_8021Q))
                return skb;
 
+       if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE)
+               skb->csum = csum_sub(skb->csum, csum_partial(skb->data
+                                       + ETH_HLEN, VLAN_HLEN, 0));
+
        memmove(skb->data + VLAN_HLEN, skb->data, 2 * VLAN_ETH_ALEN);
 
        eh = (struct ethhdr *)skb_pull(skb, VLAN_HLEN);
@@ -90,10 +94,11 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
        if (a->type == ODPAT_SET_VLAN_VID) {
                tci = ntohs(a->vlan_vid.vlan_vid);
                mask = VLAN_VID_MASK;
-               key->dl_vlan = htons(tci & mask);
+               key->dl_vlan = a->vlan_vid.vlan_vid;
        } else {
-               tci = a->vlan_pcp.vlan_pcp << 13;
+               tci = a->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT;
                mask = VLAN_PCP_MASK;
+               key->dl_vlan_pcp = a->vlan_pcp.vlan_pcp;
        }
 
        skb = make_writable(skb, VLAN_HLEN, gfp);
@@ -103,7 +108,16 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
        if (skb->protocol == htons(ETH_P_8021Q)) {
                /* Modify vlan id, but maintain other TCI values */
                struct vlan_ethhdr *vh = vlan_eth_hdr(skb);
+               __be16 old_tci = vh->h_vlan_TCI;
+
                vh->h_vlan_TCI = htons((ntohs(vh->h_vlan_TCI) & ~mask) | tci);
+
+               if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE) {
+                       __be16 diff[] = { ~old_tci, vh->h_vlan_TCI };
+
+                       skb->csum = ~csum_partial((char *)diff, sizeof(diff),
+                                               ~skb->csum);
+               }
        } else {
                /* Add vlan header */
 
@@ -112,7 +126,7 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
                 * when we send the packet out on the wire, and it will fail at
                 * that point because skb_checksum_setup() will not look inside
                 * an 802.1Q header. */
-               skb_checksum_setup(skb);
+               vswitch_skb_checksum_setup(skb);
 
                /* GSO is not implemented for packets with an 802.1Q header, so
                 * we have to do segmentation before we add that header.
@@ -143,6 +157,9 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
 
                                segs->next = NULL;
 
+                               /* GSO can change the checksum type so update.*/
+                               compute_ip_summed(segs, true);
+
                                segs = __vlan_put_tag(segs, tci);
                                err = -ENOMEM;
                                if (segs) {
@@ -166,6 +183,7 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
                        } while (segs->next);
 
                        skb = segs;
+                       compute_ip_summed(skb, true);
                }
 
                /* The hardware-accelerated version of vlan_put_tag() works
@@ -176,6 +194,12 @@ modify_vlan_tci(struct datapath *dp, struct sk_buff *skb,
                skb = __vlan_put_tag(skb, tci);
                if (!skb)
                        return ERR_PTR(-ENOMEM);
+
+               /* GSO doesn't fix up the hardware computed checksum so this
+                * will only be hit in the non-GSO case. */
+               if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE)
+                       skb->csum = csum_add(skb->csum, csum_partial(skb->data
+                                               + ETH_HLEN, VLAN_HLEN, 0));
        }
 
        return skb;
@@ -193,14 +217,20 @@ static struct sk_buff *strip_vlan(struct sk_buff *skb,
 }
 
 static struct sk_buff *set_dl_addr(struct sk_buff *skb,
+                                  struct odp_flow_key *key,
                                   const struct odp_action_dl_addr *a,
                                   gfp_t gfp)
 {
        skb = make_writable(skb, 0, gfp);
        if (skb) {
                struct ethhdr *eh = eth_hdr(skb);
-               memcpy(a->type == ODPAT_SET_DL_SRC ? eh->h_source : eh->h_dest,
-                      a->dl_addr, ETH_ALEN);
+               if (a->type == ODPAT_SET_DL_SRC) {
+                       memcpy(eh->h_source, a->dl_addr, ETH_ALEN);
+                       memcpy(key->dl_src, a->dl_addr, ETH_ALEN);
+               } else {
+                       memcpy(eh->h_dest, a->dl_addr, ETH_ALEN);
+                       memcpy(key->dl_dst, a->dl_addr, ETH_ALEN);
+               }
        }
        return skb;
 }
@@ -213,10 +243,11 @@ static void update_csum(__sum16 *sum, struct sk_buff *skb,
                        __be32 from, __be32 to, int pseudohdr)
 {
        __be32 diff[] = { ~from, to };
-       if (skb->ip_summed != CHECKSUM_PARTIAL) {
+
+       if (OVS_CB(skb)->ip_summed != OVS_CSUM_PARTIAL) {
                *sum = csum_fold(csum_partial((char *)diff, sizeof(diff),
                                ~csum_unfold(*sum)));
-               if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
+               if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE && pseudohdr)
                        skb->csum = ~csum_partial((char *)diff, sizeof(diff),
                                                ~skb->csum);
        } else if (pseudohdr)
@@ -248,6 +279,36 @@ static struct sk_buff *set_nw_addr(struct sk_buff *skb,
                }
                update_csum(&nh->check, skb, old, new, 0);
                *f = new;
+
+               if (a->type == ODPAT_SET_NW_SRC)
+                       key->nw_src = a->nw_addr;
+               else
+                       key->nw_dst = a->nw_addr;
+       }
+       return skb;
+}
+
+static struct sk_buff *set_nw_tos(struct sk_buff *skb,
+                                  struct odp_flow_key *key,
+                                  const struct odp_action_nw_tos *a,
+                                  gfp_t gfp)
+{
+       if (key->dl_type != htons(ETH_P_IP))
+               return skb;
+
+       skb = make_writable(skb, 0, gfp);
+       if (skb) {
+               struct iphdr *nh = ip_hdr(skb);
+               u8 *f = &nh->tos;
+               u8 old = *f;
+               u8 new;
+
+               /* Set the DSCP bits and preserve the ECN bits. */
+               new = a->nw_tos | (nh->tos & INET_ECN_MASK);
+               update_csum(&nh->check, skb, htons((uint16_t)old),
+                               htons((uint16_t)new), 0);
+               *f = new;
+               key->nw_tos = a->nw_tos;
        }
        return skb;
 }
@@ -275,9 +336,13 @@ set_tp_port(struct sk_buff *skb, struct odp_flow_key *key,
                u16 *f = a->type == ODPAT_SET_TP_SRC ? &th->source : &th->dest;
                u16 old = *f;
                u16 new = a->tp_port;
-               update_csum((u16*)((u8*)skb->data + check_ofs),
-                           skb, old, new, 1);
+               update_csum((u16*)(skb_transport_header(skb) + check_ofs), 
+                               skb, old, new, 0);
                *f = new;
+               if (a->type == ODPAT_SET_TP_SRC)
+                       key->tp_src = a->tp_port;
+               else
+                       key->tp_dst = a->tp_port;
        }
        return skb;
 }
@@ -302,6 +367,7 @@ int dp_xmit_skb(struct sk_buff *skb)
                return -E2BIG;
        }
 
+       forward_ip_summed(skb);
        dev_queue_xmit(skb);
 
        return len;
@@ -323,7 +389,7 @@ do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
        dev = skb->dev = p->dev;
        if (is_dp_dev(dev))
                dp_dev_recv(dev, skb);
-        else
+       else
                dp_xmit_skb(skb);
        return;
 
@@ -366,6 +432,28 @@ output_control(struct datapath *dp, struct sk_buff *skb, u32 arg, gfp_t gfp)
        return dp_output_control(dp, skb, _ODPL_ACTION_NR, arg);
 }
 
+/* Send a copy of this packet up to the sFlow agent, along with extra
+ * information about what happened to it. */
+static void sflow_sample(struct datapath *dp, struct sk_buff *skb,
+                        const union odp_action *a, int n_actions,
+                        gfp_t gfp, struct net_bridge_port *nbp)
+{
+       struct odp_sflow_sample_header *hdr;
+       unsigned int actlen = n_actions * sizeof(union odp_action);
+       unsigned int hdrlen = sizeof(struct odp_sflow_sample_header);
+       struct sk_buff *nskb;
+
+       nskb = skb_copy_expand(skb, actlen + hdrlen, 0, gfp);
+       if (!nskb)
+               return;
+
+       memcpy(__skb_push(nskb, actlen), a, actlen);
+       hdr = (struct odp_sflow_sample_header*)__skb_push(nskb, hdrlen);
+       hdr->n_actions = n_actions;
+       hdr->sample_pool = atomic_read(&nbp->sflow_pool);
+       dp_output_control(dp, nskb, _ODPL_SFLOW_NR, 0);
+}
+
 /* Execute a list of actions against 'skb'. */
 int execute_actions(struct datapath *dp, struct sk_buff *skb,
                    struct odp_flow_key *key,
@@ -377,7 +465,18 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
         * then freeing the original skbuff is wasteful.  So the following code
         * is slightly obscure just to avoid that. */
        int prev_port = -1;
-       int err = 0;
+       int err;
+
+       if (dp->sflow_probability) {
+               struct net_bridge_port *p = skb->dev->br_port;
+               if (p) {
+                       atomic_inc(&p->sflow_pool);
+                       if (dp->sflow_probability == UINT_MAX ||
+                           net_random() < dp->sflow_probability)
+                               sflow_sample(dp, skb, a, n_actions, gfp, p);
+               }
+       }
+
        for (; n_actions > 0; a++, n_actions--) {
                WARN_ON_ONCE(skb_shared(skb));
                if (prev_port != -1) {
@@ -416,7 +515,7 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
 
                case ODPAT_SET_DL_SRC:
                case ODPAT_SET_DL_DST:
-                       skb = set_dl_addr(skb, &a->dl_addr, gfp);
+                       skb = set_dl_addr(skb, key, &a->dl_addr, gfp);
                        break;
 
                case ODPAT_SET_NW_SRC:
@@ -424,6 +523,10 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
                        skb = set_nw_addr(skb, key, &a->nw_addr, gfp);
                        break;
 
+               case ODPAT_SET_NW_TOS:
+                       skb = set_nw_tos(skb, key, &a->nw_tos, gfp);
+                       break;
+
                case ODPAT_SET_TP_SRC:
                case ODPAT_SET_TP_DST:
                        skb = set_tp_port(skb, key, &a->tp_port, gfp);
@@ -436,5 +539,5 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
                do_output(dp, skb, prev_port);
        else
                kfree_skb(skb);
-       return err;
+       return 0;
 }
index ae4a4f5..cfe0175 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  * Distributed under the terms of the GNU GPL version 2.
  *
  * Significant portions of this file may be copied from parts of the Linux
@@ -12,6 +12,7 @@
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 #include <net/genetlink.h>
+#include "brc_procfs.h"
 #include "openvswitch/brcompat-netlink.h"
 
 /* This code implements a Generic Netlink command BRC_GENL_C_SET_PROC that can
@@ -49,7 +50,7 @@ static struct file_operations brc_fops = {
 static struct proc_dir_entry *proc_vlan_dir;
 static struct proc_dir_entry *proc_bonding_dir;
 
-struct proc_dir_entry *brc_lookup_entry(struct proc_dir_entry *de, const char *name)
+static struct proc_dir_entry *brc_lookup_entry(struct proc_dir_entry *de, const char *name)
 {
        int namelen = strlen(name);
        for (de = de->subdir; de; de = de->next) {
index 92fcecc..b7fb6f8 100644 (file)
@@ -485,8 +485,10 @@ static struct sk_buff *brc_send_command(struct sk_buff *request, struct nlattr *
 
        /* Wait for reply. */
        error = -ETIMEDOUT;
-       if (!wait_for_completion_timeout(&brc_done, BRC_TIMEOUT))
+       if (!wait_for_completion_timeout(&brc_done, BRC_TIMEOUT)) {
+               printk(KERN_WARNING "brcompat: timed out waiting for userspace\n");
                goto error;
+    }
 
        /* Grab reply. */
        spin_lock_irqsave(&brc_lock, flags);
index 1b5f57f..6365f94 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2008, 2009 Nicira Networks.
+ * Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks.
  * Distributed under the terms of the GNU GPL version 2.
  *
  * Significant portions of this file may be copied from parts of the Linux
@@ -41,6 +41,7 @@
 #include <linux/rculist.h>
 #include <linux/workqueue.h>
 #include <linux/dmi.h>
+#include <net/inet_ecn.h>
 #include <net/llc.h>
 
 #include "openvswitch/datapath-protocol.h"
@@ -56,9 +57,7 @@ int (*dp_ioctl_hook)(struct net_device *dev, struct ifreq *rq, int cmd);
 EXPORT_SYMBOL(dp_ioctl_hook);
 
 /* Datapaths.  Protected on the read side by rcu_read_lock, on the write side
- * by dp_mutex.  dp_mutex is almost completely redundant with genl_mutex
- * maintained by the Generic Netlink code, but the timeout path needs mutual
- * exclusion too.
+ * by dp_mutex.
  *
  * dp_mutex nests inside the RTNL lock: if you need both you must take the RTNL
  * lock first.
@@ -83,7 +82,7 @@ struct datapath *get_dp(int dp_idx)
 }
 EXPORT_SYMBOL_GPL(get_dp);
 
-struct datapath *get_dp_locked(int dp_idx)
+static struct datapath *get_dp_locked(int dp_idx)
 {
        struct datapath *dp;
 
@@ -178,7 +177,7 @@ static void release_dp(struct kobject *kobj)
        kfree(dp);
 }
 
-struct kobj_type dp_ktype = {
+static struct kobj_type dp_ktype = {
        .release = release_dp
 };
 
@@ -225,9 +224,7 @@ static int create_dp(int dp_idx, const char __user *devnamep)
 
        /* Initialize kobject for bridge.  This will be added as
         * /sys/class/net/<devname>/brif later, if sysfs is enabled. */
-       kobject_set_name(&dp->ifobj, SYSFS_BRIDGE_PORT_SUBDIR); /* "brif" */
        dp->ifobj.kset = NULL;
-       dp->ifobj.parent = NULL;
        kobject_init(&dp->ifobj, &dp_ktype);
 
        /* Allocate table. */
@@ -257,9 +254,7 @@ static int create_dp(int dp_idx, const char __user *devnamep)
        mutex_unlock(&dp_mutex);
        rtnl_unlock();
 
-#ifdef SUPPORT_SYSFS
        dp_sysfs_add_dp(dp);
-#endif
 
        return 0;
 
@@ -287,9 +282,7 @@ static void do_destroy_dp(struct datapath *dp)
                if (p->port_no != ODPP_LOCAL)
                        dp_del_port(p);
 
-#ifdef SUPPORT_SYSFS
        dp_sysfs_del_dp(dp);
-#endif
 
        rcu_assign_pointer(dps[dp->dp_idx], NULL);
 
@@ -333,8 +326,8 @@ static void release_nbp(struct kobject *kobj)
        kfree(p);
 }
 
-struct kobj_type brport_ktype = {
-#ifdef SUPPORT_SYSFS
+static struct kobj_type brport_ktype = {
+#ifdef CONFIG_SYSFS
        .sysfs_ops = &brport_sysfs_ops,
 #endif
        .release = release_nbp
@@ -357,6 +350,7 @@ static int new_nbp(struct datapath *dp, struct net_device *dev, int port_no)
        p->port_no = port_no;
        p->dp = dp;
        p->dev = dev;
+       atomic_set(&p->sflow_pool, 0);
        if (!is_dp_dev(dev))
                rcu_assign_pointer(dev->br_port, p);
        else {
@@ -365,15 +359,14 @@ static int new_nbp(struct datapath *dp, struct net_device *dev, int port_no)
                 * in dp_frame_hook().  In turn dp_frame_hook() can reject them
                 * back to network stack, but that's a waste of time. */
        }
+       dev_disable_lro(dev);
        rcu_assign_pointer(dp->ports[port_no], p);
        list_add_rcu(&p->node, &dp->port_list);
        dp->n_ports++;
 
        /* Initialize kobject for bridge.  This will be added as
         * /sys/class/net/<devname>/brport later, if sysfs is enabled. */
-       kobject_set_name(&p->kobj, SYSFS_BRIDGE_PORT_ATTR); /* "brport" */
        p->kobj.kset = NULL;
-       p->kobj.parent = &p->dev->NETDEV_DEV_MEMBER.kobj;
        kobject_init(&p->kobj, &brport_ktype);
 
        dp_ifinfo_notify(RTM_NEWLINK, p);
@@ -393,11 +386,6 @@ static int add_port(int dp_idx, struct odp_port __user *portp)
        if (copy_from_user(&port, portp, sizeof port))
                goto out;
        port.devname[IFNAMSIZ - 1] = '\0';
-       port_no = port.port;
-
-       err = -EINVAL;
-       if (port_no < 0 || port_no >= DP_MAX_PORTS)
-               goto out;
 
        rtnl_lock();
        dp = get_dp_locked(dp_idx);
@@ -405,10 +393,13 @@ static int add_port(int dp_idx, struct odp_port __user *portp)
        if (!dp)
                goto out_unlock_rtnl;
 
-       err = -EEXIST;
-       if (dp->ports[port_no])
-               goto out_unlock_dp;
+       for (port_no = 1; port_no < DP_MAX_PORTS; port_no++)
+               if (!dp->ports[port_no])
+                       goto got_port_no;
+       err = -EFBIG;
+       goto out_unlock_dp;
 
+got_port_no:
        if (!(port.flags & ODP_PORT_INTERNAL)) {
                err = -ENODEV;
                dev = dev_get_by_name(&init_net, port.devname);
@@ -431,9 +422,10 @@ static int add_port(int dp_idx, struct odp_port __user *portp)
        if (err)
                goto out_put;
 
-#ifdef SUPPORT_SYSFS
+       set_dp_devs_mtu(dp, dev);
        dp_sysfs_add_if(dp->ports[port_no]);
-#endif
+
+       err = __put_user(port_no, &portp->port);
 
 out_put:
        dev_put(dev);
@@ -449,10 +441,8 @@ int dp_del_port(struct net_bridge_port *p)
 {
        ASSERT_RTNL();
 
-#ifdef SUPPORT_SYSFS
        if (p->port_no != ODPP_LOCAL)
                dp_sysfs_del_if(p);
-#endif
        dp_ifinfo_notify(RTM_DELLINK, p);
 
        p->dp->n_ports--;
@@ -517,6 +507,11 @@ out:
 static void
 do_port_input(struct net_bridge_port *p, struct sk_buff *skb) 
 {
+       /* LRO isn't suitable for bridging.  We turn it off but make sure
+        * that it wasn't reactivated. */
+       if (skb_warn_if_lro(skb))
+               return;
+
        /* Make our own copy of the packet.  Otherwise we will mangle the
         * packet for anyone who came before us (e.g. tcpdump via AF_PACKET).
         * (No one comes after us, since we tell handle_bridge() that we took
@@ -541,6 +536,8 @@ void dp_process_received_packet(struct sk_buff *skb, struct net_bridge_port *p)
 
        WARN_ON_ONCE(skb_shared(skb));
 
+       compute_ip_summed(skb, false);
+
        /* BHs are off so we don't have to use get_cpu()/put_cpu() here. */
        stats = percpu_ptr(dp->stats_percpu, smp_processor_id());
 
@@ -588,10 +585,11 @@ static int dp_frame_hook(struct net_bridge_port *p, struct sk_buff **pskb)
 #error
 #endif
 
-#ifdef CONFIG_XEN
-/* This code is copied verbatim from net/dev/core.c in Xen's
- * linux-2.6.18-92.1.10.el5.xs5.0.0.394.644.  We can't call those functions
- * directly because they aren't exported. */
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
+/* This code is based on a skb_checksum_setup from net/dev/core.c from a
+ * combination of Lenny's 2.6.26 Xen kernel and Xen's
+ * linux-2.6.18-92.1.10.el5.xs5.0.0.394.644.  We can't call this function
+ * directly because it isn't exported in all versions. */
 static int skb_pull_up_to(struct sk_buff *skb, void *ptr)
 {
        if (ptr < (void *)skb->tail)
@@ -604,38 +602,252 @@ static int skb_pull_up_to(struct sk_buff *skb, void *ptr)
        }
 }
 
-int skb_checksum_setup(struct sk_buff *skb)
+int vswitch_skb_checksum_setup(struct sk_buff *skb)
 {
-       if (skb->proto_csum_blank) {
-               if (skb->protocol != htons(ETH_P_IP))
-                       goto out;
-               if (!skb_pull_up_to(skb, skb->nh.iph + 1))
-                       goto out;
-               skb->h.raw = (unsigned char *)skb->nh.iph + 4*skb->nh.iph->ihl;
-               switch (skb->nh.iph->protocol) {
-               case IPPROTO_TCP:
-                       skb->csum = offsetof(struct tcphdr, check);
-                       break;
-               case IPPROTO_UDP:
-                       skb->csum = offsetof(struct udphdr, check);
-                       break;
-               default:
-                       if (net_ratelimit())
-                               printk(KERN_ERR "Attempting to checksum a non-"
-                                      "TCP/UDP packet, dropping a protocol"
-                                      " %d packet", skb->nh.iph->protocol);
-                       goto out;
-               }
-               if (!skb_pull_up_to(skb, skb->h.raw + skb->csum + 2))
-                       goto out;
-               skb->ip_summed = CHECKSUM_HW;
-               skb->proto_csum_blank = 0;
+       struct iphdr *iph;
+       unsigned char *th;
+       int err = -EPROTO;
+       __u16 csum_start, csum_offset;
+
+       if (!skb->proto_csum_blank)
+               return 0;
+
+       if (skb->protocol != htons(ETH_P_IP))
+               goto out;
+
+       if (!skb_pull_up_to(skb, skb_network_header(skb) + sizeof(struct iphdr)))
+               goto out;
+
+       iph = ip_hdr(skb);
+       th = skb_network_header(skb) + 4 * iph->ihl;
+
+       csum_start = th - skb->head;
+       switch (iph->protocol) {
+       case IPPROTO_TCP:
+               csum_offset = offsetof(struct tcphdr, check);
+               break;
+       case IPPROTO_UDP:
+               csum_offset = offsetof(struct udphdr, check);
+               break;
+       default:
+               if (net_ratelimit())
+                       printk(KERN_ERR "Attempting to checksum a non-"
+                              "TCP/UDP packet, dropping a protocol"
+                              " %d packet", iph->protocol);
+               goto out;
        }
-       return 0;
+
+       if (!skb_pull_up_to(skb, th + csum_offset + 2))
+               goto out;
+
+       skb->ip_summed = CHECKSUM_PARTIAL;
+       skb->proto_csum_blank = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+       skb->csum_start = csum_start;
+       skb->csum_offset = csum_offset;
+#else
+       skb_set_transport_header(skb, csum_start - skb_headroom(skb));
+       skb->csum = csum_offset;
+#endif
+
+       err = 0;
+
 out:
-       return -EPROTO;
+       return err;
+}
+#endif /* CONFIG_XEN && HAVE_PROTO_DATA_VALID */
+
+ /* Types of checksums that we can receive (these all refer to L4 checksums):
+ * 1. CHECKSUM_NONE: Device that did not compute checksum, contains full
+ *     (though not verified) checksum in packet but not in skb->csum.  Packets
+ *     from the bridge local port will also have this type.
+ * 2. CHECKSUM_COMPLETE (CHECKSUM_HW): Good device that computes checksums,
+ *     also the GRE module.  This is the same as CHECKSUM_NONE, except it has
+ *     a valid skb->csum.  Importantly, both contain a full checksum (not
+ *     verified) in the packet itself.  The only difference is that if the
+ *     packet gets to L4 processing on this machine (not in DomU) we won't
+ *     have to recompute the checksum to verify.  Most hardware devices do not
+ *     produce packets with this type, even if they support receive checksum
+ *     offloading (they produce type #5).
+ * 3. CHECKSUM_PARTIAL (CHECKSUM_HW): Packet without full checksum and needs to
+ *     be computed if it is sent off box.  Unfortunately on earlier kernels,
+ *     this case is impossible to distinguish from #2, despite having opposite
+ *     meanings.  Xen adds an extra field on earlier kernels (see #4) in order
+ *     to distinguish the different states.  The only real user of this type
+ *     with bridging is Xen (on later kernels).
+ * 4. CHECKSUM_UNNECESSARY (with proto_csum_blank true): This packet was
+ *     generated locally by a Xen DomU and has a partial checksum.  If it is
+ *     handled on this machine (Dom0 or DomU), then the checksum will not be
+ *     computed.  If it goes off box, the checksum in the packet needs to be
+ *     completed.  Calling skb_checksum_setup converts this to CHECKSUM_HW
+ *     (CHECKSUM_PARTIAL) so that the checksum can be completed.  In later
+ *     kernels, this combination is replaced with CHECKSUM_PARTIAL.
+ * 5. CHECKSUM_UNNECESSARY (with proto_csum_blank false): Packet with a correct
+ *     full checksum or using a protocol without a checksum.  skb->csum is
+ *     undefined.  This is common from devices with receive checksum
+ *     offloading.  This is somewhat similar to CHECKSUM_NONE, except that
+ *     nobody will try to verify the checksum with CHECKSUM_UNNECESSARY.
+ *
+ * Note that on earlier kernels, CHECKSUM_COMPLETE and CHECKSUM_PARTIAL are
+ * both defined as CHECKSUM_HW.  Normally the meaning of CHECKSUM_HW is clear
+ * based on whether it is on the transmit or receive path.  After the datapath
+ * it will be intepreted as CHECKSUM_PARTIAL.  If the packet already has a
+ * checksum, we will panic.  Since we can receive packets with checksums, we
+ * assume that all CHECKSUM_HW packets have checksums and map them to
+ * CHECKSUM_NONE, which has a similar meaning (the it is only different if the
+ * packet is processed by the local IP stack, in which case it will need to
+ * be reverified).  If we receive a packet with CHECKSUM_HW that really means
+ * CHECKSUM_PARTIAL, it will be sent with the wrong checksum.  However, there
+ * shouldn't be any devices that do this with bridging.
+ *
+ * The bridge has similar behavior and this function closely resembles
+ * skb_forward_csum().  It is slightly different because we are only concerned
+ * with bridging and not other types of forwarding and can get away with
+ * slightly more optimal behavior.*/
+void
+compute_ip_summed(struct sk_buff *skb, bool xmit)
+{
+       /* For our convenience these defines change repeatedly between kernel
+        * versions, so we can't just copy them over... */
+       switch (skb->ip_summed) {
+       case CHECKSUM_NONE:
+               OVS_CB(skb)->ip_summed = OVS_CSUM_NONE;
+               break;
+       case CHECKSUM_UNNECESSARY:
+               OVS_CB(skb)->ip_summed = OVS_CSUM_UNNECESSARY;
+               break;
+#ifdef CHECKSUM_HW
+       /* In theory this could be either CHECKSUM_PARTIAL or CHECKSUM_COMPLETE.
+        * However, we should only get CHECKSUM_PARTIAL packets from Xen, which
+        * uses some special fields to represent this (see below).  Since we
+        * can only make one type work, pick the one that actually happens in
+        * practice.
+        *
+        * The one exception to this is if we are on the transmit path
+        * (basically after skb_checksum_setup() has been run) the type has
+        * already been converted, so we should stay with that. */
+       case CHECKSUM_HW:
+               if (!xmit)
+                       OVS_CB(skb)->ip_summed = OVS_CSUM_COMPLETE;
+               else
+                       OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL;
+
+               break;
+#else
+       case CHECKSUM_COMPLETE:
+               OVS_CB(skb)->ip_summed = OVS_CSUM_COMPLETE;
+               break;
+       case CHECKSUM_PARTIAL:
+               OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL;
+               break;
+#endif
+       default:
+               printk(KERN_ERR "openvswitch: unknown checksum type %d\n",
+                      skb->ip_summed);
+               /* None seems the safest... */
+               OVS_CB(skb)->ip_summed = OVS_CSUM_NONE;
+       }       
+
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
+       /* Xen has a special way of representing CHECKSUM_PARTIAL on older
+        * kernels. It should not be set on the transmit path though. */
+       if (skb->proto_csum_blank)
+               OVS_CB(skb)->ip_summed = OVS_CSUM_PARTIAL;
+
+       WARN_ON_ONCE(skb->proto_csum_blank && xmit);
+#endif
 }
+
+void
+forward_ip_summed(struct sk_buff *skb)
+{
+#ifdef CHECKSUM_HW
+       if (OVS_CB(skb)->ip_summed == OVS_CSUM_COMPLETE)
+               skb->ip_summed = CHECKSUM_NONE;
 #endif
+}
+
+/* Append each packet in 'skb' list to 'queue'.  There will be only one packet
+ * unless we broke up a GSO packet. */
+static int
+queue_control_packets(struct sk_buff *skb, struct sk_buff_head *queue,
+                     int queue_no, u32 arg)
+{
+       struct sk_buff *nskb;
+       int port_no;
+       int err;
+
+       port_no = ODPP_LOCAL;
+       if (skb->dev) {
+               if (skb->dev->br_port)
+                       port_no = skb->dev->br_port->port_no;
+               else if (is_dp_dev(skb->dev))
+                       port_no = dp_dev_priv(skb->dev)->port_no;
+       }
+
+       do {
+               struct odp_msg *header;
+
+               nskb = skb->next;
+               skb->next = NULL;
+
+               /* If a checksum-deferred packet is forwarded to the
+                * controller, correct the pointers and checksum.  This happens
+                * on a regular basis only on Xen, on which VMs can pass up
+                * packets that do not have their checksum computed.
+                */
+               err = vswitch_skb_checksum_setup(skb);
+               if (err)
+                       goto err_kfree_skbs;
+#ifndef CHECKSUM_HW
+               if (skb->ip_summed == CHECKSUM_PARTIAL) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+                       /* Until 2.6.22, the start of the transport header was
+                        * also the start of data to be checksummed.  Linux
+                        * 2.6.22 introduced the csum_start field for this
+                        * purpose, but we should point the transport header to
+                        * it anyway for backward compatibility, as
+                        * dev_queue_xmit() does even in 2.6.28. */
+                       skb_set_transport_header(skb, skb->csum_start -
+                                                skb_headroom(skb));
+#endif
+                       err = skb_checksum_help(skb);
+                       if (err)
+                               goto err_kfree_skbs;
+               }
+#else
+               if (skb->ip_summed == CHECKSUM_HW) {
+                       err = skb_checksum_help(skb, 0);
+                       if (err)
+                               goto err_kfree_skbs;
+               }
+#endif
+
+               err = skb_cow(skb, sizeof *header);
+               if (err)
+                       goto err_kfree_skbs;
+
+               header = (struct odp_msg*)__skb_push(skb, sizeof *header);
+               header->type = queue_no;
+               header->length = skb->len;
+               header->port = port_no;
+               header->reserved = 0;
+               header->arg = arg;
+               skb_queue_tail(queue, skb);
+
+               skb = nskb;
+       } while (skb);
+       return 0;
+
+err_kfree_skbs:
+       kfree_skb(skb);
+       while ((skb = nskb) != NULL) {
+               nskb = skb->next;
+               kfree_skb(skb);
+       }
+       return err;
+}
 
 int
 dp_output_control(struct datapath *dp, struct sk_buff *skb, int queue_no,
@@ -643,50 +855,16 @@ dp_output_control(struct datapath *dp, struct sk_buff *skb, int queue_no,
 {
        struct dp_stats_percpu *stats;
        struct sk_buff_head *queue;
-       int port_no;
        int err;
 
        WARN_ON_ONCE(skb_shared(skb));
-       BUG_ON(queue_no != _ODPL_MISS_NR && queue_no != _ODPL_ACTION_NR);
-
+       BUG_ON(queue_no != _ODPL_MISS_NR && queue_no != _ODPL_ACTION_NR && queue_no != _ODPL_SFLOW_NR);
        queue = &dp->queues[queue_no];
        err = -ENOBUFS;
        if (skb_queue_len(queue) >= DP_MAX_QUEUE_LEN)
                goto err_kfree_skb;
 
-       /* If a checksum-deferred packet is forwarded to the controller,
-        * correct the pointers and checksum.  This happens on a regular basis
-        * only on Xen (the CHECKSUM_HW case), on which VMs can pass up packets
-        * that do not have their checksum computed.  We also implement it for
-        * the non-Xen case, but it is difficult to trigger or test this case
-        * there, hence the WARN_ON_ONCE().
-        */
-       err = skb_checksum_setup(skb);
-       if (err)
-               goto err_kfree_skb;
-#ifndef CHECKSUM_HW
-       if (skb->ip_summed == CHECKSUM_PARTIAL) {
-               WARN_ON_ONCE(1);
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
-               /* Until 2.6.22, the start of the transport header was also the
-                * start of data to be checksummed.  Linux 2.6.22 introduced
-                * the csum_start field for this purpose, but we should point
-                * the transport header to it anyway for backward
-                * compatibility, as dev_queue_xmit() does even in 2.6.28. */
-               skb_set_transport_header(skb, skb->csum_start -
-                                             skb_headroom(skb));
-#endif
-               err = skb_checksum_help(skb);
-               if (err)
-                       goto err_kfree_skb;
-       }
-#else
-       if (skb->ip_summed == CHECKSUM_HW) {
-               err = skb_checksum_help(skb, 0);
-               if (err)
-                       goto err_kfree_skb;
-       }
-#endif
+       forward_ip_summed(skb);
 
        /* Break apart GSO packets into their component pieces.  Otherwise
         * userspace may try to stuff a 64kB packet into a 1500-byte MTU. */
@@ -705,45 +883,9 @@ dp_output_control(struct datapath *dp, struct sk_buff *skb, int queue_no,
                }
        }
 
-       /* Figure out port number. */
-       port_no = ODPP_LOCAL;
-       if (skb->dev) {
-               if (skb->dev->br_port)
-                       port_no = skb->dev->br_port->port_no;
-               else if (is_dp_dev(skb->dev))
-                       port_no = dp_dev_priv(skb->dev)->port_no;
-       }
-
-       /* Append each packet to queue.  There will be only one packet unless
-        * we broke up a GSO packet above. */
-       do {
-               struct odp_msg *header;
-               struct sk_buff *nskb = skb->next;
-               skb->next = NULL;
-
-               err = skb_cow(skb, sizeof *header);
-               if (err) {
-                       while (nskb) {
-                               kfree_skb(skb);
-                               skb = nskb;
-                               nskb = skb->next;
-                       }
-                       goto err_kfree_skb;
-               }
-
-               header = (struct odp_msg*)__skb_push(skb, sizeof *header);
-               header->type = queue_no;
-               header->length = skb->len;
-               header->port = port_no;
-               header->reserved = 0;
-               header->arg = arg;
-               skb_queue_tail(queue, skb);
-
-               skb = nskb;
-       } while (skb);
-
+       err = queue_control_packets(skb, queue, queue_no, arg);
        wake_up_interruptible(&dp->waitqueue);
-       return 0;
+       return err;
 
 err_kfree_skb:
        kfree_skb(skb);
@@ -789,6 +931,11 @@ static int validate_actions(const struct sw_flow_actions *actions)
                                return -EINVAL;
                        break;
 
+               case ODPAT_SET_NW_TOS:
+                       if (a->nw_tos.nw_tos & INET_ECN_MASK)
+                               return -EINVAL;
+                       break;
+
                default:
                        if (a->type >= ODPAT_N_ACTIONS)
                                return -EOPNOTSUPP;
@@ -838,6 +985,7 @@ static void get_stats(struct sw_flow *flow, struct odp_flow_stats *stats)
        stats->n_bytes = flow->byte_count;
        stats->ip_tos = flow->ip_tos;
        stats->tcp_flags = flow->tcp_flags;
+       stats->error = 0;
 }
 
 static void clear_stats(struct sw_flow *flow)
@@ -860,7 +1008,7 @@ static int put_flow(struct datapath *dp, struct odp_flow_put __user *ufp)
        error = -EFAULT;
        if (copy_from_user(&uf, ufp, sizeof(struct odp_flow_put)))
                goto error;
-       uf.flow.key.reserved = 0;
+       memset(uf.flow.key.reserved, 0, sizeof uf.flow.key.reserved);
 
        table = rcu_dereference(dp->table);
        flow = dp_table_lookup(table, &uf.flow.key);
@@ -965,8 +1113,6 @@ static int put_actions(const struct sw_flow *flow, struct odp_flow __user *ufp)
 
        if (!n_actions)
                return 0;
-       if (ufp->n_actions > INT_MAX / sizeof(union odp_action))
-               return -EINVAL;
 
        sf_acts = rcu_dereference(flow->sf_acts);
        if (__put_user(sf_acts->n_actions, &ufp->n_actions) ||
@@ -997,9 +1143,7 @@ static int answer_query(struct sw_flow *flow, u32 query_flags,
        return put_actions(flow, ufp);
 }
 
-static int del_or_query_flow(struct datapath *dp,
-                            struct odp_flow __user *ufp,
-                            unsigned int cmd)
+static int del_flow(struct datapath *dp, struct odp_flow __user *ufp)
 {
        struct dp_table *table = rcu_dereference(dp->table);
        struct odp_flow uf;
@@ -1009,36 +1153,31 @@ static int del_or_query_flow(struct datapath *dp,
        error = -EFAULT;
        if (copy_from_user(&uf, ufp, sizeof uf))
                goto error;
-       uf.key.reserved = 0;
+       memset(uf.key.reserved, 0, sizeof uf.key.reserved);
 
        flow = dp_table_lookup(table, &uf.key);
        error = -ENOENT;
        if (!flow)
                goto error;
 
-       if (cmd == ODP_FLOW_DEL) {
-               /* XXX redundant lookup */
-               error = dp_table_delete(table, flow);
-               if (error)
-                       goto error;
+       /* XXX redundant lookup */
+       error = dp_table_delete(table, flow);
+       if (error)
+               goto error;
 
-               /* XXX These statistics might lose a few packets, since other
-                * CPUs can be using this flow.  We used to synchronize_rcu()
-                * to make sure that we get completely accurate stats, but that
-                * blows our performance, badly. */
-               dp->n_flows--;
-               error = answer_query(flow, 0, ufp);
-               flow_deferred_free(flow);
-       } else {
-               error = answer_query(flow, uf.flags, ufp);
-       }
+       /* XXX These statistics might lose a few packets, since other CPUs can
+        * be using this flow.  We used to synchronize_rcu() to make sure that
+        * we get completely accurate stats, but that blows our performance,
+        * badly. */
+       dp->n_flows--;
+       error = answer_query(flow, 0, ufp);
+       flow_deferred_free(flow);
 
 error:
        return error;
 }
 
-static int query_multiple_flows(struct datapath *dp,
-                               const struct odp_flowvec *flowvec)
+static int query_flows(struct datapath *dp, const struct odp_flowvec *flowvec)
 {
        struct dp_table *table = rcu_dereference(dp->table);
        int i;
@@ -1050,13 +1189,13 @@ static int query_multiple_flows(struct datapath *dp,
 
                if (__copy_from_user(&uf, ufp, sizeof uf))
                        return -EFAULT;
-               uf.key.reserved = 0;
+               memset(uf.key.reserved, 0, sizeof uf.key.reserved);
 
                flow = dp_table_lookup(table, &uf.key);
                if (!flow)
-                       error = __clear_user(&ufp->stats, sizeof ufp->stats);
+                       error = __put_user(ENOENT, &ufp->stats.error);
                else
-                       error = answer_query(flow, 0, ufp);
+                       error = answer_query(flow, uf.flags, ufp);
                if (error)
                        return -EFAULT;
        }
@@ -1177,9 +1316,9 @@ static int do_execute(struct datapath *dp, const struct odp_execute *executep)
        skb_reset_mac_header(skb);
        eth = eth_hdr(skb);
 
-    /* Normally, setting the skb 'protocol' field would be handled by a
-     * call to eth_type_trans(), but it assumes there's a sending
-     * device, which we may not have. */
+       /* Normally, setting the skb 'protocol' field would be handled by a
+        * call to eth_type_trans(), but it assumes there's a sending
+        * device, which we may not have. */
        if (ntohs(eth->h_proto) >= 1536)
                skb->protocol = eth->h_proto;
        else
@@ -1199,8 +1338,7 @@ error:
        return err;
 }
 
-static int
-get_dp_stats(struct datapath *dp, struct odp_stats __user *statsp)
+static int get_dp_stats(struct datapath *dp, struct odp_stats __user *statsp)
 {
        struct odp_stats stats;
        int i;
@@ -1248,6 +1386,29 @@ int dp_min_mtu(const struct datapath *dp)
        return mtu ? mtu : ETH_DATA_LEN;
 }
 
+/* Sets the MTU of all datapath devices to the minimum of the ports. 'dev'
+ * is the device whose MTU may have changed.  Must be called with RTNL lock
+ * and dp_mutex. */
+void set_dp_devs_mtu(const struct datapath *dp, struct net_device *dev)
+{
+       struct net_bridge_port *p;
+       int mtu;
+
+       ASSERT_RTNL();
+
+       if (is_dp_dev(dev))
+               return;
+
+       mtu = dp_min_mtu(dp);
+
+       list_for_each_entry_rcu (p, &dp->port_list, node) {
+               struct net_device *br_dev = p->dev;
+
+               if (is_dp_dev(br_dev))
+                       dev_set_mtu(br_dev, mtu);
+       }
+}
+
 static int
 put_port(const struct net_bridge_port *p, struct odp_port __user *uop)
 {
@@ -1315,7 +1476,7 @@ list_ports(struct datapath *dp, struct odp_portvec __user *pvp)
                                break;
                }
        }
-       return put_user(idx, &pvp->n_ports);
+       return put_user(dp->n_ports, &pvp->n_ports);
 }
 
 /* RCU callback for freeing a dp_port_group */
@@ -1404,29 +1565,34 @@ static long openvswitch_ioctl(struct file *f, unsigned int cmd,
        int dp_idx = iminor(f->f_dentry->d_inode);
        struct datapath *dp;
        int drop_frags, listeners, port_no;
+       unsigned int sflow_probability;
        int err;
 
        /* Handle commands with special locking requirements up front. */
        switch (cmd) {
        case ODP_DP_CREATE:
-               return create_dp(dp_idx, (char __user *)argp);
+               err = create_dp(dp_idx, (char __user *)argp);
+               goto exit;
 
        case ODP_DP_DESTROY:
-               return destroy_dp(dp_idx);
+               err = destroy_dp(dp_idx);
+               goto exit;
 
        case ODP_PORT_ADD:
-               return add_port(dp_idx, (struct odp_port __user *)argp);
+               err = add_port(dp_idx, (struct odp_port __user *)argp);
+               goto exit;
 
        case ODP_PORT_DEL:
                err = get_user(port_no, (int __user *)argp);
-               if (err)
-                       break;
-               return del_port(dp_idx, port_no);
+               if (!err)
+                       err = del_port(dp_idx, port_no);
+               goto exit;
        }
 
        dp = get_dp_locked(dp_idx);
+       err = -ENODEV;
        if (!dp)
-               return -ENODEV;
+               goto exit;
 
        switch (cmd) {
        case ODP_DP_STATS:
@@ -1463,6 +1629,16 @@ static long openvswitch_ioctl(struct file *f, unsigned int cmd,
                set_listen_mask(f, listeners);
                break;
 
+       case ODP_GET_SFLOW_PROBABILITY:
+               err = put_user(dp->sflow_probability, (unsigned int __user *)argp);
+               break;
+
+       case ODP_SET_SFLOW_PROBABILITY:
+               err = get_user(sflow_probability, (unsigned int __user *)argp);
+               if (!err)
+                       dp->sflow_probability = sflow_probability;
+               break;
+
        case ODP_PORT_QUERY:
                err = query_port(dp, (struct odp_port __user *)argp);
                break;
@@ -1488,13 +1664,11 @@ static long openvswitch_ioctl(struct file *f, unsigned int cmd,
                break;
 
        case ODP_FLOW_DEL:
-       case ODP_FLOW_GET:
-               err = del_or_query_flow(dp, (struct odp_flow __user *)argp,
-                                       cmd);
+               err = del_flow(dp, (struct odp_flow __user *)argp);
                break;
 
-       case ODP_FLOW_GET_MULTIPLE:
-               err = do_flowvec_ioctl(dp, argp, query_multiple_flows);
+       case ODP_FLOW_GET:
+               err = do_flowvec_ioctl(dp, argp, query_flows);
                break;
 
        case ODP_FLOW_LIST:
@@ -1510,6 +1684,7 @@ static long openvswitch_ioctl(struct file *f, unsigned int cmd,
                break;
        }
        mutex_unlock(&dp->mutex);
+exit:
        return err;
 }
 
@@ -1606,6 +1781,8 @@ struct file_operations openvswitch_fops = {
 };
 
 static int major;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
 static struct llc_sap *dp_stp_sap;
 
 static int dp_stp_rcv(struct sk_buff *skb, struct net_device *dev,
@@ -1618,12 +1795,8 @@ static int dp_stp_rcv(struct sk_buff *skb, struct net_device *dev,
        return 0;
 }
 
-static int __init dp_init(void)
+static int dp_avoid_bridge_init(void)
 {
-       int err;
-
-       printk("Open vSwitch %s, built "__DATE__" "__TIME__"\n", VERSION BUILDNR);
-
        /* Register to receive STP packets because the bridge module also
         * attempts to do so.  Since there can only be a single listener for a
         * given protocol, this provides mutual exclusion against the bridge
@@ -1634,6 +1807,43 @@ static int __init dp_init(void)
                printk(KERN_ERR "openvswitch: can't register sap for STP (probably the bridge module is loaded)\n");
                return -EADDRINUSE;
        }
+       return 0;
+}
+
+static void dp_avoid_bridge_exit(void)
+{
+       llc_sap_put(dp_stp_sap);
+}
+#else  /* Linux 2.6.27 or later. */
+static int dp_avoid_bridge_init(void)
+{
+       /* Linux 2.6.27 introduces a way for multiple clients to register for
+        * STP packets, which interferes with what we try to do above.
+        * Instead, just check whether there's a bridge hook defined.  This is
+        * not as safe--the bridge module is willing to load over the top of
+        * us--but it provides a little bit of protection. */
+       if (br_handle_frame_hook) {
+               printk(KERN_ERR "openvswitch: bridge module is loaded, cannot load over it\n");
+               return -EADDRINUSE;
+       }
+       return 0;
+}
+
+static void dp_avoid_bridge_exit(void)
+{
+       /* Nothing to do. */
+}
+#endif /* Linux 2.6.27 or later */
+
+static int __init dp_init(void)
+{
+       int err;
+
+       printk("Open vSwitch %s, built "__DATE__" "__TIME__"\n", VERSION BUILDNR);
+
+       err = dp_avoid_bridge_init();
+       if (err)
+               return err;
 
        err = flow_init();
        if (err)
@@ -1668,7 +1878,7 @@ static void dp_cleanup(void)
        unregister_netdevice_notifier(&dp_device_notifier);
        flow_exit();
        br_handle_frame_hook = NULL;
-       llc_sap_put(dp_stp_sap);
+       dp_avoid_bridge_exit();
 }
 
 module_init(dp_init);
index 88bdbc6..38c8475 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  * Distributed under the terms of the GNU GPL version 2.
  *
  * Significant portions of this file may be copied from parts of the Linux
 #include <asm/page.h>
 #include <linux/kernel.h>
 #include <linux/mutex.h>
-#include <linux/netlink.h>
 #include <linux/netdevice.h>
 #include <linux/workqueue.h>
 #include <linux/skbuff.h>
+#include <linux/version.h>
 #include "flow.h"
 #include "dp_sysfs.h"
 
@@ -79,9 +79,22 @@ struct dp_bucket {
        struct sw_flow *flows[];
 };
 
-#define DP_N_QUEUES 2
+#define DP_N_QUEUES 3
 #define DP_MAX_QUEUE_LEN 100
 
+/**
+ * struct dp_stats_percpu - per-cpu packet processing statistics for a given
+ * datapath.
+ * @n_frags: Number of IP fragments processed by datapath.
+ * @n_hit: Number of received packets for which a matching flow was found in
+ * the flow table.
+ * @n_miss: Number of received packets that had no matching flow in the flow
+ * table.  The sum of @n_hit and @n_miss is the number of packets that have
+ * been received by the datapath.
+ * @n_lost: Number of received packets that had no matching flow in the flow
+ * table that could not be sent to userspace (normally due to an overflow in
+ * one of the datapath's queues).
+ */
 struct dp_stats_percpu {
        u64 n_frags;
        u64 n_hit;
@@ -95,10 +108,29 @@ struct dp_port_group {
        u16 ports[];
 };
 
+/**
+ * struct datapath - datapath for flow-based packet switching
+ * @mutex: Mutual exclusion for ioctls.
+ * @dp_idx: Datapath number (index into the dps[] array in datapath.c).
+ * @ifobj: Represents /sys/class/net/<devname>/brif.
+ * @drop_frags: Drop all IP fragments if nonzero.
+ * @queues: %DP_N_QUEUES sets of queued packets for userspace to handle.
+ * @waitqueue: Waitqueue, for waiting for new packets in @queues.
+ * @n_flows: Number of flows currently in flow table.
+ * @table: Current flow table (RCU protected).
+ * @groups: Port groups, used by ODPAT_OUTPUT_GROUP action (RCU protected).
+ * @n_ports: Number of ports currently in @ports.
+ * @ports: Map from port number to &struct net_bridge_port.  %ODPP_LOCAL port
+ * always exists, other ports may be %NULL.
+ * @port_list: List of all ports in @ports in arbitrary order.
+ * @stats_percpu: Per-CPU datapath statistics.
+ * @sflow_probability: Number of packets out of UINT_MAX to sample to the
+ * %ODPL_SFLOW queue, e.g. (@sflow_probability/UINT_MAX) is the probability of
+ * sampling a given packet.
+ */
 struct datapath {
        struct mutex mutex;
        int dp_idx;
-
        struct kobject ifobj;
 
        int drop_frags;
@@ -117,21 +149,56 @@ struct datapath {
        /* Switch ports. */
        unsigned int n_ports;
        struct net_bridge_port *ports[DP_MAX_PORTS];
-       struct list_head port_list; /* All ports, including local_port. */
+       struct list_head port_list;
 
        /* Stats. */
        struct dp_stats_percpu *stats_percpu;
+
+       /* sFlow Sampling */
+       unsigned int sflow_probability;
 };
 
+/**
+ * struct net_bridge_port - one port within a datapath
+ * @port_no: Index into @dp's @ports array.
+ * @dp: Datapath to which this port belongs.
+ * @dev: The network device attached to this port.  The @br_port member in @dev
+ * points back to this &struct net_bridge_port.
+ * @kobj: Represents /sys/class/net/<devname>/brport.
+ * @linkname: The name of the link from /sys/class/net/<datapath>/brif to this
+ * &struct net_bridge_port.  (We keep this around so that we can delete it
+ * if @dev gets renamed.)  Set to the null string when no link exists.
+ * @node: Element in @dp's @port_list.
+ * @sflow_pool: Number of packets that were candidates for sFlow sampling,
+ * regardless of whether they were actually chosen and sent down to userspace.
+ */
 struct net_bridge_port {
        u16 port_no;
        struct datapath *dp;
        struct net_device *dev;
        struct kobject kobj;
        char linkname[IFNAMSIZ];
-       struct list_head node;   /* Element in datapath.ports. */
+       struct list_head node;
+       atomic_t sflow_pool;
 };
 
+enum csum_type {
+       OVS_CSUM_NONE = 0,
+       OVS_CSUM_UNNECESSARY = 1,
+       OVS_CSUM_COMPLETE = 2,
+       OVS_CSUM_PARTIAL = 3,
+};
+
+/**
+ * struct ovs_skb_cb - OVS data in skb CB
+ * @ip_summed: Consistently stores L4 checksumming status across different
+ * kernel versions.
+ */
+struct ovs_skb_cb {
+       enum csum_type  ip_summed;
+};
+#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
+
 extern struct notifier_block dp_device_notifier;
 extern int (*dp_ioctl_hook)(struct net_device *dev, struct ifreq *rq, int cmd);
 
@@ -151,6 +218,7 @@ void dp_process_received_packet(struct sk_buff *, struct net_bridge_port *);
 int dp_del_port(struct net_bridge_port *);
 int dp_output_control(struct datapath *, struct sk_buff *, int, u32 arg);
 int dp_min_mtu(const struct datapath *dp);
+void set_dp_devs_mtu(const struct datapath *dp, struct net_device *dev);
 
 struct datapath *get_dp(int dp_idx);
 
@@ -159,13 +227,16 @@ static inline const char *dp_name(const struct datapath *dp)
        return dp->ports[ODPP_LOCAL]->dev->name;
 }
 
-#ifdef CONFIG_XEN
-int skb_checksum_setup(struct sk_buff *skb);
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
+int vswitch_skb_checksum_setup(struct sk_buff *skb);
 #else
-static inline int skb_checksum_setup(struct sk_buff *skb)
+static inline int vswitch_skb_checksum_setup(struct sk_buff *skb)
 {
        return 0;
 }
 #endif
 
+void compute_ip_summed(struct sk_buff *skb, bool xmit);
+void forward_ip_summed(struct sk_buff *skb);
+
 #endif /* datapath.h */
index 5417114..2bbd6fe 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  * Distributed under the terms of the GNU GPL version 2.
  *
  * Significant portions of this file may be copied from parts of the Linux
@@ -10,6 +10,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
+#include <linux/preempt.h>
 #include <linux/rcupdate.h>
 #include <linux/skbuff.h>
 #include <linux/workqueue.h>
@@ -62,9 +63,13 @@ int dp_dev_recv(struct net_device *netdev, struct sk_buff *skb)
        else
                netif_rx_ni(skb);
        netdev->last_rx = jiffies;
+
+       preempt_disable();
        lb_stats = per_cpu_ptr(dp_dev->lstats, smp_processor_id());
        lb_stats->rx_packets++;
        lb_stats->rx_bytes += len;
+       preempt_enable();
+
        return len;
 }
 
@@ -118,7 +123,7 @@ static void dp_getinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
 {
        struct dp_dev *dp_dev = dp_dev_priv(netdev);
        strcpy(info->driver, "openvswitch");
-       sprintf(info->bus_info, "%d", dp_dev->dp->dp_idx);
+       sprintf(info->bus_info, "%d.%d", dp_dev->dp->dp_idx, dp_dev->port_no);
 }
 
 static struct ethtool_ops dp_ethtool_ops = {
@@ -209,8 +214,8 @@ do_setup(struct net_device *netdev)
        netdev->dev_addr[1] = 0x23;
        netdev->dev_addr[2] = 0x20;
 
-       /* Set the top bits to indicate random Nicira address. */
-       netdev->dev_addr[3] |= 0xc0;
+       /* Set the top bit to indicate random Nicira address. */
+       netdev->dev_addr[3] |= 0x80;
 }
 
 /* Create a datapath device associated with 'dp'.  If 'dp_name' is null,
index d5a2749..0278988 100644 (file)
@@ -45,6 +45,14 @@ static int dp_device_event(struct notifier_block *unused, unsigned long event,
                        mutex_unlock(&dp->mutex);
                }
                break;
+
+       case NETDEV_CHANGEMTU:
+               if (!is_dp_dev(dev)) {
+                       mutex_lock(&dp->mutex);
+                       set_dp_devs_mtu(dp, dev);
+                       mutex_unlock(&dp->mutex);
+               }
+               break;
        }
        return NOTIFY_DONE;
 }
index c0ac01b..be044ea 100644 (file)
@@ -20,16 +20,7 @@ int dp_sysfs_del_dp(struct datapath *dp);
 int dp_sysfs_add_if(struct net_bridge_port *p);
 int dp_sysfs_del_if(struct net_bridge_port *p);
 
-#include <linux/version.h>
-#if LINUX_VERSION_CODE == KERNEL_VERSION(2,6,18)
-#define SUPPORT_SYSFS 1
-#else
-/* We only support sysfs on Linux 2.6.18 because that's the only place we
- * really need it (on Xen, for brcompat) and it's a big pain to try to support
- * multiple versions. */
-#endif
-
-#ifdef SUPPORT_SYSFS
+#ifdef CONFIG_SYSFS
 extern struct sysfs_ops brport_sysfs_ops;
 #endif
 
index 503f22e..3cd6d1a 100644 (file)
 #include "datapath.h"
 #include "dp_dev.h"
 
-#ifdef SUPPORT_SYSFS
+#ifdef CONFIG_SYSFS
 #define to_dev(obj)    container_of(obj, struct device, kobj)
 
 /* Hack to attempt to build on more platforms. */
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21)
-#define to_kobj(d) &(d)->class_dev.kobj
 #define DP_DEVICE_ATTR CLASS_DEVICE_ATTR
+#define DEVICE_PARAMS struct class_device *d
+#define DEVICE_ARGS d
+#define DEV_ATTR(NAME) class_device_attr_##NAME
 #else
-#define to_kobj(d) &(d)->dev.kobj
 #define DP_DEVICE_ATTR DEVICE_ATTR
+#define DEVICE_PARAMS struct device *d, struct device_attribute *attr
+#define DEVICE_ARGS d, attr
+#define DEV_ATTR(NAME) dev_attr_##NAME
 #endif
 
 /*
  * Common code for storing bridge parameters.
  */
-static ssize_t store_bridge_parm(struct class_device *d,
+static ssize_t store_bridge_parm(DEVICE_PARAMS,
                                 const char *buf, size_t len,
                                 void (*set)(struct datapath *, unsigned long))
 {
@@ -76,8 +80,7 @@ static ssize_t store_bridge_parm(struct class_device *d,
 }
 
 
-static ssize_t show_forward_delay(struct class_device *d,
-                                 char *buf)
+static ssize_t show_forward_delay(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -99,15 +102,15 @@ static void set_forward_delay(struct datapath *dp, unsigned long val)
 #endif
 }
 
-static ssize_t store_forward_delay(struct class_device *d,
+static ssize_t store_forward_delay(DEVICE_PARAMS,
                                   const char *buf, size_t len)
 {
-       return store_bridge_parm(d, buf, len, set_forward_delay);
+       return store_bridge_parm(DEVICE_ARGS, buf, len, set_forward_delay);
 }
 static DP_DEVICE_ATTR(forward_delay, S_IRUGO | S_IWUSR,
                   show_forward_delay, store_forward_delay);
 
-static ssize_t show_hello_time(struct class_device *d, char *buf)
+static ssize_t show_hello_time(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return sprintf(buf, "%lu\n",
@@ -129,17 +132,16 @@ static void set_hello_time(struct datapath *dp, unsigned long val)
 #endif
 }
 
-static ssize_t store_hello_time(struct class_device *d,
+static ssize_t store_hello_time(DEVICE_PARAMS,
                                const char *buf,
                                size_t len)
 {
-       return store_bridge_parm(d, buf, len, set_hello_time);
+       return store_bridge_parm(DEVICE_ARGS, buf, len, set_hello_time);
 }
 static DP_DEVICE_ATTR(hello_time, S_IRUGO | S_IWUSR, show_hello_time,
                   store_hello_time);
 
-static ssize_t show_max_age(struct class_device *d, 
-                           char *buf)
+static ssize_t show_max_age(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return sprintf(buf, "%lu\n",
@@ -161,15 +163,14 @@ static void set_max_age(struct datapath *dp, unsigned long val)
 #endif
 }
 
-static ssize_t store_max_age(struct class_device *d, 
+static ssize_t store_max_age(DEVICE_PARAMS,
                             const char *buf, size_t len)
 {
-       return store_bridge_parm(d, buf, len, set_max_age);
+       return store_bridge_parm(DEVICE_ARGS, buf, len, set_max_age);
 }
 static DP_DEVICE_ATTR(max_age, S_IRUGO | S_IWUSR, show_max_age, store_max_age);
 
-static ssize_t show_ageing_time(struct class_device *d,
-                               char *buf)
+static ssize_t show_ageing_time(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -188,16 +189,15 @@ static void set_ageing_time(struct datapath *dp, unsigned long val)
 #endif
 }
 
-static ssize_t store_ageing_time(struct class_device *d,
+static ssize_t store_ageing_time(DEVICE_PARAMS,
                                 const char *buf, size_t len)
 {
-       return store_bridge_parm(d, buf, len, set_ageing_time);
+       return store_bridge_parm(DEVICE_ARGS, buf, len, set_ageing_time);
 }
 static DP_DEVICE_ATTR(ageing_time, S_IRUGO | S_IWUSR, show_ageing_time,
                   store_ageing_time);
 
-static ssize_t show_stp_state(struct class_device *d,
-                             char *buf)
+static ssize_t show_stp_state(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -208,7 +208,7 @@ static ssize_t show_stp_state(struct class_device *d,
 }
 
 
-static ssize_t store_stp_state(struct class_device *d,
+static ssize_t store_stp_state(DEVICE_PARAMS,
                               const char *buf,
                               size_t len)
 {
@@ -236,8 +236,7 @@ static ssize_t store_stp_state(struct class_device *d,
 static DP_DEVICE_ATTR(stp_state, S_IRUGO | S_IWUSR, show_stp_state,
                   store_stp_state);
 
-static ssize_t show_priority(struct class_device *d, 
-                            char *buf)
+static ssize_t show_priority(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -257,15 +256,14 @@ static void set_priority(struct datapath *dp, unsigned long val)
 #endif
 }
 
-static ssize_t store_priority(struct class_device *d, 
+static ssize_t store_priority(DEVICE_PARAMS,
                               const char *buf, size_t len)
 {
-       return store_bridge_parm(d, buf, len, set_priority);
+       return store_bridge_parm(DEVICE_ARGS, buf, len, set_priority);
 }
 static DP_DEVICE_ATTR(priority, S_IRUGO | S_IWUSR, show_priority, store_priority);
 
-static ssize_t show_root_id(struct class_device *d, 
-                           char *buf)
+static ssize_t show_root_id(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return br_show_bridge_id(buf, &to_bridge(d)->designated_root);
@@ -275,8 +273,7 @@ static ssize_t show_root_id(struct class_device *d,
 }
 static DP_DEVICE_ATTR(root_id, S_IRUGO, show_root_id, NULL);
 
-static ssize_t show_bridge_id(struct class_device *d, 
-                             char *buf)
+static ssize_t show_bridge_id(DEVICE_PARAMS, char *buf)
 {
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
        const unsigned char *addr = dp->ports[ODPP_LOCAL]->dev->dev_addr;
@@ -287,8 +284,7 @@ static ssize_t show_bridge_id(struct class_device *d,
 }
 static DP_DEVICE_ATTR(bridge_id, S_IRUGO, show_bridge_id, NULL);
 
-static ssize_t show_root_port(struct class_device *d, 
-                             char *buf)
+static ssize_t show_root_port(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return sprintf(buf, "%d\n", to_bridge(d)->root_port);
@@ -298,8 +294,7 @@ static ssize_t show_root_port(struct class_device *d,
 }
 static DP_DEVICE_ATTR(root_port, S_IRUGO, show_root_port, NULL);
 
-static ssize_t show_root_path_cost(struct class_device *d,
-                                  char *buf)
+static ssize_t show_root_path_cost(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return sprintf(buf, "%d\n", to_bridge(d)->root_path_cost);
@@ -309,8 +304,7 @@ static ssize_t show_root_path_cost(struct class_device *d,
 }
 static DP_DEVICE_ATTR(root_path_cost, S_IRUGO, show_root_path_cost, NULL);
 
-static ssize_t show_topology_change(struct class_device *d,
-                                   char *buf)
+static ssize_t show_topology_change(DEVICE_PARAMS, char *buf)
 {
 #if 0
        return sprintf(buf, "%d\n", to_bridge(d)->topology_change);
@@ -320,8 +314,7 @@ static ssize_t show_topology_change(struct class_device *d,
 }
 static DP_DEVICE_ATTR(topology_change, S_IRUGO, show_topology_change, NULL);
 
-static ssize_t show_topology_change_detected(struct class_device *d,
-                                            char *buf)
+static ssize_t show_topology_change_detected(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -333,8 +326,7 @@ static ssize_t show_topology_change_detected(struct class_device *d,
 static DP_DEVICE_ATTR(topology_change_detected, S_IRUGO,
                   show_topology_change_detected, NULL);
 
-static ssize_t show_hello_timer(struct class_device *d,
-                               char *buf)
+static ssize_t show_hello_timer(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -345,8 +337,7 @@ static ssize_t show_hello_timer(struct class_device *d,
 }
 static DP_DEVICE_ATTR(hello_timer, S_IRUGO, show_hello_timer, NULL);
 
-static ssize_t show_tcn_timer(struct class_device *d, 
-                             char *buf)
+static ssize_t show_tcn_timer(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -357,8 +348,7 @@ static ssize_t show_tcn_timer(struct class_device *d,
 }
 static DP_DEVICE_ATTR(tcn_timer, S_IRUGO, show_tcn_timer, NULL);
 
-static ssize_t show_topology_change_timer(struct class_device *d,
-                                         char *buf)
+static ssize_t show_topology_change_timer(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -370,8 +360,7 @@ static ssize_t show_topology_change_timer(struct class_device *d,
 static DP_DEVICE_ATTR(topology_change_timer, S_IRUGO, show_topology_change_timer,
                   NULL);
 
-static ssize_t show_gc_timer(struct class_device *d, 
-                            char *buf)
+static ssize_t show_gc_timer(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -382,8 +371,7 @@ static ssize_t show_gc_timer(struct class_device *d,
 }
 static DP_DEVICE_ATTR(gc_timer, S_IRUGO, show_gc_timer, NULL);
 
-static ssize_t show_group_addr(struct class_device *d,
-                              char *buf)
+static ssize_t show_group_addr(DEVICE_PARAMS, char *buf)
 {
 #if 0
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -396,7 +384,7 @@ static ssize_t show_group_addr(struct class_device *d,
 #endif
 }
 
-static ssize_t store_group_addr(struct class_device *d,
+static ssize_t store_group_addr(DEVICE_PARAMS,
                                const char *buf, size_t len)
 {
        struct datapath *dp = dp_dev_get_dp(to_net_dev(d));
@@ -439,23 +427,23 @@ static DP_DEVICE_ATTR(group_addr, S_IRUGO | S_IWUSR,
                   show_group_addr, store_group_addr);
 
 static struct attribute *bridge_attrs[] = {
-       &class_device_attr_forward_delay.attr,
-       &class_device_attr_hello_time.attr,
-       &class_device_attr_max_age.attr,
-       &class_device_attr_ageing_time.attr,
-       &class_device_attr_stp_state.attr,
-       &class_device_attr_priority.attr,
-       &class_device_attr_bridge_id.attr,
-       &class_device_attr_root_id.attr,
-       &class_device_attr_root_path_cost.attr,
-       &class_device_attr_root_port.attr,
-       &class_device_attr_topology_change.attr,
-       &class_device_attr_topology_change_detected.attr,
-       &class_device_attr_hello_timer.attr,
-       &class_device_attr_tcn_timer.attr,
-       &class_device_attr_topology_change_timer.attr,
-       &class_device_attr_gc_timer.attr,
-       &class_device_attr_group_addr.attr,
+       &DEV_ATTR(forward_delay).attr,
+       &DEV_ATTR(hello_time).attr,
+       &DEV_ATTR(max_age).attr,
+       &DEV_ATTR(ageing_time).attr,
+       &DEV_ATTR(stp_state).attr,
+       &DEV_ATTR(priority).attr,
+       &DEV_ATTR(bridge_id).attr,
+       &DEV_ATTR(root_id).attr,
+       &DEV_ATTR(root_path_cost).attr,
+       &DEV_ATTR(root_port).attr,
+       &DEV_ATTR(topology_change).attr,
+       &DEV_ATTR(topology_change_detected).attr,
+       &DEV_ATTR(hello_timer).attr,
+       &DEV_ATTR(tcn_timer).attr,
+       &DEV_ATTR(topology_change_timer).attr,
+       &DEV_ATTR(gc_timer).attr,
+       &DEV_ATTR(group_addr).attr,
        NULL
 };
 
@@ -476,7 +464,7 @@ static struct attribute_group bridge_group = {
  */
 int dp_sysfs_add_dp(struct datapath *dp)
 {
-       struct kobject *kobj = to_kobj(dp->ports[ODPP_LOCAL]->dev);
+       struct kobject *kobj = &dp->ports[ODPP_LOCAL]->dev->NETDEV_DEV_MEMBER.kobj;
        int err;
 
        /* Create /sys/class/net/<devname>/bridge directory. */
@@ -488,11 +476,10 @@ int dp_sysfs_add_dp(struct datapath *dp)
        }
 
        /* Create /sys/class/net/<devname>/brif directory. */
-       dp->ifobj.parent = kobj;
-       err = kobject_add(&dp->ifobj);
+       err = kobject_add(&dp->ifobj, kobj, SYSFS_BRIDGE_PORT_SUBDIR);
        if (err) {
                pr_info("%s: can't add kobject (directory) %s/%s\n",
-                               __FUNCTION__, dp_name(dp), dp->ifobj.name);
+                       __FUNCTION__, dp_name(dp), kobject_name(&dp->ifobj));
                goto out2;
        }
        kobject_uevent(&dp->ifobj, KOBJ_ADD);
@@ -506,21 +493,16 @@ int dp_sysfs_add_dp(struct datapath *dp)
 
 int dp_sysfs_del_dp(struct datapath *dp)
 {
-       struct kobject *kobj = to_kobj(dp->ports[ODPP_LOCAL]->dev);
+       struct kobject *kobj = &dp->ports[ODPP_LOCAL]->dev->NETDEV_DEV_MEMBER.kobj;
 
        kobject_del(&dp->ifobj);
        sysfs_remove_group(kobj, &bridge_group);
 
        return 0;
 }
-#else /* !SUPPORT_SYSFS */
+#else /* !CONFIG_SYSFS */
 int dp_sysfs_add_dp(struct datapath *dp) { return 0; }
 int dp_sysfs_del_dp(struct datapath *dp) { return 0; }
 int dp_sysfs_add_if(struct net_bridge_port *p) { return 0; }
-int dp_sysfs_del_if(struct net_bridge_port *p)
-{
-       dev_put(p->dev);
-       kfree(p);
-       return 0;
-}
-#endif /* !SUPPORT_SYSFS */
+int dp_sysfs_del_if(struct net_bridge_port *p) { return 0; }
+#endif /* !CONFIG_SYSFS */
index ab928f6..95c26dc 100644 (file)
@@ -21,7 +21,7 @@
 #include "dp_sysfs.h"
 #include "datapath.h"
 
-#ifdef SUPPORT_SYSFS
+#ifdef CONFIG_SYSFS
 
 struct brport_attribute {
        struct attribute        attr;
@@ -278,9 +278,10 @@ int dp_sysfs_add_if(struct net_bridge_port *p)
        int err;
 
        /* Create /sys/class/net/<devname>/brport directory. */
-       err = kobject_add(&p->kobj);
+       err = kobject_add(&p->kobj, &p->dev->NETDEV_DEV_MEMBER.kobj,
+                         SYSFS_BRIDGE_PORT_ATTR);
        if (err)
-               goto err_put;
+               goto err;
 
        /* Create symlink from /sys/class/net/<devname>/brport/bridge to
         * /sys/class/net/<bridgename>. */
@@ -306,15 +307,12 @@ int dp_sysfs_add_if(struct net_bridge_port *p)
 
        kobject_uevent(&p->kobj, KOBJ_ADD);
 
-       return err;
+       return 0;
 
 err_del:
        kobject_del(&p->kobj);
-err_put:
-       kobject_put(&p->kobj);
-
-       /* Ensure that dp_sysfs_del_if becomes a no-op. */
-       p->kobj.dentry = NULL;
+err:
+       p->linkname[0] = 0;
        return err;
 }
 
@@ -322,12 +320,10 @@ int dp_sysfs_del_if(struct net_bridge_port *p)
 {
        if (p->linkname[0]) {
                sysfs_remove_link(&p->dp->ifobj, p->linkname);
-               p->linkname[0] = '\0';
-       }
-       if (p->kobj.dentry) {
                kobject_uevent(&p->kobj, KOBJ_REMOVE);
                kobject_del(&p->kobj);
+               p->linkname[0] = '\0';
        }
        return 0;
 }
-#endif /* SUPPORT_SYSFS */
+#endif /* CONFIG_SYSFS */
index ae60617..9a94d0b 100644 (file)
@@ -1,12 +1,13 @@
 /*
  * Distributed under the terms of the GNU GPL version 2.
- * Copyright (c) 2007, 2008, 2009 Nicira Networks.
+ * Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks.
  *
  * Significant portions of this file may be copied from parts of the Linux
  * kernel, by Linus Torvalds and others.
  */
 
 #include "flow.h"
+#include "datapath.h"
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/if_ether.h>
@@ -24,6 +25,7 @@
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/icmp.h>
+#include <net/inet_ecn.h>
 #include <net/ip.h>
 
 #include "compat.h"
@@ -231,6 +233,7 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct odp_flow_key *key)
                struct vlan_hdr *vh = (struct vlan_hdr*)(skb->data + nh_ofs);
                key->dl_type = vh->h_vlan_encapsulated_proto;
                key->dl_vlan = vh->h_vlan_TCI & htons(VLAN_VID_MASK);
+               key->dl_vlan_pcp = (ntohs(vh->h_vlan_TCI) & VLAN_PCP_MASK) >> VLAN_PCP_SHIFT;
                nh_ofs += sizeof(struct vlan_hdr);
        }
        memcpy(key->dl_src, eth->h_source, ETH_ALEN);
@@ -243,6 +246,7 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct odp_flow_key *key)
                int th_ofs = nh_ofs + nh->ihl * 4;
                key->nw_src = nh->saddr;
                key->nw_dst = nh->daddr;
+               key->nw_tos = nh->tos & ~INET_ECN_MASK;
                key->nw_proto = nh->protocol;
                skb_set_transport_header(skb, th_ofs);
 
@@ -293,22 +297,22 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct odp_flow_key *key)
 
                arp = (struct arp_eth_header *)skb_network_header(skb);
 
-        if (arp->ar_hrd == htons(1)
-                && arp->ar_pro == htons(ETH_P_IP)
-                && arp->ar_hln == ETH_ALEN
-                && arp->ar_pln == 4) {
-
-            /* We only match on the lower 8 bits of the opcode. */
-            if (ntohs(arp->ar_op) <= 0xff) {
-                key->nw_proto = ntohs(arp->ar_op);
-            }
-
-            if (key->nw_proto == ARPOP_REQUEST 
-                    || key->nw_proto == ARPOP_REPLY) {
-                memcpy(&key->nw_src, arp->ar_sip, sizeof(key->nw_src));
-                memcpy(&key->nw_dst, arp->ar_tip, sizeof(key->nw_dst));
-            }
-        }
+               if (arp->ar_hrd == htons(ARPHRD_ETHER)
+                               && arp->ar_pro == htons(ETH_P_IP)
+                               && arp->ar_hln == ETH_ALEN
+                               && arp->ar_pln == 4) {
+
+                       /* We only match on the lower 8 bits of the opcode. */
+                       if (ntohs(arp->ar_op) <= 0xff) {
+                               key->nw_proto = ntohs(arp->ar_op);
+                       }
+
+                       if (key->nw_proto == ARPOP_REQUEST 
+                                       || key->nw_proto == ARPOP_REPLY) {
+                               memcpy(&key->nw_src, arp->ar_sip, sizeof(key->nw_src));
+                               memcpy(&key->nw_dst, arp->ar_tip, sizeof(key->nw_dst));
+                       }
+               }
        } else {
                skb_reset_transport_header(skb);
        }
@@ -332,16 +336,3 @@ void flow_exit(void)
 {
        kmem_cache_destroy(flow_cache);
 }
-
-void print_flow(const struct odp_flow_key *key)
-{
-#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
-#define MAC_ARG(x) ((u8*)(x))[0],((u8*)(x))[1],((u8*)(x))[2],((u8*)(x))[3],((u8*)(x))[4],((u8*)(x))[5]
-    printk("port%04x:vlan%d mac"MAC_FMT"->"MAC_FMT" "
-          "type%04x proto%d ip%x->%x port%d->%d\n",
-          key->in_port, ntohs(key->dl_vlan),
-          MAC_ARG(key->dl_src), MAC_ARG(key->dl_dst),
-          ntohs(key->dl_type), key->nw_proto,
-          key->nw_src, key->nw_dst,
-          ntohs(key->tp_src), ntohs(key->tp_dst));
-}
index a825366..44cc3a6 100644 (file)
@@ -49,8 +49,6 @@ void flow_deferred_free_acts(struct sw_flow_actions *);
 int flow_extract(struct sk_buff *, u16 in_port, struct odp_flow_key *);
 void flow_used(struct sw_flow *, struct sk_buff *);
 
-void print_flow(const struct odp_flow_key *);
-
 int flow_init(void);
 void flow_exit(void);
 
index aa10e66..89f269e 100644 (file)
@@ -2,6 +2,9 @@
 /Makefile
 /Makefile.main
 /actions.c
+/brc_procfs.c
+/brc_sysfs_dp.c
+/brc_sysfs_if.c
 /brcompat.c
 /dp_sysfs_dp.c
 /dp_sysfs_if.c
 /flow.c
 /genetlink-brcompat.c
 /genetlink-openvswitch.c
+/ip_gre.c
 /kcompat.h
 /linux-2.6
 /modules.order
+/net_namespace-ip_gre.c
 /random32.c
 /table.c
 /tmp
index 0005ec4..cb2a042 100644 (file)
@@ -2,9 +2,10 @@
 export builddir = @abs_builddir@
 export srcdir = @abs_srcdir@
 export top_srcdir = @abs_top_srcdir@
-export KSRC = @KSRC26@
+export KSRC = @KBUILD26@
 export VERSION = @VERSION@
 export BUILD_VETH = @BUILD_VETH@
+export BUILD_GRE = @BUILD_GRE@
 
 include $(srcdir)/../Modules.mk
 include $(srcdir)/Modules.mk
@@ -41,9 +42,12 @@ ifeq (,$(wildcard $(VERSION_FILE)))
   $(error Linux kernel source not configured - missing version.h)
 endif
 
-CONFIG_FILE := $(KSRC)/include/linux/autoconf.h
+CONFIG_FILE := $(KSRC)/include/generated/autoconf.h
 ifeq (,$(wildcard $(CONFIG_FILE)))
-  $(error Linux kernel source not configured - missing autoconf.h)
+  CONFIG_FILE := $(KSRC)/include/linux/autoconf.h
+  ifeq (,$(wildcard $(CONFIG_FILE)))
+    $(error Linux kernel source not configured - missing autoconf.h)
+  endif
 endif
 
 default:
index 7892583..70931d8 100644 (file)
@@ -1,6 +1,8 @@
 openvswitch_sources += \
+       linux-2.6/compat-2.6/dev-openvswitch.c \
        linux-2.6/compat-2.6/genetlink-openvswitch.c \
-       linux-2.6/compat-2.6/random32.c
+       linux-2.6/compat-2.6/random32.c \
+       linux-2.6/compat-2.6/skbuff-openvswitch.c
 openvswitch_headers += \
        linux-2.6/compat-2.6/compat26.h \
        linux-2.6/compat-2.6/include/asm-generic/bug.h \
@@ -47,3 +49,30 @@ dist_modules += veth
 build_modules += $(if $(BUILD_VETH),veth)
 veth_sources = linux-2.6/compat-2.6/veth.c
 veth_headers = 
+
+dist_modules += ip_gre
+build_modules += $(if $(BUILD_GRE),ip_gre)
+ip_gre_sources = \
+       linux-2.6/compat-2.6/addrconf_core-ip_gre.c \
+       linux-2.6/compat-2.6/dev-ip_gre.c \
+       linux-2.6/compat-2.6/ip_gre.c \
+       linux-2.6/compat-2.6/ip_output-ip_gre.c \
+       linux-2.6/compat-2.6/net_namespace-ip_gre.c
+ip_gre_headers = \
+       linux-2.6/compat-2.6/compat26.h \
+       linux-2.6/compat-2.6/include/linux/if.h \
+       linux-2.6/compat-2.6/include/linux/in.h \
+       linux-2.6/compat-2.6/include/linux/inetdevice.h \
+       linux-2.6/compat-2.6/include/linux/if_ether.h \
+       linux-2.6/compat-2.6/include/linux/ip.h \
+       linux-2.6/compat-2.6/include/linux/ipv6.h \
+       linux-2.6/compat-2.6/include/linux/netdevice.h \
+       linux-2.6/compat-2.6/include/linux/skbuff.h \
+       linux-2.6/compat-2.6/include/linux/tcp.h \
+       linux-2.6/compat-2.6/include/linux/types.h \
+       linux-2.6/compat-2.6/include/net/dst.h \
+       linux-2.6/compat-2.6/include/net/ip.h \
+       linux-2.6/compat-2.6/include/net/ipip.h \
+       linux-2.6/compat-2.6/include/net/netns/generic.h \
+       linux-2.6/compat-2.6/include/net/net_namespace.h \
+       linux-2.6/compat-2.6/include/net/route.h
diff --git a/datapath/linux-2.6/compat-2.6/addrconf_core-ip_gre.c b/datapath/linux-2.6/compat-2.6/addrconf_core-ip_gre.c
new file mode 100644 (file)
index 0000000..b5a7574
--- /dev/null
@@ -0,0 +1,82 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21)
+
+/*
+ * IPv6 library code, needed by static components when full IPv6 support is
+ * not configured or static.
+ */
+
+#include <net/ipv6.h>
+
+#define IPV6_ADDR_SCOPE_TYPE(scope)    ((scope) << 16)
+
+static inline unsigned ipv6_addr_scope2type(unsigned scope)
+{
+       switch(scope) {
+       case IPV6_ADDR_SCOPE_NODELOCAL:
+               return (IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_NODELOCAL) |
+                       IPV6_ADDR_LOOPBACK);
+       case IPV6_ADDR_SCOPE_LINKLOCAL:
+               return (IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_LINKLOCAL) |
+                       IPV6_ADDR_LINKLOCAL);
+       case IPV6_ADDR_SCOPE_SITELOCAL:
+               return (IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_SITELOCAL) |
+                       IPV6_ADDR_SITELOCAL);
+       }
+       return IPV6_ADDR_SCOPE_TYPE(scope);
+}
+
+int __ipv6_addr_type(const struct in6_addr *addr)
+{
+       __be32 st;
+
+       st = addr->s6_addr32[0];
+
+       /* Consider all addresses with the first three bits different of
+          000 and 111 as unicasts.
+        */
+       if ((st & htonl(0xE0000000)) != htonl(0x00000000) &&
+           (st & htonl(0xE0000000)) != htonl(0xE0000000))
+               return (IPV6_ADDR_UNICAST |
+                       IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_GLOBAL));
+
+       if ((st & htonl(0xFF000000)) == htonl(0xFF000000)) {
+               /* multicast */
+               /* addr-select 3.1 */
+               return (IPV6_ADDR_MULTICAST |
+                       ipv6_addr_scope2type(IPV6_ADDR_MC_SCOPE(addr)));
+       }
+
+       if ((st & htonl(0xFFC00000)) == htonl(0xFE800000))
+               return (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_UNICAST |
+                       IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_LINKLOCAL));               /* addr-select 3.1 */
+       if ((st & htonl(0xFFC00000)) == htonl(0xFEC00000))
+               return (IPV6_ADDR_SITELOCAL | IPV6_ADDR_UNICAST |
+                       IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_SITELOCAL));               /* addr-select 3.1 */
+       if ((st & htonl(0xFE000000)) == htonl(0xFC000000))
+               return (IPV6_ADDR_UNICAST |
+                       IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_GLOBAL));                  /* RFC 4193 */
+
+       if ((addr->s6_addr32[0] | addr->s6_addr32[1]) == 0) {
+               if (addr->s6_addr32[2] == 0) {
+                       if (addr->s6_addr32[3] == 0)
+                               return IPV6_ADDR_ANY;
+
+                       if (addr->s6_addr32[3] == htonl(0x00000001))
+                               return (IPV6_ADDR_LOOPBACK | IPV6_ADDR_UNICAST |
+                                       IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_LINKLOCAL));       /* addr-select 3.4 */
+
+                       return (IPV6_ADDR_COMPATv4 | IPV6_ADDR_UNICAST |
+                               IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_GLOBAL));  /* addr-select 3.3 */
+               }
+
+               if (addr->s6_addr32[2] == htonl(0x0000ffff))
+                       return (IPV6_ADDR_MAPPED |
+                               IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_GLOBAL));  /* addr-select 3.3 */
+       }
+
+       return (IPV6_ADDR_RESERVED |
+               IPV6_ADDR_SCOPE_TYPE(IPV6_ADDR_SCOPE_GLOBAL));  /* addr-select 3.4 */
+}
+
+#endif /* kernel < 2.6.21 */
diff --git a/datapath/linux-2.6/compat-2.6/dev-ip_gre.c b/datapath/linux-2.6/compat-2.6/dev-ip_gre.c
new file mode 100644 (file)
index 0000000..04d830e
--- /dev/null
@@ -0,0 +1,63 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+
+struct netdev_list {
+       struct list_head unreg_list;
+       struct net_device *dev;
+};
+
+/**
+ *     unregister_netdevice_queue - remove device from the kernel
+ *     @dev: device
+ *     @head: list
+
+ *     This function shuts down a device interface and removes it
+ *     from the kernel tables.
+ *     If head not NULL, device is queued to be unregistered later.
+ *
+ *     Callers must hold the rtnl semaphore.  You may want
+ *     unregister_netdev() instead of this.
+ */
+
+void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
+{
+       ASSERT_RTNL();
+
+       if (head) {
+               struct netdev_list *list_item = kmalloc(sizeof *list_item,
+                                                       GFP_KERNEL);
+               /* If we can't queue it, probably better to try to destroy it
+                * now.  Either could potentially be bad but this is probably
+                * less likely to cause problems. */
+               if (!list_item) {
+                       unregister_netdevice(dev);
+                       return;
+               }
+
+               list_item->dev = dev;
+               list_add_tail(&list_item->unreg_list, head);
+       } else
+               unregister_netdevice(dev);
+}
+
+/**
+ *     unregister_netdevice_many - unregister many devices
+ *     @head: list of devices
+ *
+ */
+void unregister_netdevice_many(struct list_head *head)
+{
+       if (!list_empty(head)) {
+               struct netdev_list *list_item, *next;
+
+               list_for_each_entry_safe(list_item, next, head, unreg_list) {
+                       unregister_netdevice(list_item->dev);
+                       kfree(list_item);
+               }
+       }
+}
+
+#endif /* kernel < 2.6.33 */
diff --git a/datapath/linux-2.6/compat-2.6/dev-openvswitch.c b/datapath/linux-2.6/compat-2.6/dev-openvswitch.c
new file mode 100644 (file)
index 0000000..7be33f6
--- /dev/null
@@ -0,0 +1,35 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+
+#include <linux/netdevice.h>
+
+#ifndef NETIF_F_LRO
+void dev_disable_lro(struct net_device *dev) { }
+#else
+
+#include <linux/ethtool.h>
+
+/**
+ *     dev_disable_lro - disable Large Receive Offload on a device
+ *     @dev: device
+ *
+ *     Disable Large Receive Offload (LRO) on a net device.  Must be
+ *     called under RTNL.  This is needed if received packets may be
+ *     forwarded to another interface.
+ */
+void dev_disable_lro(struct net_device *dev)
+{
+       if (dev->ethtool_ops && dev->ethtool_ops->get_flags &&
+           dev->ethtool_ops->set_flags) {
+               u32 flags = dev->ethtool_ops->get_flags(dev);
+               if (flags & ETH_FLAG_LRO) {
+                       flags &= ~ETH_FLAG_LRO;
+                       dev->ethtool_ops->set_flags(dev, flags);
+               }
+       }
+       WARN_ON(dev->features & NETIF_F_LRO);
+}
+
+#endif /* NETIF_F_LRO */
+
+#endif /* kernel < 2.6.27 */
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/if.h b/datapath/linux-2.6/compat-2.6/include/linux/if.h
new file mode 100644 (file)
index 0000000..0aa9ee3
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __LINUX_IF_WRAPPER_H
+#define __LINUX_IF_WRAPPER_H 1
+
+#include_next <linux/if.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+
+#define IFF_XMIT_DST_RELEASE 0
+
+#endif /* linux kernel < 2.6.31 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/if_ether.h b/datapath/linux-2.6/compat-2.6/include/linux/if_ether.h
new file mode 100644 (file)
index 0000000..b8390e2
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __LINUX_IF_ETHER_WRAPPER_H
+#define __LINUX_IF_ETHER_WRAPPER_H 1
+
+#include_next <linux/if_ether.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+
+#define ETH_P_TEB      0x6558          /* Trans Ether Bridging         */
+
+#endif /* linux kernel < 2.6.28 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/in.h b/datapath/linux-2.6/compat-2.6/include/linux/in.h
new file mode 100644 (file)
index 0000000..f91a832
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef __LINUX_IN_WRAPPER_H
+#define __LINUX_IN_WRAPPER_H 1
+
+#include_next <linux/in.h>
+
+#ifndef HAVE_IPV4_IS_MULTICAST
+
+static inline bool ipv4_is_loopback(__be32 addr)
+{
+       return (addr & htonl(0xff000000)) == htonl(0x7f000000);
+}
+
+static inline bool ipv4_is_multicast(__be32 addr)
+{
+       return (addr & htonl(0xf0000000)) == htonl(0xe0000000);
+}
+
+static inline bool ipv4_is_local_multicast(__be32 addr)
+{
+       return (addr & htonl(0xffffff00)) == htonl(0xe0000000);
+}
+
+static inline bool ipv4_is_lbcast(__be32 addr)
+{
+       /* limited broadcast */
+       return addr == htonl(INADDR_BROADCAST);
+}
+
+static inline bool ipv4_is_zeronet(__be32 addr)
+{
+       return (addr & htonl(0xff000000)) == htonl(0x00000000);
+}
+
+#endif /* !HAVE_IPV4_IS_MULTICAST */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h b/datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h
new file mode 100644 (file)
index 0000000..813a70a
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __LINUX_INETDEVICE_WRAPPER_H
+#define __LINUX_INETDEVICE_WRAPPER_H 1
+
+#include_next <linux/inetdevice.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+#define inetdev_by_index(net, ifindex) \
+               inetdev_by_index((ifindex))
+
+#endif /* linux kernel < 2.6.25 */
+
+#endif
index c0de3d2..4cf797e 100644 (file)
@@ -4,6 +4,7 @@
 #include_next <linux/kobject.h>
 
 #include <linux/version.h>
+
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
 #define kobject_init(kobj, ktype) rpl_kobject_init(kobj, ktype)
 static inline void rpl_kobject_init(struct kobject *kobj, struct kobj_type *ktype)
@@ -11,6 +12,19 @@ static inline void rpl_kobject_init(struct kobject *kobj, struct kobj_type *ktyp
        kobj->ktype = ktype;
        (kobject_init)(kobj);
 }
+
+#define kobject_add(kobj, parent, name) rpl_kobject_add(kobj, parent, name)
+static inline int rpl_kobject_add(struct kobject *kobj,
+                                 struct kobject *parent,
+                                 const char *name)
+{
+       int err = kobject_set_name(kobj, "%s", name);
+       if (err)
+               return err;
+       kobj->parent = parent;
+       return (kobject_add)(kobj);
+}
 #endif
 
+
 #endif /* linux/kobject.h wrapper */
index b718283..924dc0d 100644 (file)
@@ -5,6 +5,7 @@
 
 struct net;
 
+#include <linux/version.h>
 /* Before 2.6.21, struct net_device has a "struct class_device" member named
  * class_dev.  Beginning with 2.6.21, struct net_device instead has a "struct
  * device" member named dev.  Otherwise the usage of these members is pretty
@@ -23,14 +24,36 @@ struct net;
 static inline
 struct net *dev_net(const struct net_device *dev)
 {
+#ifdef CONFIG_NET_NS
+       return dev->nd_net;
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
+       return &init_net;
+#else
        return NULL;
+#endif
+}
+
+static inline
+void dev_net_set(struct net_device *dev, const struct net *net)
+{
+#ifdef CONFIG_NET_NS
+       dev->nd_dev = net;
+#endif
 }
 #endif /* linux kernel < 2.6.26 */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+#define NETIF_F_NETNS_LOCAL 0
+#endif
+
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
 #define proc_net init_net.proc_net
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+typedef int netdev_tx_t;
+#endif
+
 #ifndef for_each_netdev
 /* Linux before 2.6.22 didn't have for_each_netdev at all. */
 #define for_each_netdev(net, d) for (d = dev_base; d; d = d->next)
@@ -40,6 +63,18 @@ struct net *dev_net(const struct net_device *dev)
 #define for_each_netdev(net,d) list_for_each_entry(d, &dev_base_head, dev_list)
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+#define net_xmit_eval(e)       ((e) == NET_XMIT_CN? 0 : (e))
+#endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+extern void unregister_netdevice_queue(struct net_device *dev,
+                                       struct list_head *head);
+extern void unregister_netdevice_many(struct list_head *head);
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+extern void dev_disable_lro(struct net_device *dev);
+#endif
 
 #endif
index 2831721..6a6b095 100644 (file)
@@ -5,6 +5,17 @@
 
 #include <linux/version.h>
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+/* In version 2.6.24 the return type of skb_headroom() changed from 'int' to
+ * 'unsigned int'.  We use skb_headroom() as one arm of a min(a,b) invocation
+ * in make_writable() in actions.c, so we need the correct type. */
+#define skb_headroom rpl_skb_headroom
+static inline unsigned int rpl_skb_headroom(const struct sk_buff *skb)
+{
+       return skb->data - skb->head;
+}
+#endif
+
 #ifndef HAVE_SKB_COPY_FROM_LINEAR_DATA_OFFSET
 static inline void skb_copy_from_linear_data_offset(const struct sk_buff *skb,
                                                     const int offset, void *to,
@@ -65,6 +76,29 @@ static inline int skb_cow_head(struct sk_buff *skb, unsigned int headroom)
 }
 #endif  /* !HAVE_SKB_COW */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
+static inline int skb_clone_writable(struct sk_buff *skb, int len)
+{
+       return false;
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+static inline struct dst_entry *skb_dst(const struct sk_buff *skb)
+{
+       return (struct dst_entry *)skb->dst;
+}
+
+static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
+{
+       skb->dst = dst;
+}
+
+static inline struct rtable *skb_rtable(const struct sk_buff *skb)
+{
+       return (struct rtable *)skb->dst;
+}
+#endif
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17)
 /* Emulate Linux 2.6.17 and later behavior, in which kfree_skb silently ignores 
@@ -79,9 +113,7 @@ static inline void kfree_skb_maybe_null(struct sk_buff *skb)
 
 
 #ifndef CHECKSUM_PARTIAL
-/* Note that CHECKSUM_PARTIAL is not implemented, but this allows us to at
- * least test against it: see update_csum() in forward.c. */
-#define CHECKSUM_PARTIAL 3
+#define CHECKSUM_PARTIAL CHECKSUM_HW
 #endif
 #ifndef CHECKSUM_COMPLETE
 #define CHECKSUM_COMPLETE CHECKSUM_HW
@@ -90,6 +122,7 @@ static inline void kfree_skb_maybe_null(struct sk_buff *skb)
 #ifdef HAVE_MAC_RAW
 #define mac_header mac.raw
 #define network_header nh.raw
+#define transport_header h.raw
 #endif
 
 #ifndef HAVE_SKBUFF_HEADER_HELPERS
@@ -114,6 +147,11 @@ static inline unsigned char *skb_network_header(const struct sk_buff *skb)
        return skb->nh.raw;
 }
 
+static inline void skb_reset_network_header(struct sk_buff *skb)
+{
+       skb->nh.raw = skb->data;
+}
+
 static inline void skb_set_network_header(struct sk_buff *skb, const int offset)
 {
        skb->nh.raw = skb->data + offset;
@@ -167,4 +205,41 @@ static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,
 }
 #endif /* before 2.6.18 */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+
+extern void __skb_warn_lro_forwarding(const struct sk_buff *skb);
+
+#ifndef NETIF_F_LRO
+static inline bool skb_warn_if_lro(const struct sk_buff *skb)
+{
+       return false;
+}
+#else
+static inline bool skb_warn_if_lro(const struct sk_buff *skb)
+{
+       /* LRO sets gso_size but not gso_type, whereas if GSO is really
+        * wanted then gso_type will be set. */
+       struct skb_shared_info *shinfo = skb_shinfo(skb);
+       if (shinfo->gso_size != 0 && unlikely(shinfo->gso_type == 0)) {
+               __skb_warn_lro_forwarding(skb);
+               return true;
+       }
+       return false;
+}
+#endif /* NETIF_F_LRO */
+#endif /* kernel < 2.6.27 */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+static inline struct sk_buff *netdev_alloc_skb_ip_align(struct net_device *dev,
+                                                       unsigned int length)
+{
+       struct sk_buff *skb = netdev_alloc_skb(dev, length + NET_IP_ALIGN);
+
+       if (NET_IP_ALIGN && skb)
+               skb_reserve(skb, NET_IP_ALIGN);
+       return skb;
+}
+#endif /* kernel < 2.6.33 */
+
+
 #endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/stddef.h b/datapath/linux-2.6/compat-2.6/include/linux/stddef.h
new file mode 100644 (file)
index 0000000..9b68f71
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __LINUX_STDDEF_WRAPPER_H
+#define __LINUX_STDDEF_WRAPPER_H 1
+
+#include_next <linux/stddef.h>
+
+#ifdef __KERNEL__
+
+#ifndef HAVE_BOOL_TYPE
+enum {
+       false   = 0,
+       true    = 1
+};
+#endif /* !HAVE_BOOL_TYPE */
+
+#endif /* __KERNEL__ */
+
+#endif
index c1f375e..d88baf7 100644 (file)
@@ -11,4 +11,8 @@ typedef __u32 __bitwise __wsum;
 
 #endif /* linux kernel < 2.6.20 */
 
+#ifndef HAVE_BOOL_TYPE
+typedef _Bool bool;
+#endif /* !HAVE_BOOL_TYPE */
+
 #endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/dst.h b/datapath/linux-2.6/compat-2.6/include/net/dst.h
new file mode 100644 (file)
index 0000000..edb0f75
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef __NET_DST_WRAPPER_H
+#define __NET_DST_WRAPPER_H 1
+
+#include_next <net/dst.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+
+static inline void skb_dst_drop(struct sk_buff *skb)
+{
+       if (skb->dst)
+               dst_release(skb_dst(skb));
+       skb->dst = 0UL;
+}
+
+#endif /* linux kernel < 2.6.31 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/ip.h b/datapath/linux-2.6/compat-2.6/include/net/ip.h
new file mode 100644 (file)
index 0000000..b18b968
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __NET_IP_WRAPPER_H
+#define __NET_IP_WRAPPER_H 1
+
+#include_next <net/ip.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+extern int             __ip_local_out(struct sk_buff *skb);
+extern int             ip_local_out(struct sk_buff *skb);
+
+#endif /* linux kernel < 2.6.25 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/ipip.h b/datapath/linux-2.6/compat-2.6/include/net/ipip.h
new file mode 100644 (file)
index 0000000..7fa0b28
--- /dev/null
@@ -0,0 +1,85 @@
+#ifndef __NET_IPIP_WRAPPER_H
+#define __NET_IPIP_WRAPPER_H 1
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
+#define HAVE_NETDEV_QUEUE_STATS
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+
+#include <linux/if_tunnel.h>
+#include <net/ip.h>
+
+/* Keep error state on tunnel for 30 sec */
+#define IPTUNNEL_ERR_TIMEO     (30*HZ)
+
+struct ip_tunnel
+{
+       struct ip_tunnel        *next;
+       struct net_device       *dev;
+#ifndef HAVE_NETDEV_STATS
+       struct net_device_stats stat;
+#endif
+
+       int                     err_count;      /* Number of arrived ICMP errors */
+       unsigned long           err_time;       /* Time when the last ICMP error arrived */
+
+       /* These four fields used only by GRE */
+       __u32                   i_seqno;        /* The last seen seqno  */
+       __u32                   o_seqno;        /* The last output seqno */
+       int                     hlen;           /* Precalculated GRE header length */
+       int                     mlink;
+
+       struct ip_tunnel_parm   parms;
+
+       struct ip_tunnel_prl_entry      *prl;           /* potential router list */
+       unsigned int                    prl_count;      /* # of entries in PRL */
+};
+
+/* ISATAP: default interval between RS in secondy */
+#define IPTUNNEL_RS_DEFAULT_DELAY      (900)
+
+struct ip_tunnel_prl_entry
+{
+       struct ip_tunnel_prl_entry      *next;
+       __be32                          addr;
+       u16                             flags;
+       unsigned long                   rs_delay;
+       struct timer_list               rs_timer;
+       struct ip_tunnel                *tunnel;
+       spinlock_t                      lock;
+};
+
+#ifdef HAVE_NETDEV_QUEUE_STATS
+#define UPDATE_TX_STATS()                                              \
+       txq->tx_bytes += pkt_len;                                       \
+       txq->tx_packets++;
+#else
+#define UPDATE_TX_STATS()                                              \
+       stats->tx_bytes += pkt_len;                                     \
+       stats->tx_packets++;
+#endif
+
+#define IPTUNNEL_XMIT() do {                                           \
+       int err;                                                        \
+       int pkt_len = skb->len - skb_transport_offset(skb);             \
+                                                                       \
+       skb->ip_summed = CHECKSUM_NONE;                                 \
+       ip_select_ident(iph, &rt->u.dst, NULL);                         \
+                                                                       \
+       err = ip_local_out(skb);                                        \
+       if (likely(net_xmit_eval(err) == 0)) {                          \
+               UPDATE_TX_STATS();                                      \
+       } else {                                                        \
+               stats->tx_errors++;                                     \
+               stats->tx_aborted_errors++;                             \
+       }                                                               \
+} while (0)
+
+#else
+#include_next <net/ipip.h>
+#endif /* kernel < 2.6.32 */
+
+#endif /* net/ipip.h wrapper */
diff --git a/datapath/linux-2.6/compat-2.6/include/net/net_namespace.h b/datapath/linux-2.6/compat-2.6/include/net/net_namespace.h
new file mode 100644 (file)
index 0000000..92a4e02
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef __NET_NAMESPACE_WRAPPER_H
+#define __NET_NAMESPACE_WRAPPER_H 1
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+#include_next <net/net_namespace.h>
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+struct net;
+
+struct extended_pernet_operations {
+       struct list_head list;
+       int (*init)(struct net *net);
+       void (*exit)(struct net *net);
+       int *id;
+       size_t size;
+};
+#define pernet_operations extended_pernet_operations
+
+#define register_pernet_device rpl_register_pernet_device
+int rpl_register_pernet_device(struct extended_pernet_operations *ops);
+
+#define unregister_pernet_device rpl_unregister_pernet_device
+void rpl_unregister_pernet_device(struct extended_pernet_operations *ops);
+
+#endif /* linux kernel < 2.6.33 */
+
+#endif
index e0d594d..2f979fe 100644 (file)
@@ -19,4 +19,22 @@ static inline int VERIFY_NUL_STRING(struct nlattr *attr)
 }
 #endif /* !HAVE_NLA_NUL_STRING */
 
+
+#ifndef NLA_PUT_BE16
+#define NLA_PUT_BE16(skb, attrtype, value) \
+        NLA_PUT_TYPE(skb, __be16, attrtype, value)
+#endif  /* !NLA_PUT_BE16 */
+
+
+#ifndef HAVE_NLA_GET_BE16
+/**
+ * nla_get_be16 - return payload of __be16 attribute
+ * @nla: __be16 netlink attribute
+ */
+static inline __be16 nla_get_be16(struct nlattr *nla)
+{
+        return *(__be16 *) nla_data(nla);
+}
+#endif  /* !HAVE_NLA_GET_BE16 */
+
 #endif /* net/netlink.h */
diff --git a/datapath/linux-2.6/compat-2.6/include/net/netns/generic.h b/datapath/linux-2.6/compat-2.6/include/net/netns/generic.h
new file mode 100644 (file)
index 0000000..7aedf31
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef __NET_NETNS_GENERIC_WRAPPER_H
+#define __NET_NETNS_GENERIC_WRAPPER_H 1
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#include_next <net/netns/generic.h>
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+
+#define net_assign_generic rpl_net_assign_generic
+int rpl_net_assign_generic(struct net *net, int id, void *data);
+
+#define net_generic rpl_net_generic
+void *rpl_net_generic(struct net *net, int id);
+
+#endif /* linux kernel < 2.6.33 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/route.h b/datapath/linux-2.6/compat-2.6/include/net/route.h
new file mode 100644 (file)
index 0000000..867f407
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __NET_ROUTE_WRAPPER_H
+#define __NET_ROUTE_WRAPPER_H 1
+
+#include_next <net/route.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+#define ip_route_output_key(net, rp, flp) \
+               ip_route_output_key((rp), (flp))
+
+#endif /* linux kernel < 2.6.25 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/ip_gre.c b/datapath/linux-2.6/compat-2.6/ip_gre.c
new file mode 100644 (file)
index 0000000..da54b44
--- /dev/null
@@ -0,0 +1,2356 @@
+/* ip_gre driver port to Linux 2.6.18 and greater plus enhancements */
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+#define HAVE_NETDEV_STATS
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+#define HAVE_NETDEV_HEADER_OPS
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#define HAVE_NETDEV_NEEDED_HEADROOM
+#endif
+
+/*
+ *     Linux NET3:     GRE over IP protocol decoder.
+ *
+ *     Authors: Alexey Kuznetsov (kuznet@ms2.inr.ac.ru)
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License
+ *     as published by the Free Software Foundation; either version
+ *     2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/capability.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/if_arp.h>
+#include <linux/if_vlan.h>
+#include <linux/mroute.h>
+#include <linux/init.h>
+#include <linux/in6.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <net/ipip.h>
+#include <net/ipv6.h>
+#include <net/arp.h>
+#include <net/checksum.h>
+#include <net/dsfield.h>
+#include <net/inet_ecn.h>
+#include <net/xfrm.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+#ifdef CONFIG_IPV6
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#endif
+
+#include "compat.h"
+#include "openvswitch/gre.h"
+
+#ifndef GRE_IOCTL_ONLY
+#include <net/rtnetlink.h>
+#endif
+
+/*
+   Problems & solutions
+   --------------------
+
+   1. The most important issue is detecting local dead loops.
+   They would cause complete host lockup in transmit, which
+   would be "resolved" by stack overflow or, if queueing is enabled,
+   with infinite looping in net_bh.
+
+   We cannot track such dead loops during route installation,
+   it is infeasible task. The most general solutions would be
+   to keep skb->encapsulation counter (sort of local ttl),
+   and silently drop packet when it expires. It is the best
+   solution, but it supposes maintaing new variable in ALL
+   skb, even if no tunneling is used.
+
+   Current solution: HARD_TX_LOCK lock breaks dead loops.
+
+
+
+   2. Networking dead loops would not kill routers, but would really
+   kill network. IP hop limit plays role of "t->recursion" in this case,
+   if we copy it from packet being encapsulated to upper header.
+   It is very good solution, but it introduces two problems:
+
+   - Routing protocols, using packets with ttl=1 (OSPF, RIP2),
+     do not work over tunnels.
+   - traceroute does not work. I planned to relay ICMP from tunnel,
+     so that this problem would be solved and traceroute output
+     would even more informative. This idea appeared to be wrong:
+     only Linux complies to rfc1812 now (yes, guys, Linux is the only
+     true router now :-)), all routers (at least, in neighbourhood of mine)
+     return only 8 bytes of payload. It is the end.
+
+   Hence, if we want that OSPF worked or traceroute said something reasonable,
+   we should search for another solution.
+
+   One of them is to parse packet trying to detect inner encapsulation
+   made by our node. It is difficult or even impossible, especially,
+   taking into account fragmentation. TO be short, tt is not solution at all.
+
+   Current solution: The solution was UNEXPECTEDLY SIMPLE.
+   We force DF flag on tunnels with preconfigured hop limit,
+   that is ALL. :-) Well, it does not remove the problem completely,
+   but exponential growth of network traffic is changed to linear
+   (branches, that exceed pmtu are pruned) and tunnel mtu
+   fastly degrades to value <68, where looping stops.
+   Yes, it is not good if there exists a router in the loop,
+   which does not force DF, even when encapsulating packets have DF set.
+   But it is not our problem! Nobody could accuse us, we made
+   all that we could make. Even if it is your gated who injected
+   fatal route to network, even if it were you who configured
+   fatal static route: you are innocent. :-)
+
+   XXX: Forcing the DF flag on was done only when setting up tunnels via the
+       ioctl interface and not Netlink.  Since it prevents some operations
+       and isn't very transparent I removed it.  It seems nobody really
+       cared about it anyways.
+        Moral: don't create loops.
+
+   3. Really, ipv4/ipip.c, ipv4/ip_gre.c and ipv6/sit.c contain
+   practically identical code. It would be good to glue them
+   together, but it is not very evident, how to make them modular.
+   sit is integral part of IPv6, ipip and gre are naturally modular.
+   We could extract common parts (hash table, ioctl etc)
+   to a separate module (ip_tunnel.c).
+
+   Alexey Kuznetsov.
+ */
+
+#ifndef GRE_IOCTL_ONLY
+static struct rtnl_link_ops ipgre_link_ops __read_mostly;
+static struct rtnl_link_ops ipgre_tap_ops __read_mostly;
+#endif
+static int ipgre_tunnel_init(struct net_device *dev);
+static void ipgre_tunnel_setup(struct net_device *dev);
+static void ipgre_tap_setup(struct net_device *dev);
+static int ipgre_tunnel_bind_dev(struct net_device *dev);
+static bool send_frag_needed(struct sk_buff *skb, struct net_device *dev,
+                            unsigned int mtu);
+
+#define HASH_SIZE  16
+
+/* The absolute minimum fragment size.  Note that there are many other
+ * definitions of the minimum MTU. */
+#define IP_MIN_MTU 68
+
+static inline __be16 *gre_flags(void *header_start)
+{
+       return header_start;
+}
+
+static inline __be16 *gre_protocol(void *header_start)
+{
+       return header_start + 2;
+}
+
+static int ipgre_net_id __read_mostly;
+struct ipgre_net {
+       struct ip_tunnel *tunnels[4][HASH_SIZE];
+
+       struct net_device *fb_tunnel_dev;
+};
+
+/* Tunnel hash table */
+
+/*
+   4 hash tables:
+
+   3: (remote,local)
+   2: (remote,*)
+   1: (*,local)
+   0: (*,*)
+
+   We require exact key match i.e. if a key is present in packet
+   it will match only tunnel with the same key; if it is not present,
+   it will match only keyless tunnel.
+
+   All keysless packets, if not matched configured keyless tunnels
+   will match fallback tunnel.
+ */
+
+#define HASH(addr) (((__force u32)addr^((__force u32)addr>>4))&0xF)
+
+#define tunnels_r_l    tunnels[3]
+#define tunnels_r      tunnels[2]
+#define tunnels_l      tunnels[1]
+#define tunnels_wc     tunnels[0]
+/*
+ * Locking : hash tables are protected by RCU and a spinlock
+ */
+static DEFINE_SPINLOCK(ipgre_lock);
+
+#define for_each_ip_tunnel_rcu(start) \
+       for (t = rcu_dereference(start); t; t = rcu_dereference(t->next))
+
+/* Given src, dst and key, find appropriate for input tunnel. */
+
+static struct ip_tunnel * ipgre_tunnel_lookup(struct net_device *dev,
+                                             __be32 remote, __be32 local,
+                                             __be32 key, __be16 gre_proto)
+{
+       struct net *net = dev_net(dev);
+       int link = dev->ifindex;
+       unsigned h0 = HASH(remote);
+       unsigned h1 = HASH(key);
+       struct ip_tunnel *t, *cand = NULL;
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int dev_type = (gre_proto == htons(ETH_P_TEB)) ?
+                      ARPHRD_ETHER : ARPHRD_IPGRE;
+       int score, cand_score = 4;
+
+       for_each_ip_tunnel_rcu(ign->tunnels_r_l[h0 ^ h1]) {
+               if (local != t->parms.iph.saddr ||
+                   remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for_each_ip_tunnel_rcu(ign->tunnels_r[h0 ^ h1]) {
+               if (remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for_each_ip_tunnel_rcu(ign->tunnels_l[h1]) {
+               if ((local != t->parms.iph.saddr &&
+                    (local != t->parms.iph.daddr ||
+                     !ipv4_is_multicast(local))) ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for_each_ip_tunnel_rcu(ign->tunnels_wc[h1]) {
+               if (t->parms.i_key != key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       if (cand != NULL)
+               return cand;
+
+       dev = ign->fb_tunnel_dev;
+       if (dev->flags & IFF_UP)
+               return netdev_priv(dev);
+
+       return NULL;
+}
+
+static struct ip_tunnel **__ipgre_bucket(struct ipgre_net *ign,
+               struct ip_tunnel_parm *parms)
+{
+       __be32 remote = parms->iph.daddr;
+       __be32 local = parms->iph.saddr;
+       __be32 key = parms->i_key;
+       unsigned h = HASH(key);
+       int prio = 0;
+
+       if (local)
+               prio |= 1;
+       if (remote && !ipv4_is_multicast(remote)) {
+               prio |= 2;
+               h ^= HASH(remote);
+       }
+
+       return &ign->tunnels[prio][h];
+}
+
+static inline struct ip_tunnel **ipgre_bucket(struct ipgre_net *ign,
+               struct ip_tunnel *t)
+{
+       return __ipgre_bucket(ign, &t->parms);
+}
+
+static void ipgre_tunnel_link(struct ipgre_net *ign, struct ip_tunnel *t)
+{
+       struct ip_tunnel **tp = ipgre_bucket(ign, t);
+
+       spin_lock_bh(&ipgre_lock);
+       t->next = *tp;
+       rcu_assign_pointer(*tp, t);
+       spin_unlock_bh(&ipgre_lock);
+}
+
+static void ipgre_tunnel_unlink(struct ipgre_net *ign, struct ip_tunnel *t)
+{
+       struct ip_tunnel **tp;
+
+       for (tp = ipgre_bucket(ign, t); *tp; tp = &(*tp)->next) {
+               if (t == *tp) {
+                       spin_lock_bh(&ipgre_lock);
+                       *tp = t->next;
+                       spin_unlock_bh(&ipgre_lock);
+                       break;
+               }
+       }
+}
+
+static struct ip_tunnel *ipgre_tunnel_find(struct net *net,
+                                          struct ip_tunnel_parm *parms,
+                                          int type)
+{
+       __be32 remote = parms->iph.daddr;
+       __be32 local = parms->iph.saddr;
+       __be32 key = parms->i_key;
+       int link = parms->link;
+       struct ip_tunnel *t, **tp;
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       for (tp = __ipgre_bucket(ign, parms); (t = *tp) != NULL; tp = &t->next)
+               if (local == t->parms.iph.saddr &&
+                   remote == t->parms.iph.daddr &&
+                   key == t->parms.i_key &&
+                   link == t->parms.link &&
+                   type == t->dev->type)
+                       break;
+
+       return t;
+}
+
+static struct ip_tunnel * ipgre_tunnel_locate(struct net *net,
+               struct ip_tunnel_parm *parms, int gretap, int create)
+{
+       struct ip_tunnel *t, *nt;
+       struct net_device *dev;
+       char name[IFNAMSIZ];
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       t = ipgre_tunnel_find(net, parms, gretap ? ARPHRD_ETHER : ARPHRD_IPGRE);
+       if (t || !create)
+               return t;
+
+       if (parms->name[0])
+               strlcpy(name, parms->name, IFNAMSIZ);
+       else
+               sprintf(name, "gre%%d");
+
+       dev = alloc_netdev(sizeof(*t), name, gretap ? ipgre_tap_setup
+                                                   : ipgre_tunnel_setup);
+       if (!dev)
+         return NULL;
+
+       dev_net_set(dev, net);
+
+       if (strchr(name, '%')) {
+               if (dev_alloc_name(dev, name) < 0)
+                       goto failed_free;
+       }
+
+       if (gretap)
+               random_ether_addr(dev->dev_addr);
+
+#ifndef GRE_IOCTL_ONLY
+       dev->rtnl_link_ops = gretap ? &ipgre_tap_ops : &ipgre_link_ops;
+#endif
+       nt = netdev_priv(dev);
+       nt->parms = *parms;
+
+       dev->mtu = ipgre_tunnel_bind_dev(dev);
+
+       if (register_netdevice(dev) < 0)
+               goto failed_free;
+
+       dev_hold(dev);
+       ipgre_tunnel_link(ign, nt);
+       return nt;
+
+failed_free:
+       free_netdev(dev);
+       return NULL;
+}
+
+static void ipgre_tunnel_uninit(struct net_device *dev)
+{
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       ipgre_tunnel_unlink(ign, netdev_priv(dev));
+       dev_put(dev);
+}
+
+static unsigned int tunnel_hard_header_len(struct net_device *dev)
+{
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       return dev->hard_header_len;
+#else
+       return (dev->type == ARPHRD_ETHER) ? ETH_HLEN : 0;
+#endif
+}
+
+static void icmp_err_frag(struct sk_buff *skb, struct ip_tunnel *t,
+                         __be16 encap_proto)
+{
+       int mtu = ntohs(icmp_hdr(skb)->un.frag.mtu);
+       int header_len = t->hlen + tunnel_hard_header_len(t->dev);
+       unsigned int orig_mac_header = skb_mac_header(skb) - skb->data;
+       unsigned int orig_nw_header = skb_network_header(skb) - skb->data;
+
+       /* Add the size of the IP header since this is the smallest
+        * packet size the we might do something with and we might as
+        * well fail early if we don't have it.  Plus it allows us to
+        * safely look at the VLAN header if there is one.  The final
+        * size is checked before use. */
+       if (!pskb_may_pull(skb, header_len + sizeof(struct iphdr)))
+               return;
+
+       if (t->dev->type == ARPHRD_ETHER) {
+               skb_set_mac_header(skb, t->hlen);
+               encap_proto = eth_hdr(skb)->h_proto;
+
+               if (encap_proto == htons(ETH_P_8021Q)) {
+                       header_len += VLAN_HLEN;
+                       encap_proto =
+                                  vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+               }
+       }
+
+       skb_set_network_header(skb, header_len);
+       skb->protocol = encap_proto;
+       mtu -= header_len;
+
+       if (skb->protocol == htons(ETH_P_IP)) {
+               if (mtu < IP_MIN_MTU) {
+                       if (ntohs(ip_hdr(skb)->tot_len) >= IP_MIN_MTU)
+                               mtu = IP_MIN_MTU;
+                       else
+                               goto out;
+               }
+
+               header_len += sizeof(struct iphdr);
+       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+               if (mtu < IPV6_MIN_MTU) {
+                       unsigned int packet_length;
+
+                       if (!pskb_may_pull(skb, header_len +
+                                               sizeof(struct ipv6hdr)))
+                               goto out;
+
+                       packet_length = sizeof(struct ipv6hdr) +
+                                             ntohs(ipv6_hdr(skb)->payload_len);
+
+                       if (packet_length >= IPV6_MIN_MTU
+                           || ntohs(ipv6_hdr(skb)->payload_len) == 0)
+                               mtu = IPV6_MIN_MTU;
+                       else
+                               goto out;
+               }
+
+               header_len += sizeof(struct ipv6hdr);
+       } else
+               goto out;
+
+       if (pskb_may_pull(skb, header_len)) {
+               __pskb_pull(skb, t->hlen);
+               send_frag_needed(skb, t->dev, mtu);
+               skb_push(skb, t->hlen);
+       }
+
+out:
+       skb_set_mac_header(skb, orig_mac_header);
+       skb_set_network_header(skb, orig_nw_header);
+       skb->protocol = htons(ETH_P_IP);
+}
+
+static void ipgre_err(struct sk_buff *skb, u32 info)
+{
+
+/* All the routers (except for Linux) return only
+   8 bytes of packet payload. It means, that precise relaying of
+   ICMP in the real Internet is absolutely infeasible.
+
+   Moreover, Cisco "wise men" put GRE key to the third word
+   in GRE header. It makes impossible maintaining even soft state for keyed
+   GRE tunnels with enabled checksum. Tell them "thank you".
+
+   Well, I wonder, rfc1812 was written by Cisco employee,
+   what the hell these idiots break standrads established
+   by themself???
+ */
+
+       struct iphdr *iph = (struct iphdr *)skb->data;
+       __be16 *p;
+       int grehlen = (iph->ihl << 2) + 4;
+       const int type = icmp_hdr(skb)->type;
+       const int code = icmp_hdr(skb)->code;
+       struct ip_tunnel *t;
+       __be16 flags;
+       __be16 gre_proto;
+
+       WARN_ON_ONCE(skb_shared(skb));
+
+       if (!pskb_may_pull(skb, grehlen))
+               return;
+
+       iph = (struct iphdr *)skb->data;
+       p = (__be16 *)(skb->data + (iph->ihl << 2));
+       flags = *gre_flags(p);
+       gre_proto = *gre_protocol(p);
+
+       if (flags&(GRE_CSUM|GRE_KEY|GRE_SEQ|GRE_ROUTING|GRE_VERSION)) {
+               if (flags&(GRE_VERSION|GRE_ROUTING))
+                       return;
+               if (flags&GRE_KEY) {
+                       grehlen += 4;
+                       if (flags&GRE_CSUM)
+                               grehlen += 4;
+               }
+       }
+
+       /* If only 8 bytes returned, keyed message will be dropped here */
+       if (!pskb_may_pull(skb, grehlen))
+               return;
+
+       iph = (struct iphdr *)skb->data;
+
+       switch (type) {
+       default:
+       case ICMP_PARAMETERPROB:
+               return;
+
+       case ICMP_DEST_UNREACH:
+               switch (code) {
+               case ICMP_SR_FAILED:
+                       /* Impossible event. */
+               case ICMP_PORT_UNREACH:
+                       return;
+               case ICMP_FRAG_NEEDED:
+                       /* Soft state for pmtu is maintained by IP core but we
+                        * also want to relay the message back. */
+                       break;
+               default:
+                       /* All others are translated to HOST_UNREACH.
+                          rfc2003 contains "deep thoughts" about NET_UNREACH,
+                          I believe they are just ether pollution. --ANK
+                        */
+                       break;
+               }
+               break;
+       case ICMP_TIME_EXCEEDED:
+               if (code != ICMP_EXC_TTL)
+                       return;
+               break;
+       }
+
+       rcu_read_lock();
+       t = ipgre_tunnel_lookup(skb->dev, iph->daddr, iph->saddr,
+                               flags & GRE_KEY ?
+                               *(((__be32 *)skb->data) + (grehlen / 4) - 1)
+                               : 0, gre_proto);
+
+       if (t == NULL || t->parms.iph.daddr == 0 ||
+           ipv4_is_multicast(t->parms.iph.daddr))
+               goto out;
+
+       if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+               goto out;
+
+       if (code == ICMP_FRAG_NEEDED) {
+               /* Invalidates pointers. */
+               icmp_err_frag(skb, t, gre_proto);
+               goto out;
+       }
+
+       if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
+               t->err_count++;
+       else
+               t->err_count = 1;
+       t->err_time = jiffies;
+out:
+       rcu_read_unlock();
+       return;
+}
+
+static inline void ipgre_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
+{
+       if (INET_ECN_is_ce(iph->tos)) {
+               __be16 protocol = skb->protocol;
+               unsigned int nw_header = skb_network_header(skb) - skb->data;
+
+               if (skb->dev->type == ARPHRD_ETHER
+                   && skb->protocol == htons(ETH_P_8021Q)) {
+                       if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
+                               return;
+
+                       protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+                       nw_header += VLAN_HLEN;
+               }
+
+               if (protocol == htons(ETH_P_IP)) {
+                       if (unlikely(!pskb_may_pull(skb, nw_header
+                           + sizeof(struct iphdr))))
+                               return;
+
+                       IP_ECN_set_ce((struct iphdr *)(nw_header + skb->data));
+               } else if (protocol == htons(ETH_P_IPV6)) {
+                       if (unlikely(!pskb_may_pull(skb, nw_header
+                           + sizeof(struct ipv6hdr))))
+                               return;
+
+                       IP6_ECN_set_ce((struct ipv6hdr *)(nw_header
+                                                         + skb->data));
+               }
+       }
+}
+
+static inline u8
+ipgre_ecn_encapsulate(u8 tos, struct iphdr *old_iph, struct sk_buff *skb)
+{
+       u8 inner = 0;
+       if (skb->protocol == htons(ETH_P_IP))
+               inner = old_iph->tos;
+       else if (skb->protocol == htons(ETH_P_IPV6))
+               inner = ipv6_get_dsfield((struct ipv6hdr *)old_iph);
+       return INET_ECN_encapsulate(tos, inner);
+}
+
+static int ipgre_rcv(struct sk_buff *skb)
+{
+       struct iphdr *iph;
+       u8     *h;
+       __be16    flags;
+       __sum16   csum = 0;
+       __be32 key = 0;
+       u32    seqno = 0;
+       struct ip_tunnel *tunnel;
+       int    offset = 4;
+       __be16 gre_proto;
+       unsigned int len;
+
+       if (!pskb_may_pull(skb, 16))
+               goto drop_nolock;
+
+       iph = ip_hdr(skb);
+       h = skb->data;
+       flags = *gre_flags(h);
+
+       if (flags&(GRE_CSUM|GRE_KEY|GRE_ROUTING|GRE_SEQ|GRE_VERSION)) {
+               /* - Version must be 0.
+                  - We do not support routing headers.
+                */
+               if (flags&(GRE_VERSION|GRE_ROUTING))
+                       goto drop_nolock;
+
+               if (flags&GRE_CSUM) {
+                       switch (skb->ip_summed) {
+                       case CHECKSUM_COMPLETE:
+                               csum = csum_fold(skb->csum);
+                               if (!csum)
+                                       break;
+                               /* fall through */
+                       case CHECKSUM_NONE:
+                               skb->csum = 0;
+                               csum = __skb_checksum_complete(skb);
+                               skb->ip_summed = CHECKSUM_COMPLETE;
+                       }
+                       offset += 4;
+               }
+               if (flags&GRE_KEY) {
+                       key = *(__be32*)(h + offset);
+                       offset += 4;
+               }
+               if (flags&GRE_SEQ) {
+                       seqno = ntohl(*(__be32*)(h + offset));
+                       offset += 4;
+               }
+       }
+
+       gre_proto = *gre_protocol(h);
+
+       rcu_read_lock();
+       if ((tunnel = ipgre_tunnel_lookup(skb->dev,
+                                         iph->saddr, iph->daddr, key,
+                                         gre_proto))) {
+               struct net_device_stats *stats;
+#ifdef HAVE_NETDEV_STATS
+               stats = &tunnel->dev->stats;
+#else
+               stats = &tunnel->stat;
+#endif
+
+               secpath_reset(skb);
+
+               skb->protocol = gre_proto;
+               /* WCCP version 1 and 2 protocol decoding.
+                * - Change protocol to IP
+                * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
+                */
+               if (flags == 0 && gre_proto == htons(ETH_P_WCCP)) {
+                       skb->protocol = htons(ETH_P_IP);
+                       if ((*(h + offset) & 0xF0) != 0x40)
+                               offset += 4;
+               }
+
+               skb->mac_header = skb->network_header;
+               __pskb_pull(skb, offset);
+               skb_postpull_rcsum(skb, skb_transport_header(skb), offset);
+               skb->pkt_type = PACKET_HOST;
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+               if (ipv4_is_multicast(iph->daddr)) {
+                       /* Looped back packet, drop it! */
+                       if (skb_rtable(skb)->fl.iif == 0)
+                               goto drop;
+                       stats->multicast++;
+                       skb->pkt_type = PACKET_BROADCAST;
+               }
+#endif
+
+               if (((flags&GRE_CSUM) && csum) ||
+                   (!(flags&GRE_CSUM) && tunnel->parms.i_flags&GRE_CSUM)) {
+                       stats->rx_crc_errors++;
+                       stats->rx_errors++;
+                       goto drop;
+               }
+               if (tunnel->parms.i_flags&GRE_SEQ) {
+                       if (!(flags&GRE_SEQ) ||
+                           (tunnel->i_seqno && (s32)(seqno - tunnel->i_seqno) < 0)) {
+                               stats->rx_fifo_errors++;
+                               stats->rx_errors++;
+                               goto drop;
+                       }
+                       tunnel->i_seqno = seqno + 1;
+               }
+
+               len = skb->len;
+
+               /* Warning: All skb pointers will be invalidated! */
+               if (tunnel->dev->type == ARPHRD_ETHER) {
+                       if (!pskb_may_pull(skb, ETH_HLEN)) {
+                               stats->rx_length_errors++;
+                               stats->rx_errors++;
+                               goto drop;
+                       }
+
+                       iph = ip_hdr(skb);
+                       skb->protocol = eth_type_trans(skb, tunnel->dev);
+                       skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
+               }
+
+               stats->rx_packets++;
+               stats->rx_bytes += len;
+               skb->dev = tunnel->dev;
+               skb_dst_drop(skb);
+               nf_reset(skb);
+
+               skb_reset_network_header(skb);
+
+               /* Invalidates pointers. */
+               ipgre_ecn_decapsulate(iph, skb);
+
+               netif_rx(skb);
+               rcu_read_unlock();
+               return(0);
+       }
+       icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+
+drop:
+       rcu_read_unlock();
+drop_nolock:
+       kfree_skb(skb);
+       return(0);
+}
+
+static bool check_ipv4_address(__be32 addr)
+{
+       if (ipv4_is_multicast(addr) || ipv4_is_lbcast(addr)
+           || ipv4_is_loopback(addr) || ipv4_is_zeronet(addr))
+               return false;
+
+       return true;
+}
+
+static bool ipv4_should_icmp(struct sk_buff *skb)
+{
+       struct iphdr *old_iph = ip_hdr(skb);
+
+       /* Don't respond to L2 broadcast. */
+       if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
+               return false;
+
+       /* Don't respond to L3 broadcast or invalid addresses. */
+       if (!check_ipv4_address(old_iph->daddr) ||
+           !check_ipv4_address(old_iph->saddr))
+               return false;
+
+       /* Only respond to the first fragment. */
+       if (old_iph->frag_off & htons(IP_OFFSET))
+               return false;
+
+       /* Don't respond to ICMP error messages. */
+       if (old_iph->protocol == IPPROTO_ICMP) {
+               u8 icmp_type, *icmp_typep;
+
+               icmp_typep = skb_header_pointer(skb, (u8 *)old_iph +
+                                               (old_iph->ihl << 2) +
+                                               offsetof(struct icmphdr, type) -
+                                               skb->data, sizeof(icmp_type),
+                                               &icmp_type);
+
+               if (!icmp_typep)
+                       return false;
+
+               if (*icmp_typep > NR_ICMP_TYPES
+                       || (*icmp_typep <= ICMP_PARAMETERPROB
+                               && *icmp_typep != ICMP_ECHOREPLY
+                               && *icmp_typep != ICMP_ECHO))
+                       return false;
+       }
+
+       return true;
+}
+
+static void ipv4_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+                           unsigned int mtu, unsigned int payload_length)
+{
+       struct iphdr *iph, *old_iph = ip_hdr(skb);
+       struct icmphdr *icmph;
+       u8 *payload;
+
+       iph = (struct iphdr *)skb_put(nskb, sizeof(struct iphdr));
+       icmph = (struct icmphdr *)skb_put(nskb, sizeof(struct icmphdr));
+       payload = skb_put(nskb, payload_length);
+
+       /* IP */
+       iph->version            =       4;
+       iph->ihl                =       sizeof(struct iphdr) >> 2;
+       iph->tos                =       (old_iph->tos & IPTOS_TOS_MASK) |
+                                       IPTOS_PREC_INTERNETCONTROL;
+       iph->tot_len            =       htons(sizeof(struct iphdr)
+                                             + sizeof(struct icmphdr)
+                                             + payload_length);
+       get_random_bytes(&iph->id, sizeof iph->id);
+       iph->frag_off           =       0;
+       iph->ttl                =       IPDEFTTL;
+       iph->protocol           =       IPPROTO_ICMP;
+       iph->daddr              =       old_iph->saddr;
+       iph->saddr              =       old_iph->daddr;
+
+       ip_send_check(iph);
+
+       /* ICMP */
+       icmph->type             =       ICMP_DEST_UNREACH;
+       icmph->code             =       ICMP_FRAG_NEEDED;
+       icmph->un.gateway       =       htonl(mtu);
+       icmph->checksum         =       0;
+
+       nskb->csum = csum_partial((u8 *)icmph, sizeof *icmph, 0);
+       nskb->csum = skb_copy_and_csum_bits(skb, (u8 *)old_iph - skb->data,
+                                           payload, payload_length,
+                                           nskb->csum);
+       icmph->checksum = csum_fold(nskb->csum);
+}
+
+static bool ipv6_should_icmp(struct sk_buff *skb)
+{
+       struct ipv6hdr *old_ipv6h = ipv6_hdr(skb);
+       int addr_type;
+       int payload_off = (u8 *)(old_ipv6h + 1) - skb->data;
+       u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+
+       /* Check source address is valid. */
+       addr_type = ipv6_addr_type(&old_ipv6h->saddr);
+       if (addr_type & IPV6_ADDR_MULTICAST || addr_type == IPV6_ADDR_ANY)
+               return false;
+
+       /* Don't reply to unspecified addresses. */
+       if (ipv6_addr_type(&old_ipv6h->daddr) == IPV6_ADDR_ANY)
+               return false;
+
+       /* Don't respond to ICMP error messages. */
+       payload_off = ipv6_skip_exthdr(skb, payload_off, &nexthdr);
+       if (payload_off < 0)
+               return false;
+
+       if (nexthdr == NEXTHDR_ICMP) {
+               u8 icmp_type, *icmp_typep;
+
+               icmp_typep = skb_header_pointer(skb, payload_off +
+                                               offsetof(struct icmp6hdr,
+                                                       icmp6_type),
+                                               sizeof(icmp_type), &icmp_type);
+
+               if (!icmp_typep || !(*icmp_typep & ICMPV6_INFOMSG_MASK))
+                       return false;
+       }
+
+       return true;
+}
+
+static void ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+                           unsigned int mtu, unsigned int payload_length)
+{
+       struct ipv6hdr *ipv6h, *old_ipv6h = ipv6_hdr(skb);
+       struct icmp6hdr *icmp6h;
+       u8 *payload;
+
+       ipv6h = (struct ipv6hdr *)skb_put(nskb, sizeof(struct ipv6hdr));
+       icmp6h = (struct icmp6hdr *)skb_put(nskb, sizeof(struct icmp6hdr));
+       payload = skb_put(nskb, payload_length);
+
+       /* IPv6 */
+       ipv6h->version          =       6;
+       ipv6h->priority         =       0;
+       memset(&ipv6h->flow_lbl, 0, sizeof ipv6h->flow_lbl);
+       ipv6h->payload_len      =       htons(sizeof(struct icmp6hdr)
+                                             + payload_length);
+       ipv6h->nexthdr          =       NEXTHDR_ICMP;
+       ipv6h->hop_limit        =       IPV6_DEFAULT_HOPLIMIT;
+       ipv6_addr_copy(&ipv6h->daddr, &old_ipv6h->saddr);
+       ipv6_addr_copy(&ipv6h->saddr, &old_ipv6h->daddr);
+
+       /* ICMPv6 */
+       icmp6h->icmp6_type      =       ICMPV6_PKT_TOOBIG;
+       icmp6h->icmp6_code      =       0;
+       icmp6h->icmp6_cksum     =       0;
+       icmp6h->icmp6_mtu       =       htonl(mtu);
+
+       nskb->csum = csum_partial((u8 *)icmp6h, sizeof *icmp6h, 0);
+       nskb->csum = skb_copy_and_csum_bits(skb, (u8 *)old_ipv6h - skb->data,
+                                           payload, payload_length,
+                                           nskb->csum);
+       icmp6h->icmp6_cksum = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
+                                               sizeof(struct icmp6hdr)
+                                               + payload_length,
+                                               ipv6h->nexthdr, nskb->csum);
+}
+
+static bool send_frag_needed(struct sk_buff *skb, struct net_device *dev,
+                            unsigned int mtu)
+{
+       unsigned int eth_hdr_len = ETH_HLEN;
+       unsigned int total_length, header_length, payload_length;
+       struct ethhdr *eh, *old_eh = eth_hdr(skb);
+       struct sk_buff *nskb;
+       struct net_device_stats *stats;
+
+       /* Normal IP stack. */
+       if (!dev->br_port) {
+               if (skb->protocol == htons(ETH_P_IP)) {
+                       icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
+                                 htonl(mtu));
+                       return true;
+               } else {
+#ifdef CONFIG_IPV6
+                       icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+                       return true;
+#else
+                       return false;
+#endif
+               }
+       }
+
+       /* Sanity check */
+       if (skb->protocol == htons(ETH_P_IP)) {
+               if (mtu < IP_MIN_MTU)
+                       return false;
+
+               if (!ipv4_should_icmp(skb))
+                       return true;
+       } else {
+               if (mtu < IPV6_MIN_MTU)
+                       return false;
+
+               /* In theory we should do PMTUD on IPv6 multicast messages but
+                * we don't have an address to send from so just fragment. */
+               if (ipv6_addr_type(&ipv6_hdr(skb)->daddr) & IPV6_ADDR_MULTICAST)
+                       return false;
+
+               if (!ipv6_should_icmp(skb))
+                       return true;
+       }
+
+       /* Allocate */
+       if (old_eh->h_proto == htons(ETH_P_8021Q))
+               eth_hdr_len = VLAN_ETH_HLEN;
+
+       payload_length = skb->len - eth_hdr_len;
+       if (skb->protocol == htons(ETH_P_IP)) {
+               header_length = sizeof(struct iphdr) + sizeof(struct icmphdr);
+               total_length = min_t(unsigned int, header_length +
+                                                  payload_length, 576);
+       } else {
+               header_length = sizeof(struct ipv6hdr) +
+                               sizeof(struct icmp6hdr);
+               total_length = min_t(unsigned int, header_length +
+                                                 payload_length, IPV6_MIN_MTU);
+       }
+       total_length = min(total_length, dev->mtu);
+       payload_length = total_length - header_length;
+
+       nskb = netdev_alloc_skb_ip_align(dev, eth_hdr_len + header_length
+                                             + payload_length);
+       if (!nskb)
+               return false;
+
+       /* Ethernet / VLAN */
+       eh = (struct ethhdr *)skb_put(nskb, eth_hdr_len);
+       memcpy(eh->h_dest, old_eh->h_source, ETH_ALEN);
+       memcpy(eh->h_source, dev->dev_addr, ETH_ALEN);
+       eh->h_proto = old_eh->h_proto;
+       if (old_eh->h_proto == htons(ETH_P_8021Q)) {
+               struct vlan_ethhdr *vh = (struct vlan_ethhdr *)eh;
+
+               vh->h_vlan_TCI = vlan_eth_hdr(skb)->h_vlan_TCI;
+               vh->h_vlan_encapsulated_proto = skb->protocol;
+       }
+       nskb->protocol = eth_type_trans(nskb, dev);
+
+       /* Protocol */
+       if (skb->protocol == htons(ETH_P_IP))
+               ipv4_build_icmp(skb, nskb, mtu, payload_length);
+       else
+               ipv6_build_icmp(skb, nskb, mtu, payload_length);
+
+       /* Send */
+#ifdef HAVE_NETDEV_STATS
+       stats = &dev->stats;
+#else
+       stats = &((struct ip_tunnel *)netdev_priv(dev))->stat;
+#endif
+       stats->rx_packets++;
+       stats->rx_bytes += nskb->len;
+
+       netif_rx(nskb);
+       return true;
+}
+
+static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct net_device_stats *stats;
+#ifdef HAVE_NETDEV_QUEUE_STATS
+       struct netdev_queue *txq = netdev_get_tx_queue(dev, 0);
+#endif
+       struct iphdr  *old_iph;
+       struct ipv6hdr *old_ipv6h;
+       struct iphdr  *tiph;
+       u8     tos;
+       __be16 df;
+       struct rtable *rt;                      /* Route to the other host */
+       struct net_device *tdev;                /* Device to other host */
+       struct iphdr  *iph;                     /* Our new IP header */
+       unsigned int max_headroom;              /* The extra header space needed */
+       int    gre_hlen;
+       __be32 dst;
+       int    mtu;
+       __be16 original_protocol;
+       bool is_vlan = false;
+
+#ifdef HAVE_NETDEV_STATS
+       stats = &dev->stats;
+#else
+       stats = &tunnel->stat;
+#endif
+
+       WARN_ON_ONCE(skb_shared(skb));
+
+       /* Validate the protocol headers before we try to use them. */
+       original_protocol = skb->protocol;
+
+       if (dev->type == ARPHRD_ETHER && skb->protocol == htons(ETH_P_8021Q)) {
+               if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
+                       goto tx_error;
+
+               skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+               skb_set_network_header(skb, VLAN_ETH_HLEN);
+               is_vlan = true;
+       }
+
+       old_iph = ip_hdr(skb);
+       old_ipv6h = ipv6_hdr(skb);
+
+       if (skb->protocol == htons(ETH_P_IP)) {
+               if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
+                   + sizeof(struct iphdr) - skb->data)))
+                       skb->protocol = 0;
+       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+               if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
+                   + sizeof(struct ipv6hdr) - skb->data)))
+                       skb->protocol = 0;
+       }
+
+       if (dev->type == ARPHRD_ETHER)
+               IPCB(skb)->flags = 0;
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+       if (dev->header_ops && dev->type == ARPHRD_IPGRE) {
+#else
+       if (dev->hard_header && dev->type == ARPHRD_IPGRE) {
+#endif
+               gre_hlen = 0;
+               tiph = (struct iphdr *)skb->data;
+       } else {
+               gre_hlen = tunnel->hlen;
+               tiph = &tunnel->parms.iph;
+       }
+
+       if ((dst = tiph->daddr) == 0) {
+               /* NBMA tunnel */
+
+               if (skb_dst(skb) == NULL) {
+                       stats->tx_fifo_errors++;
+                       goto tx_error;
+               }
+
+               if (skb->protocol == htons(ETH_P_IP)) {
+                       rt = skb_rtable(skb);
+                       if ((dst = rt->rt_gateway) == 0)
+                               goto tx_error_icmp;
+               }
+#ifdef CONFIG_IPV6
+               else if (skb->protocol == htons(ETH_P_IPV6)) {
+                       struct in6_addr *addr6;
+                       int addr_type;
+                       struct neighbour *neigh = skb_dst(skb)->neighbour;
+
+                       if (neigh == NULL)
+                               goto tx_error;
+
+                       addr6 = (struct in6_addr *)&neigh->primary_key;
+                       addr_type = ipv6_addr_type(addr6);
+
+                       if (addr_type == IPV6_ADDR_ANY) {
+                               addr6 = &ipv6_hdr(skb)->daddr;
+                               addr_type = ipv6_addr_type(addr6);
+                       }
+
+                       if ((addr_type & IPV6_ADDR_COMPATv4) == 0)
+                               goto tx_error_icmp;
+
+                       dst = addr6->s6_addr32[3];
+               }
+#endif
+               else
+                       goto tx_error;
+       }
+
+       tos = tiph->tos;
+       if (tos == 1) {
+               tos = 0;
+               if (skb->protocol == htons(ETH_P_IP))
+                       tos = old_iph->tos;
+               else if (skb->protocol == htons(ETH_P_IPV6))
+                       tos = ipv6_get_dsfield(ipv6_hdr(skb));
+       }
+
+       {
+               struct flowi fl = { .oif = tunnel->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = dst,
+                                               .saddr = tiph->saddr,
+                                               .tos = RT_TOS(tos) } },
+                                   .proto = IPPROTO_GRE };
+               if (ip_route_output_key(dev_net(dev), &rt, &fl)) {
+                       stats->tx_carrier_errors++;
+                       goto tx_error;
+               }
+       }
+       tdev = rt->u.dst.dev;
+
+       if (tdev == dev) {
+               ip_rt_put(rt);
+               stats->collisions++;
+               goto tx_error;
+       }
+
+       df = tiph->frag_off;
+       if (df)
+               mtu = dst_mtu(&rt->u.dst) - tunnel_hard_header_len(dev)
+                       - (is_vlan ? VLAN_HLEN : 0)
+                       - tunnel->hlen;
+       else
+               mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;
+
+       if (skb->protocol == htons(ETH_P_IP))
+               mtu = max(mtu, IP_MIN_MTU);
+       if (skb->protocol == htons(ETH_P_IPV6))
+               mtu = max(mtu, IPV6_MIN_MTU);
+
+       if (skb_dst(skb))
+               skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu);
+
+       if (skb->protocol == htons(ETH_P_IP)) {
+               df |= (old_iph->frag_off&htons(IP_DF));
+
+               if ((old_iph->frag_off&htons(IP_DF)) &&
+                   mtu < ntohs(old_iph->tot_len)) {
+                       if (send_frag_needed(skb, dev, mtu)) {
+                               ip_rt_put(rt);
+                               goto tx_error;
+                       }
+               }
+       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+               unsigned int packet_length = skb->len
+                                            - tunnel_hard_header_len(dev)
+                                            - (is_vlan ? VLAN_HLEN : 0);
+
+#ifdef CONFIG_IPV6
+               struct rt6_info *rt6 = (struct rt6_info *)skb_dst(skb);
+
+               if (rt6 && mtu < dst_mtu(skb_dst(skb)) && mtu >= IPV6_MIN_MTU) {
+                       if ((tunnel->parms.iph.daddr &&
+                            !ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
+                           rt6->rt6i_dst.plen == 128) {
+                               rt6->rt6i_flags |= RTF_MODIFIED;
+                               skb_dst(skb)->metrics[RTAX_MTU-1] = mtu;
+                       }
+               }
+#endif
+
+               /* IPv6 requires PMTUD if the packet is above the minimum MTU.*/
+               if (packet_length > IPV6_MIN_MTU)
+                       df = htons(IP_DF);
+
+               if (mtu < packet_length - tunnel->hlen + gre_hlen) {
+                       if (send_frag_needed(skb, dev, mtu)) {
+                               ip_rt_put(rt);
+                               goto tx_error;
+                       }
+               }
+       }
+
+       if (tunnel->err_count > 0) {
+               if (time_before(jiffies,
+                               tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {
+                       tunnel->err_count--;
+
+                       dst_link_failure(skb);
+               } else
+                       tunnel->err_count = 0;
+       }
+
+       max_headroom = LL_RESERVED_SPACE(tdev) + gre_hlen;
+
+       if (skb_headroom(skb) < max_headroom ||
+           (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
+               struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+               if (!new_skb) {
+                       ip_rt_put(rt);
+#ifdef HAVE_NETDEV_QUEUE_STATS
+                       txq->tx_dropped++;
+#else
+                       stats->tx_dropped++;
+#endif
+                       dev_kfree_skb(skb);
+                       return NETDEV_TX_OK;
+               }
+               if (skb->sk)
+                       skb_set_owner_w(new_skb, skb->sk);
+               dev_kfree_skb(skb);
+               skb = new_skb;
+               old_iph = ip_hdr(skb);
+       }
+
+       skb_reset_transport_header(skb);
+       skb_push(skb, gre_hlen);
+       skb_reset_network_header(skb);
+       memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+       IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
+                             IPSKB_REROUTED);;
+
+       skb_dst_drop(skb);
+       skb_dst_set(skb, &rt->u.dst);
+
+       /*
+        *      Push down and install the GRE header.
+        */
+
+       iph                     =       ip_hdr(skb);
+       iph->version            =       4;
+       iph->ihl                =       sizeof(struct iphdr) >> 2;
+       iph->frag_off           =       df;
+       iph->protocol           =       IPPROTO_GRE;
+       iph->tos                =       ipgre_ecn_encapsulate(tos, old_iph, skb);
+       iph->daddr              =       rt->rt_dst;
+       iph->saddr              =       rt->rt_src;
+
+       /* Allow our local IP stack to fragment the outer packet even if the
+        * DF bit is set.  If we got this far there is nothing more that we
+        * can do with the inner packet. */
+       skb->local_df = 1;
+
+       if ((iph->ttl = tiph->ttl) == 0) {
+               if (skb->protocol == htons(ETH_P_IP))
+                       iph->ttl = old_iph->ttl;
+               else if (skb->protocol == htons(ETH_P_IPV6))
+                       iph->ttl = ((struct ipv6hdr *)old_iph)->hop_limit;
+               else
+                       iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
+       }
+
+       *gre_flags(iph + 1) = tunnel->parms.o_flags;
+       *gre_protocol(iph + 1) = (dev->type == ARPHRD_ETHER) ?
+                                  htons(ETH_P_TEB) : original_protocol;
+
+       if (tunnel->parms.o_flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
+               __be32 *ptr = (__be32*)(((u8*)iph) + tunnel->hlen - 4);
+
+               if (tunnel->parms.o_flags&GRE_SEQ) {
+                       ++tunnel->o_seqno;
+                       *ptr = htonl(tunnel->o_seqno);
+                       ptr--;
+               }
+               if (tunnel->parms.o_flags&GRE_KEY) {
+                       *ptr = tunnel->parms.o_key;
+                       ptr--;
+               }
+               if (tunnel->parms.o_flags&GRE_CSUM) {
+                       *ptr = 0;
+                       *(__sum16*)ptr = ip_compute_csum((void*)(iph+1), skb->len - sizeof(struct iphdr));
+               }
+       }
+
+       nf_reset(skb);
+
+       IPTUNNEL_XMIT();
+       return NETDEV_TX_OK;
+
+tx_error_icmp:
+       dst_link_failure(skb);
+
+tx_error:
+       stats->tx_errors++;
+       dev_kfree_skb(skb);
+       return NETDEV_TX_OK;
+}
+
+static int ipgre_tunnel_bind_dev(struct net_device *dev)
+{
+       struct net_device *tdev = NULL;
+       struct ip_tunnel *tunnel;
+       struct iphdr *iph;
+       int hlen = LL_MAX_HEADER;
+       int mtu = ETH_DATA_LEN;
+       int addend = sizeof(struct iphdr) + 4;
+
+       tunnel = netdev_priv(dev);
+       iph = &tunnel->parms.iph;
+
+       /* Guess output device to choose reasonable mtu and needed_headroom */
+
+       if (iph->daddr) {
+               struct flowi fl = { .oif = tunnel->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = iph->daddr,
+                                               .saddr = iph->saddr,
+                                               .tos = RT_TOS(iph->tos) } },
+                                   .proto = IPPROTO_GRE };
+               struct rtable *rt;
+               if (!ip_route_output_key(dev_net(dev), &rt, &fl)) {
+                       tdev = rt->u.dst.dev;
+                       ip_rt_put(rt);
+               }
+
+               if (dev->type != ARPHRD_ETHER)
+                       dev->flags |= IFF_POINTOPOINT;
+       }
+
+       if (!tdev && tunnel->parms.link)
+               tdev = __dev_get_by_index(dev_net(dev), tunnel->parms.link);
+
+       if (tdev) {
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+               hlen = tdev->hard_header_len + tdev->needed_headroom;
+#else
+               hlen = tdev->hard_header_len;
+#endif
+               mtu = tdev->mtu;
+       }
+       dev->iflink = tunnel->parms.link;
+
+       /* Precalculate GRE options length */
+       if (tunnel->parms.o_flags&(GRE_CSUM|GRE_KEY|GRE_SEQ)) {
+               if (tunnel->parms.o_flags&GRE_CSUM)
+                       addend += 4;
+               if (tunnel->parms.o_flags&GRE_KEY)
+                       addend += 4;
+               if (tunnel->parms.o_flags&GRE_SEQ)
+                       addend += 4;
+       }
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       dev->needed_headroom = hlen + addend;
+#else
+       dev->hard_header_len = hlen + addend;
+#endif
+       mtu -= tunnel_hard_header_len(dev) + addend;
+       tunnel->hlen = addend;
+
+       if (mtu < IP_MIN_MTU)
+               mtu = IP_MIN_MTU;
+
+       /* If we could be connected to a bridge set the normal Ethernet MTU
+        * since all devices on the bridge are required to have the same MTU.
+        * Even though this isn't our optimal MTU we can handle it. */
+       if (dev->type == ARPHRD_ETHER)
+               mtu = ETH_DATA_LEN;
+
+       return mtu;
+}
+
+static int
+ipgre_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+       int err = 0;
+       struct ip_tunnel_parm p;
+       struct ip_tunnel *t;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int add_tunnel, gretap;
+
+       switch (cmd) {
+       case SIOCGETTUNNEL:
+               t = NULL;
+               if (dev == ign->fb_tunnel_dev) {
+                       if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
+                               err = -EFAULT;
+                               break;
+                       }
+                       t = ipgre_tunnel_locate(net, &p, false, 0);
+               }
+               if (t == NULL)
+                       t = netdev_priv(dev);
+               memcpy(&p, &t->parms, sizeof(p));
+               if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
+                       err = -EFAULT;
+               break;
+
+       case SIOCADDTUNNEL:
+       case SIOCCHGTUNNEL:
+       case SIOCADDGRETAP:
+       case SIOCCHGGRETAP:
+               err = -EPERM;
+               if (!capable(CAP_NET_ADMIN))
+                       goto done;
+
+               err = -EFAULT;
+               if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+                       goto done;
+
+               err = -EINVAL;
+               if (p.iph.version != 4 || p.iph.protocol != IPPROTO_GRE ||
+                   p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)) ||
+                   ((p.i_flags|p.o_flags)&(GRE_VERSION|GRE_ROUTING)))
+                       goto done;
+
+               add_tunnel = (cmd == SIOCADDTUNNEL || cmd == SIOCADDGRETAP);
+               gretap = (cmd == SIOCADDGRETAP || cmd == SIOCCHGGRETAP);
+
+               if (!(p.i_flags&GRE_KEY))
+                       p.i_key = 0;
+               if (!(p.o_flags&GRE_KEY))
+                       p.o_key = 0;
+
+               t = ipgre_tunnel_locate(net, &p, gretap, add_tunnel);
+
+               if (dev != ign->fb_tunnel_dev && !add_tunnel) {
+                       if (t != NULL) {
+                               if (t->dev != dev) {
+                                       err = -EEXIST;
+                                       break;
+                               }
+                       } else {
+                               unsigned nflags = 0;
+
+                               t = netdev_priv(dev);
+
+                               if (ipv4_is_multicast(p.iph.daddr))
+                                       nflags = IFF_BROADCAST;
+                               else if (p.iph.daddr)
+                                       nflags = IFF_POINTOPOINT;
+
+                               if ((dev->flags^nflags)&(IFF_POINTOPOINT|IFF_BROADCAST)) {
+                                       err = -EINVAL;
+                                       break;
+                               }
+                               ipgre_tunnel_unlink(ign, t);
+                               t->parms.iph.saddr = p.iph.saddr;
+                               t->parms.iph.daddr = p.iph.daddr;
+                               t->parms.i_key = p.i_key;
+                               t->parms.o_key = p.o_key;
+                               memcpy(dev->dev_addr, &p.iph.saddr, 4);
+                               memcpy(dev->broadcast, &p.iph.daddr, 4);
+                               ipgre_tunnel_link(ign, t);
+                               netdev_state_change(dev);
+                       }
+               }
+
+               if (t) {
+                       err = 0;
+                       if (!add_tunnel) {
+                               t->parms.iph.ttl = p.iph.ttl;
+                               t->parms.iph.tos = p.iph.tos;
+                               t->parms.iph.frag_off = p.iph.frag_off;
+                               if (t->parms.link != p.link) {
+                                       t->parms.link = p.link;
+                                       dev->mtu = ipgre_tunnel_bind_dev(dev);
+                                       netdev_state_change(dev);
+                               }
+                       }
+                       if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+                               err = -EFAULT;
+               } else
+                       err = (add_tunnel ? -ENOBUFS : -ENOENT);
+               break;
+
+       case SIOCDELTUNNEL:
+               err = -EPERM;
+               if (!capable(CAP_NET_ADMIN))
+                       goto done;
+
+               if (dev == ign->fb_tunnel_dev) {
+                       err = -EFAULT;
+                       if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+                               goto done;
+                       err = -ENOENT;
+                       if ((t = ipgre_tunnel_locate(net, &p, false, 0)) == NULL)
+                               goto done;
+                       err = -EPERM;
+                       if (t == netdev_priv(ign->fb_tunnel_dev))
+                               goto done;
+                       dev = t->dev;
+               }
+               unregister_netdevice(dev);
+               err = 0;
+               break;
+
+       default:
+               err = -EINVAL;
+       }
+
+done:
+       return err;
+}
+
+#ifndef HAVE_NETDEV_STATS
+static struct net_device_stats *ipgre_tunnel_get_stats(struct net_device *dev)
+{
+       return &(((struct ip_tunnel*)netdev_priv(dev))->stat);
+}
+#endif
+
+static int ipgre_tunnel_change_mtu(struct net_device *dev, int new_mtu)
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       if (new_mtu < IP_MIN_MTU ||
+           new_mtu > 0xFFF8 - tunnel_hard_header_len(dev) - tunnel->hlen)
+               return -EINVAL;
+       dev->mtu = new_mtu;
+       return 0;
+}
+
+/* Nice toy. Unfortunately, useless in real life :-)
+   It allows to construct virtual multiprotocol broadcast "LAN"
+   over the Internet, provided multicast routing is tuned.
+
+
+   I have no idea was this bicycle invented before me,
+   so that I had to set ARPHRD_IPGRE to a random value.
+   I have an impression, that Cisco could make something similar,
+   but this feature is apparently missing in IOS<=11.2(8).
+
+   I set up 10.66.66/24 and fec0:6666:6666::0/96 as virtual networks
+   with broadcast 224.66.66.66. If you have access to mbone, play with me :-)
+
+   ping -t 255 224.66.66.66
+
+   If nobody answers, mbone does not work.
+
+   ip tunnel add Universe mode gre remote 224.66.66.66 local <Your_real_addr> ttl 255
+   ip addr add 10.66.66.<somewhat>/24 dev Universe
+   ifconfig Universe up
+   ifconfig Universe add fe80::<Your_real_addr>/10
+   ifconfig Universe add fec0:6666:6666::<Your_real_addr>/96
+   ftp 10.66.66.66
+   ...
+   ftp fec0:6666:6666::193.233.7.65
+   ...
+
+ */
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static int ipgre_header(struct sk_buff *skb, struct net_device *dev,
+                       unsigned short type,
+                       const void *daddr, const void *saddr, unsigned len)
+#else
+static int ipgre_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+                       void *daddr, void *saddr, unsigned len)
+#endif
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+       struct iphdr *iph = (struct iphdr *)skb_push(skb, t->hlen);
+       __be16 *p = (__be16*)(iph+1);
+
+       memcpy(iph, &t->parms.iph, sizeof(struct iphdr));
+       p[0]            = t->parms.o_flags;
+       p[1]            = htons(type);
+
+       /*
+        *      Set the source hardware address.
+        */
+
+       if (saddr)
+               memcpy(&iph->saddr, saddr, 4);
+
+       if (daddr) {
+               memcpy(&iph->daddr, daddr, 4);
+               return t->hlen;
+       }
+       if (iph->daddr && !ipv4_is_multicast(iph->daddr))
+               return t->hlen;
+
+       return -t->hlen;
+}
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static int ipgre_header_parse(const struct sk_buff *skb, unsigned char *haddr)
+#else
+static int ipgre_header_parse(struct sk_buff *skb, unsigned char *haddr)
+#endif
+{
+       struct iphdr *iph = (struct iphdr *) skb_mac_header(skb);
+       memcpy(haddr, &iph->saddr, 4);
+       return 4;
+}
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static const struct header_ops ipgre_header_ops = {
+       .create = ipgre_header,
+       .parse  = ipgre_header_parse,
+};
+#endif
+
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+static int ipgre_open(struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+
+       if (ipv4_is_multicast(t->parms.iph.daddr)) {
+               struct flowi fl = { .oif = t->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = t->parms.iph.daddr,
+                                               .saddr = t->parms.iph.saddr,
+                                               .tos = RT_TOS(t->parms.iph.tos) } },
+                                   .proto = IPPROTO_GRE };
+               struct rtable *rt;
+               if (ip_route_output_key(dev_net(dev), &rt, &fl))
+                       return -EADDRNOTAVAIL;
+               dev = rt->u.dst.dev;
+               ip_rt_put(rt);
+               if (__in_dev_get_rtnl(dev) == NULL)
+                       return -EADDRNOTAVAIL;
+               t->mlink = dev->ifindex;
+               ip_mc_inc_group(__in_dev_get_rtnl(dev), t->parms.iph.daddr);
+       }
+       return 0;
+}
+
+static int ipgre_close(struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+
+       if (ipv4_is_multicast(t->parms.iph.daddr) && t->mlink) {
+               struct in_device *in_dev;
+               in_dev = inetdev_by_index(dev_net(dev), t->mlink);
+               if (in_dev) {
+                       ip_mc_dec_group(in_dev, t->parms.iph.daddr);
+                       in_dev_put(in_dev);
+               }
+       }
+       return 0;
+}
+
+#endif
+
+static void ethtool_getinfo(struct net_device *dev,
+                           struct ethtool_drvinfo *info)
+{
+       strcpy(info->driver, "ip_gre");
+       strcpy(info->version, "Open vSwitch "VERSION BUILDNR);
+       strcpy(info->bus_info, dev->type == ARPHRD_ETHER ? "gretap" : "gre");
+}
+
+static struct ethtool_ops ethtool_ops = {
+       .get_drvinfo            = ethtool_getinfo,
+};
+
+#ifdef HAVE_NET_DEVICE_OPS
+static const struct net_device_ops ipgre_netdev_ops = {
+       .ndo_init               = ipgre_tunnel_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+       .ndo_open               = ipgre_open,
+       .ndo_stop               = ipgre_close,
+#endif
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_do_ioctl           = ipgre_tunnel_ioctl,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+#endif
+
+static void ipgre_tunnel_setup(struct net_device *dev)
+{
+#ifdef HAVE_NET_DEVICE_OPS
+       dev->netdev_ops         = &ipgre_netdev_ops;
+#else
+       dev->init               = ipgre_tunnel_init;
+       dev->uninit             = ipgre_tunnel_uninit;
+       dev->hard_start_xmit    = ipgre_tunnel_xmit;
+#ifndef HAVE_NETDEV_STATS
+       dev->get_stats          = ipgre_tunnel_get_stats;
+#endif
+       dev->do_ioctl           = ipgre_tunnel_ioctl;
+       dev->change_mtu         = ipgre_tunnel_change_mtu;
+#endif /* HAVE_NET_DEVICE_OPS */
+       dev->destructor         = free_netdev;
+
+       dev->type               = ARPHRD_IPGRE;
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       dev->needed_headroom    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
+#else
+       dev->hard_header_len    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
+#endif
+       dev->mtu                = ETH_DATA_LEN - sizeof(struct iphdr) - 4;
+       dev->flags              = IFF_NOARP;
+       dev->iflink             = 0;
+       dev->addr_len           = 4;
+       dev->features           |= NETIF_F_NETNS_LOCAL;
+       dev->priv_flags         &= ~IFF_XMIT_DST_RELEASE;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
+}
+
+static int ipgre_tunnel_init(struct net_device *dev)
+{
+       struct ip_tunnel *tunnel;
+       struct iphdr *iph;
+
+       tunnel = netdev_priv(dev);
+       iph = &tunnel->parms.iph;
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
+       memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
+
+       if (iph->daddr) {
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+               if (ipv4_is_multicast(iph->daddr)) {
+                       if (!iph->saddr)
+                               return -EINVAL;
+                       dev->flags = IFF_BROADCAST;
+#ifdef HAVE_NETDEV_HEADER_OPS
+                       dev->header_ops = &ipgre_header_ops;
+#else
+                       dev->hard_header = ipgre_header;
+                       dev->hard_header_parse = ipgre_header_parse;
+#endif
+#ifndef HAVE_NET_DEVICE_OPS
+                       dev->open = ipgre_open;
+                       dev->stop = ipgre_close;
+#endif
+               }
+#endif
+       } else {
+#ifdef HAVE_NETDEV_HEADER_OPS
+               dev->header_ops = &ipgre_header_ops;
+#else
+               dev->hard_header = ipgre_header;
+               dev->hard_header_parse = ipgre_header_parse;
+#endif
+       }
+
+       return 0;
+}
+
+#ifdef HAVE_NET_DEVICE_OPS
+static void ipgre_fb_tunnel_init(struct net_device *dev)
+#else
+static int ipgre_fb_tunnel_init(struct net_device *dev)
+#endif
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct iphdr *iph = &tunnel->parms.iph;
+       struct ipgre_net *ign = net_generic(dev_net(dev), ipgre_net_id);
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       iph->version            = 4;
+       iph->protocol           = IPPROTO_GRE;
+       iph->ihl                = 5;
+       tunnel->hlen            = sizeof(struct iphdr) + 4;
+
+       dev_hold(dev);
+       ign->tunnels_wc[0]      = tunnel;
+
+#ifndef HAVE_NET_DEVICE_OPS
+       return 0;
+#endif
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+static struct net_protocol ipgre_protocol = {
+#else
+static const struct net_protocol ipgre_protocol = {
+#endif
+       .handler        =       ipgre_rcv,
+       .err_handler    =       ipgre_err,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+       .netns_ok       =       1,
+#endif
+};
+
+static void ipgre_destroy_tunnels(struct ipgre_net *ign, struct list_head *head)
+{
+       int prio;
+
+       for (prio = 0; prio < 4; prio++) {
+               int h;
+               for (h = 0; h < HASH_SIZE; h++) {
+                       struct ip_tunnel *t = ign->tunnels[prio][h];
+
+                       while (t != NULL) {
+                               unregister_netdevice_queue(t->dev, head);
+                               t = t->next;
+                       }
+               }
+       }
+}
+
+static int ipgre_init_net(struct net *net)
+{
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int err;
+
+       ign->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), GRE_IOCTL_DEVICE,
+                                          ipgre_tunnel_setup);
+       if (!ign->fb_tunnel_dev) {
+               err = -ENOMEM;
+               goto err_alloc_dev;
+       }
+       dev_net_set(ign->fb_tunnel_dev, net);
+
+#ifdef HAVE_NET_DEVICE_OPS
+       ipgre_fb_tunnel_init(ign->fb_tunnel_dev);
+#else
+       ign->fb_tunnel_dev->init = ipgre_fb_tunnel_init;
+#endif
+#ifndef GRE_IOCTL_ONLY
+       ign->fb_tunnel_dev->rtnl_link_ops = &ipgre_link_ops;
+#endif
+
+       if ((err = register_netdev(ign->fb_tunnel_dev)))
+               goto err_reg_dev;
+
+       return 0;
+
+err_reg_dev:
+       free_netdev(ign->fb_tunnel_dev);
+err_alloc_dev:
+       return err;
+}
+
+static void ipgre_exit_net(struct net *net)
+{
+       struct ipgre_net *ign;
+       LIST_HEAD(list);
+
+       ign = net_generic(net, ipgre_net_id);
+       rtnl_lock();
+       ipgre_destroy_tunnels(ign, &list);
+       unregister_netdevice_many(&list);
+       rtnl_unlock();
+}
+
+static struct pernet_operations ipgre_net_ops = {
+       .init = ipgre_init_net,
+       .exit = ipgre_exit_net,
+       .id   = &ipgre_net_id,
+       .size = sizeof(struct ipgre_net),
+};
+
+static int ipgre_tap_init(struct net_device *dev)
+{
+       struct ip_tunnel *tunnel;
+
+       tunnel = netdev_priv(dev);
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       ipgre_tunnel_bind_dev(dev);
+
+       return 0;
+}
+
+#ifdef HAVE_NET_DEVICE_OPS
+static const struct net_device_ops ipgre_tap_netdev_ops = {
+       .ndo_init               = ipgre_tap_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_set_mac_address    = eth_mac_addr,
+       .ndo_validate_addr      = eth_validate_addr,
+       .ndo_do_ioctl           = ipgre_tunnel_ioctl,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+#endif
+
+static void ipgre_tap_setup(struct net_device *dev)
+{
+       ether_setup(dev);
+
+#ifdef HAVE_NET_DEVICE_OPS
+       dev->netdev_ops         = &ipgre_tap_netdev_ops;
+#else
+       dev->init               = ipgre_tap_init;
+       dev->uninit             = ipgre_tunnel_uninit;
+       dev->hard_start_xmit    = ipgre_tunnel_xmit;
+#ifndef HAVE_NETDEV_STATS
+       dev->get_stats          = ipgre_tunnel_get_stats;
+#endif
+       dev->do_ioctl           = ipgre_tunnel_ioctl;
+       dev->change_mtu         = ipgre_tunnel_change_mtu;
+#endif /* HAVE_NET_DEVICE_OPS */
+       dev->destructor         = free_netdev;
+
+       dev->iflink             = 0;
+       dev->features           |= NETIF_F_NETNS_LOCAL;
+       dev->tx_queue_len       = 0;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
+}
+
+#ifndef GRE_IOCTL_ONLY
+static int ipgre_tunnel_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be16 flags;
+
+       if (!data)
+               return 0;
+
+       flags = 0;
+       if (data[IFLA_GRE_IFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_IFLAGS]);
+       if (data[IFLA_GRE_OFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_OFLAGS]);
+       if (flags & (GRE_VERSION|GRE_ROUTING))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int ipgre_tap_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be32 daddr;
+
+       if (tb[IFLA_ADDRESS]) {
+               if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
+                       return -EINVAL;
+               if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
+                       return -EADDRNOTAVAIL;
+       }
+
+       if (!data)
+               goto out;
+
+       if (data[IFLA_GRE_REMOTE]) {
+               memcpy(&daddr, nla_data(data[IFLA_GRE_REMOTE]), 4);
+               if (!daddr)
+                       return -EINVAL;
+       }
+
+out:
+       return ipgre_tunnel_validate(tb, data);
+}
+
+static void ipgre_netlink_parms(struct nlattr *data[],
+                               struct ip_tunnel_parm *parms)
+{
+       memset(parms, 0, sizeof(*parms));
+
+       parms->iph.protocol = IPPROTO_GRE;
+
+       if (!data)
+               return;
+
+       if (data[IFLA_GRE_LINK])
+               parms->link = nla_get_u32(data[IFLA_GRE_LINK]);
+
+       if (data[IFLA_GRE_IFLAGS])
+               parms->i_flags = nla_get_be16(data[IFLA_GRE_IFLAGS]);
+
+       if (data[IFLA_GRE_OFLAGS])
+               parms->o_flags = nla_get_be16(data[IFLA_GRE_OFLAGS]);
+
+       if (data[IFLA_GRE_IKEY])
+               parms->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
+
+       if (data[IFLA_GRE_OKEY])
+               parms->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
+
+       if (data[IFLA_GRE_LOCAL])
+               parms->iph.saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
+
+       if (data[IFLA_GRE_REMOTE])
+               parms->iph.daddr = nla_get_be32(data[IFLA_GRE_REMOTE]);
+
+       if (data[IFLA_GRE_TTL])
+               parms->iph.ttl = nla_get_u8(data[IFLA_GRE_TTL]);
+
+       if (data[IFLA_GRE_TOS])
+               parms->iph.tos = nla_get_u8(data[IFLA_GRE_TOS]);
+
+       if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC]))
+               parms->iph.frag_off = htons(IP_DF);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
+static int ipgre_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[],
+                        struct nlattr *data[])
+#else
+static int ipgre_newlink(struct net_device *dev, struct nlattr *tb[],
+                        struct nlattr *data[])
+#endif
+{
+       struct ip_tunnel *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int mtu;
+       int err;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &nt->parms);
+
+       if (ipgre_tunnel_find(net, &nt->parms, dev->type))
+               return -EEXIST;
+
+       if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
+               random_ether_addr(dev->dev_addr);
+
+       mtu = ipgre_tunnel_bind_dev(dev);
+       if (!tb[IFLA_MTU])
+               dev->mtu = mtu;
+
+       err = register_netdevice(dev);
+       if (err)
+               goto out;
+
+       dev_hold(dev);
+       ipgre_tunnel_link(ign, nt);
+
+out:
+       return err;
+}
+
+static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
+                           struct nlattr *data[])
+{
+       struct ip_tunnel *t, *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       struct ip_tunnel_parm p;
+       int mtu;
+
+       if (dev == ign->fb_tunnel_dev)
+               return -EINVAL;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &p);
+
+       t = ipgre_tunnel_locate(net, &p, false, 0);
+
+       if (t) {
+               if (t->dev != dev)
+                       return -EEXIST;
+       } else {
+               t = nt;
+
+               if (dev->type != ARPHRD_ETHER) {
+                       unsigned nflags = 0;
+
+                       if (ipv4_is_multicast(p.iph.daddr))
+                               nflags = IFF_BROADCAST;
+                       else if (p.iph.daddr)
+                               nflags = IFF_POINTOPOINT;
+
+                       if ((dev->flags ^ nflags) &
+                           (IFF_POINTOPOINT | IFF_BROADCAST))
+                               return -EINVAL;
+               }
+
+               ipgre_tunnel_unlink(ign, t);
+               t->parms.iph.saddr = p.iph.saddr;
+               t->parms.iph.daddr = p.iph.daddr;
+               t->parms.i_key = p.i_key;
+               if (dev->type != ARPHRD_ETHER) {
+                       memcpy(dev->dev_addr, &p.iph.saddr, 4);
+                       memcpy(dev->broadcast, &p.iph.daddr, 4);
+               }
+               ipgre_tunnel_link(ign, t);
+               netdev_state_change(dev);
+       }
+
+       t->parms.o_key = p.o_key;
+       t->parms.iph.ttl = p.iph.ttl;
+       t->parms.iph.tos = p.iph.tos;
+       t->parms.iph.frag_off = p.iph.frag_off;
+
+       if (t->parms.link != p.link) {
+               t->parms.link = p.link;
+               mtu = ipgre_tunnel_bind_dev(dev);
+               if (!tb[IFLA_MTU])
+                       dev->mtu = mtu;
+               netdev_state_change(dev);
+       }
+
+       return 0;
+}
+
+static size_t ipgre_get_size(const struct net_device *dev)
+{
+       return
+               /* IFLA_GRE_LINK */
+               nla_total_size(4) +
+               /* IFLA_GRE_IFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_OFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_IKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_OKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_LOCAL */
+               nla_total_size(4) +
+               /* IFLA_GRE_REMOTE */
+               nla_total_size(4) +
+               /* IFLA_GRE_TTL */
+               nla_total_size(1) +
+               /* IFLA_GRE_TOS */
+               nla_total_size(1) +
+               /* IFLA_GRE_PMTUDISC */
+               nla_total_size(1) +
+               0;
+}
+
+static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+       struct ip_tunnel_parm *p = &t->parms;
+
+       NLA_PUT_U32(skb, IFLA_GRE_LINK, p->link);
+       NLA_PUT_BE16(skb, IFLA_GRE_IFLAGS, p->i_flags);
+       NLA_PUT_BE16(skb, IFLA_GRE_OFLAGS, p->o_flags);
+       NLA_PUT_BE32(skb, IFLA_GRE_IKEY, p->i_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_OKEY, p->o_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_LOCAL, p->iph.saddr);
+       NLA_PUT_BE32(skb, IFLA_GRE_REMOTE, p->iph.daddr);
+       NLA_PUT_U8(skb, IFLA_GRE_TTL, p->iph.ttl);
+       NLA_PUT_U8(skb, IFLA_GRE_TOS, p->iph.tos);
+       NLA_PUT_U8(skb, IFLA_GRE_PMTUDISC, !!(p->iph.frag_off & htons(IP_DF)));
+
+       return 0;
+
+nla_put_failure:
+       return -EMSGSIZE;
+}
+
+static const struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
+       [IFLA_GRE_LINK]         = { .type = NLA_U32 },
+       [IFLA_GRE_IFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_OFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_IKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_OKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_LOCAL]        = { .len = FIELD_SIZEOF(struct iphdr, saddr) },
+       [IFLA_GRE_REMOTE]       = { .len = FIELD_SIZEOF(struct iphdr, daddr) },
+       [IFLA_GRE_TTL]          = { .type = NLA_U8 },
+       [IFLA_GRE_TOS]          = { .type = NLA_U8 },
+       [IFLA_GRE_PMTUDISC]     = { .type = NLA_U8 },
+};
+
+static struct rtnl_link_ops ipgre_link_ops __read_mostly = {
+       .kind           = "gre",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tunnel_setup,
+       .validate       = ipgre_tunnel_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
+};
+
+static struct rtnl_link_ops ipgre_tap_ops __read_mostly = {
+       .kind           = "gretap",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tap_setup,
+       .validate       = ipgre_tap_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
+};
+#endif
+
+/*
+ *     And now the modules code and kernel interface.
+ */
+
+static int __init ipgre_init(void)
+{
+       int err;
+
+       printk(KERN_INFO "Open vSwitch GRE over IPv4, built "__DATE__" "
+                        __TIME__"\n");
+
+       if (inet_add_protocol(&ipgre_protocol, IPPROTO_GRE) < 0) {
+               printk(KERN_INFO "ipgre init: can't add protocol\n");
+               return -EAGAIN;
+       }
+
+       err = register_pernet_device(&ipgre_net_ops);
+       if (err < 0)
+               goto pernet_device_failed;
+
+#ifndef GRE_IOCTL_ONLY
+       err = rtnl_link_register(&ipgre_link_ops);
+       if (err < 0)
+               goto rtnl_link_failed;
+
+       err = rtnl_link_register(&ipgre_tap_ops);
+       if (err < 0)
+               goto tap_ops_failed;
+#endif
+
+out:
+       return err;
+
+#ifndef GRE_IOCTL_ONLY
+tap_ops_failed:
+       rtnl_link_unregister(&ipgre_link_ops);
+rtnl_link_failed:
+       unregister_pernet_device(&ipgre_net_ops);
+#endif
+pernet_device_failed:
+       inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
+       goto out;
+
+}
+
+static void __exit ipgre_fini(void)
+{
+#ifndef GRE_IOCTL_ONLY
+       rtnl_link_unregister(&ipgre_tap_ops);
+       rtnl_link_unregister(&ipgre_link_ops);
+#endif
+       unregister_pernet_device(&ipgre_net_ops);
+       if (inet_del_protocol(&ipgre_protocol, IPPROTO_GRE) < 0)
+               printk(KERN_INFO "ipgre close: can't remove protocol\n");
+}
+
+module_init(ipgre_init);
+module_exit(ipgre_fini);
+MODULE_DESCRIPTION("GRE over IPv4 tunneling driver");
+MODULE_LICENSE("GPL");
+#ifndef GRE_IOCTL_ONLY
+MODULE_ALIAS_RTNL_LINK("gre");
+MODULE_ALIAS_RTNL_LINK("gretap");
+#endif
+
diff --git a/datapath/linux-2.6/compat-2.6/ip_output-ip_gre.c b/datapath/linux-2.6/compat-2.6/ip_output-ip_gre.c
new file mode 100644 (file)
index 0000000..a09fcbc
--- /dev/null
@@ -0,0 +1,33 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+#include <linux/netfilter_ipv4.h>
+#include <net/ip.h>
+
+int __ip_local_out(struct sk_buff *skb)
+{
+       struct iphdr *iph = ip_hdr(skb);
+
+       iph->tot_len = htons(skb->len);
+       ip_send_check(iph);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+       return nf_hook(PF_INET, NF_IP_LOCAL_OUT, &skb, NULL, skb->dst->dev,
+                      dst_output);
+#else
+       return nf_hook(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dst->dev,
+                      dst_output);
+#endif /* kernel < 2.6.24 */
+}
+
+int ip_local_out(struct sk_buff *skb)
+{
+       int err;
+
+       err = __ip_local_out(skb);
+       if (likely(err == 1))
+               err = dst_output(skb);
+
+       return err;
+}
+
+#endif /* kernel < 2.6.25 */
diff --git a/datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c b/datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c
new file mode 100644 (file)
index 0000000..21a6717
--- /dev/null
@@ -0,0 +1,117 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+
+#include <linux/sched.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+#undef pernet_operations
+#undef register_pernet_device
+#undef unregister_pernet_device
+#undef net_assign_generic
+#undef net_generic
+
+/* This trivial implementation assumes that there is only a single pernet
+ * device registered and that the caller is well behaved.  It only weakly
+ * attempts to check that these conditions are true. */
+
+static struct extended_pernet_operations *dev_ops;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+static void *ng_data;
+#else
+static struct pernet_operations new_ops;
+#endif
+
+static int device_init_net(struct net *net)
+{
+       int err;
+       if (dev_ops->id && dev_ops->size) {
+               void *data = kzalloc(dev_ops->size, GFP_KERNEL);
+               if (!data)
+                       return -ENOMEM;
+
+               err = rpl_net_assign_generic(net, *dev_ops->id, data);
+               if (err) {
+                       kfree(data);
+                       return err;
+               }
+       }
+       if (dev_ops->init)
+               return dev_ops->init(net);
+       return 0;
+}
+
+static void device_exit_net(struct net *net)
+{
+       if (dev_ops->exit)
+               dev_ops->exit(net);
+
+       if (dev_ops->id && dev_ops->size) {
+               int id = *dev_ops->id;
+               kfree(rpl_net_generic(net, id));
+       }
+}
+
+int rpl_register_pernet_device(struct extended_pernet_operations *ops)
+{
+       BUG_ON(dev_ops);
+       dev_ops = ops;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+       if (dev_ops->id)
+               *dev_ops->id = 1;
+
+       return device_init_net(NULL);
+#else
+       memcpy(&new_ops, dev_ops, sizeof new_ops);
+       new_ops.init = device_init_net;
+       new_ops.exit = device_exit_net;
+
+       if (ops->id)
+               return register_pernet_gen_device(dev_ops->id, &new_ops);
+       else
+               return register_pernet_device(&new_ops);
+#endif
+}
+
+void rpl_unregister_pernet_device(struct extended_pernet_operations *ops)
+{
+       BUG_ON(!dev_ops);
+       BUG_ON(dev_ops != ops);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+       device_exit_net(NULL);
+#else
+       if (ops->id)
+               unregister_pernet_gen_device(*dev_ops->id, &new_ops);
+       else
+               unregister_pernet_device(&new_ops);
+#endif
+
+       dev_ops = NULL;
+}
+
+int rpl_net_assign_generic(struct net *net, int id, void *data)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+       BUG_ON(id != 1);
+
+       ng_data = data;
+       return 0;
+#else
+       return net_assign_generic(net, id, data);
+#endif
+}
+
+void *rpl_net_generic(struct net *net, int id)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+       BUG_ON(id != 1);
+
+       return ng_data;
+#else
+       return net_generic(net, id);
+#endif
+}
+
+#endif /* kernel < 2.6.33 */
diff --git a/datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c b/datapath/linux-2.6/compat-2.6/skbuff-openvswitch.c
new file mode 100644 (file)
index 0000000..a9743ad
--- /dev/null
@@ -0,0 +1,13 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+
+#include <linux/netdevice.h>
+
+void __skb_warn_lro_forwarding(const struct sk_buff *skb)
+{
+       if (net_ratelimit())
+               printk(KERN_WARNING "%s: received packets cannot be forwarded"
+                                   " while LRO is enabled\n", skb->dev->name);
+}
+
+#endif /* kernel < 2.6.27 */
index 3cda336..e609c62 100644 (file)
@@ -437,22 +437,18 @@ static ssize_t veth_store_veth_pairs(struct class *cls, const char *buffer,
 {
        int c = *buffer++;
        int retval;
-       printk("1\n");
        if (c == '+') {
                char devname[IFNAMSIZ + 1] = "";
                char peername[IFNAMSIZ + 1] = "";
                char *comma = strchr(buffer, ',');
-               printk("2\n");
                if (!comma)
                        goto err_no_cmd;
                strncat(devname, buffer,
                        min_t(int, sizeof devname, comma - buffer));
                strncat(peername, comma + 1,
                        min_t(int, sizeof peername, strcspn(comma + 1, "\n")));
-               printk("3 '%s' '%s'\n", devname, peername);
                if (!dev_valid_name(devname) || !dev_valid_name(peername))
                        goto err_no_cmd;
-               printk("4\n");
                rtnl_lock();
                retval = veth_newlink(devname, peername);
                rtnl_unlock();
index 813987e..1b64945 100644 (file)
@@ -1,7 +1,5 @@
 EXTRA_DIST += \
        debian/changelog \
-       debian/commands/reconfigure \
-       debian/commands/update \
        debian/compat \
        debian/control \
        debian/control.modules.in \
@@ -9,8 +7,6 @@ EXTRA_DIST += \
        debian/corekeeper.cron.daily \
        debian/corekeeper.init \
        debian/dirs \
-       debian/ovs-switch-setup \
-       debian/ovs-switch-setup.8 \
        debian/openvswitch-common.dirs \
        debian/openvswitch-common.install \
        debian/openvswitch-common.manpages \
@@ -26,6 +22,10 @@ EXTRA_DIST += \
        debian/openvswitch-datapath-source.copyright \
        debian/openvswitch-datapath-source.dirs \
        debian/openvswitch-datapath-source.install \
+       debian/openvswitch-monitor.default \
+       debian/openvswitch-monitor.dirs \
+       debian/openvswitch-monitor.init \
+       debian/openvswitch-monitor.install \
        debian/openvswitch-pki-server.apache2 \
        debian/openvswitch-pki-server.dirs \
        debian/openvswitch-pki-server.install \
@@ -45,6 +45,19 @@ EXTRA_DIST += \
        debian/openvswitch-switch.postinst \
        debian/openvswitch-switch.postrm \
        debian/openvswitch-switch.template \
+       debian/openvswitch-switchui.copyright \
+       debian/openvswitch-switchui.default \
+       debian/openvswitch-switchui.dirs \
+       debian/openvswitch-switchui.init \
+       debian/openvswitch-switchui.install \
+       debian/openvswitch-wdt.default \
+       debian/openvswitch-wdt.dirs \
+       debian/openvswitch-wdt.init \
+       debian/openvswitch-wdt.install \
+       debian/ovs-switch-setup \
+       debian/ovs-switch-setup.8 \
        debian/po/POTFILES.in \
        debian/po/templates.pot \
-       debian/rules
+       debian/reconfigure \
+       debian/rules \
+       debian/rules.modules
index 4aa1f90..eb1d40a 100644 (file)
@@ -1,5 +1,5 @@
-openvswitch (0.90.0) unstable; urgency=low
+openvswitch (0.90.6) unstable; urgency=low
 
   * Development version.
 
- -- Open vSwitch developers <ovs-dev@openvswitch.org>  Mon, 19 Nov 2007 14:57:52 -0800
+ -- Ben Pfaff <blp@nicira.com>  Tue, 15 Dec 2009 11:00:11 -0800
diff --git a/debian/commands/update b/debian/commands/update
deleted file mode 100755 (executable)
index 545e3c2..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#! /bin/sh
-set -e
-apt-get update -qy
-apt-get upgrade -qy
index 09eda11..458d23d 100644 (file)
@@ -1,13 +1,13 @@
 Source: openvswitch
 Section: net
 Priority: extra
-Maintainer: Open vSwitch developers <ovs-dev@openvswitch.org>
-Build-Depends: debhelper (>= 5), autoconf (>= 2.60), automake1.10, libssl-dev, pkg-config (>= 0.21), po-debconf, bzip2, openssl, libncurses5-dev, libpcre3-dev
+Maintainer: Open vSwitch developers <dev@openvswitch.org>
+Build-Depends: debhelper (>= 5), autoconf (>= 2.64), automake1.10, libssl-dev, pkg-config (>= 0.21), po-debconf, bzip2, openssl, libncurses5-dev, libpcre3-dev
 Standards-Version: 3.7.3
 
 Package: openvswitch-datapath-source
 Architecture: all
-Depends: module-assistant, bzip2, debhelper (>= 5.0.37)
+Depends: module-assistant, bzip2, debhelper (>= 5.0.37), ${misc:Depends}
 Suggests: openvswitch-switch
 Description: Source code for Open vSwitch datapath Linux module
  This package provides the Open vSwitch datapath module source code
@@ -15,39 +15,35 @@ Description: Source code for Open vSwitch datapath Linux module
  from it using module-assistant or make-kpkg.  README.Debian in this
  package provides further instructions.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-common
 Architecture: any
-Depends: ${shlibs:Depends}, openssl
+Depends: ${shlibs:Depends}, openssl, ${misc:Depends}
 Description: Open vSwitch common components
  openvswitch-common provides components required by both openvswitch-switch
  and openvswitch-controller.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-switch
 Architecture: any
 Suggests: openvswitch-datapath-module
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common, dhcp3-client, module-init-tools, dmidecode, procps, debianutils
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${binary:Version}), module-init-tools, procps
 Description: Open vSwitch switch implementations
  openvswitch-switch provides the userspace components and utilities for
  the Open vSwitch kernel-based switch.  
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-switch-config
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-switch, libwww-perl, libdigest-sha1-perl
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-switch (= ${binary:Version}), libwww-perl, libdigest-sha1-perl
 Description: Open vSwitch switch implementations
  openvswitch-switch-config provides a utility for interactively configuring
  the Open vSwitch switch provided in the openvswitch-switch package.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-switchui
 Architecture: any
@@ -65,18 +61,17 @@ Description: Monitoring utility for OpenFlow switches
 
 Package: openvswitch-pki
 Architecture: all
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${binary:Version})
 Description: Open vSwitch public key infrastructure
  openvswitch-pki provides PKI (public key infrastructure) support for
  Open vSwitch switches and controllers, reducing the risk of
  man-in-the-middle attacks on the Open vSwitch network infrastructure.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-pki-server
 Architecture: all
-Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, openvswitch-pki, apache2
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, openvswitch-pki (= ${binary:Version}), apache2
 Description: Open vSwitch public key infrastructure (HTTP server support)
  openvswitch-pki-server provides HTTP access to the Open vSwitch PKI (public
  key infrastructure) maintained on the local machine by the
@@ -84,29 +79,28 @@ Description: Open vSwitch public key infrastructure (HTTP server support)
  convenient OpenFlow switch setup using the ovs-switch-setup program
  in the openvswitch-switch package.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: openvswitch-controller
 Architecture: any
-Depends: ${shlibs:Depends}, openvswitch-common, openvswitch-pki
+Depends: ${shlibs:Depends}, openvswitch-common (= ${binary:Version}), openvswitch-pki (= ${binary:Version}), ${misc:Depends}
 Description: Open vSwitch controller implementation
  The Open vSwitch controller enables OpenFlow switches that connect to it
  to act as MAC-learning Ethernet switches.
  .
- Open vSwitch is a software-based Ethernet switch targeted at virtual
- servers.
+ Open vSwitch is a full-featured software-based Ethernet switch.
 
 Package: corekeeper
 Architecture: all
-Depends: tmpreaper
+Depends: tmpreaper, ${misc:Depends}
 Description: Core file centralizer and reaper
  The corekeeper package configures the system to dump all core files to
  /var/log/core.  It also deletes core files older than 7 days.
 
 Package: openvswitch-dbg
+Section: debug
 Architecture: any
-Depends: ${shlibs:Depends}
+Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: Debug symbols for Open vSwitch packages
  This package contains the debug symbols for all the other openvswitch-*
  packages.  Install it to debug one of them or to examine a core dump
@@ -117,9 +111,9 @@ Architecture: any
 Recommends: openvswitch-switch
 Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: Monitor utility for Open vSwitch switches
- The ovs-monitor utility included in this package monitors the secure
- channel and datapath.  If either become unresponsive, the switch is
rebooted.
+ The ovs-monitor utility included in this package monitors the
+ ovs-openflowd process and the kernel datapath.  If either become
unresponsive, it reboots the machine.
 
 Package: openvswitch-wdt
 Architecture: any
@@ -132,11 +126,11 @@ Description: Watchdog utility for Open vSwitch switches
 Package: nicira-switch
 Architecture: all
 Depends: 
- openvswitch-common (= ${source:Version}), 
- openvswitch-switch (= ${source:Version}), 
- openvswitch-switchui (= ${source:Version}), 
- openvswitch-datapath-module (= ${source:Version}),
- corekeeper, openvswitch-monitor, openvswitch-wdt
+ openvswitch-common (= ${binary:Version}), 
+ openvswitch-switch (= ${binary:Version}), 
+ openvswitch-switchui (= ${binary:Version}), 
+ openvswitch-datapath-module (= ${binary:Version}),
+ corekeeper, openvswitch-monitor, openvswitch-wdt, ${misc:Depends}
 Description: Metapackage for installing a Nicira Open vSwitch switch
  Installing this package will install everything needed for a Nicira
  Portwell-based Open vSwitch switch, including monitoring and the switch UI.
index 4da85b4..af44a58 100644 (file)
@@ -1,7 +1,7 @@
 Source: openvswitch
 Section: net
 Priority: extra
-Maintainer: Open vSwitch developers <ovs-dev@openvswitch.org>
+Maintainer: Open vSwitch developers <dev@openvswitch.org>
 Build-Depends: debhelper (>= 5.0.37)
 Standards-Version: 3.7.3
 
index d820b02..b7b2d4d 100755 (executable)
@@ -42,7 +42,7 @@ set -e
 
 case "$1" in
   start)
-       log_daemon_msg "Initializing core dump location..."
+        log_daemon_msg "Initializing core dump location..."
         if echo "/var/log/core/core.%e.%t.%p" > /proc/sys/kernel/core_pattern
         then
             log_progress_msg "success"
@@ -52,13 +52,13 @@ case "$1" in
             log_end_msg 1
             exit 1
         fi
-       ;;
+        ;;
   stop|restart|force-reload|status|reload)
         exit 0
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
index 1967ccc..fab9916 100644 (file)
@@ -1,3 +1,7 @@
+_debian/ovsdb/ovsdb-client usr/bin
+_debian/ovsdb/ovsdb-tool usr/bin
 _debian/utilities/ovs-appctl usr/sbin
+_debian/utilities/ovs-ofctl usr/sbin
 _debian/utilities/ovs-parse-leaks usr/bin
 _debian/utilities/ovs-pki usr/sbin
+vswitchd/vswitch.ovsschema usr/share/openvswitch
index 99c48bd..5243247 100644 (file)
@@ -1,2 +1,5 @@
+_debian/ovsdb/ovsdb-client.1
+_debian/ovsdb/ovsdb-tool.1
 _debian/utilities/ovs-appctl.8
+_debian/utilities/ovs-ofctl.8
 _debian/utilities/ovs-pki.8
index 18819a7..94b95c4 100644 (file)
@@ -6,7 +6,8 @@ README.Debian for openvswitch-controller
 
 * To enable OpenFlow switches to automatically discover the location
   of the controller, you must install and configure a DHCP server.
-  The secchan(8) manpage (found in the openvswitch-switch package) gives
-  a working example configuration file for the ISC DHCP server.
+  The ovs-openflowd(8) manpage (found in the openvswitch-switch
+  package) gives a working example configuration file for the ISC DHCP
+  server.
 
- -- Ben Pfaff <blp@nicira.com>, Mon, 11 May 2009 13:26:38 -0700
+ -- Ben Pfaff <blp@nicira.com>, Wed,  8 Jul 2009 09:39:53 -0700
index ee9c44d..c5f8eb8 100755 (executable)
@@ -57,7 +57,7 @@ LOGFILE=$LOGDIR/$NAME.log  # Server logfile
 # Include defaults if available
 default=/etc/default/openvswitch-controller
 if [ -f $default ] ; then
-       . $default
+    . $default
 fi
 
 # Check that the user exists (if we set a user)
@@ -142,7 +142,7 @@ start_server() {
                         $SSL_OPTS
             errcode=$?
         fi
-       return $errcode
+        return $errcode
 }
 
 stop_server() {
@@ -158,7 +158,7 @@ stop_server() {
             errcode=$?
         fi
 
-       return $errcode
+        return $errcode
 }
 
 reload_server() {
@@ -171,27 +171,27 @@ reload_server() {
 
 force_stop() {
 # Force the process to die killing it manually
-       [ ! -e "$PIDFILE" ] && return
-       if running ; then
-               kill -15 $pid
-       # Is it really dead?
-               sleep "$DIETIME"s
-               if running ; then
-                       kill -9 $pid
-                       sleep "$DIETIME"s
-                       if running ; then
-                               echo "Cannot kill $NAME (pid=$pid)!"
-                               exit 1
-                       fi
-               fi
-       fi
-       rm -f $PIDFILE
+    [ ! -e "$PIDFILE" ] && return
+    if running ; then
+        kill -15 $pid
+        # Is it really dead?
+        sleep "$DIETIME"s
+        if running ; then
+            kill -9 $pid
+            sleep "$DIETIME"s
+            if running ; then
+                echo "Cannot kill $NAME (pid=$pid)!"
+                exit 1
+            fi
+        fi
+    fi
+    rm -f $PIDFILE
 }
 
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -209,7 +209,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -232,7 +232,7 @@ case "$1" in
             force_stop
             log_end_msg $?
         fi
-       ;;
+        ;;
   restart|force-reload)
         log_daemon_msg "Restarting $DESC" "$NAME"
         stop_server
@@ -241,7 +241,7 @@ case "$1" in
         start_server
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
 
         log_daemon_msg "Checking status of $DESC" "$NAME"
@@ -260,10 +260,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index d1acc89..943245c 100644 (file)
@@ -2,5 +2,4 @@ debian/changelog usr/src/modules/openvswitch-datapath/debian
 debian/control usr/src/modules/openvswitch-datapath/debian
 debian/compat usr/src/modules/openvswitch-datapath/debian
 debian/*.modules.in usr/src/modules/openvswitch-datapath/debian
-debian/rules usr/src/modules/openvswitch-datapath/debian
 _debian/openvswitch.tar.gz usr/src/modules/openvswitch-datapath
index f0c356e..3b6ccdf 100644 (file)
 # it reboots the system.  A value of zero disables the monitor.
 THRESHOLD=3
 
-# INTERVAL: The number of seconds to wait between probing secchan and
-# the datapath.
+# INTERVAL: The number of seconds to wait between probing
+# ovs-openflowd and the datapath.
 INTERVAL=1
 
 # LOG_FILE: File to log messages related to monitoring.
 LOG_FILE="/var/log/openvswitch/monitor"
 
-# SWITCH_VCONN: The vconn used to connect to the switch (secchan).
-# The secchan must be configured to listen to this vconn.  The default
-# here set is also listened to by default by the openvswitch-switch
-# package, so ordinarily there is no need to modify this.
-SWITCH_VCONN="/var/run/secchan.mgmt"
+# SWITCH_VCONN: The vconn used to connect to the switch
+# (ovs-openflowd).  The ovs-openflowd must be configured to listen to
+# this vconn.  The default here set is also listened to by default by
+# the openvswitch-switch package, so ordinarily there is no need to
+# modify this.
+SWITCH_VCONN="/var/run/ovs-openflowd.mgmt"
index 8c7e1ad..d83dabc 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the daemon to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -105,7 +105,7 @@ stop_daemon() {
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -123,7 +123,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -147,7 +147,7 @@ case "$1" in
         start_daemon
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
         log_daemon_msg "Checking status of $DESC" "$NAME"
         if running ;  then
@@ -165,10 +165,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index 5945ad7..8ea2d5b 100644 (file)
@@ -64,7 +64,7 @@ _Description: Preparing to discover controller.
  The setup program will now attempt to discover the OpenFlow controller.
  Controller discovery may take up to 30 seconds.  Please be patient.
  .
- See secchan(8) for instructions on how to configure a DHCP server for
+ See ovs-openflowd(8) for instructions on how to configure a DHCP server for
  controller discovery.
 
 Template: openvswitch-switch/discovery-failure
@@ -73,7 +73,7 @@ _Description: Controller discovery failed.
  The controller's location could not be determined automatically.
  .
  Ensure that the OpenFlow DHCP server is properly configured.  See
secchan(8) for instructions on how to configure a DHCP server for
ovs-openflowd(8) for instructions on how to configure a DHCP server for
  controller discovery.
 
 Template: openvswitch-switch/discovery-success
index c579a08..3db7ace 100755 (executable)
 ### END INIT INFO
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/sbin/secchan
-NAME=secchan
-DESC=secchan
+ovs_vswitchd=/usr/sbin/ovs-vswitchd
+ovsdb_server=/usr/bin/ovsdb-server
 
-test -x $DAEMON || exit 0
+(test -x $ovsdb_server && test -x $ovs_vswitchd) || exit 0
 
-NICIRA_OUI="002320"
-
-LOGDIR=/var/log/openvswitch
-PIDFILE=/var/run/$NAME.pid
-DHCLIENT_PIDFILE=/var/run/dhclient.of0.pid
 DODTIME=1                   # Time to wait for the server to die, in seconds
                             # If this value is set too low you might not
                             # let some servers to die gracefully and
                             # 'restart' will not work
 
-# Include secchan defaults if available
-unset NETDEVS
-unset MODE
-unset SWITCH_IP
-unset CONTROLLER
-unset PRIVKEY
-unset CERT
-unset CACERT
-unset CACERT_MODE
-unset MGMT_VCONNS
-unset COMMANDS
-unset DAEMON_OPTS
+# Include ovs-openflowd defaults if available
+unset OVSDB_SERVER_OPTS
+unset OVS_VSWITCHD_OPTS
 unset CORE_LIMIT
-unset DATAPATH_ID
+unset ENABLE_MONITOR
 default=/etc/default/openvswitch-switch
 if [ -f $default ] ; then
-       . $default
+    . $default
 fi
 
+: ${ENABLE_MONITOR:=y}
+
 set -e
 
+# running_pid pid name
+#
+# Check if 'pid' is a process named 'name'
 running_pid()
 {
-    # Check if a given process pid's cmdline matches a given name
-    pid=$1
-    name=$2
+    local pid=$1 name=$2
     [ -z "$pid" ] && return 1 
     [ ! -d /proc/$pid ] &&  return 1
     cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
@@ -75,36 +63,47 @@ running_pid()
     esac
 }
 
+# running name
+#
+# Checks for a running process named 'name' by looking for a pidfile
+# named /var/run/${name}.pid
 running()
 {
-# Check if the process is running looking at /proc
-# (works for all users)
+    local name=$1
+    local pidfile=/var/run/${name}.pid
 
     # No pidfile, probably no daemon present
-    [ ! -f "$PIDFILE" ] && return 1
+    [ ! -f "$pidfile" ] && return 1
+
     # Obtain the pid and check it against the binary name
-    pid=`cat $PIDFILE`
-    running_pid $pid $NAME || return 1
+    pid=`cat $pidfile`
+    running_pid $pid $name || return 1
     return 0
 }
 
+# force_stop name
+#
+# Checks for a running process named 'name', by looking for a pidfile
+# named /var/run/${name}.pid, and then kills it and waits for it to
+# die.
 force_stop() {
-# Forcefully kill the process
-    [ ! -f "$PIDFILE" ] && return
-    if running ; then
-        kill -15 $pid
-        # Is it really dead?
+    local name=$1
+    local pidfile=/var/run/${name}.pid
+
+    [ ! -f "$pidfile" ] && return
+    if running $name; then
+        kill $pid
         [ -n "$DODTIME" ] && sleep "$DODTIME"s
-        if running ; then
-            kill -9 $pid
+        if running $name; then
+            kill -KILL $pid
             [ -n "$DODTIME" ] && sleep "$DODTIME"s
-            if running ; then
-                echo "Cannot kill $NAME (pid=$pid)!"
+            if running $name; then
+                echo "Cannot kill $name (pid=$pid)!"
                 exit 1
             fi
         fi
     fi
-    rm -f $PIDFILE
+    rm -f $pidfile
     return 0
 }
 
@@ -129,300 +128,199 @@ check_op() {
     fi
 }
 
-configure_ssl() {
-    if (test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap) \
-       || test ! -e "$PRIVKEY" || test ! -e "$CERT" \
-       || (test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap); then
-        if test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap
-        then
-            echo "CACERT_MODE is not set to 'secure' or 'bootstrap'"
-        fi
-        if test ! -e "$PRIVKEY"; then
-            echo "$PRIVKEY: private key missing" >&2
-        fi
-        if test ! -e "$CERT"; then
-            echo "$CERT: certificate for private key missing" >&2
-        fi
-        if test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap; then
-            echo "$CACERT: CA certificate missing (and CA certificate bootstrapping not enabled)" >&2
-        fi
-        echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-        if test "$MODE" = discovery; then
-            echo "You may also delete or rename $PRIVKEY to disable SSL requirement" >&2
-        fi
-        exit 1
-    fi
+# is_module_loaded module
+#
+# Returns 0 if 'module' is loaded, 1 otherwise.
 
-    SSL_OPTS="--private-key=$PRIVKEY --certificate=$CERT"
-    if test ! -e "$CACERT" && test "$CACERT_MODE" = bootstrap; then
-        SSL_OPTS="$SSL_OPTS --bootstrap-ca-cert=$CACERT"
-    else
-        SSL_OPTS="$SSL_OPTS --ca-cert=$CACERT"
-    fi
+is_module_loaded() {
+    local module=$1
+    grep -q "^$module " /proc/modules
 }
 
-check_int_var() {
-    eval value=\$$1
-    if test -n "$value"; then
-        if expr "X$value" : 'X[0-9][0-9]*$' > /dev/null 2>&1; then
-            if test $value -lt $2; then
-                echo "warning: The $1 option may not be set to a value below $2, treating as $2" >&2
-                eval $1=$2
-            fi
+# load_module module
+#
+# Loads 'module' into the running kernel, if it is not already loaded.
+load_module() {
+    local module=$1
+    echo -n "Loading $module: "
+    if is_module_loaded $module; then
+        echo "already loaded, nothing to do."
+    elif modprobe $module; then
+        echo "success."
+    else
+        echo "ERROR."
+        echo "$module has probably not been built for this kernel."
+        if ! test -d /usr/share/doc/openvswitch-datapath-source; then
+            echo "Install the openvswitch-datapath-source package, then read"
+            echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
         else
-            echo "warning: The $1 option must be set to a number, ignoring" >&2
-            unset $1
+            echo "For instructions, read"
+            echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
         fi
+        exit 1
     fi
 }
 
-check_new_option() {
-    case $DAEMON_OPTS in
-        *$1*)
-            echo "warning: The $1 option in DAEMON_OPTS may now be set with the $2 variable in $default.  The setting in DAEMON_OPTS will override the $2 variable, which will prevent the switch UI from configuring $1." >&2
-            ;;
-    esac
-}
-
-case "$1" in
-    start)
-        if test -z "$NETDEVS"; then
-            echo "$default: No network devices configured, switch disabled" >&2
-            echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-            exit 0
-        fi
-        if test "$MODE" = discovery; then
-            unset CONTROLLER
-        elif test "$MODE" = in-band || test "$MODE" = out-of-band; then
-            if test -z "$CONTROLLER"; then
-                echo "$default: No controller configured and not configured for discovery, switch disabled" >&2
-                echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-                exit 0
-            fi
-        else
-            echo "$default: MODE must set to 'discovery', 'in-band', or 'out-of-band'" >&2
-            echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-            exit 1
-        fi
-        : ${PRIVKEY:=/etc/openvswitch-switch/of0-privkey.pem}
-        : ${CERT:=/etc/openvswitch-switch/of0-cert.pem}
-        : ${CACERT:=/etc/openvswitch-switch/cacert.pem}
-        case $CONTROLLER in
-            '')
-                # Discovery mode.
-                if test -e "$PRIVKEY"; then
-                    configure_ssl
-                fi
-                ;;
-            tcp:*)
-                ;;
-            ssl:*)
-                configure_ssl
-                ;;
-            *)
-                echo "$default: CONTROLLER must be in the form 'ssl:IP[:PORT]' or 'tcp:IP[:PORT]' when not in discovery mode" >&2
-                echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-                exit 1
-        esac
-        case $DISCONNECTED_MODE in
-            ''|switch|drop) ;; 
-            *) echo "$default: warning: DISCONNECTED_MODE is not 'switch' or 'drop'" >&2 ;;
-        esac
-
-        check_int_var RATE_LIMIT 100
-        check_int_var INACTIVITY_PROBE 5
-        check_int_var MAX_BACKOFF 1
-
-        check_new_option --fail DISCONNECTED_MODE
-        check_new_option --stp STP
-        check_new_option --rate-limit RATE_LIMIT
-        check_new_option --inactivity INACTIVITY_PROBE
-        check_new_option --max-backoff MAX_BACKOFF
-        case $DAEMON_OPTS in
-            *--rate-limit*)
-                echo "$default: --rate-limit may now be set with RATE_LIMIT" >&2
-        esac
-
-        echo -n "Loading openvswitch_mod: "
-        if grep -q '^openvswitch_mod$' /proc/modules; then
-            echo "already loaded, nothing to do."
-        elif modprobe openvswitch_mod; then
+# unload_module module
+#
+# Unloads 'module' from the running kernel, if it is loaded.
+unload_module() {
+    local module=$1
+    echo -n "Unloading $module: "
+    if is_module_loaded $module; then
+        if rmmod $module; then
             echo "success."
         else
             echo "ERROR."
-            echo "openvswitch_mod has probably not been built for this kernel."
-            if ! test -d /usr/share/doc/openvswitch-datapath-source; then
-                echo "Install the openvswitch-datapath-source package, then read"
-                echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
-            else
-                echo "For instructions, read"
-                echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
-            fi
             exit 1
         fi
+    else
+        echo "not loaded, nothing to do."
+    fi
+}
 
-        for netdev in $NETDEVS; do
-            check_op "Removing IP address from $netdev" ifconfig $netdev 0.0.0.0
+unload_modules() {
+    if is_module_loaded openvswitch_mod; then
+        for dp in $(ovs-dpctl dump-dps); do
+            echo -n "Deleting datapath $dp: "
+            if ovs-dpctl del-dp $dp; then
+                echo "success."
+            else
+                echo "ERROR."
+            fi
         done
+    fi
+    unload_module openvswitch_mod
+    unload_module ip_gre_mod
+}
 
-        must_succeed "Creating datapath" ovs-dpctl add-dp of0 $NETDEVS
+case "$1" in
+    start)
+        load_module openvswitch_mod
+        unload_module ip_gre
+        load_module ip_gre_mod
 
-        xx='[0-9abcdefABCDEF][0-9abcdefABCDEF]'
-        case $DATAPATH_ID in
-            '')
-                # Check if the DMI System UUID contains a Nicira mac address
-                # that should be used for this datapath.  The UUID is assumed 
-                # to be RFC 4122 compliant.
-                DMIDECODE=`which dmidecode`
-                if [ -n $DMIDECODE ]; then
-                    UUID_MAC=`$DMIDECODE -s system-uuid | cut -d'-' -f 5`
-                    case $UUID_MAC in
-                        $NICIRA_OUI*)
-                            ifconfig of0 down
-                            must_succeed "Setting of0 MAC address to $UUID_MAC" ifconfig of0 hw ether $UUID_MAC
-                            ifconfig of0 up
-                            ;;
-                    esac
-                fi  
-                ;;
-            $xx:$xx:$xx:$xx:$xx:$xx)
-                ifconfig of0 down
-                must_succeed "Setting of0 MAC address to $DATAPATH_ID" ifconfig of0 hw ether $DATAPATH_ID
-                ifconfig of0 up
-                ;;
-            *)
-                echo "DATAPATH_ID is not a valid MAC address in the form XX:XX:XX:XX:XX:XX, ignoring" >&2
-                ;;
-        esac
+        if test -n "$CORE_LIMIT"; then
+            check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT"
+        fi
 
-        if test "$MODE" = in-band; then
-            if test "$SWITCH_IP" = dhcp; then
-                must_succeed "Temporarily disabling of0" ifconfig of0 down
-            else
-                COMMAND="ifconfig of0 $SWITCH_IP"
-                if test -n "$SWITCH_NETMASK"; then
-                    COMMAND="$COMMAND netmask $SWITCH_NETMASK"
-                fi
-                must_succeed "Configuring of0: $COMMAND" $COMMAND
-                if test -n "$SWITCH_GATEWAY"; then
-                    # This can fail because the route already exists,
-                    # so we don't insist that it succeed.
-                    COMMAND="route add default gw $SWITCH_GATEWAY"
-                    check_op "Adding default route: $COMMAND" $COMMAND
-                fi
-            fi
+        # Create an empty configuration database if it doesn't exist.
+        if test ! -e /etc/openvswitch-switch/conf; then
+            # Create configuration database.
+            ovsdb-tool -vANY:console:emer \
+                create /etc/openvswitch-switch/conf \
+                /usr/share/openvswitch/vswitch.ovsschema
         else
-            must_succeed "Disabling of0" ifconfig of0 down
+            # Upgrade or downgrade schema and compact database.
+            ovsdb-tool -vANY:console:emer \
+                convert /etc/openvswitch-switch/conf \
+                /usr/share/openvswitch/vswitch.ovsschema
         fi
 
-        if test -n "$CORE_LIMIT"; then
-            check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT"
+        if test "$ENABLE_MONITOR" = y; then
+            monitor_opt=--monitor
+        else
+            monitor_opt=
         fi
 
-        # Compose secchan options.
+        # Start ovsdb-server.
         set --
         set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err
         set -- "$@" --log-file
-        set -- "$@" --detach --pidfile=$PIDFILE
-        for vconn in $MGMT_VCONNS; do
-            set -- "$@" --listen="$vconn"
-        done
-        if test -n "$COMMANDS"; then
-            set -- "$@" --command-acl="$COMMANDS"
-        fi
-        case $STP in
-            yes) set -- "$@" --stp ;;
-            no) set -- "$@" --no-stp ;;
-        esac
-        case $DISCONNECTED_MODE in
-            switch) set -- "$@" --fail=open ;;
-            drop) set -- "$@" --fail=closed ;;
-        esac
-        if test -n "$RATE_LIMIT"; then
-            set -- "$@" --rate-limit=$RATE_LIMIT
-        fi
-        if test -n "$INACTIVITY_PROBE"; then
-            set -- "$@" --inactivity-probe=$INACTIVITY_PROBE
-        fi
-        if test -n "$MAX_BACKOFF"; then
-            set -- "$@" --max-backoff=$MAX_BACKOFF
-        fi
-        set -- "$@" $SSL_OPTS $DAEMON_OPTS
-        if test "$MODE" = out-of-band; then
-            set -- "$@" --out-of-band
-        fi
-        set -- "$@" of0 "$CONTROLLER"
-       echo -n "Starting $DESC: "
-       start-stop-daemon --start --quiet --pidfile $PIDFILE \
-           --exec $DAEMON -- "$@"
-        if running; then
-            echo "$NAME."
+        set -- "$@" --detach --pidfile $monitor_opt
+        set -- "$@" --remote punix:/var/run/ovsdb-server
+        set -- "$@" /etc/openvswitch-switch/conf
+        set -- "$@" --private-key=db:SSL,private_key
+        set -- "$@" --certificate=db:SSL,certificate
+        set -- "$@" --bootstrap-ca-cert=db:SSL,ca_cert
+        set -- "$@" $OVSDB_SERVER_OPTS
+        echo -n "Starting ovsdb-server: "
+        start-stop-daemon --start --quiet --pidfile /var/run/ovsdb-server.pid \
+            --exec $ovsdb_server -- "$@"
+        if running ovsdb-server; then
+            echo "ovsdb-server."
         else
             echo " ERROR."
         fi
 
-        if test "$MODE" = in-band && test "$SWITCH_IP" = dhcp; then
-            echo -n "Starting dhclient on of0: "
-           start-stop-daemon --start --quiet --pidfile $DHCLIENT_PIDFILE \
-               --exec /sbin/dhclient -- -q -pf $DHCLIENT_PIDFILE of0
-            if running; then
-                echo "dhclient."
-            else
-                echo " ERROR."
-            fi
+        ovs-vsctl --no-wait init
+
+        # Start ovs-vswitchd.
+        set --
+        set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err
+        set -- "$@" --log-file
+        set -- "$@" --detach --pidfile $monitor_opt
+        set -- "$@" unix:/var/run/ovsdb-server
+        set -- "$@" $OVS_VSWITCHD_OPTS
+        echo -n "Starting ovs-vswitchd: "
+        start-stop-daemon --start --quiet --pidfile /var/run/ovs-vswitchd.pid \
+            --exec $ovs_vswitchd -- "$@"
+        if running ovs-vswitchd; then
+            echo "ovs-vswitchd."
+        else
+            echo " ERROR."
         fi
-       ;;
+        ;;
     stop)
-        if test -e /var/run/dhclient.of0.pid; then
-           echo -n "Stopping dhclient on of0: "
-           start-stop-daemon --stop --quiet --oknodo \
-                --pidfile $DHCLIENT_PIDFILE --exec /sbin/dhclient
-           echo "dhclient."
-        fi            
+        echo -n "Stopping ovs-vswitchd: "
+        start-stop-daemon --stop --quiet --oknodo \
+            --pidfile /var/run/ovs-vswitchd.pid \
+            --exec $ovs_vswitchd
+        echo "ovs-vswitchd."
 
-       echo -n "Stopping $DESC: "
-       start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE \
-           --exec $DAEMON
-       echo "$NAME."
-
-        check_op "Deleting datapath" ovs-dpctl del-dp of0
-        check_op "Unloading kernel module" modprobe -r openvswitch_mod
-       ;;
+        echo -n "Stopping ovsdb-server: "
+        start-stop-daemon --stop --quiet --oknodo \
+            --pidfile /var/run/ovsdb-server.pid \
+            --exec $ovsdb_server
+        echo "ovsdb-server."
+        ;;
     force-stop)
-       echo -n "Forcefully stopping $DESC: "
-        force_stop
-        if ! running; then
-            echo "$NAME."
+        echo -n "Forcefully stopping ovs-vswitchd: "
+        force_stop ovs-vswitchd
+        if ! running ovs-vswitchd; then
+            echo "ovs-vswitchd."
         else
             echo " ERROR."
         fi
-       ;;
+
+        echo -n "Forcefully stopping ovsdb-server: "
+        force_stop ovsdb-server
+        if ! running ovsdb-server; then
+            echo "ovsdb-server."
+        else
+            echo " ERROR."
+        fi
+        ;;
+    unload)
+        unload_modules
+        ;;
     reload)
         ;;
     force-reload)
-       start-stop-daemon --stop --test --quiet --pidfile \
-           $PIDFILE --exec $DAEMON \
-           && $0 restart \
-           || exit 0
-       ;;
+        # Nothing to do, since ovs-vswitchd automatically reloads
+        # whenever its configuration changes, and ovsdb-server doesn't
+        # have anything to reload.
+        ;;
     restart)
         $0 stop || true
         $0 start
-       ;;
+        ;;
     status)
-        echo -n "$NAME is "
-        if running ;  then
-            echo "running"
-        else
-            echo " not running."
-            exit 1
-        fi
+        for daemon in ovs-vswitchd ovsdb-server; do
+            echo -n "$daemon is "
+            if running $daemon;  then
+                echo "running"
+            else
+                echo " not running."
+                exit 1
+            fi
+        done
         ;;
     *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status|force-stop|unload}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index 9fddacf..7b988da 100644 (file)
@@ -1,7 +1,6 @@
-_debian/secchan/secchan usr/sbin
-_debian/utilities/ovs-dpctl usr/sbin
+_debian/ovsdb/ovsdb-server usr/bin
 _debian/utilities/ovs-discover usr/sbin
+_debian/utilities/ovs-dpctl usr/sbin
 _debian/utilities/ovs-kill usr/sbin
-_debian/utilities/ovs-ofctl usr/sbin
-debian/openvswitch/usr/share/openvswitch/commands/* usr/share/openvswitch/commands
-debian/commands/* usr/share/openvswitch/commands
+_debian/utilities/ovs-vsctl usr/sbin
+_debian/vswitchd/ovs-vswitchd usr/sbin
index 41394e8..3da57e0 100644 (file)
@@ -1,4 +1,4 @@
-/var/log/openvswitch/secchan.log {
+/var/log/openvswitch/ovs-openflowd.log {
         daily
         compress
         create 640 root adm
@@ -6,6 +6,6 @@
         missingok
         rotate 30
         postrotate
-                ovs-appctl --target /var/run/secchan.pid --reopen
+                ovs-appctl --target=ovs-openflowd vlog/reopen
         endscript
 }
index f789eba..a899114 100644 (file)
@@ -1,5 +1,6 @@
-_debian/secchan/secchan.8
+_debian/ovsdb/ovsdb-server.1
 _debian/utilities/ovs-discover.8
 _debian/utilities/ovs-dpctl.8
 _debian/utilities/ovs-kill.8
-_debian/utilities/ovs-ofctl.8
+_debian/utilities/ovs-vsctl.8
+_debian/vswitchd/ovs-vswitchd.8
index 74b52ba..4be5a30 100755 (executable)
@@ -33,6 +33,17 @@ case "$1" in
                 fi
             done
        fi
+
+        if /etc/init.d/openvswitch-switch status >/dev/null 2>&1; then
+            running=true
+            /etc/init.d/openvswitch-switch stop
+        else
+            running=false
+        fi
+
+        if $running; then
+            /etc/init.d/openvswitch-switch start
+        fi
         ;;
 
     abort-upgrade|abort-remove|abort-deconfigure)
index 19e8ebe..e4a736a 100755 (executable)
@@ -21,16 +21,19 @@ set -e
 
 case "$1" in
     purge)
-       rm -f /etc/default/openvswitch-switch
-       ;;
+        rm -f /etc/openvswitch-switch/conf
+        rm -f /etc/openvswitch-switch/.conf.~lock~
+        rm -f /etc/default/openvswitch-switch
+        rm -f /var/log/openvswitch/*
+        ;;
 
     remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-       ;;
+        ;;
 
     *)
         echo "postrm called with unknown argument \`$1'" >&2
         exit 1
-       ;;
+        ;;
 esac
 
 # dh_installdeb will replace this with shell code automatically
index 69f1de6..e36fc68 100644 (file)
 # This is a POSIX shell fragment                -*- sh -*-
 
-# To configure the secure channel, fill in the following properly and
-# uncomment them.  Afterward, the secure channel will come up
+# To configure the OpenFlow switch, fill in the following properly and
+# uncomment them.  Afterward, the switch will come up
 # automatically at boot time.  It can be started immediately with
 #       /etc/init.d/openvswitch-switch start
-# Alternatively, use the ovs-switch-setup program (from the
-# openvswitch-switch-config package) to do everything automatically.
 
-# NETDEVS: Which network devices should the OpenFlow switch include?
-#
-# List the network devices that should become part of the OpenFlow
-# switch, separated by spaces.  At least two devices must be selected
-# for this machine to be a useful switch.  Unselecting all network
-# devices will disable the OpenFlow switch entirely.
-# 
-# The network devices that you select should not be configured with IP
-# or IPv6 addresses, even if the switch contacts the controller over
-# one of the selected network devices.  This is because a running
-# Open vSwitch switch takes over network devices at a low level: they
-# become part of the switch and cannot be used for other purposes.
-#NETDEVS=""
-
-# MODE: The OpenFlow switch has three modes that determine how it
-# reaches the controller:
-#
-# * in-band with discovery: A single network is used for OpenFlow
-#   traffic and other data traffic; that is, the switch contacts the
-#   controller over one of the network devices selected as OpenFlow
-#   switch ports.  The switch automatically determines the location of
-#   the controller using a DHCP request with an OpenFlow-specific
-#   vendor option.  This is the most common case.
-# 
-# * in-band: As above, but the location of the controller is manually
-#   configured.
-# 
-# * out-of-band: OpenFlow traffic uses a network separate from the
-#   data traffic that it controls.  If this is the case, the control
-#   network must already be configured on a network device other than
-#   one of those selected as an Open vSwitch switch port in the previous
-#   question.
-#
-# Set MODE to 'discovery', 'in-band', or 'out-of-band' for these
-# respective cases.
-MODE=discovery
-
-# SWITCH_IP: In 'in-band' mode, the switch's IP address may be
-# configured statically or dynamically:
-# 
-# * For static configuration, specify the switch's IP address as a
-#   string.  In this case you may also set SWITCH_NETMASK and
-#   SWITCH_GATEWAY appropriately (see below).
-# 
-# * For dynamic configuration with DHCP (the most common case),
-#   specify "dhcp".  Configuration with DHCP will only work reliably
-#   if the network topology allows the switch to contact the DHCP
-#   server before it connects to the OpenFlow controller.
-#
-# This setting has no effect unless MODE is set to 'in-band'.
-SWITCH_IP=dhcp
-
-# SWITCH_NETMASK: IP netmask to use in 'in-band' mode when the switch
-# IP address is not 'dhcp'.
-#SWITCH_NETMASK=255.255.255.0
-
-# SWITCH_GATEWAY: IP gateway to use in 'in-band' mode when the switch
-# IP address is not 'dhcp'.
-#SWITCH_GATEWAY=192.168.1.1
-
-# CONTROLLER: Location of controller.
-# One of the following formats:
-#  tcp:IP[:PORT]         via TCP to PORT (default: 6633) at IP
-#  ssl:IP[:PORT]         via SSL to PORT (default: 6633) at IP
-# The default below assumes that the controller is running locally.
-# This setting has no effect when MODE is set to 'discovery'.
-#CONTROLLER="tcp:127.0.0.1"
-
-# PRIVKEY: Name of file containing switch's private key.
-# Required if SSL enabled.
-#PRIVKEY=/etc/openvswitch-switch/of0-privkey.pem
-
-# CERT: Name of file containing certificate for private key.
-# Required if SSL enabled.
-#CERT=/etc/openvswitch-switch/of0-cert.pem
-
-# CACERT: Name of file containing controller CA certificate.
-# Required if SSL enabled.
-#CACERT=/etc/openvswitch-switch/cacert.pem
+# OVSDB_SERVER_OPTS: Additional options to pass to ovsdb-server,
+# e.g. "--fail=open"
+OVSDB_SERVER_OPTS=
 
-# CACERT_MODE: Two modes are available:
-#
-# * secure: The controller CA certificate named in CACERT above must exist.
-#   (You must copy it manually from the PKI server or another trusted source.)
-#
-# * bootstrap: If the controller CA certificate named in CACERT above does
-#   not exist, the switch will obtain it from the controller the first time
-#   it connects and save a copy to the file named in CACERT.  This is insecure,
-#   in the same way that initial connections with ssh are insecure, but
-#   it is convenient.
-# 
-# Set CACERT_MODE to 'secure' or 'bootstrap' for these respective cases.
-#CACERT_MODE=secure
-
-# MGMT_VCONNS: List of vconns (space-separated) on which secchan
-# should listen for management connections from ovs-ofctl, etc.
-# openvswitch-switchui by default connects to
-# unix:/var/run/secchan.mgmt, so do not disable this if you want to
-# use openvswitch-switchui.
-MGMT_VCONNS="punix:/var/run/secchan.mgmt"
-
-# COMMANDS: Access control list for the commands that can be executed
-# remotely over the OpenFlow protocol, as a comma-separated list of
-# shell glob patterns.  Negative patterns (beginning with !) act as a
-# blacklist.  To be executable, a command name must match one positive
-# pattern and not match any negative patterns.
-#COMMANDS="reboot,update"
-
-# DISCONNECTED_MODE: Switch behavior when attempts to connect to the
-# controller repeatedly fail, either 'switch', to act as an L2 switch
-# in this case, or 'drop', to drop all packets (except those necessary
-# to connect to the controller).  If unset, the default is 'drop'.
-#DISCONNECTED_MODE=switch
-
-# STP: Enable or disabled 802.1D-1998 Spanning Tree Protocol.  Set to
-# 'yes' to enable STP, 'no' to disable it.  If unset, secchan's
-# current default is 'no' (but this may change in the future).
-#STP=no
-
-# RATE_LIMIT: Maximum number of received frames, that do not match any
-# existing switch flow, to forward up to the controller per second.
-# The valid range is 100 and up.  If unset, this rate will not be
-# limited.
-#RATE_LIMIT=1000
-
-# INACTIVITY_PROBE: The maximum number of seconds of inactivity on the
-# controller connection before secchan sends an inactivity probe
-# message to the controller.  The valid range is 5 and up.  If unset,
-# secchan defaults to 5 seconds.
-#INACTIVITY_PROBE=5
-
-# MAX_BACKOFF: The maximum time that secchan will wait between
-# attempts to connect to the controller.  The valid range is 1 and up.
-# If unset, secchan defaults to 8 seconds.
-#MAX_BACKOFF=8
-
-# DAEMON_OPTS: Additional options to pass to secchan, e.g. "--fail=open"
-DAEMON_OPTS=""
+# OVS_VSWITCHD_OPTS: Additional options to pass to ovs-openflowd,
+# e.g. "--fail=open"
+OVS_VSWITCHD_OPTS=
 
 # CORE_LIMIT: Maximum size for core dumps.
 #
@@ -153,13 +20,6 @@ DAEMON_OPTS=""
 # core files regardless of size.
 #CORE_LIMIT=unlimited
 
-# DATAPATH_ID: Identifier for this switch.
-#
-# By default, the switch checks if the DMI System UUID contains a Nicira 
-# mac address to use as a datapath ID.  If not, then the switch generates 
-# a new, random datapath ID every time it starts up.  By setting this
-# value, the supplied datapath ID will always be used.
-#
-# Set DATAPATH_ID to a MAC address in the form XX:XX:XX:XX:XX:XX where each
-# X is a hexadecimal digit (0-9 or a-f).
-#DATAPATH_ID=XX:XX:XX:XX:XX:XX 
+# ENABLE_MONITOR: If 'y' then monitor daemon processes and restart them
+#    if they die due to an error signal.
+# ENABLE_MONITOR=y
index 6cdbf7a..a759af8 100644 (file)
@@ -1,7 +1,7 @@
 # This is a POSIX shell fragment                -*- sh -*-
 
-# To configure the switch monitor, modify the following.  Afterward,
-# the secure channel will come up automatically at boot time.  It can
+# To configure the switch UI, modify the following.  Afterward,
+# the switch UI will come up automatically at boot time.  It can
 # be restarted immediately with
 #       /etc/init.d/openvswitch-switchui start
 
@@ -9,11 +9,11 @@
 # sourced by /etc/init.d/openvswitch-switchui
 # installed at /etc/default/openvswitch-switchui by the maintainer scripts
 
-# SWITCH_VCONN: The vconn used to connect to the switch (secchan).
-# The secchan must be configured to listen to this vconn.  The default
+# SWITCH_VCONN: The vconn used to connect to the switch (ovs-openflowd).
+# The ovs-openflowd must be configured to listen to this vconn.  The default
 # here set is also listened to by default by the openvswitch-switch
 # package, so ordinarily there is no need to modify this.
-SWITCH_VCONN="unix:/var/run/secchan.mgmt"
+SWITCH_VCONN="unix:/var/run/ovs-openflowd.mgmt"
 
 # EZIO3_DEVICE: To display the switch monitor on an EZIO3 (aka
 # MTB-134) 16x2 LCD displays found on server appliances made by
index 7a02c5e..7ecb14a 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the server to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -99,10 +99,10 @@ start_server() {
 
     # Wait up to 3 seconds for the daemon to start.
     for i in 1 2 3; do
-       if running; then
-           break
-       fi
-       sleep 1
+        if running; then
+        break
+    fi
+    sleep 1
     done
 }
 
@@ -112,27 +112,27 @@ stop_server() {
 
 force_stop() {
 # Force the process to die killing it manually
-       [ ! -e "$PIDFILE" ] && return
-       if running ; then
-               kill -15 $pid
-       # Is it really dead?
-               sleep "$DIETIME"s
-               if running ; then
-                       kill -9 $pid
-                       sleep "$DIETIME"s
-                       if running ; then
-                               echo "Cannot kill $NAME (pid=$pid)!"
-                               exit 1
-                       fi
-               fi
-       fi
-       rm -f $PIDFILE
+    [ ! -e "$PIDFILE" ] && return
+    if running ; then
+        kill -15 $pid
+        # Is it really dead?
+        sleep "$DIETIME"s
+        if running ; then
+            kill -9 $pid
+            sleep "$DIETIME"s
+            if running ; then
+                echo "Cannot kill $NAME (pid=$pid)!"
+                exit 1
+            fi
+        fi
+    fi
+    rm -f $PIDFILE
 }
 
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -150,7 +150,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -173,7 +173,7 @@ case "$1" in
             force_stop
             log_end_msg $?
         fi
-       ;;
+        ;;
   restart|force-reload)
         log_daemon_msg "Restarting $DESC" "$NAME"
         stop_server
@@ -182,7 +182,7 @@ case "$1" in
         start_server
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
 
         log_daemon_msg "Checking status of $DESC" "$NAME"
@@ -201,10 +201,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index f2872c8..0cfb290 100644 (file)
@@ -1,2 +1,3 @@
 _debian/extras/ezio/ezio-term usr/sbin
 _debian/extras/ezio/ovs-switchui usr/bin
+debian/reconfigure usr/share/openvswitch-switchui
index b1c0ec5..e254115 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the daemon to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -107,7 +107,7 @@ stop_daemon() {
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -125,7 +125,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -149,7 +149,7 @@ case "$1" in
         start_daemon
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
         log_daemon_msg "Checking status of $DESC" "$NAME"
         if running ;  then
@@ -167,10 +167,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index 696ad36..9c0da4c 100644 (file)
@@ -38,4 +38,4 @@ obtained from the OpenFlow PKI server.
 
 .BR ovs\-dpctl (8),
 .BR ovs-pki (8),
-.BR secchan (8)
+.BR ovs-openflowd (8)
index 39dee7a..bf9953f 100644 (file)
@@ -7,8 +7,8 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: ovs-dev@openvswitch.org\n"
-"POT-Creation-Date: 2009-05-11 13:38-0700\n"
+"Report-Msgid-Bugs-To: dev@openvswitch.org\n"
+"POT-Creation-Date: 2009-12-15 09:36-0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -168,7 +168,7 @@ msgstr ""
 #. Description
 #: ../openvswitch-switch-config.templates:5001
 msgid ""
-"See secchan(8) for instructions on how to configure a DHCP server for "
+"See ovs-openflowd(8) for instructions on how to configure a DHCP server for "
 "controller discovery."
 msgstr ""
 
@@ -188,8 +188,9 @@ msgstr ""
 #. Description
 #: ../openvswitch-switch-config.templates:6001
 msgid ""
-"Ensure that the OpenFlow DHCP server is properly configured.  See secchan(8) "
-"for instructions on how to configure a DHCP server for controller discovery."
+"Ensure that the OpenFlow DHCP server is properly configured.  See ovs-"
+"openflowd(8) for instructions on how to configure a DHCP server for "
+"controller discovery."
 msgstr ""
 
 #. Type: boolean
@@ -293,9 +294,9 @@ msgstr ""
 #. Description
 #: ../openvswitch-switch-config.templates:11001
 msgid ""
-"The controller location must be specifed as \"ssl:IP[:PORT]\" to connect "
-"to the controller over SSL (recommended for security) or \"tcp:IP[:PORT]\" "
-"to connect over cleartext TCP."
+"The controller location must be specifed as \"ssl:IP[:PORT]\" to connect to "
+"the controller over SSL (recommended for security) or \"tcp:IP[:PORT]\" to "
+"connect over cleartext TCP."
 msgstr ""
 
 #. Type: string
index 4cf5b38..0d0abf1 100755 (executable)
 # Modified to make a template file for a multi-binary package with separated
 # build-arch and build-indep targets  by Bill Allombert 2001
 
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-# This has to be exported to make some magic below work.
-export DH_OPTIONS
-
-# prefix of the target package name
-PACKAGE=openvswitch-datapath-module
-# modifieable for experiments or debugging m-a
-MA_DIR ?= /usr/share/modass
-# load generic variable handling
--include $(MA_DIR)/include/generic.make
-# load default rules
--include $(MA_DIR)/include/common-rules.make
-
-DATAPATH_CONFIGURE_OPTS =
-
 # Official build number.  Leave set to 0 if not an official build.
 BUILD_NUMBER = 0
 
@@ -46,6 +29,7 @@ configure-stamp:
        dh_testdir
        test -e configure || ./boot.sh
        test -d _debian || mkdir _debian
+       echo $$CC
        cd _debian && ( \
                test -e Makefile || \
                ../configure --prefix=/usr --localstatedir=/var --enable-ssl \
@@ -78,31 +62,6 @@ clean:
        dh_clean 
        debconf-updatepo
 
-kdist_clean:
-       dh_clean
-       rm -rf openvswitch
-
-kdist_config: prep-deb-files
-
-binary-modules: DSTDIR = $(CURDIR)/debian/$(PKGNAME)/lib/modules/$(KVERS)
-binary-modules: prep-deb-files
-       dh_testdir
-       dh_testroot
-       dh_clean -k
-       tar xzf openvswitch.tar.gz
-       cd openvswitch && ./configure --with-l26=$(KSRC) $(DATAPATH_CONFIGURE_OPTS) --with-build-number=$(BUILD_NUMBER)
-       cd openvswitch && $(MAKE) -C datapath/linux-2.6
-       install -d -m755 $(DSTDIR)
-       install -m644 openvswitch/datapath/linux-2.6/*_mod.ko $(DSTDIR)/
-       dh_installdocs
-       dh_installchangelogs
-       dh_compress
-       dh_fixperms
-       dh_installdeb
-       dh_gencontrol
-       dh_md5sums
-       dh_builddeb --destdir=$(DEB_DESTDIR)
-
 install: install-indep install-arch
 install-indep: build-indep
        dh_testdir
@@ -110,6 +69,10 @@ install-indep: build-indep
        dh_clean -k -i 
        dh_installdirs -i
        dh_install -i
+       sed 's/^BUILD_NUMBER = .*/BUILD_NUMBER = $(BUILD_NUMBER)/' \
+               < debian/rules.modules \
+               > debian/openvswitch-datapath-source/usr/src/modules/openvswitch-datapath/debian/rules
+       chmod 755 debian/openvswitch-datapath-source/usr/src/modules/openvswitch-datapath/debian/rules
        cd debian/openvswitch-datapath-source/usr/src && tar -c modules | bzip2 -9 > openvswitch-datapath.tar.bz2 && rm -rf modules
        install -m644 debian/openvswitch-pki-server.apache2 debian/openvswitch-pki-server/etc/apache2/sites-available/openvswitch-pki
        install -m1777 -d debian/corekeeper/var/log/core
diff --git a/debian/rules.modules b/debian/rules.modules
new file mode 100755 (executable)
index 0000000..4f39cee
--- /dev/null
@@ -0,0 +1,41 @@
+#! /usr/bin/make -f
+
+PACKAGE=openvswitch-datapath-module
+MA_DIR ?= /usr/share/modass
+-include $(MA_DIR)/include/generic.make
+-include $(MA_DIR)/include/common-rules.make
+
+DATAPATH_CONFIGURE_OPTS =
+
+# Official build number.  Leave set to 0 if not an official build.
+BUILD_NUMBER = 0
+
+kdist_clean:
+       dh_testdir      
+       dh_testroot
+       dh_clean
+       rm -rf openvswitch
+
+.PHONY: kdist_config
+kdist_config: prep-deb-files
+
+.PHONY: binary-modules
+binary-modules: DSTDIR = $(CURDIR)/debian/$(PKGNAME)/lib/modules/$(KVERS)
+binary-modules: prep-deb-files
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       tar xzf openvswitch.tar.gz
+       cd openvswitch && ./configure --with-l26=$(KSRC) $(DATAPATH_CONFIGURE_OPTS) --with-build-number=$(BUILD_NUMBER)
+       cd openvswitch && $(MAKE) -C datapath/linux-2.6
+       install -d -m755 $(DSTDIR)
+       install -m644 openvswitch/datapath/linux-2.6/*_mod.ko $(DSTDIR)/
+       dh_installdocs
+       dh_installchangelogs
+       dh_compress
+       dh_fixperms
+       dh_installdeb
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb --destdir=$(DEB_DESTDIR)
+
index 7c7db24..a615fc8 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, 2009 Nicira Networks, Inc.
+# Copyright (C) 2008, 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -8,7 +8,7 @@
 EXTRA_DIST += extras/ezio/ezio3.ti
 
 if HAVE_CURSES
-if HAVE_PCRE
+if HAVE_PCRE_PARTIAL
 install-data-hook:
        @echo tic -x $(srcdir)/extras/ezio/ezio3.ti
        @if ! tic -x $(srcdir)/extras/ezio/ezio3.ti; then                             \
@@ -25,8 +25,6 @@ install-data-hook:
 
 bin_PROGRAMS += extras/ezio/ezio-term
 extras_ezio_ezio_term_SOURCES = \
-       extras/ezio/byteq.c \
-       extras/ezio/byteq.h \
        extras/ezio/ezio-term.c \
        extras/ezio/ezio.c \
        extras/ezio/ezio.h \
@@ -50,5 +48,5 @@ extras_ezio_ovs_switchui_LDADD = \
        $(PCRE_LIBS) \
        $(SSL_LIBS) \
        -lm
-endif # HAVE_PCRE
+endif # HAVE_PCRE_PARTIAL
 endif # HAVE_CURSES
index 846ccfd..cedc5c9 100644 (file)
@@ -26,8 +26,8 @@
 #include <stdlib.h>
 #include <term.h>
 #include <unistd.h>
+#include "byteq.h"
 #include "command-line.h"
-#include "extras/ezio/byteq.h"
 #include "extras/ezio/tty.h"
 #include "extras/ezio/vt.h"
 #include "daemon.h"
@@ -96,6 +96,7 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
index 7c360f3..16a6903 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -114,7 +114,7 @@ static void compose_messages(const struct dict *, struct rconn *rconn);
 
 static void show_flows(struct rconn *);
 static void show_dpid_ip(struct rconn *, const struct dict *);
-static void show_secchan_state(const struct dict *);
+static void show_ofproto_state(const struct dict *);
 static void show_fail_open_state(const struct dict *);
 static void show_discovery_state(const struct dict *);
 static void show_remote_state(const struct dict *);
@@ -144,6 +144,7 @@ main(int argc, char *argv[])
     long long int last_key_time = 0;
     int repeat_count = 0;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
@@ -165,7 +166,7 @@ main(int argc, char *argv[])
     daemonize();
 
     initialize_terminal();
-    fatal_signal_add_hook(restore_terminal, NULL, true);
+    fatal_signal_add_hook(restore_terminal, NULL, NULL, true);
 
     msg = NULL;
     countdown = 0;
@@ -257,7 +258,7 @@ compose_messages(const struct dict *dict, struct rconn *rconn)
     if (!show_reboot_state()) {
         show_flows(rconn);
         show_dpid_ip(rconn, dict);
-        show_secchan_state(dict);
+        show_ofproto_state(dict);
         show_fail_open_state(dict);
         show_discovery_state(dict);
         show_remote_state(dict);
@@ -648,7 +649,7 @@ addf(const char *format, ...)
 }
 
 static void
-show_secchan_state(const struct dict *dict)
+show_ofproto_state(const struct dict *dict)
 {
     static struct message *msg;
     const char *is_connected;
@@ -995,7 +996,7 @@ initialize_terminal(void)
 }
 
 static void
-restore_terminal(void *aux UNUSED)
+restore_terminal(void *aux OVS_UNUSED)
 {
     endwin();
 }
@@ -1104,6 +1105,7 @@ do_show_data_rates(void *rates_)
     }
     if (!rates->xid) {
         struct ofp_stats_request *rq;
+        struct ofp_port_stats_request *psr;
         struct ofpbuf *b;
 
         rates->xid = random_uint32();
@@ -1111,6 +1113,10 @@ do_show_data_rates(void *rates_)
                                rates->xid, &b);
         rq->type = htons(OFPST_PORT);
         rq->flags = htons(0);
+        psr = ofpbuf_put_uninit(b, sizeof *psr);
+        memset(psr, 0, sizeof *psr);
+        psr->port_no = htons(OFPP_NONE);
+        update_openflow_length(b);
         rconn_send_with_limit(rates->rconn, b, counter, 10);
     }
 
@@ -1247,7 +1253,7 @@ allocate_message(struct message **msgp)
 {
     if (!*msgp) {
         /* Allocate and initialize message. */
-        *msgp = xcalloc(1, sizeof **msgp);
+        *msgp = xzalloc(sizeof **msgp);
         (*msgp)->index = n_messages;
 
         /* Add to list of messages. */
@@ -1444,7 +1450,7 @@ init_reboot_notifier(void)
 }
 
 static void
-sigusr1_handler(int signr UNUSED)
+sigusr1_handler(int signr OVS_UNUSED)
 {
     sigusr1_triggered = true;
 }
@@ -1874,7 +1880,7 @@ yesno(const char *title, bool def)
 }
 
 static void
-cmd_show_version(const struct dict *dict UNUSED)
+cmd_show_version(const struct dict *dict OVS_UNUSED)
 {
     show_string(VERSION BUILDNR);
 }
@@ -1895,7 +1901,7 @@ cmd_browse_status(const struct dict *dict)
 }
 
 static void
-cmd_shell(const struct dict *dict UNUSED)
+cmd_shell(const struct dict *dict OVS_UNUSED)
 {
     const char *home;
 
@@ -1919,7 +1925,7 @@ cmd_shell(const struct dict *dict UNUSED)
 }
 
 static void
-cmd_show_motto(const struct dict *dict UNUSED)
+cmd_show_motto(const struct dict *dict OVS_UNUSED)
 {
     show_string("\"Just Add Ice\"");
 }
@@ -2079,7 +2085,7 @@ save_config(const struct svec *settings)
     }
 
     svec_init(&argv);
-    svec_add(&argv, "/usr/share/openvswitch/commands/reconfigure");
+    svec_add(&argv, "/usr/share/openvswitch-switchui/reconfigure");
     svec_append(&argv, settings);
     svec_terminate(&argv);
     ok = run_and_report_failure(argv.names, "Save failed");
@@ -2457,7 +2463,7 @@ abbreviate_netdevs(const struct svec *netdevs, struct ds *abbrev)
 static void
 choose_netdevs(struct svec *choices)
 {
-    struct svec netdevs;
+    struct svec netdevs = SVEC_EMPTY_INITIALIZER;
     struct menu menu;
     size_t i;
 
@@ -2478,9 +2484,9 @@ choose_netdevs(struct svec *choices)
             continue;
         }
 
-        retval = netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev);
+        retval = netdev_open_default(name, &netdev);
         if (!retval) {
-            bool exclude = netdev_get_in4(netdev, NULL, NULL);
+            bool exclude = netdev_get_in4(netdev, NULL, NULL) == 0;
             netdev_close(netdev);
             if (exclude) {
                 continue;
@@ -2547,7 +2553,7 @@ disconnected_string(int value)
 }
 
 static void
-cmd_configure(const struct dict *dict UNUSED)
+cmd_configure(const struct dict *dict OVS_UNUSED)
 {
     bool debug_mode = dict_get_bool(dict, "debug", false);
     struct dict config_dict;
@@ -2801,7 +2807,8 @@ cmd_configure(const struct dict *dict UNUSED)
             out = prompt("Ctlr rate limit:", in,
                          "^(Disabled|("NUM100_TO_99999_RE")/s)$");
             free(in);
-            config.rate_limit = isdigit(out[0]) ? atoi(out) : -1;
+            config.rate_limit
+                    = isdigit((unsigned char)out[0]) ? atoi(out) : -1;
             free(out);
             break;
 
@@ -2812,7 +2819,8 @@ cmd_configure(const struct dict *dict UNUSED)
             out = prompt("Activity probe:", in,
                          "^(Default|("NUM5_TO_99999_RE") s)$");
             free(in);
-            config.inactivity_probe = isdigit(out[0]) ? atoi(out) : -1;
+            config.inactivity_probe
+                    = isdigit((unsigned char)out[0]) ? atoi(out) : -1;
             free(out);
             break;
 
@@ -2823,7 +2831,8 @@ cmd_configure(const struct dict *dict UNUSED)
             out = prompt("Max backoff:", in,
                          "^(Default|("NUM1_TO_99999_RE") s)$");
             free(in);
-            config.max_backoff = isdigit(out[0]) ? atoi(out) : -1;
+            config.max_backoff
+                    = isdigit((unsigned char)out[0]) ? atoi(out) : -1;
             free(out);
             break;
         }
@@ -2886,7 +2895,7 @@ cmd_configure(const struct dict *dict UNUSED)
 }
 
 static void
-cmd_set_up_pki(const struct dict *dict UNUSED)
+cmd_set_up_pki(const struct dict *dict OVS_UNUSED)
 {
     static const char def_privkey_file[]
         = "/etc/openflow-switch/of0-privkey.pem";
index e41a987..090e80e 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -97,7 +97,7 @@ terminal_run(struct terminal *term, struct ezio *ezio, int input_fd)
 }
 
 void
-terminal_wait(struct terminal *term UNUSED, int input_fd)
+terminal_wait(struct terminal *term OVS_UNUSED, int input_fd)
 {
     poll_fd_wait(input_fd, POLLIN);
 }
index 709b802..4328828 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -263,7 +263,7 @@ tty_set_raw_mode(int fd, speed_t speed)
             return errno;
         }
         s->tios = tios;
-        fatal_signal_add_hook(restore_termios, s, true);
+        fatal_signal_add_hook(restore_termios, NULL, s, true);
 
         tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                           | INLCR | IGNCR | ICRNL | IXON);
@@ -374,7 +374,9 @@ tty_fork_child(int master_fd, char *argv[])
 }
 
 int
-tty_set_window_size(int fd UNUSED, int rows UNUSED, int columns UNUSED)
+tty_set_window_size(int fd OVS_UNUSED,
+                    int rows OVS_UNUSED,
+                    int columns OVS_UNUSED)
 {
 #ifdef TIOCGWINSZ
     struct winsize win;
diff --git a/include/openflow/.gitignore b/include/openflow/.gitignore
new file mode 100644 (file)
index 0000000..db736db
--- /dev/null
@@ -0,0 +1 @@
+/openflow.h.stamp
index d473155..b8dbc71 100644 (file)
@@ -1,4 +1,21 @@
 noinst_HEADERS += \
-       include/openflow/openflow-mgmt.h \
        include/openflow/nicira-ext.h \
        include/openflow/openflow.h
+
+if HAVE_PYTHON
+all-local: include/openflow/openflow.h.stamp
+include/openflow/openflow.h.stamp: \
+       include/openflow/openflow.h build-aux/check-structs
+       $(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h
+       touch $@
+DISTCLEANFILES += include/openflow/openflow.h.stamp
+
+all-local: include/openflow/nicira-ext.h.stamp
+include/openflow/nicira-ext.h.stamp: include/openflow/openflow.h include/openflow/nicira-ext.h build-aux/check-structs
+       $(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h $(srcdir)/include/openflow/nicira-ext.h
+       touch $@
+DISTCLEANFILES += include/openflow/nicira-ext.h.stamp
+endif
+
+EXTRA_DIST += build-aux/check-structs
+
index c4c6062..7232d57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,30 +37,14 @@ enum nicira_type {
      * pairs in the form "key=value\n". */
     NXT_STATUS_REPLY,
 
-    /* Configure an action.  Most actions do not require configuration
-     * beyond that supplied in the actual action call. */
-    NXT_ACT_SET_CONFIG,
-
-    /* Get configuration of action. */
-    NXT_ACT_GET_CONFIG,
-
-    /* Remote command execution.  The request body is a sequence of strings
-     * delimited by null bytes.  The first string is a command name.
-     * Subsequent strings are command arguments. */
-    NXT_COMMAND_REQUEST,
-
-    /* Remote command execution reply, sent when the command's execution
-     * completes.  The reply body is struct nx_command_reply. */
-    NXT_COMMAND_REPLY,
-
     /* No longer used. */
+    NXT_ACT_SET_CONFIG__OBSOLETE,
+    NXT_ACT_GET_CONFIG__OBSOLETE,
+    NXT_COMMAND_REQUEST__OBSOLETE,
+    NXT_COMMAND_REPLY__OBSOLETE,
     NXT_FLOW_END_CONFIG__OBSOLETE,
-
-    /* No longer used. */
     NXT_FLOW_END__OBSOLETE,
-
-    /* Management protocol.  See "openflow-mgmt.h". */
-    NXT_MGMT,
+    NXT_MGMT__OBSOLETE,
 };
 
 struct nicira_header {
@@ -68,7 +52,7 @@ struct nicira_header {
     uint32_t vendor;            /* NX_VENDOR_ID. */
     uint32_t subtype;           /* One of NXT_* above. */
 };
-OFP_ASSERT(sizeof(struct nicira_header) == sizeof(struct ofp_vendor_header) + 4);
+OFP_ASSERT(sizeof(struct nicira_header) == 16);
 
 
 enum nx_action_subtype {
@@ -97,24 +81,4 @@ struct nx_action_header {
 };
 OFP_ASSERT(sizeof(struct nx_action_header) == 16);
 
-/* Status bits for NXT_COMMAND_REPLY. */
-enum {
-    NXT_STATUS_EXITED = 1 << 31,   /* Exited normally. */
-    NXT_STATUS_SIGNALED = 1 << 30, /* Exited due to signal. */
-    NXT_STATUS_UNKNOWN = 1 << 29,  /* Exited for unknown reason. */
-    NXT_STATUS_COREDUMP = 1 << 28, /* Exited with core dump. */
-    NXT_STATUS_ERROR = 1 << 27,    /* Command could not be executed. */
-    NXT_STATUS_STARTED = 1 << 26,  /* Command was started. */
-    NXT_STATUS_EXITSTATUS = 0xff,  /* Exit code mask if NXT_STATUS_EXITED. */
-    NXT_STATUS_TERMSIG = 0xff,     /* Signal number if NXT_STATUS_SIGNALED. */
-};
-
-/* NXT_COMMAND_REPLY. */
-struct nx_command_reply {
-    struct nicira_header nxh;
-    uint32_t status;            /* Status bits defined above. */
-    /* Followed by any number of bytes of process output. */
-};
-OFP_ASSERT(sizeof(struct nx_command_reply) == 20);
-
 #endif /* openflow/nicira-ext.h */
diff --git a/include/openflow/openflow-mgmt.h b/include/openflow/openflow-mgmt.h
deleted file mode 100644 (file)
index 04017d4..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (c) 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef OPENFLOW_OPENFLOW_MGMT_H
-#define OPENFLOW_OPENFLOW_MGMT_H 1
-
-#include "openflow/nicira-ext.h"
-
-enum ofmp_type {
-    OFMPT_CAPABILITY_REQUEST,
-    OFMPT_CAPABILITY_REPLY,
-    OFMPT_RESOURCES_REQUEST,
-    OFMPT_RESOURCES_UPDATE,
-    OFMPT_CONFIG_REQUEST,
-    OFMPT_CONFIG_UPDATE,
-    OFMPT_CONFIG_UPDATE_ACK,
-    OFMPT_ERROR,
-    OFMPT_EXTENDED_DATA
-};
-
-/* Header on all OpenFlow management packets. */
-struct ofmp_header {
-    struct nicira_header header;
-    uint16_t type;           /* One of OFMPT_* above. */
-    uint8_t pad[2];
-};
-OFP_ASSERT(sizeof(struct ofmp_header) == sizeof(struct nicira_header) + 4);
-
-
-/* Generic TLV header. */
-struct ofmp_tlv {
-    uint16_t type;        /* Type of value (one of OFMPTLV_*). */
-    uint16_t len;         /* Length of TLV (includes this header). */
-    uint8_t data[0];      /* Value of data as defined by type and length. */
-};
-OFP_ASSERT(sizeof(struct ofmp_tlv) == 4);
-
-/* Universal TLV terminator.  Used to indicate end of TLV list. */
-struct ofmp_tlv_end {
-    uint16_t type;        /* Type is 0. */
-    uint16_t len;         /* Length is 4. */
-};
-OFP_ASSERT(sizeof(struct ofmp_tlv_end) == 4);
-
-
-/* Bitmask of capability description styles. */
-enum ofmp_capability_format {
-    OFMPCAF_SIMPLE  = 0 << 0,             /* "ovs-vswitchd.conf" style. */
-};
-
-/* Body of capbility request.
- *
- * OFMPT_CAPABILITY_REQUEST (controller -> switch) */
-struct ofmp_capability_request {
-    struct ofmp_header header;
-    uint32_t format;                      /* One of OFMPCAF_*. */
-};
-OFP_ASSERT(sizeof(struct ofmp_capability_request) == 24);
-
-/* Body of reply to capability request.  
- *
- * OFMPT_CAPABILITY_REPLY (switch -> controller). */
-struct ofmp_capability_reply {
-    struct ofmp_header header;
-    uint32_t format;                      /* One of OFMPCAF_*. */
-    uint64_t mgmt_id;                     /* Management ID. */
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_capability_reply) == 32);
-
-
-/* Resource TLV for datapath description. */
-struct ofmptsr_dp {
-    uint16_t type;                        /* OFMPTSR_DP. */
-    uint16_t len;                         /* 32. */
-    uint8_t pad[4];
-    uint64_t dp_id;                       /* Datapath ID. */
-    uint8_t name[OFP_MAX_PORT_NAME_LEN];  /* Null-terminated name. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_dp) == 32);
-
-/* UUIDs will be passed around as *non-terminated* strings in their
- * canonical form (e.g., 550e8400-e29b-41d4-a716-446655440000).
- */
-#define OFMP_UUID_LEN 36
-
-/* Resource TLV for XenServer UUIDs associated with this datapath. */
-struct ofmptsr_dp_uuid {
-    uint16_t type;                        /* OFMPTSR_DP_UUID. */
-    uint16_t len;                         /* Length. */
-    uint8_t pad[4];
-    uint64_t dp_id;                       /* Datapath ID. */
-    uint8_t uuid_list[0];                 /* List of UUIDs associated with 
-                                           * this datapath. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_dp_uuid) == 16);
-
-/* Resource TLV for XenServer UUID associated with this managment 
- * instance. 
- */
-struct ofmptsr_mgmt_uuid {
-    uint16_t type;                        /* OFMPTSR_MGMT_UUID. */
-    uint16_t len;                         /* 52. */
-    uint8_t pad[4];
-    uint64_t mgmt_id;                     /* Management ID. */
-    uint8_t uuid[OFMP_UUID_LEN];          /* System UUID. */
-    uint8_t pad2[4];                      /* Pad for 64-bit systems. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_mgmt_uuid) == 56);
-
-/* Resource TLV for details about this XenServer vif. */
-struct ofmptsr_vif {
-    uint16_t type;                        /* OFMPTSR_VIF. */
-    uint16_t len;                         /* 136. */
-    uint8_t name[OFP_MAX_PORT_NAME_LEN];  /* Null-terminated name. */
-    uint8_t vif_uuid[OFMP_UUID_LEN];      /* VIF UUID. */
-    uint8_t vm_uuid[OFMP_UUID_LEN];       /* VM UUID. */
-    uint8_t net_uuid[OFMP_UUID_LEN];      /* Network UUID. */
-    uint64_t vif_mac;                     /* Management ID. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_vif) == 136);
-
-/* TLV types for switch resource descriptions. */
-enum ofmp_switch_resources {
-    OFMPTSR_END = 0,                      /* Terminator. */
-    OFMPTSR_DP,                           /* Datapath. */
-    OFMPTSR_DP_UUID,                      /* Xen: datapath uuid's. */
-    OFMPTSR_MGMT_UUID,                    /* Xen: management uuid. */
-    OFMPTSR_VIF,                          /* Xen: vif details. */
-};
-
-/* Body of resources request.
- *
- * OFMPT_RESOURCES_REQUEST (controller -> switch) */
-struct ofmp_resources_request {
-    struct ofmp_header header;
-};
-
-/* Body of capbility update.  Sent in response to a resources request or
- * sent asynchronously when resources change on the switch. 
- *
- * OFMPT_RESOURCES_UPDATE (switch -> controller) */
-struct ofmp_resources_update {
-    struct ofmp_header header;
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_resources_update) == 20);
-
-
-/* Bitmask of capability description styles. */
-enum ofmp_config_format {
-    OFMPCOF_SIMPLE  = 0 << 0,           /* "ovs-vswitchd.conf" style. */
-};
-
-#define CONFIG_COOKIE_LEN 20
-
-/* Body of configuration request.
- *
- * OFMPT_CONFIG_REQUEST (controller -> switch) */
-struct ofmp_config_request {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-};
-OFP_ASSERT(sizeof(struct ofmp_config_request) == 24);
-
-/* Body of configuration update.  Sent in response to a configuration 
- * request from the controller.  May be sent asynchronously by either
- * the controller or switch to modify configuration or notify of
- * changes, respectively.  If sent by the controller, the switch must
- * respond with a OFMPT_CONFIG_UPDATE_ACK.
- *
- * OFMPT_CONFIG_UPDATE (switch <-> controller) */
-struct ofmp_config_update {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-    uint8_t cookie[CONFIG_COOKIE_LEN];  /* Cookie of config attempting to be
-                                         * replaced by this update. */
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_config_update) == 44);
-
-/* Bitmask of configuration update ack flags. */
-enum ofmp_config_update_ack_flags {
-    OFMPCUAF_SUCCESS = 1 << 0,          /* Config succeeded. */
-};
-
-/* Body of configuration update ack.  Sent in response to a configuration 
- * udpate request.
- *
- * OFMPT_CONFIG_UPDATE_ACK (switch -> controller) */
-struct ofmp_config_update_ack {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-    uint32_t flags;                     /* One of OFMPCUAF_*. */
-    uint8_t cookie[CONFIG_COOKIE_LEN];  /* Cookie of current configuration 
-                                         * being used in the switch. */
-};
-OFP_ASSERT(sizeof(struct ofmp_config_update_ack) == 48);
-
-/* Values for 'type' in ofmp_error_msg. */
-enum ofmp_error_type {
-    OFMPET_BAD_CONFIG                   /* Problem with configuration. */
-};
-
-/* ofmp_error_msg 'code' values for OFMPET_BAD_CONFIG.  'data' contains
- * at least the first 64 bytes of the failed request. */
-enum ofmp_bad_config_code {
-    OFMPBCC_BUSY,                       /* Config updating, try again. */
-    OFMPBCC_OLD_COOKIE,                 /* Config has changed. */
-};
-
-/* Body of error message.  May be sent by either the switch or the
- * controller to indicate some error condition.
- *
- * OFMPT_ERROR (switch <-> controller) */
-struct ofmp_error_msg {
-    struct ofmp_header header;
-
-    uint16_t type;            /* One of OFMPET_*. */
-    uint16_t code;            /* Code depending on 'type'. */
-    uint8_t data[0];          /* Variable-length data.  Interpreted based 
-                                 on the type and code. */
-};
-OFP_ASSERT(sizeof(struct ofmp_error_msg) == 24);
-
-/* Bitmask of extended data message flags. */
-enum ofmp_extended_data_flags {
-    OFMPEDF_MORE_DATA = 1 << 0,         /* More data follows. */
-};
-
-/* Body of extended data message.  May be sent by either the switch or the
- * controller to send messages that are greater than 65535 bytes in
- * length.  The OpenFlow transaction id (xid) must be the same for all
- * the individual OpenFlow messages that make up an extended message.
- *
- * OFMPT_EXTENDED_DATA (switch <-> controller) */
-struct ofmp_extended_data {
-    struct ofmp_header header;
-
-    uint16_t type;            /* Type code of the encapsulated message. */
-    uint8_t flags;            /* One of OFMPEDF_*. */
-    uint8_t pad;
-    uint8_t data[0];          /* Variable-length data. */
-};
-OFP_ASSERT(sizeof(struct ofmp_extended_data) == 24);
-
-#endif /* openflow/openflow-mgmt.h */
index f780f07..b77cd70 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #define OFP_ASSERT BOOST_STATIC_ASSERT
 #endif /* __cplusplus */
 
-#ifndef SWIG
-#define OFP_PACKED __attribute__((packed))
-#else
-#define OFP_PACKED              /* SWIG doesn't understand __attribute. */
-#endif
-
+/* Version number:
+ * Non-experimental versions released: 0x01
+ * Experimental versions released: 0x81 -- 0x99
+ */
 /* The most significant bit being set in the version field indicates an
- * experimental OpenFlow version.  
+ * experimental OpenFlow version.
  */
-#define OFP_VERSION   0x97
+#define OFP_VERSION   0x01
 
 #define OFP_MAX_TABLE_NAME_LEN 32
 #define OFP_MAX_PORT_NAME_LEN  16
 
 #define OFP_ETH_ALEN 6          /* Bytes in an Ethernet address. */
 
-/* Port numbering.  Physical ports are numbered starting from 0. */
+/* Port numbering.  Physical ports are numbered starting from 1. */
 enum ofp_port {
     /* Maximum number of physical switch ports. */
     OFPP_MAX = 0xff00,
 
     /* Fake output "ports". */
-    OFPP_IN_PORT    = 0xfff8,  /* Send the packet out the input port.  This 
-                                  virtual port must be explicitly used 
-                                  in order to send back out of the input 
+    OFPP_IN_PORT    = 0xfff8,  /* Send the packet out the input port.  This
+                                  virtual port must be explicitly used
+                                  in order to send back out of the input
                                   port. */
-    OFPP_TABLE      = 0xfff9,  /* Perform actions in flow table.  
+    OFPP_TABLE      = 0xfff9,  /* Perform actions in flow table.
                                   NB: This can only be the destination
                                   port for packet-out messages. */
     OFPP_NORMAL     = 0xfffa,  /* Process with normal L2/L3 switching. */
-    OFPP_FLOOD      = 0xfffb,  /* All physical ports except input port and 
+    OFPP_FLOOD      = 0xfffb,  /* All physical ports except input port and
                                   those disabled by STP. */
     OFPP_ALL        = 0xfffc,  /* All physical ports except input port. */
-    OFPP_CONTROLLER = 0xfffd,  /* Send to controller. */ 
+    OFPP_CONTROLLER = 0xfffd,  /* Send to controller. */
     OFPP_LOCAL      = 0xfffe,  /* Local openflow "port". */
     OFPP_NONE       = 0xffff   /* Not associated with a physical port. */
 };
@@ -95,7 +93,7 @@ enum ofp_type {
 
     /* Asynchronous messages. */
     OFPT_PACKET_IN,           /* Async message */
-    OFPT_FLOW_EXPIRED,        /* Async message */
+    OFPT_FLOW_REMOVED,        /* Async message */
     OFPT_PORT_STATUS,         /* Async message */
 
     /* Controller command messages. */
@@ -105,7 +103,15 @@ enum ofp_type {
 
     /* Statistics messages. */
     OFPT_STATS_REQUEST,       /* Controller/switch message */
-    OFPT_STATS_REPLY          /* Controller/switch message */
+    OFPT_STATS_REPLY,         /* Controller/switch message */
+
+    /* Barrier messages. */
+    OFPT_BARRIER_REQUEST,     /* Controller/switch message */
+    OFPT_BARRIER_REPLY,       /* Controller/switch message */
+
+    /* Queue Configuration messages. */
+    OFPT_QUEUE_GET_CONFIG_REQUEST,  /* Controller/switch message */
+    OFPT_QUEUE_GET_CONFIG_REPLY     /* Controller/switch message */
 };
 
 /* Header on all OpenFlow packets. */
@@ -128,14 +134,11 @@ struct ofp_hello {
 #define OFP_DEFAULT_MISS_SEND_LEN   128
 
 enum ofp_config_flags {
-    /* Tells datapath to notify the controller of expired flow entries. */
-    OFPC_SEND_FLOW_EXP = 1 << 0,
-
     /* Handling of IP fragments. */
-    OFPC_FRAG_NORMAL   = 0 << 1,  /* No special handling for fragments. */
-    OFPC_FRAG_DROP     = 1 << 1,  /* Drop fragments. */
-    OFPC_FRAG_REASM    = 2 << 1,  /* Reassemble (only if OFPC_IP_REASM set). */
-    OFPC_FRAG_MASK     = 3 << 1
+    OFPC_FRAG_NORMAL   = 0,  /* No special handling for fragments. */
+    OFPC_FRAG_DROP     = 1,  /* Drop fragments. */
+    OFPC_FRAG_REASM    = 2,  /* Reassemble (only if OFPC_IP_REASM set). */
+    OFPC_FRAG_MASK     = 3
 };
 
 /* Switch configuration. */
@@ -153,20 +156,23 @@ enum ofp_capabilities {
     OFPC_TABLE_STATS    = 1 << 1,  /* Table statistics. */
     OFPC_PORT_STATS     = 1 << 2,  /* Port statistics. */
     OFPC_STP            = 1 << 3,  /* 802.1d spanning tree. */
-    OFPC_MULTI_PHY_TX   = 1 << 4,  /* Supports transmitting through multiple
-                                      physical interfaces */
-    OFPC_IP_REASM       = 1 << 5   /* Can reassemble IP fragments. */
+    OFPC_RESERVED       = 1 << 4,  /* Reserved, must not be set. */
+    OFPC_IP_REASM       = 1 << 5,  /* Can reassemble IP fragments. */
+    OFPC_QUEUE_STATS    = 1 << 6,  /* Queue statistics. */
+    OFPC_ARP_MATCH_IP   = 1 << 7   /* Match IP addresses in ARP
+                                      pkts. */
 };
 
 /* Flags to indicate behavior of the physical port.  These flags are
  * used in ofp_phy_port to describe the current configuration.  They are
- * used in the ofp_port_mod message to configure the port's behavior. 
+ * used in the ofp_port_mod message to configure the port's behavior.
  */
 enum ofp_port_config {
     OFPPC_PORT_DOWN    = 1 << 0,  /* Port is administratively down. */
 
     OFPPC_NO_STP       = 1 << 1,  /* Disable 802.1D spanning tree on port. */
-    OFPPC_NO_RECV      = 1 << 2,  /* Drop most packets received on port. */
+    OFPPC_NO_RECV      = 1 << 2,  /* Drop all packets except 802.1D
+                                     spanning tree packets. */
     OFPPC_NO_RECV_STP  = 1 << 3,  /* Drop received 802.1D STP packets. */
     OFPPC_NO_FLOOD     = 1 << 4,  /* Do not include this port when flooding. */
     OFPPC_NO_FWD       = 1 << 5,  /* Drop packets forwarded to port. */
@@ -227,8 +233,9 @@ OFP_ASSERT(sizeof(struct ofp_phy_port) == 48);
 /* Switch features. */
 struct ofp_switch_features {
     struct ofp_header header;
-    uint64_t datapath_id;   /* Datapath unique ID.  Only the lower 48-bits
-                               are meaningful. */
+    uint64_t datapath_id;   /* Datapath unique ID.  The lower 48-bits are for
+                               a MAC address, while the upper 16-bits are
+                               implementer-defined. */
 
     uint32_t n_buffers;     /* Max packets buffered at once. */
 
@@ -266,16 +273,16 @@ OFP_ASSERT(sizeof(struct ofp_port_status) == 64);
 struct ofp_port_mod {
     struct ofp_header header;
     uint16_t port_no;
-    uint8_t hw_addr[OFP_ETH_ALEN]; /* The hardware address is not 
-                                      configurable.  This is used to 
-                                      sanity-check the request, so it must 
+    uint8_t hw_addr[OFP_ETH_ALEN]; /* The hardware address is not
+                                      configurable.  This is used to
+                                      sanity-check the request, so it must
                                       be the same as returned in an
                                       ofp_phy_port struct. */
 
     uint32_t config;        /* Bitmap of OFPPC_* flags. */
     uint32_t mask;          /* Bitmap of OFPPC_* flags to be changed. */
 
-    uint32_t advertise;     /* Bitmap of "ofp_port_features"s.  Zero all 
+    uint32_t advertise;     /* Bitmap of "ofp_port_features"s.  Zero all
                                bits to prevent any action taking place. */
     uint8_t pad[4];         /* Pad to 64-bits. */
 };
@@ -296,7 +303,7 @@ struct ofp_packet_in {
     uint8_t reason;         /* Reason packet is being sent (one of OFPR_*) */
     uint8_t pad;
     uint8_t data[0];        /* Ethernet frame, halfway through 32-bit word,
-                               so the IP header is 32-bit aligned.  The 
+                               so the IP header is 32-bit aligned.  The
                                amount of data is inferred from the length
                                field in the header.  Because of padding,
                                offsetof(struct ofp_packet_in, data) ==
@@ -313,15 +320,17 @@ enum ofp_action_type {
     OFPAT_SET_DL_DST,       /* Ethernet destination address. */
     OFPAT_SET_NW_SRC,       /* IP source address. */
     OFPAT_SET_NW_DST,       /* IP destination address. */
+    OFPAT_SET_NW_TOS,       /* IP ToS (DSCP field, 6 bits). */
     OFPAT_SET_TP_SRC,       /* TCP/UDP source port. */
     OFPAT_SET_TP_DST,       /* TCP/UDP destination port. */
+    OFPAT_ENQUEUE,          /* Output to queue. */
     OFPAT_VENDOR = 0xffff
 };
 
-/* Action structure for OFPAT_OUTPUT, which sends packets out 'port'.  
- * When the 'port' is the OFPP_CONTROLLER, 'max_len' indicates the max 
- * number of bytes to send.  A 'max_len' of zero means the entire packet 
- * should be sent. */
+/* Action structure for OFPAT_OUTPUT, which sends packets out 'port'.
+ * When the 'port' is the OFPP_CONTROLLER, 'max_len' indicates the max
+ * number of bytes to send.  A 'max_len' of zero means no bytes of the
+ * packet should be sent. */
 struct ofp_action_output {
     uint16_t type;                  /* OFPAT_OUTPUT. */
     uint16_t len;                   /* Length is 8. */
@@ -351,7 +360,7 @@ struct ofp_action_vlan_pcp {
     uint8_t vlan_pcp;               /* VLAN priority. */
     uint8_t pad[3];
 };
-OFP_ASSERT(sizeof(struct ofp_action_vlan_vid) == 8);
+OFP_ASSERT(sizeof(struct ofp_action_vlan_pcp) == 8);
 
 /* Action structure for OFPAT_SET_DL_SRC/DST. */
 struct ofp_action_dl_addr {
@@ -370,6 +379,15 @@ struct ofp_action_nw_addr {
 };
 OFP_ASSERT(sizeof(struct ofp_action_nw_addr) == 8);
 
+/* Action structure for OFPAT_SET_NW_TOS. */
+struct ofp_action_nw_tos {
+    uint16_t type;                  /* OFPAT_SET_TW_TOS. */
+    uint16_t len;                   /* Length is 8. */
+    uint8_t nw_tos;                 /* IP TOS (DSCP field, 6 bits). */
+    uint8_t pad[3];
+};
+OFP_ASSERT(sizeof(struct ofp_action_nw_tos) == 8);
+
 /* Action structure for OFPAT_SET_TP_SRC/DST. */
 struct ofp_action_tp_port {
     uint16_t type;                  /* OFPAT_SET_TP_SRC/DST. */
@@ -383,19 +401,19 @@ OFP_ASSERT(sizeof(struct ofp_action_tp_port) == 8);
 struct ofp_action_vendor_header {
     uint16_t type;                  /* OFPAT_VENDOR. */
     uint16_t len;                   /* Length is a multiple of 8. */
-    uint32_t vendor;                /* Vendor ID, which takes the same form 
-                                       as in "struct ofp_vendor_header". */ 
+    uint32_t vendor;                /* Vendor ID, which takes the same form
+                                       as in "struct ofp_vendor_header". */
 };
 OFP_ASSERT(sizeof(struct ofp_action_vendor_header) == 8);
 
-/* Action header that is common to all actions.  The length includes the 
- * header and any padding used to make the action 64-bit aligned.  
+/* Action header that is common to all actions.  The length includes the
+ * header and any padding used to make the action 64-bit aligned.
  * NB: The length of an action *must* always be a multiple of eight. */
 struct ofp_action_header {
     uint16_t type;                  /* One of OFPAT_*. */
-    uint16_t len;                   /* Length of action, including this 
-                                       header.  This is the length of action, 
-                                       including any padding to make it 
+    uint16_t len;                   /* Length of action, including this
+                                       header.  This is the length of action,
+                                       including any padding to make it
                                        64-bit aligned. */
     uint8_t pad[4];
 };
@@ -409,6 +427,7 @@ union ofp_action {
     struct ofp_action_vlan_vid vlan_vid;
     struct ofp_action_vlan_pcp vlan_pcp;
     struct ofp_action_nw_addr nw_addr;
+    struct ofp_action_nw_tos nw_tos;
     struct ofp_action_tp_port tp_port;
 };
 OFP_ASSERT(sizeof(union ofp_action) == 8);
@@ -420,8 +439,8 @@ struct ofp_packet_out {
     uint16_t in_port;             /* Packet's input port (OFPP_NONE if none). */
     uint16_t actions_len;         /* Size of action array in bytes. */
     struct ofp_action_header actions[0]; /* Actions. */
-    /* uint8_t data[0]; */        /* Packet data.  The length is inferred 
-                                     from the length field in the header.  
+    /* uint8_t data[0]; */        /* Packet data.  The length is inferred
+                                     from the length field in the header.
                                      (Only meaningful if buffer_id == -1.) */
 };
 OFP_ASSERT(sizeof(struct ofp_packet_out) == 16);
@@ -436,14 +455,14 @@ enum ofp_flow_mod_command {
 
 /* Flow wildcards. */
 enum ofp_flow_wildcards {
-    OFPFW_IN_PORT  = 1 << 0,  /* Switch input port. */
-    OFPFW_DL_VLAN  = 1 << 1,  /* VLAN. */
-    OFPFW_DL_SRC   = 1 << 2,  /* Ethernet source address. */
-    OFPFW_DL_DST   = 1 << 3,  /* Ethernet destination address. */
-    OFPFW_DL_TYPE  = 1 << 4,  /* Ethernet frame type. */
-    OFPFW_NW_PROTO = 1 << 5,  /* IP protocol. */
-    OFPFW_TP_SRC   = 1 << 6,  /* TCP/UDP source port. */
-    OFPFW_TP_DST   = 1 << 7,  /* TCP/UDP destination port. */
+    OFPFW_IN_PORT    = 1 << 0,  /* Switch input port. */
+    OFPFW_DL_VLAN    = 1 << 1,  /* VLAN vid. */
+    OFPFW_DL_SRC     = 1 << 2,  /* Ethernet source address. */
+    OFPFW_DL_DST     = 1 << 3,  /* Ethernet destination address. */
+    OFPFW_DL_TYPE    = 1 << 4,  /* Ethernet frame type. */
+    OFPFW_NW_PROTO   = 1 << 5,  /* IP protocol. */
+    OFPFW_TP_SRC     = 1 << 6,  /* TCP/UDP source port. */
+    OFPFW_TP_DST     = 1 << 7,  /* TCP/UDP destination port. */
 
     /* IP source address wildcard bit count.  0 is exact match, 1 ignores the
      * LSB, 2 ignores the 2 least-significant bits, ..., 32 and higher wildcard
@@ -460,11 +479,14 @@ enum ofp_flow_wildcards {
     OFPFW_NW_DST_MASK = ((1 << OFPFW_NW_DST_BITS) - 1) << OFPFW_NW_DST_SHIFT,
     OFPFW_NW_DST_ALL = 32 << OFPFW_NW_DST_SHIFT,
 
+    OFPFW_DL_VLAN_PCP = 1 << 20, /* VLAN priority. */
+    OFPFW_NW_TOS = 1 << 21, /* IP ToS (DSCP field, 6 bits). */
+
     /* Wildcard all fields. */
-    OFPFW_ALL = ((1 << 20) - 1)
+    OFPFW_ALL = ((1 << 22) - 1)
 };
 
-/* The wildcards for ICMP type and code fields use the transport source 
+/* The wildcards for ICMP type and code fields use the transport source
  * and destination port fields, respectively. */
 #define OFPFW_ICMP_TYPE OFPFW_TP_SRC
 #define OFPFW_ICMP_CODE OFPFW_TP_DST
@@ -492,17 +514,21 @@ struct ofp_match {
     uint8_t dl_src[OFP_ETH_ALEN]; /* Ethernet source address. */
     uint8_t dl_dst[OFP_ETH_ALEN]; /* Ethernet destination address. */
     uint16_t dl_vlan;          /* Input VLAN. */
+    uint8_t dl_vlan_pcp;       /* Input VLAN priority. */
+    uint8_t pad1[1];           /* Align to 64-bits. */
     uint16_t dl_type;          /* Ethernet frame type. */
-    uint8_t nw_proto;          /* IP protocol. */
-    uint8_t pad;               /* Align to 32-bits. */
+    uint8_t nw_tos;            /* IP ToS (DSCP field, 6 bits). */
+    uint8_t nw_proto;          /* IP protocol or lower 8 bits of
+                                  ARP opcode. */
+    uint8_t pad2[2];           /* Align to 64-bits. */
     uint32_t nw_src;           /* IP source address. */
     uint32_t nw_dst;           /* IP destination address. */
     uint16_t tp_src;           /* TCP/UDP source port. */
     uint16_t tp_dst;           /* TCP/UDP destination port. */
 };
-OFP_ASSERT(sizeof(struct ofp_match) == 36);
+OFP_ASSERT(sizeof(struct ofp_match) == 40);
 
-/* The match fields for ICMP type and code use the transport source and 
+/* The match fields for ICMP type and code use the transport source and
  * destination port fields, respectively. */
 #define icmp_type tp_src
 #define icmp_code tp_dst
@@ -514,51 +540,63 @@ OFP_ASSERT(sizeof(struct ofp_match) == 36);
 /* By default, choose a priority in the middle. */
 #define OFP_DEFAULT_PRIORITY 0x8000
 
+enum ofp_flow_mod_flags {
+    OFPFF_SEND_FLOW_REM = 1 << 0,  /* Send flow removed message when flow
+                                    * expires or is deleted. */
+    OFPFF_CHECK_OVERLAP = 1 << 1,  /* Check for overlapping entries first. */
+    OFPFF_EMERG         = 1 << 2   /* Ramark this is for emergency. */
+};
+
 /* Flow setup and teardown (controller -> datapath). */
 struct ofp_flow_mod {
     struct ofp_header header;
     struct ofp_match match;      /* Fields to match */
+    uint64_t cookie;             /* Opaque controller-issued identifier. */
 
     /* Flow actions. */
     uint16_t command;             /* One of OFPFC_*. */
     uint16_t idle_timeout;        /* Idle time before discarding (seconds). */
     uint16_t hard_timeout;        /* Max time before discarding (seconds). */
     uint16_t priority;            /* Priority level of flow entry. */
-    uint32_t buffer_id;           /* Buffered packet to apply to (or -1). 
+    uint32_t buffer_id;           /* Buffered packet to apply to (or -1).
                                      Not meaningful for OFPFC_DELETE*. */
-    uint16_t out_port;            /* For OFPFC_DELETE* commands, require 
-                                     matching entries to include this as an 
-                                     output port.  A value of OFPP_NONE 
+    uint16_t out_port;            /* For OFPFC_DELETE* commands, require
+                                     matching entries to include this as an
+                                     output port.  A value of OFPP_NONE
                                      indicates no restriction. */
-    uint8_t pad[2];               /* Align to 32-bits. */
-    uint32_t reserved;            /* Reserved for future use. */
-    struct ofp_action_header actions[0]; /* The action length is inferred 
-                                            from the length field in the 
+    uint16_t flags;               /* One of OFPFF_*. */
+    struct ofp_action_header actions[0]; /* The action length is inferred
+                                            from the length field in the
                                             header. */
 };
-OFP_ASSERT(sizeof(struct ofp_flow_mod) == 64);
+OFP_ASSERT(sizeof(struct ofp_flow_mod) == 72);
 
-/* Why did this flow expire? */
-enum ofp_flow_expired_reason {
-    OFPER_IDLE_TIMEOUT,         /* Flow idle time exceeded idle_timeout. */
-    OFPER_HARD_TIMEOUT          /* Time exceeded hard_timeout. */
+/* Why was this flow removed? */
+enum ofp_flow_removed_reason {
+    OFPRR_IDLE_TIMEOUT,         /* Flow idle time exceeded idle_timeout. */
+    OFPRR_HARD_TIMEOUT,         /* Time exceeded hard_timeout. */
+    OFPRR_DELETE                /* Evicted by a DELETE flow mod. */
 };
 
-/* Flow expiration (datapath -> controller). */
-struct ofp_flow_expired {
+/* Flow removed (datapath -> controller). */
+struct ofp_flow_removed {
     struct ofp_header header;
     struct ofp_match match;   /* Description of fields. */
+    uint64_t cookie;          /* Opaque controller-issued identifier. */
 
     uint16_t priority;        /* Priority level of flow entry. */
-    uint8_t reason;           /* One of OFPER_*. */
+    uint8_t reason;           /* One of OFPRR_*. */
     uint8_t pad[1];           /* Align to 32-bits. */
 
-    uint32_t duration;        /* Time flow was alive in seconds. */
-    uint8_t pad2[4];          /* Align to 64-bits. */
-    uint64_t packet_count;    
+    uint32_t duration_sec;    /* Time flow was alive in seconds. */
+    uint32_t duration_nsec;   /* Time flow was alive in nanoseconds beyond
+                                 duration_sec. */
+    uint16_t idle_timeout;    /* Idle timeout from original flow mod. */
+    uint8_t pad2[2];          /* Align to 64-bits. */
+    uint64_t packet_count;
     uint64_t byte_count;
 };
-OFP_ASSERT(sizeof(struct ofp_flow_expired) == 72);
+OFP_ASSERT(sizeof(struct ofp_flow_removed) == 88);
 
 /* Values for 'type' in ofp_error_message.  These values are immutable: they
  * will not change in future versions of the protocol (although new values may
@@ -568,13 +606,15 @@ enum ofp_error_type {
     OFPET_BAD_REQUEST,          /* Request was not understood. */
     OFPET_BAD_ACTION,           /* Error in action description. */
     OFPET_FLOW_MOD_FAILED,      /* Problem modifying flow entry. */
-    OFPET_PORT_MOD_FAILED       /* OFPT_PORT_MOD failed. */
+    OFPET_PORT_MOD_FAILED,      /* OFPT_PORT_MOD failed. */
+    OFPET_QUEUE_OP_FAILED       /* Queue operation failed. */
 };
 
 /* ofp_error_msg 'code' values for OFPET_HELLO_FAILED.  'data' contains an
  * ASCII text string that may give failure details. */
 enum ofp_hello_failed_code {
-    OFPHFC_INCOMPATIBLE         /* No compatible version. */
+    OFPHFC_INCOMPATIBLE,        /* No compatible version. */
+    OFPHFC_EPERM                /* Permissions error. */
 };
 
 /* ofp_error_msg 'code' values for OFPET_BAD_REQUEST.  'data' contains at least
@@ -583,15 +623,16 @@ enum ofp_bad_request_code {
     OFPBRC_BAD_VERSION,         /* ofp_header.version not supported. */
     OFPBRC_BAD_TYPE,            /* ofp_header.type not supported. */
     OFPBRC_BAD_STAT,            /* ofp_stats_request.type not supported. */
-    OFPBRC_BAD_VENDOR,          /* Vendor not supported (in ofp_vendor_header 
+    OFPBRC_BAD_VENDOR,          /* Vendor not supported (in ofp_vendor_header
                                  * or ofp_stats_request or ofp_stats_reply). */
     OFPBRC_BAD_SUBTYPE,         /* Vendor subtype not supported. */
-    OFPBRC_BAD_LENGTH,          /* Wrong request length for type. */
+    OFPBRC_EPERM,               /* Permissions error. */
+    OFPBRC_BAD_LEN,             /* Wrong request length for type. */
     OFPBRC_BUFFER_EMPTY,        /* Specified buffer has already been used. */
-    OFPBRC_BAD_COOKIE           /* Specified buffer does not exist. */
+    OFPBRC_BUFFER_UNKNOWN       /* Specified buffer does not exist. */
 };
 
-/* ofp_error_msg 'code' values for OFPET_BAD_ACTION.  'data' contains at least 
+/* ofp_error_msg 'code' values for OFPET_BAD_ACTION.  'data' contains at least
  * the first 64 bytes of the failed request. */
 enum ofp_bad_action_code {
     OFPBAC_BAD_TYPE,           /* Unknown action type. */
@@ -600,14 +641,23 @@ enum ofp_bad_action_code {
     OFPBAC_BAD_VENDOR_TYPE,    /* Unknown action type for vendor id. */
     OFPBAC_BAD_OUT_PORT,       /* Problem validating output action. */
     OFPBAC_BAD_ARGUMENT,       /* Bad action argument. */
-    OFPBAC_TOO_MANY            /* Can't handle this many actions. */
+    OFPBAC_EPERM,              /* Permissions error. */
+    OFPBAC_TOO_MANY,           /* Can't handle this many actions. */
+    OFPBAC_BAD_QUEUE           /* Problem validating output queue. */
 };
 
-/* ofp_error_msg 'code' values for OFPET_FLOW_MOD_FAILED.  'data' contains 
+/* ofp_error_msg 'code' values for OFPET_FLOW_MOD_FAILED.  'data' contains
  * at least the first 64 bytes of the failed request. */
 enum ofp_flow_mod_failed_code {
     OFPFMFC_ALL_TABLES_FULL,    /* Flow not added because of full tables. */
-    OFPFMFC_BAD_COMMAND         /* Unknown command. */
+    OFPFMFC_OVERLAP,            /* Attempted to add overlapping flow with
+                                 * CHECK_OVERLAP flag set. */
+    OFPFMFC_EPERM,              /* Permissions error. */
+    OFPFMFC_BAD_EMERG_TIMEOUT,  /* Flow not added because of non-zero idle/hard
+                                 * timeout. */
+    OFPFMFC_BAD_COMMAND,        /* Unknown command. */
+    OFPFMFC_UNSUPPORTED         /* Unsupported action list - cannot process in
+                                   the order specified. */
 };
 
 /* ofp_error_msg 'code' values for OFPET_PORT_MOD_FAILED.  'data' contains
@@ -617,19 +667,27 @@ enum ofp_port_mod_failed_code {
     OFPPMFC_BAD_HW_ADDR,         /* Specified hardware address is wrong. */
 };
 
+/* ofp_error msg 'code' values for OFPET_QUEUE_OP_FAILED. 'data' contains
+ * at least the first 64 bytes of the failed request */
+enum ofp_queue_op_failed_code {
+    OFPQOFC_BAD_PORT,           /* Invalid port (or port does not exist). */
+    OFPQOFC_BAD_QUEUE,          /* Queue does not exist. */
+    OFPQOFC_EPERM               /* Permissions error. */
+};
+
 /* OFPT_ERROR: Error message (datapath -> controller). */
 struct ofp_error_msg {
     struct ofp_header header;
 
     uint16_t type;
     uint16_t code;
-    uint8_t data[0];          /* Variable-length data.  Interpreted based 
+    uint8_t data[0];          /* Variable-length data.  Interpreted based
                                  on the type and code. */
 };
 OFP_ASSERT(sizeof(struct ofp_error_msg) == 12);
 
 enum ofp_stats_types {
-    /* Description of this OpenFlow switch. 
+    /* Description of this OpenFlow switch.
      * The request body is empty.
      * The reply body is struct ofp_desc_stats. */
     OFPST_DESC,
@@ -650,13 +708,18 @@ enum ofp_stats_types {
     OFPST_TABLE,
 
     /* Physical port statistics.
-     * The request body is empty.
+     * The request body is struct ofp_port_stats_request.
      * The reply body is an array of struct ofp_port_stats. */
     OFPST_PORT,
 
+    /* Queue statistics for a port
+     * The request body defines the port
+     * The reply body is an array of struct ofp_queue_stats */
+    OFPST_QUEUE,
+
     /* Vendor extension.
      * The request and reply bodies begin with a 32-bit vendor ID, which takes
-     * the same form as in "struct ofp_vendor_header".  The request and reply 
+     * the same form as in "struct ofp_vendor_header".  The request and reply
      * bodies are otherwise vendor-defined. */
     OFPST_VENDOR = 0xffff
 };
@@ -683,15 +746,17 @@ OFP_ASSERT(sizeof(struct ofp_stats_reply) == 12);
 
 #define DESC_STR_LEN   256
 #define SERIAL_NUM_LEN 32
-/* Body of reply to OFPST_DESC request.  Each entry is a NULL-terminated 
+/* Body of reply to OFPST_DESC request.  Each entry is a NULL-terminated
  * ASCII string. */
 struct ofp_desc_stats {
     char mfr_desc[DESC_STR_LEN];       /* Manufacturer description. */
     char hw_desc[DESC_STR_LEN];        /* Hardware description. */
     char sw_desc[DESC_STR_LEN];        /* Software description. */
     char serial_num[SERIAL_NUM_LEN];   /* Serial number. */
+    char dp_desc[DESC_STR_LEN];        /* Human readable description of
+                                          the datapath. */
 };
-OFP_ASSERT(sizeof(struct ofp_desc_stats) == 800);
+OFP_ASSERT(sizeof(struct ofp_desc_stats) == 1056);
 
 /* Body for ofp_stats_request of type OFPST_FLOW. */
 struct ofp_flow_stats_request {
@@ -699,11 +764,11 @@ struct ofp_flow_stats_request {
     uint8_t table_id;         /* ID of table to read (from ofp_table_stats)
                                  or 0xff for all tables. */
     uint8_t pad;              /* Align to 32 bits. */
-    uint16_t out_port;        /* Require matching entries to include this 
-                                 as an output port.  A value of OFPP_NONE 
+    uint16_t out_port;        /* Require matching entries to include this
+                                 as an output port.  A value of OFPP_NONE
                                  indicates no restriction. */
 };
-OFP_ASSERT(sizeof(struct ofp_flow_stats_request) == 40);
+OFP_ASSERT(sizeof(struct ofp_flow_stats_request) == 44);
 
 /* Body of reply to OFPST_FLOW request. */
 struct ofp_flow_stats {
@@ -711,17 +776,20 @@ struct ofp_flow_stats {
     uint8_t table_id;         /* ID of table flow came from. */
     uint8_t pad;
     struct ofp_match match;   /* Description of fields. */
-    uint32_t duration;        /* Time flow has been alive in seconds. */
+    uint32_t duration_sec;    /* Time flow has been alive in seconds. */
+    uint32_t duration_nsec;   /* Time flow has been alive in nanoseconds
+                                 beyond duration_sec. */
     uint16_t priority;        /* Priority of the entry. Only meaningful
                                  when this is not an exact-match entry. */
     uint16_t idle_timeout;    /* Number of seconds idle before expiration. */
     uint16_t hard_timeout;    /* Number of seconds before expiration. */
-    uint16_t pad2[3];         /* Pad to 64 bits. */
+    uint8_t pad2[6];          /* Align to 64 bits. */
+    uint64_t cookie;          /* Opaque controller-issued identifier. */
     uint64_t packet_count;    /* Number of packets in flow. */
     uint64_t byte_count;      /* Number of bytes in flow. */
     struct ofp_action_header actions[0]; /* Actions. */
 };
-OFP_ASSERT(sizeof(struct ofp_flow_stats) == 72);
+OFP_ASSERT(sizeof(struct ofp_flow_stats) == 88);
 
 /* Body for ofp_stats_request of type OFPST_AGGREGATE. */
 struct ofp_aggregate_stats_request {
@@ -729,11 +797,11 @@ struct ofp_aggregate_stats_request {
     uint8_t table_id;         /* ID of table to read (from ofp_table_stats)
                                  or 0xff for all tables. */
     uint8_t pad;              /* Align to 32 bits. */
-    uint16_t out_port;        /* Require matching entries to include this 
-                                 as an output port.  A value of OFPP_NONE 
+    uint16_t out_port;        /* Require matching entries to include this
+                                 as an output port.  A value of OFPP_NONE
                                  indicates no restriction. */
 };
-OFP_ASSERT(sizeof(struct ofp_aggregate_stats_request) == 40);
+OFP_ASSERT(sizeof(struct ofp_aggregate_stats_request) == 44);
 
 /* Body of reply to OFPST_AGGREGATE request. */
 struct ofp_aggregate_stats_reply {
@@ -746,11 +814,11 @@ OFP_ASSERT(sizeof(struct ofp_aggregate_stats_reply) == 24);
 
 /* Body of reply to OFPST_TABLE request. */
 struct ofp_table_stats {
-    uint8_t table_id;        /* Identifier of table.  Lower numbered tables 
+    uint8_t table_id;        /* Identifier of table.  Lower numbered tables
                                 are consulted first. */
     uint8_t pad[3];          /* Align to 32-bits. */
     char name[OFP_MAX_TABLE_NAME_LEN];
-    uint32_t wildcards;      /* Bitmap of OFPFW_* wildcards that are 
+    uint32_t wildcards;      /* Bitmap of OFPFW_* wildcards that are
                                 supported by the table. */
     uint32_t max_entries;    /* Max number of entries supported. */
     uint32_t active_count;   /* Number of active entries. */
@@ -759,6 +827,15 @@ struct ofp_table_stats {
 };
 OFP_ASSERT(sizeof(struct ofp_table_stats) == 64);
 
+/* Body for ofp_stats_request of type OFPST_PORT. */
+struct ofp_port_stats_request {
+    uint16_t port_no;        /* OFPST_PORT message may request statistics
+                                for a single port (specified with port_no)
+                                or for all ports (port_no == OFPP_NONE). */
+    uint8_t pad[6];
+};
+OFP_ASSERT(sizeof(struct ofp_port_stats_request) == 8);
+
 /* Body of reply to OFPST_PORT request. If a counter is unsupported, set
  * the field to all ones. */
 struct ofp_port_stats {
@@ -768,17 +845,17 @@ struct ofp_port_stats {
     uint64_t tx_packets;     /* Number of transmitted packets. */
     uint64_t rx_bytes;       /* Number of received bytes. */
     uint64_t tx_bytes;       /* Number of transmitted bytes. */
-    uint64_t rx_dropped;     /* Number of packets dropped by RX. */ 
-    uint64_t tx_dropped;     /* Number of packets dropped by TX. */ 
+    uint64_t rx_dropped;     /* Number of packets dropped by RX. */
+    uint64_t tx_dropped;     /* Number of packets dropped by TX. */
     uint64_t rx_errors;      /* Number of receive errors.  This is a super-set
                                 of receive errors and should be great than or
                                 equal to the sum of all rx_*_err values. */
     uint64_t tx_errors;      /* Number of transmit errors.  This is a super-set
                                 of transmit errors. */
-    uint64_t rx_frame_err;   /* Number of frame alignment errors. */ 
-    uint64_t rx_over_err;    /* Number of packets with RX overrun. */ 
-    uint64_t rx_crc_err;     /* Number of CRC errors. */ 
-    uint64_t collisions;     /* Number of collisions. */ 
+    uint64_t rx_frame_err;   /* Number of frame alignment errors. */
+    uint64_t rx_over_err;    /* Number of packets with RX overrun. */
+    uint64_t rx_crc_err;     /* Number of CRC errors. */
+    uint64_t collisions;     /* Number of collisions. */
 };
 OFP_ASSERT(sizeof(struct ofp_port_stats) == 104);
 
index 889a21f..92e0718 100644 (file)
@@ -1,4 +1,5 @@
 noinst_HEADERS += \
+       include/openvswitch/gre.h \
        include/openvswitch/brcompat-netlink.h \
        include/openvswitch/datapath-protocol.h
 
index a532d71..6c53545 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * This file is offered under your choice of two licenses: Apache 2.0 or GNU
  * GPL 2.0 or later.  The permission statements for each of these licenses is
@@ -37,7 +37,7 @@
  * ----------------------------------------------------------------------
  */
 
-/* Protocol between secchan and datapath. */
+/* Protocol between userspace and kernel datapath. */
 
 #ifndef OPENVSWITCH_DATAPATH_PROTOCOL_H
 #define OPENVSWITCH_DATAPATH_PROTOCOL_H 1
 #define ODP_PORT_GROUP_GET      _IOWR('O', 12, struct odp_port_group)
 
 #define ODP_FLOW_GET            _IOWR('O', 13, struct odp_flow)
-#define ODP_FLOW_GET_MULTIPLE   _IOWR('O', 14, struct odp_flowvec)
+#define ODP_FLOW_PUT            _IOWR('O', 14, struct odp_flow)
 #define ODP_FLOW_LIST           _IOWR('O', 15, struct odp_flowvec)
-
 #define ODP_FLOW_FLUSH          _IO('O', 16)
-#define ODP_FLOW_PUT            _IOWR('O', 17, struct odp_flow)
-#define ODP_FLOW_DEL            _IOWR('O', 18, struct odp_flow)
+#define ODP_FLOW_DEL            _IOWR('O', 17, struct odp_flow)
+
+#define ODP_EXECUTE             _IOR('O', 18, struct odp_execute)
 
-#define ODP_EXECUTE             _IOR('O', 19, struct odp_execute)
+#define ODP_SET_SFLOW_PROBABILITY _IOR('O', 19, int)
+#define ODP_GET_SFLOW_PROBABILITY _IOW('O', 20, int)
 
 struct odp_stats {
     /* Flows. */
@@ -100,27 +101,64 @@ struct odp_stats {
     /* Queues. */
     __u16 max_miss_queue;       /* Max length of ODPL_MISS queue. */
     __u16 max_action_queue;     /* Max length of ODPL_ACTION queue. */
+    __u16 max_sflow_queue;      /* Max length of ODPL_SFLOW queue. */
 };
 
 /* Logical ports. */
 #define ODPP_LOCAL      ((__u16)0)
 #define ODPP_NONE       ((__u16)-1)
+#define ODPP_NORMAL     ((__u16)-2)
 
 /* Listening channels. */
 #define _ODPL_MISS_NR   0       /* Packet missed in flow table. */
 #define ODPL_MISS       (1 << _ODPL_MISS_NR)
 #define _ODPL_ACTION_NR 1       /* Packet output to ODPP_CONTROLLER. */
 #define ODPL_ACTION     (1 << _ODPL_ACTION_NR)
-#define ODPL_ALL        (ODPL_MISS | ODPL_ACTION)
-
-/* Format of messages read from datapath fd. */
+#define _ODPL_SFLOW_NR  2       /* sFlow samples. */
+#define ODPL_SFLOW      (1 << _ODPL_SFLOW_NR)
+#define ODPL_ALL        (ODPL_MISS | ODPL_ACTION | ODPL_SFLOW)
+
+/**
+ * struct odp_msg - format of messages read from datapath fd.
+ * @type: One of the %_ODPL_* constants.
+ * @length: Total length of message, including this header.
+ * @port: Port that received the packet embedded in this message.
+ * @reserved: Not currently used.  Should be set to 0.
+ * @arg: Argument value whose meaning depends on @type.
+ *
+ * For @type == %_ODPL_MISS_NR, the header is followed by packet data.  The
+ * @arg member is unused and set to 0.
+ *
+ * For @type == %_ODPL_ACTION_NR, the header is followed by packet data.  The
+ * @arg member is copied from the &struct odp_action_controller that caused
+ * the &struct odp_msg to be composed.
+ *
+ * For @type == %_ODPL_SFLOW_NR, the header is followed by &struct
+ * odp_sflow_sample_header, then by an array of &union odp_action (the number
+ * of which is specified in &struct odp_sflow_sample_header), then by packet
+ * data.
+ */
 struct odp_msg {
-    __u32 type;                 /* _ODPL_MISS_NR or _ODPL_ACTION_NR. */
-    __u32 length;               /* Message length, including header. */
-    __u16 port;                 /* Port on which frame was received. */
+    __u32 type;
+    __u32 length;
+    __u16 port;
     __u16 reserved;
-    __u32 arg;                  /* Argument value specified in action. */
-    /* Followed by packet data. */
+    __u32 arg;
+};
+
+/**
+ * struct odp_sflow_sample_header - header added to sFlow sampled packet.
+ * @sample_pool: Number of packets that were candidates for sFlow sampling,
+ * regardless of whether they were actually chosen and sent down to userspace.
+ * @n_actions: Number of "union odp_action"s immediately following this header.
+ *
+ * This header follows &struct odp_msg when that structure's @type is
+ * %_ODPL_SFLOW_NR, and it is itself followed by an array of &union odp_action
+ * (the number of which is specified in @n_actions) and then by packet data.
+ */
+struct odp_sflow_sample_header {
+    __u32 sample_pool;
+    __u32 n_actions;
 };
 
 #define ODP_PORT_INTERNAL (1 << 0) /* This port is simulated. */
@@ -149,7 +187,7 @@ struct odp_flow_stats {
     __u32 used_nsec;
     __u8 tcp_flags;
     __u8 ip_tos;
-    __u16 reserved;
+    __u16 error;                /* Used by ODP_FLOW_GET. */
 };
 
 struct odp_flow_key {
@@ -164,7 +202,9 @@ struct odp_flow_key {
     __u8   dl_dst[ETH_ALEN];     /* Ethernet destination address. */
     __u8   nw_proto;             /* IP protocol or lower 8 bits of 
                                     ARP opcode. */
-    __u8   reserved;             /* Pad to 64 bits. */
+    __u8   dl_vlan_pcp;          /* Input VLAN priority. */
+    __u8   nw_tos;               /* IP ToS (DSCP field, 6 bits). */
+    __u8   reserved[3];          /* Align to 32-bits...must be zeroed. */
 };
 
 /* Flags for ODP_FLOW. */
@@ -210,9 +250,10 @@ struct odp_flowvec {
 #define ODPAT_SET_DL_DST        7    /* Ethernet destination address. */
 #define ODPAT_SET_NW_SRC        8    /* IP source address. */
 #define ODPAT_SET_NW_DST        9    /* IP destination address. */
-#define ODPAT_SET_TP_SRC        10   /* TCP/UDP source port. */
-#define ODPAT_SET_TP_DST        11   /* TCP/UDP destination port. */
-#define ODPAT_N_ACTIONS         12
+#define ODPAT_SET_NW_TOS        10   /* IP ToS/DSCP field (6 bits). */
+#define ODPAT_SET_TP_SRC        11   /* TCP/UDP source port. */
+#define ODPAT_SET_TP_DST        12   /* TCP/UDP destination port. */
+#define ODPAT_N_ACTIONS         13
 
 struct odp_action_output {
     __u16 type;                  /* ODPAT_OUTPUT. */
@@ -264,6 +305,14 @@ struct odp_action_nw_addr {
     __be32 nw_addr;             /* IP address. */
 };
 
+struct odp_action_nw_tos {
+    __u16 type;                  /* ODPAT_SET_NW_TOS. */
+    __u8 nw_tos;                 /* IP ToS/DSCP field (6 bits). */
+    __u8 reserved1;
+    __u16 reserved2;
+    __u16 reserved3;
+};
+
 /* Action structure for ODPAT_SET_TP_SRC/DST. */
 struct odp_action_tp_port {
     __u16 type;                  /* ODPAT_SET_TP_SRC/DST. */
@@ -281,6 +330,7 @@ union odp_action {
     struct odp_action_vlan_pcp vlan_pcp;
     struct odp_action_dl_addr dl_addr;
     struct odp_action_nw_addr nw_addr;
+    struct odp_action_nw_tos nw_tos;
     struct odp_action_tp_port tp_port;
 };
 
diff --git a/include/openvswitch/gre.h b/include/openvswitch/gre.h
new file mode 100644 (file)
index 0000000..2b24cf6
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * This file is offered under your choice of two licenses: Apache 2.0 or GNU
+ * GPL 2.0 or later.  The permission statements for each of these licenses is
+ * given below.  You may license your modifications to this file under either
+ * of these licenses or both.  If you wish to license your modifications under
+ * only one of these licenses, delete the permission text for the other
+ * license.
+ *
+ * ----------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ----------------------------------------------------------------------
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * ----------------------------------------------------------------------
+ */
+
+#ifndef OPENVSWITCH_GRE_H
+#define OPENVSWITCH_GRE_H 1
+
+#include <linux/if_tunnel.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
+#define GRE_IOCTL_ONLY
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+enum
+{
+       IFLA_GRE_UNSPEC,
+       IFLA_GRE_LINK,
+       IFLA_GRE_IFLAGS,
+       IFLA_GRE_OFLAGS,
+       IFLA_GRE_IKEY,
+       IFLA_GRE_OKEY,
+       IFLA_GRE_LOCAL,
+       IFLA_GRE_REMOTE,
+       IFLA_GRE_TTL,
+       IFLA_GRE_TOS,
+       IFLA_GRE_PMTUDISC,
+       __IFLA_GRE_MAX,
+};
+
+#define IFLA_GRE_MAX   (__IFLA_GRE_MAX - 1)
+#endif
+
+#define GRE_IOCTL_DEVICE "gre0"
+
+#define SIOCGETGRETAP  SIOCGETTUNNEL
+#define SIOCADDGRETAP  (SIOCDEVPRIVATE + 10)
+#define SIOCDELGRETAP  SIOCDELTUNNEL
+#define SIOCCHGGRETAP  (SIOCDEVPRIVATE + 11)
+
+#endif /* openvswitch/gre.h */
index 6a3f65c..d56deec 100644 (file)
@@ -1,4 +1,5 @@
 /Makefile
 /Makefile.in
 /dhparams.c
+/dirs.c
 /coverage-counters.c
diff --git a/lib/aes128.c b/lib/aes128.c
new file mode 100644 (file)
index 0000000..aa7f560
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#include <config.h>
+
+#include "aes128.h"
+
+#include <assert.h>
+
+#include "util.h"
+
+static const uint32_t Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+
+static const uint32_t Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+
+static const uint32_t Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+
+static const uint32_t Te3[256] = {
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+
+static const uint32_t Te4[256] = {
+    0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+    0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+    0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+    0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+    0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+    0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+    0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+    0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+    0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+    0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+    0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+    0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+    0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+    0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+    0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+    0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+    0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+    0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+    0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+    0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+    0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+    0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+    0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+    0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+    0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+    0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+    0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+    0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+    0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+    0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+    0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+    0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+    0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+    0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+    0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+    0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+    0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+    0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+    0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+    0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+    0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+    0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+    0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+    0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+    0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+    0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+    0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+    0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+    0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+    0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+    0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+    0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+    0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+    0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+    0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+    0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+    0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+    0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+    0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+    0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+    0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+    0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+    0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+    0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+
+static const uint32_t Td0[256] = {
+    0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+    0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+    0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+    0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+    0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+    0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+    0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+    0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+    0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+    0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+    0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+    0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+    0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+    0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+    0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+    0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+    0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+    0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+    0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+    0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+    0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+    0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+    0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+    0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+    0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+    0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+    0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+    0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+    0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+    0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+    0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+    0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+    0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+    0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+    0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+    0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+    0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+    0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+    0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+    0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+    0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+    0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+    0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+    0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+    0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+    0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+    0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+    0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+    0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+    0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+    0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+    0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+    0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+    0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+    0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+    0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+    0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+    0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+    0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+    0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+    0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+    0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+    0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+    0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+
+static const uint32_t Td1[256] = {
+    0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+    0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+    0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+    0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+    0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+    0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+    0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+    0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+    0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+    0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+    0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+    0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+    0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+    0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+    0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+    0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+    0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+    0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+    0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+    0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+    0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+    0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+    0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+    0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+    0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+    0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+    0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+    0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+    0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+    0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+    0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+    0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+    0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+    0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+    0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+    0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+    0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+    0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+    0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+    0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+    0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+    0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+    0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+    0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+    0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+    0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+    0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+    0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+    0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+    0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+    0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+    0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+    0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+    0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+    0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+    0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+    0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+    0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+    0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+    0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+    0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+    0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+    0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+    0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+
+static const uint32_t Td2[256] = {
+    0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+    0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+    0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+    0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+    0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+    0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+    0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+    0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+    0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+    0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+    0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+    0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+    0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+    0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+    0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+    0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+    0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+    0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+    0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+    0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+    0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+    0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+    0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+    0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+    0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+    0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+    0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+    0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+    0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+    0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+    0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+    0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+    0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+    0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+    0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+    0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+    0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+    0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+    0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+    0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+    0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+    0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+    0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+    0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+    0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+    0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+    0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+    0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+    0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+    0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+    0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+    0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+    0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+    0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+    0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+    0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+    0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+    0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+    0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+    0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+    0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+    0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+    0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+    0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+
+static const uint32_t Td3[256] = {
+    0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+    0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+    0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+    0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+    0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+    0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+    0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+    0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+    0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+    0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+    0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+    0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+    0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+    0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+    0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+    0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+    0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+    0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+    0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+    0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+    0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+    0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+    0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+    0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+    0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+    0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+    0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+    0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+    0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+    0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+    0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+    0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+    0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+    0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+    0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+    0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+    0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+    0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+    0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+    0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+    0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+    0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+    0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+    0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+    0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+    0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+    0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+    0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+    0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+    0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+    0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+    0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+    0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+    0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+    0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+    0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+    0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+    0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+    0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+    0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+    0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+    0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+    0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+    0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+
+static const uint32_t Td4[256] = {
+    0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U,
+    0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U,
+    0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU,
+    0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU,
+    0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U,
+    0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U,
+    0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U,
+    0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU,
+    0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U,
+    0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU,
+    0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU,
+    0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU,
+    0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U,
+    0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U,
+    0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U,
+    0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U,
+    0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U,
+    0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U,
+    0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU,
+    0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U,
+    0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U,
+    0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU,
+    0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U,
+    0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U,
+    0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U,
+    0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU,
+    0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U,
+    0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U,
+    0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU,
+    0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U,
+    0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U,
+    0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU,
+    0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U,
+    0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU,
+    0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU,
+    0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U,
+    0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U,
+    0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U,
+    0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U,
+    0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU,
+    0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U,
+    0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U,
+    0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU,
+    0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU,
+    0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU,
+    0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U,
+    0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU,
+    0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U,
+    0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U,
+    0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U,
+    0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U,
+    0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU,
+    0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U,
+    0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU,
+    0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU,
+    0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU,
+    0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU,
+    0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U,
+    0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU,
+    0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U,
+    0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU,
+    0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U,
+    0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U,
+    0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU,
+};
+
+static const uint32_t rcon[] = {
+    0x01000000, 0x02000000, 0x04000000, 0x08000000,
+    0x10000000, 0x20000000, 0x40000000, 0x80000000,
+    0x1B000000, 0x36000000,
+};
+
+static uint32_t
+get_u32(const uint8_t *p)
+{
+    uint32_t p0 = p[0];
+    uint32_t p1 = p[1];
+    uint32_t p2 = p[2];
+    uint32_t p3 = p[3];
+    return (p0 << 24) | (p1 << 16) | (p2 << 8) | p3;
+}
+
+static void
+put_u32(uint8_t *p, uint32_t x)
+{
+    p[0] = x >> 24;
+    p[1] = x >> 16;
+    p[2] = x >> 8;
+    p[3] = x;
+}
+
+/* Expands 128-bit 'key' into the encryption key 'schedule'. */
+void
+aes128_schedule(struct aes128 *aes, const uint8_t key[16])
+{
+    uint32_t *rk = aes->rk;
+    int i;
+
+    rk[0] = get_u32(key);
+    rk[1] = get_u32(key + 4);
+    rk[2] = get_u32(key + 8);
+    rk[3] = get_u32(key + 12);
+    for (i = 0; i < 10; i++, rk += 4) {
+        uint32_t temp = rk[3];
+        rk[4] = (rk[0]
+                 ^ (Te4[(temp >> 16) & 0xff] & 0xff000000)
+                 ^ (Te4[(temp >>  8) & 0xff] & 0x00ff0000)
+                 ^ (Te4[(temp      ) & 0xff] & 0x0000ff00)
+                 ^ (Te4[(temp >> 24)       ] & 0x000000ff)
+                 ^ rcon[i]);
+        rk[5] = rk[1] ^ rk[4];
+        rk[6] = rk[2] ^ rk[5];
+        rk[7] = rk[3] ^ rk[6];
+    }
+    assert(rk == &aes->rk[40]);
+}
+
+void
+aes128_encrypt(const struct aes128 *aes, const void *input_, void *output_)
+{
+    const uint8_t *input = input_;
+    uint8_t *output = output_;
+    const uint32_t *rk = aes->rk;
+    uint32_t s0, s1, s2, s3;
+    uint32_t t0, t1, t2, t3;
+    int r;
+
+    /* Map byte array block to cipher state and add initial round key. */
+    s0 = get_u32(input     ) ^ rk[0];
+    s1 = get_u32(input +  4) ^ rk[1];
+    s2 = get_u32(input +  8) ^ rk[2];
+    s3 = get_u32(input + 12) ^ rk[3];
+
+    /* 10 full rounds. */
+    r = 10 / 2;
+    for (;;) {
+        t0 = (Te0[(s0 >> 24)       ]
+              ^ Te1[(s1 >> 16) & 0xff]
+              ^ Te2[(s2 >>  8) & 0xff]
+              ^ Te3[(s3      ) & 0xff]
+              ^ rk[4]);
+        t1 = (Te0[(s1 >> 24)       ]
+              ^ Te1[(s2 >> 16) & 0xff]
+              ^ Te2[(s3 >>  8) & 0xff]
+              ^ Te3[(s0      ) & 0xff]
+              ^ rk[5]);
+        t2 = (Te0[(s2 >> 24)       ]
+              ^ Te1[(s3 >> 16) & 0xff]
+              ^ Te2[(s0 >>  8) & 0xff]
+              ^ Te3[(s1      ) & 0xff]
+              ^ rk[6]);
+        t3 = (Te0[(s3 >> 24)       ]
+              ^ Te1[(s0 >> 16) & 0xff]
+              ^ Te2[(s1 >>  8) & 0xff]
+              ^ Te3[(s2      ) & 0xff]
+              ^ rk[7]);
+
+        rk += 8;
+        if (--r == 0) {
+            break;
+        }
+
+        s0 = (Te0[(t0 >> 24)       ]
+              ^ Te1[(t1 >> 16) & 0xff]
+              ^ Te2[(t2 >>  8) & 0xff]
+              ^ Te3[(t3      ) & 0xff]
+              ^ rk[0]);
+        s1 = (Te0[(t1 >> 24)       ]
+              ^ Te1[(t2 >> 16) & 0xff]
+              ^ Te2[(t3 >>  8) & 0xff]
+              ^ Te3[(t0      ) & 0xff]
+              ^ rk[1]);
+        s2 = (Te0[(t2 >> 24)       ]
+              ^ Te1[(t3 >> 16) & 0xff]
+              ^ Te2[(t0 >>  8) & 0xff]
+              ^ Te3[(t1      ) & 0xff]
+              ^ rk[2]);
+        s3 = (Te0[(t3 >> 24)       ]
+              ^ Te1[(t0 >> 16) & 0xff]
+              ^ Te2[(t1 >>  8) & 0xff]
+              ^ Te3[(t2      ) & 0xff]
+              ^ rk[3]);
+    }
+
+    /* Apply last round and map cipher state to byte array block. */
+    s0 = ((Te4[(t0 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t1 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t2 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t3      ) & 0xff] & 0x000000ff)
+          ^ rk[0]);
+    put_u32(output     , s0);
+    s1 = ((Te4[(t1 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t2 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t3 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t0      ) & 0xff] & 0x000000ff)
+          ^ rk[1]);
+    put_u32(output +  4, s1);
+    s2 = ((Te4[(t2 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t3 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t0 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t1      ) & 0xff] & 0x000000ff)
+          ^ rk[2]);
+    put_u32(output +  8, s2);
+    s3 = ((Te4[(t3 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t0 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t1 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t2      ) & 0xff] & 0x000000ff)
+          ^ rk[3]);
+    put_u32(output + 12, s3);
+}
diff --git a/lib/aes128.h b/lib/aes128.h
new file mode 100644 (file)
index 0000000..bbd7475
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#ifndef AES128_H
+#define AES128_H
+
+#include <stdint.h>
+
+struct aes128 {
+    uint32_t rk[128/8 + 28];
+};
+
+void aes128_schedule(struct aes128 *, const uint8_t key[16]);
+void aes128_encrypt(const struct aes128 *, const void *, void *);
+
+#endif  /* aes128.h */
index 2474dad..51d3c11 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -8,12 +8,14 @@
 noinst_LIBRARIES += lib/libopenvswitch.a
 
 lib_libopenvswitch_a_SOURCES = \
+       lib/aes128.c \
+       lib/aes128.h \
        lib/backtrace.c \
        lib/backtrace.h \
        lib/bitmap.c \
        lib/bitmap.h \
-       lib/cfg.c \
-       lib/cfg.h \
+       lib/byteq.c \
+       lib/byteq.h \
        lib/classifier.c \
        lib/classifier.h \
        lib/command-line.c \
@@ -32,26 +34,37 @@ lib_libopenvswitch_a_SOURCES = \
        lib/dhcp.h \
        lib/dhparams.h \
        lib/dirs.h \
+       lib/dpif-linux.c \
+       lib/dpif-netdev.c \
+       lib/dpif-provider.h \
+       lib/dpif.c \
+       lib/dpif.h \
        lib/dynamic-string.c \
        lib/dynamic-string.h \
        lib/fatal-signal.c \
        lib/fatal-signal.h \
-       lib/fault.c \
-       lib/fault.h \
        lib/flow.c \
        lib/flow.h \
        lib/hash.c \
        lib/hash.h \
        lib/hmap.c \
        lib/hmap.h \
+       lib/json.c \
+       lib/json.h \
+       lib/jsonrpc.c \
+       lib/jsonrpc.h \
        lib/leak-checker.c \
        lib/leak-checker.h \
        lib/learning-switch.c \
        lib/learning-switch.h \
        lib/list.c \
        lib/list.h \
+       lib/lockfile.c \
+       lib/lockfile.h \
        lib/mac-learning.c \
        lib/mac-learning.h \
+       lib/netdev-linux.c \
+       lib/netdev-provider.h \
        lib/netdev.c \
        lib/netdev.h \
        lib/odp-util.c \
@@ -60,6 +73,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/ofp-print.h \
        lib/ofpbuf.c \
        lib/ofpbuf.h \
+       lib/ovsdb-data.c \
+       lib/ovsdb-data.h \
+       lib/ovsdb-error.c \
+       lib/ovsdb-error.h \
+       lib/ovsdb-idl-provider.h \
+       lib/ovsdb-idl.c \
+       lib/ovsdb-idl.h \
+       lib/ovsdb-parser.c \
+       lib/ovsdb-parser.h \
+       lib/ovsdb-types.c \
+       lib/ovsdb-types.h \
        lib/packets.c \
        lib/packets.h \
        lib/pcap.c \
@@ -76,6 +100,10 @@ lib_libopenvswitch_a_SOURCES = \
        lib/random.h \
        lib/rconn.c \
        lib/rconn.h \
+       lib/reconnect.c \
+       lib/reconnect.h \
+       lib/rtnetlink.c \
+       lib/rtnetlink.h \
        lib/sat-math.h \
        lib/sha1.c \
        lib/sha1.h \
@@ -85,8 +113,19 @@ lib_libopenvswitch_a_SOURCES = \
        lib/signals.h \
        lib/socket-util.c \
        lib/socket-util.h \
+       lib/sort.c \
+       lib/sort.h \
        lib/stp.c \
        lib/stp.h \
+       lib/stream-fd.c \
+       lib/stream-fd.h \
+       lib/stream-provider.h \
+       lib/stream-ssl.h \
+       lib/stream-tcp.c \
+       lib/stream-unix.c \
+       lib/stream.c \
+       lib/stream.h \
+       lib/string.h \
        lib/svec.c \
        lib/svec.h \
        lib/tag.c \
@@ -94,17 +133,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/timeval.c \
        lib/timeval.h \
        lib/type-props.h \
+       lib/unicode.c \
+       lib/unicode.h \
        lib/unixctl.c \
        lib/unixctl.h \
        lib/util.c \
        lib/util.h \
+       lib/uuid.c \
+       lib/uuid.h \
        lib/valgrind.h \
        lib/vconn-provider.h \
-       lib/vconn-ssl.h \
        lib/vconn-stream.c \
-       lib/vconn-stream.h \
-       lib/vconn-tcp.c \
-       lib/vconn-unix.c \
        lib/vconn.c \
        lib/vconn.h \
        lib/vlog-modules.def \
@@ -116,18 +155,28 @@ nodist_lib_libopenvswitch_a_SOURCES = \
        lib/dirs.c
 CLEANFILES += $(nodist_lib_libopenvswitch_a_SOURCES)
 
+noinst_LIBRARIES += lib/libsflow.a
+lib_libsflow_a_SOURCES = \
+       lib/sflow_api.h \
+       lib/sflow.h \
+       lib/sflow_agent.c \
+       lib/sflow_sampler.c \
+       lib/sflow_poller.c \
+       lib/sflow_receiver.c
+lib_libsflow_a_CFLAGS = $(AM_CFLAGS)
+if HAVE_WNO_UNUSED
+lib_libsflow_a_CFLAGS += -Wno-unused
+endif
+
 if HAVE_NETLINK
 lib_libopenvswitch_a_SOURCES += \
-       lib/dpif.c \
-       lib/dpif.h \
        lib/netlink-protocol.h \
        lib/netlink.c \
        lib/netlink.h
 endif
 
 if HAVE_OPENSSL
-lib_libopenvswitch_a_SOURCES += \
-       lib/vconn-ssl.c 
+lib_libopenvswitch_a_SOURCES += lib/stream-ssl.c
 nodist_lib_libopenvswitch_a_SOURCES += lib/dhparams.c
 lib/dhparams.c: lib/dh1024.pem lib/dh2048.pem lib/dh4096.pem
        (echo '#include "lib/dhparams.h"' &&                            \
@@ -146,10 +195,20 @@ EXTRA_DIST += \
 
 EXTRA_DIST += \
        lib/common.man \
+       lib/common-syn.man \
        lib/daemon.man \
+       lib/daemon-syn.man \
        lib/dpif.man \
        lib/leak-checker.man \
+       lib/ssl-bootstrap.man \
+       lib/ssl-bootstrap-syn.man \
+       lib/ssl-peer-ca-cert.man \
+       lib/ssl.man \
+       lib/ssl-syn.man \
+       lib/vconn-active.man \
+       lib/vconn-passive.man \
        lib/vlog-unixctl.man \
+       lib/vlog-syn.man \
        lib/vlog.man
 
 
@@ -168,25 +227,27 @@ install-data-local:
 
 # All the source files that have coverage counters.
 COVERAGE_FILES = \
-       lib/cfg.c \
        lib/dpif.c \
        lib/flow.c \
+       lib/lockfile.c \
        lib/hmap.c \
        lib/mac-learning.c \
        lib/netdev.c \
+       lib/netdev-linux.c \
        lib/netlink.c \
        lib/odp-util.c \
        lib/poll-loop.c \
        lib/process.c \
        lib/rconn.c \
+       lib/rtnetlink.c \
+       lib/stream.c \
        lib/timeval.c \
        lib/unixctl.c \
        lib/util.c \
        lib/vconn.c \
-       secchan/ofproto.c \
-       secchan/pktbuf.c \
+       ofproto/ofproto.c \
+       ofproto/pktbuf.c \
        vswitchd/bridge.c \
-       vswitchd/mgmt.c \
        vswitchd/ovs-brcompatd.c
 lib/coverage-counters.c: $(COVERAGE_FILES) lib/coverage-scan.pl
        (cd $(srcdir) && $(PERL) lib/coverage-scan.pl $(COVERAGE_FILES)) > $@.tmp
index 2f47809..80cae54 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
 #define THIS_MODULE VLM_backtrace
 #include "vlog.h"
 
-static uintptr_t UNUSED
+static uintptr_t OVS_UNUSED
 get_max_stack(void)
 {
     static const char file_name[] = "/proc/self/maps";
index f021a7e..df3c4eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -53,3 +53,20 @@ bitmap_equal(const unsigned long *a, const unsigned long *b, size_t n)
     }
     return true;
 }
+
+/* Scans 'bitmap' from bit offset 'start' to 'end', excluding 'end' itself.
+ * Returns the bit offset of the lowest-numbered bit set to 1, or 'end' if
+ * all of the bits are set to 0. */
+size_t
+bitmap_scan(const unsigned long int *bitmap, size_t start, size_t end)
+{
+    /* XXX slow */
+    size_t i;
+
+    for (i = start; i < end; i++) {
+        if (bitmap_is_set(bitmap, i)) {
+            break;
+        }
+    }
+    return i;
+}
index 9c420eb..fd05d3d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,10 +35,22 @@ bitmap_bit__(size_t offset)
     return 1UL << (offset % BITMAP_ULONG_BITS);
 }
 
+static inline size_t
+bitmap_n_longs(size_t n_bits)
+{
+    return DIV_ROUND_UP(n_bits, BITMAP_ULONG_BITS);
+}
+
+static inline size_t
+bitmap_n_bytes(size_t n_bits)
+{
+    return bitmap_n_longs(n_bits) * sizeof(unsigned long int);
+}
+
 static inline unsigned long *
 bitmap_allocate(size_t n_bits)
 {
-    return xcalloc(1, ROUND_UP(n_bits, BITMAP_ULONG_BITS));
+    return xzalloc(bitmap_n_bytes(n_bits));
 }
 
 static inline void
@@ -78,5 +90,10 @@ bitmap_set(unsigned long *bitmap, size_t offset, bool value)
 void bitmap_set_multiple(unsigned long *, size_t start, size_t count,
                          bool value);
 bool bitmap_equal(const unsigned long *, const unsigned long *, size_t n);
+size_t bitmap_scan(const unsigned long int *, size_t start, size_t end);
+
+#define BITMAP_FOR_EACH_1(IDX, SIZE, BITMAP) \
+    for ((IDX) = bitmap_scan(BITMAP, 0, SIZE); (IDX) < (SIZE); \
+         (IDX) = bitmap_scan(BITMAP, (IDX) + 1, SIZE))
 
 #endif /* bitmap.h */
similarity index 81%
rename from extras/ezio/byteq.c
rename to lib/byteq.c
index b7df400..aa1e06f 100644 (file)
@@ -14,7 +14,7 @@
  */
 
 #include <config.h>
-#include "extras/ezio/byteq.h"
+#include "byteq.h"
 #include <assert.h>
 #include <errno.h>
 #include <string.h>
 /* The queue size must be a power of 2. */
 BUILD_ASSERT_DECL(!(BYTEQ_SIZE & (BYTEQ_SIZE - 1)));
 
-static uint8_t *head(struct byteq *);
-static int headroom(const struct byteq *);
-static void advance_head(struct byteq *, unsigned int n);
-static int tailroom(const struct byteq *);
-static const uint8_t *tail(const struct byteq *);
-static void advance_tail(struct byteq *, unsigned int n);
-
 /* Initializes 'q' as empty. */
 void
 byteq_init(struct byteq *q)
@@ -73,7 +66,7 @@ void
 byteq_put(struct byteq *q, uint8_t c)
 {
     assert(!byteq_is_full(q));
-    *head(q) = c;
+    *byteq_head(q) = c;
     q->head++;
 }
 
@@ -85,9 +78,9 @@ byteq_putn(struct byteq *q, const void *p_, size_t n)
     const uint8_t *p = p_;
     assert(byteq_avail(q) >= n);
     while (n > 0) {
-        size_t chunk = MIN(n, headroom(q));
-        memcpy(head(q), p, chunk);
-        advance_head(q, chunk);
+        size_t chunk = MIN(n, byteq_headroom(q));
+        memcpy(byteq_head(q), p, chunk);
+        byteq_advance_head(q, chunk);
         p += chunk;
         n -= chunk;
     }
@@ -108,7 +101,7 @@ byteq_get(struct byteq *q)
 {
     uint8_t c;
     assert(!byteq_is_empty(q));
-    c = *tail(q);
+    c = *byteq_tail(q);
     q->tail++;
     return c;
 }
@@ -120,9 +113,9 @@ int
 byteq_write(struct byteq *q, int fd)
 {
     while (!byteq_is_empty(q)) {
-        ssize_t n = write(fd, tail(q), tailroom(q));
+        ssize_t n = write(fd, byteq_tail(q), byteq_tailroom(q));
         if (n > 0) {
-            advance_tail(q, n);
+            byteq_advance_tail(q, n);
         } else {
             assert(n < 0);
             return errno;
@@ -139,20 +132,20 @@ int
 byteq_read(struct byteq *q, int fd)
 {
     while (!byteq_is_full(q)) {
-        ssize_t n = read(fd, head(q), headroom(q));
+        ssize_t n = read(fd, byteq_head(q), byteq_headroom(q));
         if (n > 0) {
-            advance_head(q, n);
+            byteq_advance_head(q, n);
         } else {
             return !n ? EOF : errno;
         }
     }
     return 0;
 }
-\f
+
 /* Returns the number of contiguous bytes of in-use space starting at the tail
  * of 'q'. */
-static int
-tailroom(const struct byteq *q)
+int
+byteq_tailroom(const struct byteq *q)
 {
     int used = byteq_used(q);
     int tail_to_end = BYTEQ_SIZE - (q->tail & (BYTEQ_SIZE - 1));
@@ -161,33 +154,33 @@ tailroom(const struct byteq *q)
 
 /* Returns the first in-use byte of 'q', the point at which data is removed
  * from 'q'. */
-static const uint8_t *
-tail(const struct byteq *q)
+const uint8_t *
+byteq_tail(const struct byteq *q)
 {
     return &q->buffer[q->tail & (BYTEQ_SIZE - 1)];
 }
 
 /* Removes 'n' bytes from the tail of 'q', which must have at least 'n' bytes
  * of tailroom. */
-static void
-advance_tail(struct byteq *q, unsigned int n)
+void
+byteq_advance_tail(struct byteq *q, unsigned int n)
 {
-    assert(tailroom(q) >= n);
+    assert(byteq_tailroom(q) >= n);
     q->tail += n;
 }
 
 /* Returns the byte after the last in-use byte of 'q', the point at which new
  * data will be added to 'q'. */
-static uint8_t *
-head(struct byteq *q)
+uint8_t *
+byteq_head(struct byteq *q)
 {
     return &q->buffer[q->head & (BYTEQ_SIZE - 1)];
 }
 
 /* Returns the number of contiguous bytes of free space starting at the head
  * of 'q'. */
-static int
-headroom(const struct byteq *q)
+int
+byteq_headroom(const struct byteq *q)
 {
     int avail = byteq_avail(q);
     int head_to_end = BYTEQ_SIZE - (q->head & (BYTEQ_SIZE - 1));
@@ -196,9 +189,9 @@ headroom(const struct byteq *q)
 
 /* Adds to 'q' the 'n' bytes after the last currently in-use byte of 'q'.  'q'
  * must have at least 'n' bytes of headroom. */
-static void
-advance_head(struct byteq *q, unsigned int n)
+void
+byteq_advance_head(struct byteq *q, unsigned int n)
 {
-    assert(headroom(q) >= n);
+    assert(byteq_headroom(q) >= n);
     q->head += n;
 }
similarity index 83%
rename from extras/ezio/byteq.h
rename to lib/byteq.h
index 0a016cd..84d8696 100644 (file)
@@ -42,4 +42,11 @@ uint8_t byteq_get(struct byteq *);
 int byteq_write(struct byteq *, int fd);
 int byteq_read(struct byteq *, int fd);
 
+uint8_t *byteq_head(struct byteq *);
+int byteq_headroom(const struct byteq *);
+void byteq_advance_head(struct byteq *, unsigned int n);
+int byteq_tailroom(const struct byteq *);
+const uint8_t *byteq_tail(const struct byteq *);
+void byteq_advance_tail(struct byteq *, unsigned int n);
+
 #endif /* byteq.h */
diff --git a/lib/cfg.c b/lib/cfg.c
deleted file mode 100644 (file)
index d0170b6..0000000
--- a/lib/cfg.c
+++ /dev/null
@@ -1,1193 +0,0 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "cfg.h"
-#include <arpa/inet.h>
-#include <assert.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
-#include <inttypes.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "coverage.h"
-#include "dynamic-string.h"
-#include "ofpbuf.h"
-#include "packets.h"
-#include "svec.h"
-#include "timeval.h"
-#include "util.h"
-
-#define THIS_MODULE VLM_cfg
-#include "vlog.h"
-
-/* XXX This file really needs a unit test!  For a while, cfg_get_string(0,
- * "bridge.a.controller") would return the value of
- * "bridge.a.controller.in-band", if it existed, and I'm really not certain
- * that the fix didn't break other things. */
-
-/* Configuration file name. */
-static char *cfg_name;
-
-/* Put the temporary file in the same directory as cfg_name, so that
- * they are guaranteed to be in the same file system and therefore we can
- * rename() tmp_name over cfg_name. */
-static char *tmp_name;
-
-/* Lock information. */
-static char *lock_name;
-static int lock_fd = -1;
-
-/* Flag to indicate whether local modifications have been made. */
-static bool dirty;
-
-static uint8_t cfg_cookie[CFG_COOKIE_LEN];
-
-/* Current configuration.  Maintained in sorted order. */
-static struct svec cfg = SVEC_EMPTY_INITIALIZER;
-
-static bool has_double_dot(const char *key, size_t len);
-static bool is_valid_key(const char *key, size_t len,
-                         const char *file_name, int line_number,
-                         const char *id);
-static char *parse_section(const char *file_name, int line_number,
-                           const char *);
-static void parse_setting(const char *file_name, int line_number,
-                          const char *section, const char *);
-static int compare_key(const char *a, const char *b);
-static char **find_key_le(const char *key);
-static char **find_key_ge(const char *key);
-static char *find_key(const char *);
-static bool parse_mac(const char *, uint8_t mac[6]);
-static bool parse_dpid(const char *, uint64_t *);
-static bool is_key(const char *);
-static bool is_int(const char *);
-static bool is_bool(const char *);
-static const char *extract_value(const char *key);
-static const char *get_nth_value(int idx, const char *key);
-static bool is_type(const char *s, enum cfg_flags);
-
-#define CC_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-#define CC_DIGIT "0123456789"
-#define CC_ALNUM CC_ALPHA CC_DIGIT
-#define CC_SPACE " \t\r\n\v"
-
-#define CC_FILE_NAME CC_ALNUM "._-"
-#define CC_KEY CC_ALNUM "._-@$:+"
-
-void
-cfg_init(void)
-{
-    svec_terminate(&cfg);
-}
-
-/* Sets 'file_name' as the configuration file read by cfg_read().  Returns 0 on
- * success, otherwise a positive errno value if 'file_name' cannot be opened.
- *
- * This function does not actually read the named file or directory.  Use
- * cfg_read() to (re)read all the configuration files. */
-int
-cfg_set_file(const char *file_name)
-{
-    const char *slash;
-    int fd;
-
-    if (cfg_name) {
-        assert(lock_fd < 0);
-        free(cfg_name);
-        free(lock_name);
-        free(tmp_name);
-        cfg_name = lock_name = tmp_name = NULL;
-    }
-
-    /* Make sure that we can open this file for reading. */
-    fd = open(file_name, O_RDONLY);
-    if (fd < 0) {
-        return errno;
-    }
-    close(fd);
-
-    cfg_name = xstrdup(file_name);
-
-    /* Put the temporary file in the same directory as cfg_name, so that they
-     * are guaranteed to be in the same file system, to guarantee that
-     * rename(tmp_name, cfg_name) will work. */
-    tmp_name = xasprintf("%s.~tmp~", file_name);
-
-    /* Put the lock file in the same directory as cfg_name, but prefixed by
-     * a dot so as not to garner administrator interest. */
-    slash = strrchr(file_name, '/');
-    if (slash) {
-        lock_name = xasprintf("%.*s/.%s.~lock~",
-                              (int) (slash - file_name), file_name, slash + 1);
-    } else {
-        lock_name = xasprintf(".%s.~lock~", file_name);
-    }
-
-    VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file",
-              file_name, lock_name);
-    return 0;
-}
-
-static int
-update_cookie(void)
-{
-    struct sha1_ctx context;
-    int i;
-
-    sha1_init(&context);
-    for (i = 0; i < cfg.n; i++) {
-        sha1_update(&context, cfg.names[i], strlen(cfg.names[i]));
-        sha1_update(&context, "\n", 1);
-    }
-    sha1_final(&context, cfg_cookie);
-
-    return 0;
-}
-
-/* Reads all of the configuration files or directories that have been added
- * with cfg_add_file(), merges their content.  Any previous configuration is
- * replaced.  Returns 0 if successful, otherwise a positive errno value. */
-int
-cfg_read(void)
-{
-    struct svec old_cfg;
-    struct ds ds;
-    FILE *file;
-    char *section;
-    int line_number;
-
-
-    if (!cfg_name) {
-        return ENODEV;
-    }
-
-    /* Save old configuration data and clear the active configuration. */
-    svec_init(&old_cfg);
-    svec_swap(&old_cfg, &cfg);
-
-    /* Read new configuration. */
-    VLOG_DBG("reading configuration from %s", cfg_name);
-
-    file = fopen(cfg_name, "r");
-    if (!file) {
-        VLOG_ERR("failed to open \"%s\": %s", cfg_name, strerror(errno));
-        svec_terminate(&cfg);
-        return errno;
-    }
-
-    ds_init(&ds);
-    section = NULL;
-    line_number = 0;
-    while (!ds_get_line(&ds, file)) {
-        const char *s = ds_cstr(&ds);
-        size_t indent = strspn(s, CC_SPACE);
-
-        line_number++;
-        s += indent;
-        if (*s == '#' || *s == '\0') {
-            /* Ignore comments and lines that contain only white space. */
-        } else if (*s == '[') {
-            if (!indent) {
-                free(section);
-                section = parse_section(cfg_name, line_number, s);
-            } else {
-                VLOG_ERR("%s:%d: ignoring indented section header",
-                         cfg_name, line_number);
-            }
-        } else if (indent && !section) {
-            VLOG_ERR("%s:%d: ignoring indented line outside any section",
-                     cfg_name, line_number);
-        } else {
-            if (!indent) {
-                free(section);
-                section = NULL;
-            }
-            parse_setting(cfg_name, line_number, section, s);
-        }
-    }
-    ds_destroy(&ds);
-    free(section);
-
-    svec_sort(&cfg);
-    svec_terminate(&cfg);
-    update_cookie();
-
-    fclose(file);
-
-    if (VLOG_IS_DBG_ENABLED()) {
-        struct svec removed, added;
-        size_t i;
-
-        svec_diff(&old_cfg, &cfg, &removed, NULL, &added);
-        if (removed.n || added.n) {
-            VLOG_DBG("configuration changes:");
-            for (i = 0; i < removed.n; i++) {
-                VLOG_DBG("-%s", removed.names[i]);
-            }
-            for (i = 0; i < added.n; i++) {
-                VLOG_DBG("+%s", added.names[i]);
-            }
-        } else {
-            VLOG_DBG("configuration unchanged");
-        }
-        svec_destroy(&added);
-        svec_destroy(&removed);
-    }
-    svec_destroy(&old_cfg);
-
-    dirty = false;
-
-    return 0;
-}
-
-/* Fills 'svec' with the entire configuration file. */
-void
-cfg_get_all(struct svec *svec)
-{
-    svec_clear(svec);
-    svec_append(svec, &cfg);
-}
-
-int
-cfg_get_cookie(uint8_t *cookie)
-{
-    if (dirty) {
-        update_cookie();
-    }
-
-    memcpy(cookie, cfg_cookie, sizeof(cfg_cookie));
-    return 0;
-}
-
-void
-cfg_unlock(void)
-{
-    if (lock_fd != -1) {
-        COVERAGE_INC(cfg_unlock);
-        close(lock_fd);
-        lock_fd = -1;
-    }
-}
-
-static int
-open_lockfile(const char *name)
-{
-    for (;;) {
-        /* Try to open an existing lock file. */
-        int fd = open(name, O_RDWR);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != ENOENT) {
-            VLOG_WARN("%s: failed to open lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Try to create a new lock file. */
-        VLOG_INFO("%s: lock file does not exist, creating", name);
-        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != EEXIST) {
-            VLOG_WARN("%s: failed to create lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Someone else created the lock file.  Try again. */
-    }
-}
-
-static int
-try_lock(int fd, bool block)
-{
-    struct flock l;
-    memset(&l, 0, sizeof l);
-    l.l_type = F_WRLCK;
-    l.l_whence = SEEK_SET;
-    l.l_start = 0;
-    l.l_len = 0;
-    return fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
-}
-
-/* Locks the configuration file against modification by other processes and
- * re-reads it from disk.
- *
- * The 'timeout' specifies the maximum number of milliseconds to wait for the
- * config file to become free.  Use 0 to avoid waiting or INT_MAX to wait
- * forever.
- *
- * Returns 0 on success, otherwise a positive errno value. */
-int
-cfg_lock(uint8_t *cookie, int timeout)
-{
-    long long int start;
-    long long int elapsed = 0;
-    int fd;
-    uint8_t curr_cookie[CFG_COOKIE_LEN];
-
-    assert(lock_fd < 0);
-    COVERAGE_INC(cfg_lock);
-
-    time_refresh();
-    start = time_msec();
-    for (;;) {
-        int error;
-
-        /* Open lock file. */
-        fd = open_lockfile(lock_name);
-        if (fd < 0) {
-            return -fd;
-        }
-
-        /* Try to lock it.  This will block (if 'timeout' > 0). */
-        error = try_lock(fd, timeout > 0);
-        time_refresh();
-        elapsed = time_msec() - start;
-        if (!error) {
-            /* Success! */
-            break;
-        }
-
-        /* Lock failed.  Close the lock file and reopen it on the next
-         * iteration, just in case someone deletes it underneath us (even
-         * though that should not happen). */
-        close(fd);
-        if (error != EINTR) {
-            /* Hard error, give up. */
-            COVERAGE_INC(cfg_lock_error);
-            VLOG_WARN("%s: failed to lock file "
-                      "(after %lld ms, with %d-ms timeout): %s",
-                      lock_name, elapsed, timeout, strerror(error));
-            return error;
-        }
-
-        /* Probably, the periodic timer set up by time_init() woke up us.  Just
-         * check whether it's time to give up. */
-        if (timeout != INT_MAX && elapsed >= timeout) {
-            COVERAGE_INC(cfg_lock_timeout);
-            VLOG_WARN("%s: giving up on lock file after %lld ms",
-                      lock_name, elapsed);
-            return ETIMEDOUT;
-        }
-        COVERAGE_INC(cfg_lock_retry);
-    }
-    if (elapsed) {
-        VLOG_WARN("%s: waited %lld ms for lock file", lock_name, elapsed);
-    }
-    lock_fd = fd;
-
-    cfg_read();
-
-    if (cookie) {
-        cfg_get_cookie(curr_cookie);
-
-        if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) {
-            /* Configuration has changed, so reject. */
-            cfg_unlock();
-            return EINVAL;
-        }
-    }
-
-    return 0;
-}
-
-static int
-do_write_config(const void *data, size_t len)
-{
-    FILE *file;
-    int error;
-
-    file = fopen(tmp_name, "w");
-    if (file == NULL) {
-        VLOG_WARN("could not open %s for writing: %s",
-                  tmp_name, strerror(errno));
-        return errno;
-    }
-
-    fwrite(data, 1, len, file);
-
-    /* This is essentially equivalent to:
-     *       error = ferror(file) || fflush(file) || fclose(file);
-     * but it doesn't short-circuit, so that it always closes 'file'. */
-    error = ferror(file);
-    error = fflush(file) || error;
-    error = fclose(file) || error;
-    if (error) {
-        VLOG_WARN("problem writing to %s: %s", tmp_name, strerror(errno));
-        return errno;
-    }
-
-    if (rename(tmp_name, cfg_name) < 0) {
-        VLOG_WARN("could not rename %s to %s: %s",
-                  tmp_name, cfg_name, strerror(errno));
-        return errno;
-    }
-
-    dirty = false;
-
-    return 0;
-}
-
-/* Write the current configuration into the configuration file.  Returns 0 if
- * successful, otherwise a negative errno value. */
-int
-cfg_write(void)
-{
-    char *content;
-    int retval;
-
-    svec_sort(&cfg);
-    content = (cfg.n
-               ? svec_join(&cfg, "\n", "\n")
-               : xstrdup("# This file intentionally left blank.\n"));
-    retval = do_write_config(content, strlen(content));
-    free(content);
-
-    return retval;
-}
-
-int
-cfg_write_data(uint8_t *data, size_t len)
-{
-    int retval = do_write_config(data, len);
-    if (!retval) {
-        cfg_read();
-    }
-    return retval;
-}
-
-/* Returns true if the configuration has changed since the last time it was
- * read or written. */
-bool
-cfg_is_dirty(void)
-{
-    return dirty;
-}
-
-void
-cfg_buf_put(struct ofpbuf *buffer)
-{
-    int i;
-
-    for (i = 0; i < cfg.n; i++) {
-        ofpbuf_put(buffer, cfg.names[i], strlen(cfg.names[i]));
-        ofpbuf_put(buffer, "\n", 1);
-    }
-}
-
-/* Formats the printf()-style format string in the parameter 'format', which
- * must be the function's last parameter, into string variable 'dst'.  The
- * function is responsible for freeing 'dst'. */
-#define FORMAT_KEY(FORMAT, DST)                 \
-    do {                                        \
-      va_list args__;                           \
-      va_start(args__, FORMAT);                 \
-      (DST) = xvasprintf(FORMAT, args__);       \
-      va_end(args__);                           \
-    } while (0)
-
-/* Returns true if the configuration includes a key named 'key'. */
-bool
-cfg_has(const char *key_, ...)
-{
-    char *key;
-    bool retval;
-
-    FORMAT_KEY(key_, key);
-    retval = find_key(key) != NULL;
-    free(key);
-    return retval;
-}
-
-bool
-cfg_is_valid(enum cfg_flags flags, const char *key_, ...)
-{
-    char *key, **first, **last, **p;
-    size_t n;
-    bool retval;
-
-    FORMAT_KEY(key_, key);
-    first = find_key_le(key);
-    last = find_key_ge(key);
-    n = last - first;
-    retval = ((!(flags & CFG_REQUIRED) || n)
-              && (!(flags & CFG_MULTIPLE) || n <= 1));
-    for (p = first; retval && p < last; p++) {
-        retval = is_type(strchr(*p, '=') + 1, flags);
-    }
-    free(key);
-    return retval;
-}
-
-/* Returns true if the configuration includes at least one key whose name
- * begins with 'section' followed by a dot. */
-bool
-cfg_has_section(const char *section_, ...)
-{
-    struct ds section;
-    bool retval = false;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            retval = true;
-            break;
-        }
-    }
-
-    ds_destroy(&section);
-    return retval;
-}
-
-/* Returns the number of values for the given 'key'.  The return value is 0 if
- * no values exist for 'key'. */
-int
-cfg_count(const char *key_, ...)
-{
-    char *key;
-    int retval;
-
-    FORMAT_KEY(key_, key);
-    retval = find_key_ge(key) - find_key_le(key);
-    free(key);
-    return retval;
-}
-
-/* Fills 'svec' with all of the immediate subsections of 'section'.  For
- * example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
- * and bridge.c.x.y.z exist, then 'svec' would be initialized to a, b, and
- * c.  The caller must first initialize 'svec'. */
-void
-cfg_get_subsections(struct svec *svec, const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    svec_clear(svec);
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            const char *ss = *p + section.length;
-            size_t ss_len = strcspn(ss, ".=");
-            svec_add_nocopy(svec, xmemdup0(ss, ss_len));
-        }
-    }
-    svec_unique(svec);
-    ds_destroy(&section);
-}
-
-void
-cfg_add_entry(const char *entry_, ...)
-{
-    char *entry;
-
-    FORMAT_KEY(entry_, entry);
-    svec_add_nocopy(&cfg, entry);
-    svec_sort(&cfg);
-    svec_terminate(&cfg);
-    dirty = true;
-}
-
-void
-cfg_del_entry(const char *entry_, ...)
-{
-    char *entry;
-
-    FORMAT_KEY(entry_, entry);
-    svec_del(&cfg, entry);
-    svec_terminate(&cfg);
-    free(entry);
-    dirty = true;
-}
-
-void
-cfg_del_section(const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) {
-        if (!strncmp(section.string, *p, section.length)) {
-            free(*p);
-            *p = NULL;
-        }
-    }
-    svec_compact(&cfg);
-    svec_terminate(&cfg);
-
-    ds_destroy(&section);
-    dirty = true;
-}
-
-void
-cfg_del_match(const char *pattern_, ...)
-{
-    bool matched = false;
-    char *pattern;
-    char **p;
-
-    FORMAT_KEY(pattern_, pattern);
-
-    for (p = cfg.names; *p; p++) {
-        if (!fnmatch(pattern, *p, 0)) {
-            free(*p);
-            *p = NULL;
-            matched = true;
-        }
-    }
-    if (matched) {
-        svec_compact(&cfg);
-        svec_terminate(&cfg);
-        dirty = true;
-    }
-
-    free(pattern);
-}
-
-/* Fills 'svec' with all of the key-value pairs that match shell glob pattern
- * 'pattern'.  The caller must first initialize 'svec'. */
-void
-cfg_get_matches(struct svec *svec, const char *pattern_, ...)
-{
-    char *pattern;
-    char **p;
-
-    FORMAT_KEY(pattern_, pattern);
-
-    for (p = cfg.names; *p; p++) {
-        if (!fnmatch(pattern, *p, 0)) {
-            svec_add(svec, *p);
-        }
-    }
-
-    free(pattern);
-}
-
-/* Fills 'svec' with all of the key-value pairs that have sections that
- * begin with 'section'.  The caller must first initialize 'svec'. */
-void
-cfg_get_section(struct svec *svec, const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            svec_add(svec, *p);
-        }
-    }
-    ds_destroy(&section);
-}
-
-/* Returns the value numbered 'idx' of 'key'.  Returns a null pointer if 'idx'
- * is greater than or equal to cfg_count(key).  The caller must not modify or
- * free the returned string or retain its value beyond the next call to
- * cfg_read(). */
-const char *
-cfg_get_string(int idx, const char *key_, ...)
-{
-    const char *retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    retval = get_nth_value(idx, key);
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key'.  Returns a null pointer if 'idx'
- * is greater than or equal to cfg_count(key) or if the value 'idx' of 'key' is
- * not a valid key.  The caller must not modify or free the returned string or
- * retain its value beyond the next call to cfg_read(). */
-const char *
-cfg_get_key(int idx, const char *key_, ...)
-{
-    const char *value, *retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_key(value) ? value : NULL;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer.  Returns
- * 0 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer.  */
-int
-cfg_get_int(int idx, const char *key_, ...)
-{
-    const char *value;
-    int retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_int(value) ? atoi(value) : 0;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to a boolean value.
- * Returns false if 'idx' is greater than or equal to cfg_count(key) or if the
- * value 'idx' of 'key' is not a valid boolean.  */
-bool
-cfg_get_bool(int idx, const char *key_, ...)
-{
-    const char *value;
-    bool retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_bool(value) ? !strcmp(value, "true") : false;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an IP address in
- * network byte order.  Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid IP address (as
- * determined by inet_aton()).  */
-uint32_t
-cfg_get_ip(int idx, const char *key_, ...)
-{
-    struct in_addr addr;
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !inet_aton(value, &addr)) {
-        addr.s_addr = htonl(0);
-    }
-    free(key);
-    return addr.s_addr;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an MAC address in
- * host byte order.  Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid MAC address in
- * the format "##:##:##:##:##:##".  */
-uint64_t
-cfg_get_mac(int idx, const char *key_, ...)
-{
-    uint8_t mac[ETH_ADDR_LEN];
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !parse_mac(value, mac)) {
-        memset(mac, 0, sizeof mac);
-    }
-    free(key);
-    return eth_addr_to_uint64(mac);
-}
-
-/* Returns the value numbered 'idx' of 'key', parsed as an datapath ID.
- * Returns 0 if 'idx' is greater than or equal to cfg_count(key) or if the
- * value 'idx' of 'key' is not a valid datapath ID consisting of exactly 12
- * hexadecimal digits.  */
-uint64_t
-cfg_get_dpid(int idx, const char *key_, ...)
-{
-    uint64_t dpid;
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !parse_dpid(value, &dpid)) {
-        dpid = 0;
-    }
-    free(key);
-    return dpid;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer.  Returns
- * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer between 0 and 4095.  */
-int
-cfg_get_vlan(int idx, const char *key_, ...)
-{
-    const char *value;
-    int retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (value && is_int(value)) {
-        retval = atoi(value);
-        if (retval < 0 || retval > 4095) {
-            retval = -1;
-        }
-    } else {
-        retval = -1;
-    }
-    free(key);
-    return retval;
-}
-
-/* Fills 'svec' with all of the string values of 'key'.  The caller must
- * first initialize 'svec'. */
-void
-cfg_get_all_strings(struct svec *svec, const char *key_, ...)
-{
-    char **p, **q;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    svec_clear(svec);
-    for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
-        svec_add(svec, extract_value(*p));
-    }
-    free(key);
-}
-
-/* Fills 'svec' with all of the values of 'key' that are valid keys.
- * Values of 'key' that are not valid keys are omitted.   The caller 
- * must first initialize 'svec'. */
-void
-cfg_get_all_keys(struct svec *svec, const char *key_, ...)
-{
-    char **p, **q;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    svec_clear(svec);
-    for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
-        const char *value = extract_value(*p);
-        if (is_key(value)) {
-            svec_add(svec, value);
-        }
-    }
-    free(key);
-}
-\f
-static bool
-has_double_dot(const char *key, size_t len)
-{
-    if (len >= 2) {
-        size_t i;
-
-        for (i = 0; i < len - 1; i++) {
-            if (key[i] == '.' && key[i + 1] == '.') {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-static bool
-is_valid_key(const char *key, size_t len,
-                 const char *file_name, int line_number, const char *id)
-{
-    if (!len) {
-        VLOG_ERR("%s:%d: missing %s name", file_name, line_number, id);
-        return false;
-    } else if (key[0] == '.') {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" begins with invalid character '.'",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else if (key[len - 1] == '.') {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" ends with invalid character '.'",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else if (has_double_dot(key, len)) {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" contains '..', which is not allowed",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else {
-        return true;
-    }
-}
-
-static char *
-parse_section(const char *file_name, int line_number, const char *s)
-{
-    struct ds section;
-    size_t len;
-
-    ds_init(&section);
-
-    /* Skip [ and any white space. */
-    s++;
-    s += strspn(s, CC_SPACE);
-
-    /* Obtain the section name. */
-    len = strspn(s, CC_KEY);
-    if (!is_valid_key(s, len, file_name, line_number, "section")) {
-        goto error;
-    }
-    ds_put_buffer(&section, s, len);
-    s += len;
-
-    /* Obtain the subsection name, if any. */
-    s += strspn(s, CC_SPACE);
-    if (*s == '"') {
-        s++;
-        len = strspn(s, CC_KEY);
-        if (!is_valid_key(s, len, file_name, line_number, "subsection")) {
-            goto error;
-        }
-        ds_put_char(&section, '.');
-        ds_put_buffer(&section, s, len);
-        s += len;
-        if (*s != '"') {
-            VLOG_ERR("%s:%d: missing '\"' following subsection name",
-                     file_name, line_number);
-            goto error;
-        }
-        s++;
-        s += strspn(s, CC_SPACE);
-    }
-
-    /* Check for ]. */
-    if (*s != ']') {
-        VLOG_ERR("%s:%d: missing ']' following section name",
-                 file_name, line_number);
-        goto error;
-    }
-    s++;
-    s += strspn(s, CC_SPACE);
-    if (*s != '\0') {
-        VLOG_ERR("%s:%d: trailing garbage following ']'",
-                 file_name, line_number);
-        goto error;
-    }
-
-    return ds_cstr(&section);
-
-error:
-    ds_destroy(&section);
-    return NULL;
-}
-
-static void
-parse_setting(const char *file_name, int line_number, const char *section,
-              const char *s)
-{
-    struct ds key = DS_EMPTY_INITIALIZER;
-    struct ds value = DS_EMPTY_INITIALIZER;
-    size_t len;
-
-    if (section) {
-        ds_put_format(&key, "%s.", section);
-    }
-
-    /* Obtain the key. */
-    len = strspn(s, CC_KEY);
-    if (!len) {
-        VLOG_ERR("%s:%d: missing key name", file_name, line_number);
-        goto done;
-    }
-    if (!is_valid_key(s, len, file_name, line_number, "key")) {
-        goto done;
-    }
-    ds_put_buffer(&key, s, len);
-    s += len;
-
-    /* Skip the '='. */
-    s += strspn(s, CC_SPACE);
-    if (*s != '=') {
-        VLOG_ERR("%s:%d: missing '=' following key", file_name, line_number);
-        goto done;
-    }
-    s++;
-    s += strspn(s, CC_SPACE);
-
-    /* Obtain the value. */
-    ds_put_cstr(&value, s);
-    while (value.length > 0 && strchr(CC_SPACE, ds_last(&value))) {
-        value.length--;
-    }
-
-    /* Add the setting. */
-    svec_add_nocopy(&cfg, xasprintf("%s=%s", ds_cstr(&key), ds_cstr(&value)));
-
-done:
-    ds_destroy(&key);
-    ds_destroy(&value);
-}
-
-static int
-compare_key(const char *a, const char *b)
-{
-    for (;;) {
-        int ac = *a == '\0' || *a == '=' ? INT_MAX : *a;
-        int bc = *b == '\0' || *b == '=' ? INT_MAX : *b;
-        if (ac != bc) {
-            return ac < bc ? -1 : 1;
-        } else if (ac == INT_MAX) {
-            return 0;
-        }
-        a++;
-        b++;
-    }
-}
-
-/* Returns the address of the greatest configuration string with a key less
- * than or equal to 'key'.  Returns the address of the null terminator if all
- * configuration strings are greater than 'key'. */
-static char **
-find_key_le(const char *key)
-{
-    int low = 0;
-    int len = cfg.n;
-    while (len > 0) {
-        int half = len >> 1;
-        int middle = low + half;
-        if (compare_key(cfg.names[middle], key) < 0) {
-            low = middle + 1;
-            len -= half + 1;
-        } else {
-            len = half;
-        }
-    }
-    return &cfg.names[low];
-}
-
-/* Returns the address of the least configuration string with a key greater
- * than or equal to 'key'.  Returns the address of the null terminator if all
- * configuration strings are less than 'key'. */
-static char **
-find_key_ge(const char *key)
-{
-    int low = 0;
-    int len = cfg.n;
-    while (len > 0) {
-        int half = len >> 1;
-        int middle = low + half;
-        if (compare_key(cfg.names[middle], key) > 0) {
-            len = half;
-        } else {
-            low = middle + 1;
-            len -= half + 1;
-        }
-    }
-    return &cfg.names[low];
-}
-
-static char *
-find_key(const char *key)
-{
-    char **p = find_key_le(key);
-    return p < &cfg.names[cfg.n] && !compare_key(*p, key) ? *p : NULL;
-}
-
-static bool
-parse_mac(const char *s, uint8_t mac[6])
-{
-    return (sscanf(s, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))
-            == ETH_ADDR_SCAN_COUNT);
-}
-
-static bool
-parse_dpid(const char *s, uint64_t *dpid)
-{
-    if (strlen(s) == 12 && strspn(s, "0123456789abcdefABCDEF") == 12) {
-        *dpid = strtoll(s, NULL, 16);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-static bool
-is_key(const char *s)
-{
-    /* XXX needs to check the same things as is_valid_key() too. */
-    return *s && s[strspn(s, CC_KEY)] == '\0';
-}
-
-static bool
-is_int(const char *s)
-{
-    return *s && s[strspn(s, CC_DIGIT)] == '\0';
-}
-
-static bool
-is_bool(const char *s)
-{
-    return !strcmp(s, "true") || !strcmp(s, "false");
-}
-
-static const char *
-extract_value(const char *key)
-{
-    const char *p = strchr(key, '=');
-    return p ? p + 1 : NULL;
-}
-
-static const char *
-get_nth_value(int idx, const char *key)
-{
-    char **p = find_key_le(key);
-    char **q = find_key_ge(key);
-    return idx < q - p ? extract_value(p[idx]) : NULL;
-}
-
-static bool
-is_type(const char *s, enum cfg_flags flags)
-{
-    uint8_t mac[ETH_ADDR_LEN];
-    struct in_addr addr;
-    uint64_t dpid;
-
-    return (flags & CFG_STRING
-            || (flags & CFG_KEY && is_key(s))
-            || (flags & CFG_INT && is_int(s))
-            || (flags & CFG_BOOL && is_bool(s))
-            || (flags & CFG_IP && inet_aton(s, &addr))
-            || (flags & CFG_MAC && parse_mac(s, mac))
-            || (flags & CFG_DPID && parse_dpid(s, &dpid)));
-}
diff --git a/lib/cfg.h b/lib/cfg.h
deleted file mode 100644 (file)
index e159244..0000000
--- a/lib/cfg.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef VSWITCHD_CFG_H
-#define VSWITCHD_CFG_H 1
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <unistd.h>
-#include "compiler.h"
-#include "sha1.h"
-
-struct svec;
-struct ofpbuf;
-
-void cfg_init(void);
-int cfg_set_file(const char *file_name);
-int cfg_read(void);
-int cfg_lock(uint8_t *cookie, int timeout);
-void cfg_unlock(void);
-int cfg_write(void);
-int cfg_write_data(uint8_t *data, size_t len);
-bool cfg_is_dirty(void);
-
-void cfg_get_all(struct svec *);
-
-#define CFG_COOKIE_LEN SHA1_DIGEST_SIZE
-int cfg_get_cookie(uint8_t *cookie);
-
-void cfg_buf_put(struct ofpbuf *buffer);
-void cfg_get_subsections(struct svec *, const char *, ...) PRINTF_FORMAT(2, 3);
-
-enum cfg_flags {
-    /* Types allowed. */
-    CFG_STRING = 1 << 0,        /* Arbitrary content. */
-    CFG_KEY = 1 << 0,           /* Valid key name. */
-    CFG_INT = 1 << 2,           /* Integer value. */
-    CFG_BOOL = 1 << 3,          /* Boolean. */
-    CFG_IP = 1 << 4,            /* IPv4 address. */
-    CFG_MAC = 1 << 5,           /* MAC address. */
-    CFG_VLAN = 1 << 6,          /* Integer in range 0...4095. */
-    CFG_DPID = 1 << 7,          /* 12 hexadecimal digits. */
-
-    /* Number allowed. */
-    CFG_REQUIRED = 1 << 8,      /* At least one value allowed. */
-    CFG_MULTIPLE = 1 << 9       /* More than one value allowed. */
-};
-void cfg_register(const char *key_spec, enum cfg_flags);
-
-void cfg_add_entry(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_entry(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_section(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_match(const char *pattern, ...) PRINTF_FORMAT(1, 2);
-void cfg_get_matches(struct svec *svec, const char *pattern, ...)
-    PRINTF_FORMAT(2, 3);
-void cfg_get_section(struct svec *svec, const char *key, ...) 
-    PRINTF_FORMAT(2, 3);
-
-bool cfg_has(const char *key, ...) PRINTF_FORMAT(1, 2);
-bool cfg_is_valid(enum cfg_flags, const char *key, ...) PRINTF_FORMAT(2, 3);
-bool cfg_has_section(const char *key, ...) PRINTF_FORMAT(1, 2);
-int cfg_count(const char *key, ...) PRINTF_FORMAT(1, 2);
-
-const char *cfg_get_string(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-const char *cfg_get_key(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_int(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-bool cfg_get_bool(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint32_t cfg_get_ip(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint64_t cfg_get_mac(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_vlan(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint64_t cfg_get_dpid(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-void cfg_get_all_strings(struct svec *, const char *key, ...)
-    PRINTF_FORMAT(2, 3);
-void cfg_get_all_keys(struct svec *, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-#endif /* vswitchd/cfg.h */
index 94c8380..ee78dad 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <netinet/in.h>
+#include "dynamic-string.h"
 #include "flow.h"
 #include "hash.h"
 
@@ -48,6 +49,8 @@ static struct cls_rule *search_exact_table(const struct classifier *,
                                            size_t hash, const flow_t *);
 static bool rules_match_1wild(const struct cls_rule *fixed,
                               const struct cls_rule *wild, int field_idx);
+static bool rules_match_2wild(const struct cls_rule *wild1,
+                              const struct cls_rule *wild2, int field_idx);
 
 /* Converts the flow in 'flow' into a cls_rule in 'rule', with the given
  * 'wildcards' and 'priority'.*/
@@ -55,7 +58,7 @@ void
 cls_rule_from_flow(struct cls_rule *rule, const flow_t *flow,
                    uint32_t wildcards, unsigned int priority)
 {
-    assert(flow->reserved == 0);
+    assert(!flow->reserved[0] && !flow->reserved[1] && !flow->reserved[2]);
     rule->flow = *flow;
     flow_wildcards_init(&rule->wc, wildcards);
     rule->priority = priority;
@@ -75,6 +78,18 @@ cls_rule_from_match(struct cls_rule *rule, const struct ofp_match *match,
     rule->table_idx = table_idx_from_wildcards(rule->wc.wildcards);
 }
 
+/* Converts 'rule' to a string and returns the string.  The caller must free
+ * the string (with free()). */
+char *
+cls_rule_to_string(const struct cls_rule *rule)
+{
+    struct ds s = DS_EMPTY_INITIALIZER;
+    ds_put_format(&s, "wildcards=%x priority=%u ",
+                  rule->wc.wildcards, rule->priority);
+    flow_format(&s, &rule->flow);
+    return ds_cstr(&s);
+}
+
 /* Prints cls_rule 'rule', for debugging.
  *
  * (The output could be improved and expanded, but this was good enough to
@@ -102,7 +117,8 @@ cls_rule_moved(struct classifier *cls, struct cls_rule *old,
         if (new->wc.wildcards) {
             list_moved(&new->node.list);
         } else {
-            hmap_moved(&cls->exact_table, &old->node.hmap, &new->node.hmap);
+            hmap_node_moved(&cls->exact_table,
+                            &old->node.hmap, &new->node.hmap);
         }
     }
 }
@@ -335,6 +351,43 @@ classifier_find_rule_exactly(const struct classifier *cls,
     return NULL;
 }
 
+/* Checks if the flow defined by 'target' with 'wildcards' at 'priority' 
+ * overlaps with any other rule at the same priority in the classifier.  
+ * Two rules are considered overlapping if a packet could match both. */
+bool
+classifier_rule_overlaps(const struct classifier *cls,
+                         const flow_t *target, uint32_t wildcards,
+                         unsigned int priority)
+{
+    struct cls_rule target_rule;
+    const struct hmap *tbl;
+
+    if (!wildcards) {
+        return search_exact_table(cls, flow_hash(target, 0), target) ?
+            true : false;
+    }
+
+    cls_rule_from_flow(&target_rule, target, wildcards, priority);
+
+    for (tbl = &cls->tables[0]; tbl < &cls->tables[CLS_N_FIELDS]; tbl++) {
+        struct cls_bucket *bucket;
+
+        HMAP_FOR_EACH (bucket, struct cls_bucket, hmap_node, tbl) {
+            struct cls_rule *rule;
+
+            LIST_FOR_EACH (rule, struct cls_rule, node.list,
+                           &bucket->rules) {
+                if (rule->priority == priority 
+                        && rules_match_2wild(rule, &target_rule, 0)) {
+                    return true;
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
 /* Ignores target->priority.
  *
  * 'callback' is allowed to delete the rule that is passed as its argument, but
@@ -587,14 +640,14 @@ bucket_insert(struct cls_bucket *bucket, struct cls_rule *rule)
 {
     struct cls_rule *pos;
     LIST_FOR_EACH (pos, struct cls_rule, node.list, &bucket->rules) {
-        if (pos->priority <= rule->priority) {
-            if (pos->priority == rule->priority
-                && pos->wc.wildcards == rule->wc.wildcards
+        if (pos->priority == rule->priority) {
+            if (pos->wc.wildcards == rule->wc.wildcards
                 && rules_match_1wild(pos, rule, rule->table_idx))
             {
                 list_replace(&rule->node.list, &pos->node.list);
                 return pos;
             }
+        } else if (pos->priority < rule->priority) {
             break;
         }
     }
@@ -763,6 +816,23 @@ rules_match_1wild(const struct cls_rule *fixed, const struct cls_rule *wild,
                        wild->wc.nw_dst_mask, field_idx);
 }
 
+/* Returns true if 'wild1' and 'wild2' match, that is, if their fields
+ * are equal modulo wildcards in 'wild1' or 'wild2'.
+ *
+ * 'field_idx' is the index of the first field to be compared; fields before
+ * 'field_idx' are assumed to match.  Always returns true if 'field_idx' is
+ * CLS_N_FIELDS. */
+static bool
+rules_match_2wild(const struct cls_rule *wild1, const struct cls_rule *wild2,
+                  int field_idx)
+{
+    return rules_match(wild1, wild2, 
+                       wild1->wc.wildcards | wild2->wc.wildcards, 
+                       wild1->wc.nw_src_mask & wild2->wc.nw_src_mask,
+                       wild1->wc.nw_dst_mask & wild2->wc.nw_dst_mask, 
+                       field_idx);
+}
+
 /* Searches 'bucket' for a rule that matches 'target'.  Returns the
  * highest-priority match, if one is found, or a null pointer if there is no
  * match.
index 8b095e9..126d149 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * fields after F tend to be wildcarded as well.  If this assumption is
  * violated, then the classifier will still classify flows correctly, but its
  * performance will suffer.
+ *
+ * The classifier uses a collection of CLS_N_FIELDS hash tables for wildcarded
+ * flows.  Each of these tables contains the flows that wildcard a given field
+ * and do not wildcard any of the fields that precede F in the ordering.  The
+ * key for each hash table is the value of the fields preceding F that are not
+ * wildcarded.  All the flows that fall within a table and have the same key
+ * are kept as a linked list ordered from highest to lowest priority.
+ *
+ * The classifier also maintains a separate hash table of exact-match flows.
+ *
+ * To search the classifier we first search the table of exact-match flows,
+ * since exact-match flows always have highest priority.  If there is a match,
+ * we're done.  Otherwise, we search each of the CLS_N_FIELDS hash tables in
+ * turn, looking for the highest-priority match, and return it (if any).
  */
 
 #include "flow.h"
     /*        -----------------  -----------  -------- */   \
     CLS_FIELD(OFPFW_IN_PORT,     in_port,     IN_PORT)      \
     CLS_FIELD(OFPFW_DL_VLAN,     dl_vlan,     DL_VLAN)      \
+    CLS_FIELD(OFPFW_DL_VLAN_PCP, dl_vlan_pcp, DL_VLAN_PCP)  \
     CLS_FIELD(OFPFW_DL_SRC,      dl_src,      DL_SRC)       \
     CLS_FIELD(OFPFW_DL_DST,      dl_dst,      DL_DST)       \
     CLS_FIELD(OFPFW_DL_TYPE,     dl_type,     DL_TYPE)      \
     CLS_FIELD(OFPFW_NW_SRC_MASK, nw_src,      NW_SRC)       \
     CLS_FIELD(OFPFW_NW_DST_MASK, nw_dst,      NW_DST)       \
     CLS_FIELD(OFPFW_NW_PROTO,    nw_proto,    NW_PROTO)     \
+    CLS_FIELD(OFPFW_NW_TOS,      nw_tos,      NW_TOS)       \
     CLS_FIELD(OFPFW_TP_SRC,      tp_src,      TP_SRC)       \
     CLS_FIELD(OFPFW_TP_DST,      tp_dst,      TP_DST)
 
@@ -109,6 +125,7 @@ void cls_rule_from_flow(struct cls_rule *, const flow_t *, uint32_t wildcards,
                         unsigned int priority);
 void cls_rule_from_match(struct cls_rule *, const struct ofp_match *,
                          unsigned int priority);
+char *cls_rule_to_string(const struct cls_rule *);
 void cls_rule_print(const struct cls_rule *);
 void cls_rule_moved(struct classifier *,
                     struct cls_rule *old, struct cls_rule *new);
@@ -128,6 +145,8 @@ struct cls_rule *classifier_lookup_wild(const struct classifier *,
                                         const flow_t *);
 struct cls_rule *classifier_lookup_exact(const struct classifier *,
                                          const flow_t *);
+bool classifier_rule_overlaps(const struct classifier *, const flow_t *, 
+                              uint32_t wildcards, unsigned int priority);
 
 typedef void cls_cb_func(struct cls_rule *, void *aux);
 
index 6b79c5e..17344c3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
 #include "command-line.h"
 #include <getopt.h>
 #include <limits.h>
+#include <stdlib.h>
 #include "util.h"
-#include "vlog.h"
 
 /* Given the GNU-style long options in 'options', returns a string that may be
  * passed to getopt() with the corresponding short options.  The caller is
@@ -47,3 +47,150 @@ long_options_to_short_options(const struct option options[])
     return xstrdup(short_options);
 }
 
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like "run_command(argc - optind, argv + optind, my_commands);". */
+void
+run_command(int argc, char *argv[], const struct command commands[])
+{
+    const struct command *p;
+
+    if (argc < 1) {
+        ovs_fatal(0, "missing command name; use --help for help");
+    }
+
+    for (p = commands; p->name != NULL; p++) {
+        if (!strcmp(p->name, argv[0])) {
+            int n_arg = argc - 1;
+            if (n_arg < p->min_args) {
+                ovs_fatal(0, "'%s' command requires at least %d arguments",
+                          p->name, p->min_args);
+            } else if (n_arg > p->max_args) {
+                ovs_fatal(0, "'%s' command takes at most %d arguments",
+                          p->name, p->max_args);
+            } else {
+                p->handler(argc, argv);
+                if (ferror(stdout)) {
+                    ovs_fatal(0, "write to stdout failed");
+                }
+                if (ferror(stderr)) {
+                    ovs_fatal(0, "write to stderr failed");
+                }
+                return;
+            }
+        }
+    }
+
+    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+}
+\f
+/* Process title. */
+
+#ifdef __linux__
+static char *argv_start;       /* Start of command-line arguments in memory. */
+static size_t argv_size;       /* Number of bytes of command-line arguments. */
+static char *saved_proctitle;  /* Saved command-line arguments. */
+
+/* Prepares the process so that proctitle_set() can later succeed.
+ *
+ * This modifies the argv[] array so that it no longer points into the memory
+ * that it originally does.  Later, proctitle_set() might overwrite that
+ * memory.  That means that this function should be called before anything else
+ * that accesses the process's argv[] array.  Ideally, it should be called
+ * before anything else, period, at the very beginning of program
+ * execution.  */
+void
+proctitle_init(int argc, char **argv)
+{
+    int i;
+
+    if (!argc || !argv[0]) {
+        /* This situation should never occur, but... */
+        return;
+    }
+
+    /* Specialized version of first loop iteration below. */
+    argv_start = argv[0];
+    argv_size = strlen(argv[0]) + 1;
+    argv[0] = xstrdup(argv[0]);
+
+    for (i = 1; i < argc; i++) {
+        size_t size = strlen(argv[i]) + 1;
+
+        /* Add (argv[i], strlen(argv[i])+1) to (argv_start, argv_size). */
+        if (argv[i] + size == argv_start) {
+            /* Arguments grow downward in memory. */
+            argv_start -= size;
+            argv_size += size;
+        } else if (argv[i] == argv_start + argv_size) {
+            /* Arguments grow upward in memory. */
+            argv_size += size;
+        } else {
+            /* Arguments not contiguous.  (Is this really Linux?) */
+        }
+
+        /* Copy out the old argument so we can reuse the space. */
+        argv[i] = xstrdup(argv[i]);
+    }
+}
+
+/* Changes the name of the process, as shown by "ps", to 'format', which is
+ * formatted as if by printf(). */
+void
+proctitle_set(const char *format, ...)
+{
+    va_list args;
+    int n;
+
+    if (!argv_start || argv_size < 8) {
+        return;
+    }
+
+    if (!saved_proctitle) {
+        saved_proctitle = xmemdup(argv_start, argv_size);
+    }
+
+    va_start(args, format);
+    n = vsnprintf(argv_start, argv_size, format, args);
+    if (n >= argv_size) {
+        /* The name is too long, so add an ellipsis at the end. */
+        strcpy(&argv_start[argv_size - 4], "...");
+    } else {
+        /* Fill the extra space with null bytes, so that trailing bytes don't
+         * show up in the command line. */
+        memset(&argv_start[n], '\0', argv_size - n);
+    }
+    va_end(args);
+}
+
+/* Restores the process's original command line, as seen by "ps". */
+void
+proctitle_restore(void)
+{
+    if (saved_proctitle) {
+        memcpy(argv_start, saved_proctitle, argv_size);
+        free(saved_proctitle);
+        saved_proctitle = NULL;
+    }
+}
+#else  /* !__linux__ */
+/* Stubs that don't do anything on non-Linux systems. */
+
+void
+proctitle_init(int argc OVS_UNUSED, char **argv OVS_UNUSED)
+{
+}
+
+void
+proctitle_set(const char *format OVS_UNUSED, ...)
+{
+}
+
+void
+proctitle_restore(void)
+{
+}
+#endif  /* !__linux__ */
index 426ce62..1c88003 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 /* Utilities for command-line parsing. */
 
+#include "compiler.h"
+
 struct option;
+
+struct command {
+    const char *name;
+    int min_args;
+    int max_args;
+    void (*handler)(int argc, char *argv[]);
+};
+
 char *long_options_to_short_options(const struct option *options);
+void run_command(int argc, char *argv[], const struct command[]);
+
+void proctitle_init(int argc, char **argv);
+void proctitle_set(const char *, ...)
+    PRINTF_FORMAT(1, 2);
+void proctitle_restore(void);
 
 #endif /* command-line.h */
diff --git a/lib/common-syn.man b/lib/common-syn.man
new file mode 100644 (file)
index 0000000..ae1bed5
--- /dev/null
@@ -0,0 +1,4 @@
+.IP "Common options:"
+[\fB-h\fR | \fB--help\fR]
+[\fB-V\fR | \fB--version\fR]
+
index 84e81c2..ad165d7 100644 (file)
@@ -1,7 +1,7 @@
 .TP
 \fB-h\fR, \fB--help\fR
 Prints a brief help message to the console.
-
+.
 .TP
 \fB-V\fR, \fB--version\fR
 Prints version information to the console.
index 17e245f..6bf5144 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef COMPILER_H
 #define COMPILER_H 1
 
+#ifdef __GNUC__
 #define NO_RETURN __attribute__((__noreturn__))
-#define UNUSED __attribute__((__unused__))
-#define PACKED __attribute__((__packed__))
+#define OVS_UNUSED __attribute__((__unused__))
 #define PRINTF_FORMAT(FMT, ARG1) __attribute__((__format__(printf, FMT, ARG1)))
 #define STRFTIME_FORMAT(FMT) __attribute__((__format__(__strftime__, FMT, 0)))
 #define MALLOC_LIKE __attribute__((__malloc__))
 #define ALWAYS_INLINE __attribute__((always_inline))
-#define likely(x) __builtin_expect((x),1)
-#define unlikely(x) __builtin_expect((x),0)
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+#else
+#define NO_RETURN
+#define OVS_UNUSED
+#define PRINTF_FORMAT(FMT, ARG1)
+#define STRFTIME_FORMAT(FMT)
+#define MALLOC_LIKE
+#define ALWAYS_INLINE
+#define WARN_UNUSED_RESULT
+#endif
 
 #endif /* compiler.h */
index cdc796e..5c99c18 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,8 @@
 static unsigned int epoch;
 
 static void
-coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
+coverage_unixctl_log(struct unixctl_conn *conn, const char *args OVS_UNUSED,
+                     void *aux OVS_UNUSED)
 {
     coverage_log(VLL_WARN, false);
     unixctl_command_reply(conn, 200, NULL);
@@ -39,7 +40,7 @@ coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
 void
 coverage_init(void)
 {
-    unixctl_command_register("coverage/log", coverage_unixctl_log);
+    unixctl_command_register("coverage/log", coverage_unixctl_log, NULL);
 }
 
 /* Sorts coverage counters in descending order by count, within equal counts
@@ -108,7 +109,7 @@ coverage_hit(uint32_t hash)
     unsigned int word_mask = 1u << (bit_index % BITS_PER_WORD);
 
     if (hit[word_index] & word_mask) {
       return true;
+ return true;
     } else {
         hit[word_index] |= word_mask;
         return false;
index 6f04447..922f686 100644 (file)
@@ -76,7 +76,10 @@ csum_continue(uint32_t partial, const void *data_, size_t n)
 uint16_t
 csum_finish(uint32_t partial)
 {
-    return ~((partial & 0xffff) + (partial >> 16));
+    while (partial >> 16) {
+        partial = (partial & 0xffff) + (partial >> 16);
+    }
+    return ~partial;
 }
 
 /* Returns the new checksum for a packet in which the checksum field previously
@@ -93,8 +96,7 @@ recalc_csum16(uint16_t old_csum, uint16_t old_u16, uint16_t new_u16)
     uint16_t m_complement = ~old_u16;
     uint16_t m_prime = new_u16;
     uint32_t sum = hc_complement + m_complement + m_prime;
-    uint16_t hc_prime_complement = sum + (sum >> 16);
-    return ~hc_prime_complement;
+    return csum_finish(sum);
 }
 
 /* Returns the new checksum for a packet in which the checksum field previously
diff --git a/lib/daemon-syn.man b/lib/daemon-syn.man
new file mode 100644 (file)
index 0000000..4970564
--- /dev/null
@@ -0,0 +1,5 @@
+.IP "Daemon options:"
+[\fB--pidfile\fR[\fB=\fIpidfile\fR]]
+[\fB--overwrite-pidfile\fR]
+[\fB--detach\fR]
+[\fB--no-chdir\fR]
index a35c639..081912b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/wait.h>
 #include <unistd.h>
+#include "command-line.h"
 #include "fatal-signal.h"
 #include "dirs.h"
+#include "lockfile.h"
+#include "process.h"
+#include "socket-util.h"
 #include "timeval.h"
 #include "util.h"
 
@@ -38,17 +43,24 @@ static char *pidfile;
 /* Create pidfile even if one already exists and is locked? */
 static bool overwrite_pidfile;
 
-/* Should we chdir to "/". */
+/* Should we chdir to "/"? */
 static bool chdir_ = true;
 
+/* File descriptor used by daemonize_start() and daemonize_complete(). */
+static int daemonize_fd = -1;
+
+/* --monitor: Should a supervisory process monitor the daemon and restart it if
+ * it dies due to an error signal? */
+static bool monitor;
+
 /* Returns the file name that would be used for a pidfile if 'name' were
  * provided to set_pidfile().  The caller must free the returned string. */
 char *
 make_pidfile_name(const char *name) 
 {
-    return (!name ? xasprintf("%s/%s.pid", ovs_rundir, program_name)
-            : *name == '/' ? xstrdup(name)
-            : xasprintf("%s/%s", ovs_rundir, name));
+    return (!name
+            ? xasprintf("%s/%s.pid", ovs_rundir, program_name)
+            : abs_file_name(ovs_rundir, name));
 }
 
 /* Sets up a following call to daemonize() to create a pidfile named 'name'.
@@ -80,6 +92,13 @@ set_no_chdir(void)
     chdir_ = false;
 }
 
+/* Will we chdir to "/" as part of daemonizing? */
+bool
+is_chdir_enabled(void)
+{
+    return chdir_;
+}
+
 /* Normally, die_if_already_running() will terminate the program with a message
  * if a locked pidfile already exists.  If this function is called,
  * die_if_already_running() will merely log a warning. */
@@ -97,6 +116,21 @@ set_detach(void)
     detach = true;
 }
 
+/* Will daemonize() really detach? */
+bool
+get_detach(void)
+{
+    return detach;
+}
+
+/* Sets up a following call to daemonize() to fork a supervisory process to
+ * monitor the daemon and restart it if it dies due to an error signal.  */
+void
+daemon_set_monitor(void)
+{
+    monitor = true;
+}
+
 /* If a pidfile has been configured and that pidfile already exists and is
  * locked by a running process, returns the pid of the running process.
  * Otherwise, returns 0. */
@@ -196,43 +230,209 @@ make_pidfile(void)
 void
 daemonize(void)
 {
-    if (detach) {
-        char c = 0;
-        int fds[2];
-        if (pipe(fds) < 0) {
-            ovs_fatal(errno, "pipe failed");
+    daemonize_start();
+    daemonize_complete();
+}
+
+static pid_t
+fork_and_wait_for_startup(int *fdp)
+{
+    int fds[2];
+    pid_t pid;
+
+    if (pipe(fds) < 0) {
+        ovs_fatal(errno, "pipe failed");
+    }
+
+    pid = fork();
+    if (pid > 0) {
+        /* Running in parent process. */
+        char c;
+
+        close(fds[1]);
+        fatal_signal_fork();
+        if (read(fds[0], &c, 1) != 1) {
+            int retval;
+            int status;
+
+            do {
+                retval = waitpid(pid, &status, 0);
+            } while (retval == -1 && errno == EINTR);
+
+            if (retval == pid
+                && WIFEXITED(status)
+                && WEXITSTATUS(status)) {
+                /* Child exited with an error.  Convey the same error to
+                 * our parent process as a courtesy. */
+                exit(WEXITSTATUS(status));
+            }
+
+            ovs_fatal(errno, "fork child failed to signal startup");
+        }
+        close(fds[0]);
+        *fdp = -1;
+    } else if (!pid) {
+        /* Running in child process. */
+        close(fds[0]);
+        time_postfork();
+        lockfile_postfork();
+        *fdp = fds[1];
+    } else {
+        ovs_fatal(errno, "could not fork");
+    }
+
+    return pid;
+}
+
+static void
+fork_notify_startup(int fd)
+{
+    if (fd != -1) {
+        size_t bytes_written;
+        int error;
+
+        error = write_fully(fd, "", 1, &bytes_written);
+        if (error) {
+            ovs_fatal(error, "could not write to pipe");
+        }
+
+        close(fd);
+    }
+}
+
+static bool
+should_restart(int status)
+{
+    if (WIFSIGNALED(status)) {
+        static const int error_signals[] = {
+            SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV,
+            SIGXCPU, SIGXFSZ
+        };
+
+        size_t i;
+
+        for (i = 0; i < ARRAY_SIZE(error_signals); i++) {
+            if (error_signals[i] == WTERMSIG(status)) {
+                return true;
+            }
         }
+    }
+    return false;
+}
+
+static void
+monitor_daemon(pid_t daemon_pid)
+{
+    /* XXX Should limit the rate at which we restart the daemon. */
+    /* XXX Should log daemon's stderr output at startup time. */
+    const char *saved_program_name;
+    char *status_msg;
+
+    saved_program_name = program_name;
+    program_name = xasprintf("monitor(%s)", program_name);
+    status_msg = xstrdup("healthy");
+    for (;;) {
+        int retval;
+        int status;
+
+        proctitle_set("%s: monitoring pid %lu (%s)",
+                      saved_program_name, (unsigned long int) daemon_pid,
+                      status_msg);
+
+        do {
+            retval = waitpid(daemon_pid, &status, 0);
+        } while (retval == -1 && errno == EINTR);
 
-        switch (fork()) {
-        default:
-            /* Parent process: wait for child to create pidfile, then exit. */
-            close(fds[1]);
-            fatal_signal_fork();
-            if (read(fds[0], &c, 1) != 1) {
-                ovs_fatal(errno, "daemon child failed to signal startup");
+        if (retval == -1) {
+            ovs_fatal(errno, "waitpid failed");
+        } else if (retval == daemon_pid) {
+            char *s = process_status_msg(status);
+            free(status_msg);
+            status_msg = xasprintf("pid %lu died, %s",
+                                   (unsigned long int) daemon_pid, s);
+            free(s);
+
+            if (should_restart(status)) {
+                VLOG_ERR("%s, restarting", status_msg);
+                daemon_pid = fork_and_wait_for_startup(&daemonize_fd);
+                if (!daemon_pid) {
+                    break;
+                }
+            } else {
+                VLOG_INFO("%s, exiting", status_msg);
+                exit(0);
             }
+        }
+    }
+    free(status_msg);
+
+    /* Running in new daemon process. */
+    proctitle_restore();
+    free((char *) program_name);
+    program_name = saved_program_name;
+}
+
+/* Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
+ * then this keeps us from holding that session open artificially. */
+static void
+close_standard_fds(void)
+{
+    int null_fd = get_null_fd();
+    if (null_fd >= 0) {
+        dup2(null_fd, STDIN_FILENO);
+        dup2(null_fd, STDOUT_FILENO);
+        dup2(null_fd, STDERR_FILENO);
+    }
+}
+
+/* If daemonization is configured, then starts daemonization, by forking and
+ * returning in the child process.  The parent process hangs around until the
+ * child lets it know either that it completed startup successfully (by calling
+ * daemon_complete()) or that it failed to start up (by exiting with a nonzero
+ * exit code). */
+void
+daemonize_start(void)
+{
+    daemonize_fd = -1;
+
+    if (detach) {
+        if (fork_and_wait_for_startup(&daemonize_fd) > 0) {
+            /* Running in parent process. */
             exit(0);
+        }
+        /* Running in daemon or monitor process. */
+    }
 
-        case 0:
-            /* Child process. */
-            close(fds[0]);
-            make_pidfile();
-            write(fds[1], &c, 1);
-            close(fds[1]);
-            setsid();
-            if (chdir_) {
-                chdir("/");
-            }
-            time_postfork();
-            break;
+    if (monitor) {
+        int saved_daemonize_fd = daemonize_fd;
+        pid_t daemon_pid;
 
-        case -1:
-            /* Error. */
-            ovs_fatal(errno, "could not fork");
-            break;
+        daemon_pid = fork_and_wait_for_startup(&daemonize_fd);
+        if (daemon_pid > 0) {
+            /* Running in monitor process. */
+            fork_notify_startup(saved_daemonize_fd);
+            close_standard_fds();
+            monitor_daemon(daemon_pid);
         }
-    } else {
-        make_pidfile();
+        /* Running in daemon process. */
+    }
+
+    make_pidfile();
+}
+
+/* If daemonization is configured, then this function notifies the parent
+ * process that the child process has completed startup successfully. */
+void
+daemonize_complete(void)
+{
+    fork_notify_startup(daemonize_fd);
+
+    if (detach) {
+        setsid();
+        if (chdir_) {
+            ignore(chdir("/"));
+        }
+        close_standard_fds();
     }
 }
 
index d0c324c..1d63076 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,13 +26,15 @@ enum {
     OPT_NO_CHDIR,
     OPT_OVERWRITE_PIDFILE,
     OPT_PIDFILE,
+    OPT_MONITOR
 };
 
 #define DAEMON_LONG_OPTIONS                                          \
         {"detach",            no_argument, 0, OPT_DETACH},           \
         {"no-chdir",          no_argument, 0, OPT_NO_CHDIR},         \
         {"pidfile",           optional_argument, 0, OPT_PIDFILE},    \
-        {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE}
+        {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE},\
+        {"monitor",           no_argument, 0, OPT_MONITOR}
 
 #define DAEMON_OPTION_HANDLERS                  \
         case OPT_DETACH:                        \
@@ -49,14 +51,23 @@ enum {
                                                 \
         case OPT_OVERWRITE_PIDFILE:             \
             ignore_existing_pidfile();          \
+            break;                              \
+                                                \
+        case OPT_MONITOR:                       \
+            daemon_set_monitor();               \
             break;
 
 char *make_pidfile_name(const char *name);
 void set_pidfile(const char *name);
 const char *get_pidfile(void);
 void set_no_chdir(void);
+bool is_chdir_enabled(void);
 void set_detach(void);
+bool get_detach(void);
+void daemon_set_monitor(void);
 void daemonize(void);
+void daemonize_start(void);
+void daemonize_complete(void);
 void die_if_already_running(void);
 void ignore_existing_pidfile(void);
 void daemon_usage(void);
index ea0561d..046f9ea 100644 (file)
@@ -4,21 +4,32 @@ Causes a file (by default, \fB\*(PN.pid\fR) to be created indicating
 the PID of the running process.  If \fIpidfile\fR is not specified, or
 if it does not begin with \fB/\fR, then it is created in
 \fB@RUNDIR@\fR.
-
+.
 .TP
 \fB--overwrite-pidfile\fR
 By default, when \fB--pidfile\fR is specified and the specified pidfile 
 already exists and is locked by a running process, \fB\*(PN\fR refuses 
 to start.  Specify \fB--overwrite-pidfile\fR to cause it to instead 
 overwrite the pidfile.
-
+.IP
 When \fB--pidfile\fR is not specified, this option has no effect.
-
+.
 .TP
 \fB--detach\fR
 Causes \fB\*(PN\fR to detach itself from the foreground session and
 run as a background process.
-
+.
+.TP
+\fB--monitor\fR
+Creates an additional process to monitor the \fB\*(PN\fR daemon.  If
+the daemon dies due to a signal that indicates a programming error
+(e.g. \fBSIGSEGV\fR, \fBSIGABRT\fR), then the monitor process starts a
+new copy of it.  If the daemon die or exits for another reason, the
+monitor process exits.
+.IP
+This option is normally used with \fB--detach\fR, but it also
+functions without it.
+.
 .TP
 \fB--no-chdir\fR
 By default, when \fB--detach\fR is specified, \fB\*(PN\fR 
index 9057121..30ac56c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -151,12 +151,19 @@ dhclient_create(const char *netdev_name,
                 void *aux, struct dhclient **cli_)
 {
     struct dhclient *cli;
+    struct netdev_options netdev_options;
     struct netdev *netdev;
     int error;
 
     *cli_ = NULL;
 
-    error = netdev_open(netdev_name, ETH_TYPE_IP, &netdev);
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = netdev_name;
+    netdev_options.ethertype = ETH_TYPE_IP;
+    netdev_options.may_create = true;
+    netdev_options.may_open = true;
+
+    error = netdev_open(&netdev_options, &netdev);
     /* XXX install socket filter to catch only DHCP packets. */
     if (error) {
         VLOG_ERR("could not open %s network device: %s",
@@ -172,7 +179,7 @@ dhclient_create(const char *netdev_name,
         return error;
     }
 
-    cli = xcalloc(1, sizeof *cli);
+    cli = xzalloc(sizeof *cli);
     cli->modify_request = modify_request;
     cli->validate_offer = validate_offer;
     cli->aux = aux;
@@ -411,7 +418,7 @@ dhclient_configure_netdev(struct dhclient *cli)
     }
 
     if (!error && router.s_addr) {
-        error = netdev_add_router(router);
+        error = netdev_add_router(cli->netdev, router);
         if (error) {
             VLOG_ERR("failed to add default route to "IP_FMT" on %s: %s",
                      IP_ARGS(&router), netdev_get_name(cli->netdev),
@@ -768,7 +775,7 @@ dhclient_run_REBINDING(struct dhclient *cli)
 }
 
 static void
-dhclient_run_RELEASED(struct dhclient *cli UNUSED)
+dhclient_run_RELEASED(struct dhclient *cli OVS_UNUSED)
 {
     /* Nothing to do. */
 }
@@ -882,7 +889,7 @@ dhclient_msg_init(struct dhclient *cli, enum dhcp_msg_type type,
     msg->xid = cli->xid;
     msg->secs = cli->secs;
     msg->type = type;
-    memcpy(msg->chaddr, netdev_get_etheraddr(cli->netdev), ETH_ADDR_LEN);
+    netdev_get_etheraddr(cli->netdev, msg->chaddr);
 }
 
 /* If time goes backward this returns a large number, which makes it look like
@@ -905,9 +912,13 @@ timeout(struct dhclient *cli, unsigned int secs)
 static bool
 do_receive_msg(struct dhclient *cli, struct dhcp_msg *msg)
 {
+    uint8_t cli_mac[ETH_ADDR_LEN];
     struct ofpbuf b;
+    int mtu;
 
-    ofpbuf_init(&b, netdev_get_mtu(cli->netdev) + VLAN_ETH_HEADER_LEN);
+    netdev_get_mtu(cli->netdev, &mtu);
+    ofpbuf_init(&b, mtu + VLAN_ETH_HEADER_LEN);
+    netdev_get_etheraddr(cli->netdev, cli_mac);
     for (; cli->received < 50; cli->received++) {
         const struct ip_header *ip;
         const struct dhcp_header *dhcp;
@@ -925,8 +936,7 @@ do_receive_msg(struct dhclient *cli, struct dhcp_msg *msg)
             || flow.nw_proto != IP_TYPE_UDP
             || flow.tp_dst != htons(DHCP_CLIENT_PORT)
             || !(eth_addr_is_broadcast(flow.dl_dst)
-                 || eth_addr_equals(flow.dl_dst,
-                                    netdev_get_etheraddr(cli->netdev)))) {
+                 || eth_addr_equals(flow.dl_dst, cli_mac))) {
             continue;
         }
 
@@ -977,7 +987,7 @@ do_send_msg(struct dhclient *cli, const struct dhcp_msg *msg)
 
     dhcp_assemble(msg, &b);
 
-    memcpy(eh.eth_src, netdev_get_etheraddr(cli->netdev), ETH_ADDR_LEN);
+    netdev_get_etheraddr(cli->netdev, eh.eth_src);
     memcpy(eh.eth_dst, eth_addr_broadcast, ETH_ADDR_LEN);
     eh.eth_type = htons(ETH_TYPE_IP);
 
index f0c3644..e16176b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -497,7 +497,7 @@ dhcp_option_equals(const struct dhcp_option *a, const struct dhcp_option *b)
 {
     return ((a->data != NULL) == (b->data != NULL)
             && a->n == b->n
-            && !memcmp(a->data, b->data, a->n));
+            && (!a->data || !memcmp(a->data, b->data, a->n)));
 }
 
 /* Replaces 'ds' by a string representation of 'msg'.  If 'multiline' is
@@ -674,7 +674,7 @@ dhcp_parse(struct dhcp_msg *msg, const struct ofpbuf *b_)
     msg->giaddr = dhcp->giaddr;
     memcpy(msg->chaddr, dhcp->chaddr, ETH_ADDR_LEN);
 
-    cookie = ofpbuf_try_pull(&b, sizeof cookie);
+    cookie = ofpbuf_try_pull(&b, sizeof *cookie);
     if (cookie) {
         if (ntohl(*cookie) == DHCP_OPTS_COOKIE) {
             uint8_t overload;
diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c
new file mode 100644 (file)
index 0000000..1eaba74
--- /dev/null
@@ -0,0 +1,780 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "dpif.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <linux/ethtool.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "dpif-provider.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "rtnetlink.h"
+#include "svec.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_dpif_linux
+
+/* Datapath interface for the openvswitch Linux kernel module. */
+struct dpif_linux {
+    struct dpif dpif;
+    int fd;
+
+    /* Used by dpif_linux_get_all_names(). */
+    char *local_ifname;
+    int minor;
+
+    /* Change notification. */
+    int local_ifindex;          /* Ifindex of local port. */
+    struct svec changed_ports;  /* Ports that have changed. */
+    struct rtnetlink_notifier port_notifier;
+    bool change_error;
+};
+
+static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(9999, 5);
+
+static int do_ioctl(const struct dpif *, int cmd, const void *arg);
+static int lookup_minor(const char *name, int *minor);
+static int finish_open(struct dpif *, const char *local_ifname);
+static int get_openvswitch_major(void);
+static int create_minor(const char *name, int minor, struct dpif **dpifp);
+static int open_minor(int minor, struct dpif **dpifp);
+static int make_openvswitch_device(int minor, char **fnp);
+static void dpif_linux_port_changed(const struct rtnetlink_change *,
+                                    void *dpif);
+
+static struct dpif_linux *
+dpif_linux_cast(const struct dpif *dpif)
+{
+    dpif_assert_class(dpif, &dpif_linux_class);
+    return CONTAINER_OF(dpif, struct dpif_linux, dpif);
+}
+
+static int
+dpif_linux_enumerate(struct svec *all_dps)
+{
+    int major;
+    int error;
+    int i;
+
+    /* Check that the Open vSwitch module is loaded. */
+    major = get_openvswitch_major();
+    if (major < 0) {
+        return -major;
+    }
+
+    error = 0;
+    for (i = 0; i < ODP_MAX; i++) {
+        struct dpif *dpif;
+        char devname[16];
+        int retval;
+
+        sprintf(devname, "dp%d", i);
+        retval = dpif_open(devname, "system", &dpif);
+        if (!retval) {
+            svec_add(all_dps, devname);
+            dpif_uninit(dpif, true);
+        } else if (retval != ENODEV && !error) {
+            error = retval;
+        }
+    }
+    return error;
+}
+
+static int
+dpif_linux_open(const char *name, const char *type OVS_UNUSED, bool create,
+                struct dpif **dpifp)
+{
+    int minor;
+
+    minor = !strncmp(name, "dp", 2)
+            && isdigit((unsigned char)name[2]) ? atoi(name + 2) : -1;
+    if (create) {
+        if (minor >= 0) {
+            return create_minor(name, minor, dpifp);
+        } else {
+            /* Scan for unused minor number. */
+            for (minor = 0; minor < ODP_MAX; minor++) {
+                int error = create_minor(name, minor, dpifp);
+                if (error != EBUSY) {
+                    return error;
+                }
+            }
+
+            /* All datapath numbers in use. */
+            return ENOBUFS;
+        }
+    } else {
+        struct dpif_linux *dpif;
+        struct odp_port port;
+        int error;
+
+        if (minor < 0) {
+            error = lookup_minor(name, &minor);
+            if (error) {
+                return error;
+            }
+        }
+
+        error = open_minor(minor, dpifp);
+        if (error) {
+            return error;
+        }
+        dpif = dpif_linux_cast(*dpifp);
+
+        /* We need the local port's ifindex for the poll function.  Start by
+         * getting the local port's name. */
+        memset(&port, 0, sizeof port);
+        port.port = ODPP_LOCAL;
+        if (ioctl(dpif->fd, ODP_PORT_QUERY, &port)) {
+            error = errno;
+            if (error != ENODEV) {
+                VLOG_WARN("%s: probe returned unexpected error: %s",
+                          dpif_name(*dpifp), strerror(error));
+            }
+            dpif_uninit(*dpifp, true);
+            return error;
+        }
+
+        /* Then use that to finish up opening. */
+        return finish_open(&dpif->dpif, port.devname);
+    }
+}
+
+static void
+dpif_linux_close(struct dpif *dpif_)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    rtnetlink_notifier_unregister(&dpif->port_notifier);
+    svec_destroy(&dpif->changed_ports);
+    free(dpif->local_ifname);
+    close(dpif->fd);
+    free(dpif);
+}
+
+static int
+dpif_linux_get_all_names(const struct dpif *dpif_, struct svec *all_names)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+
+    svec_add_nocopy(all_names, xasprintf("dp%d", dpif->minor));
+    svec_add(all_names, dpif->local_ifname);
+    return 0;
+}
+
+static int
+dpif_linux_destroy(struct dpif *dpif_)
+{
+    return do_ioctl(dpif_, ODP_DP_DESTROY, NULL);
+}
+
+static int
+dpif_linux_get_stats(const struct dpif *dpif_, struct odp_stats *stats)
+{
+    memset(stats, 0, sizeof *stats);
+    return do_ioctl(dpif_, ODP_DP_STATS, stats);
+}
+
+static int
+dpif_linux_get_drop_frags(const struct dpif *dpif_, bool *drop_fragsp)
+{
+    int drop_frags;
+    int error;
+
+    error = do_ioctl(dpif_, ODP_GET_DROP_FRAGS, &drop_frags);
+    if (!error) {
+        *drop_fragsp = drop_frags & 1;
+    }
+    return error;
+}
+
+static int
+dpif_linux_set_drop_frags(struct dpif *dpif_, bool drop_frags)
+{
+    int drop_frags_int = drop_frags;
+    return do_ioctl(dpif_, ODP_SET_DROP_FRAGS, &drop_frags_int);
+}
+
+static int
+dpif_linux_port_add(struct dpif *dpif_, const char *devname, uint16_t flags,
+                    uint16_t *port_no)
+{
+    struct odp_port port;
+    int error;
+
+    memset(&port, 0, sizeof port);
+    strncpy(port.devname, devname, sizeof port.devname);
+    port.flags = flags;
+    error = do_ioctl(dpif_, ODP_PORT_ADD, &port);
+    if (!error) {
+        *port_no = port.port;
+    }
+    return error;
+}
+
+static int
+dpif_linux_port_del(struct dpif *dpif_, uint16_t port_no)
+{
+    int tmp = port_no;
+    return do_ioctl(dpif_, ODP_PORT_DEL, &tmp);
+}
+
+static int
+dpif_linux_port_query_by_number(const struct dpif *dpif_, uint16_t port_no,
+                          struct odp_port *port)
+{
+    memset(port, 0, sizeof *port);
+    port->port = port_no;
+    return do_ioctl(dpif_, ODP_PORT_QUERY, port);
+}
+
+static int
+dpif_linux_port_query_by_name(const struct dpif *dpif_, const char *devname,
+                              struct odp_port *port)
+{
+    memset(port, 0, sizeof *port);
+    strncpy(port->devname, devname, sizeof port->devname);
+    return do_ioctl(dpif_, ODP_PORT_QUERY, port);
+}
+
+static int
+dpif_linux_flow_flush(struct dpif *dpif_)
+{
+    return do_ioctl(dpif_, ODP_FLOW_FLUSH, NULL);
+}
+
+static int
+dpif_linux_port_list(const struct dpif *dpif_, struct odp_port *ports, int n)
+{
+    struct odp_portvec pv;
+    int error;
+
+    pv.ports = ports;
+    pv.n_ports = n;
+    error = do_ioctl(dpif_, ODP_PORT_LIST, &pv);
+    return error ? -error : pv.n_ports;
+}
+
+static int
+dpif_linux_port_poll(const struct dpif *dpif_, char **devnamep)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+
+    if (dpif->change_error) {
+        dpif->change_error = false;
+        svec_clear(&dpif->changed_ports);
+        return ENOBUFS;
+    } else if (dpif->changed_ports.n) {
+        *devnamep = dpif->changed_ports.names[--dpif->changed_ports.n];
+        return 0;
+    } else {
+        return EAGAIN;
+    }
+}
+
+static void
+dpif_linux_port_poll_wait(const struct dpif *dpif_)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    if (dpif->changed_ports.n || dpif->change_error) {
+        poll_immediate_wake();
+    } else {
+        rtnetlink_notifier_wait();
+    }
+}
+
+static int
+dpif_linux_port_group_get(const struct dpif *dpif_, int group,
+                          uint16_t ports[], int n)
+{
+    struct odp_port_group pg;
+    int error;
+
+    assert(n <= UINT16_MAX);
+    pg.group = group;
+    pg.ports = ports;
+    pg.n_ports = n;
+    error = do_ioctl(dpif_, ODP_PORT_GROUP_GET, &pg);
+    return error ? -error : pg.n_ports;
+}
+
+static int
+dpif_linux_port_group_set(struct dpif *dpif_, int group,
+                          const uint16_t ports[], int n)
+{
+    struct odp_port_group pg;
+
+    assert(n <= UINT16_MAX);
+    pg.group = group;
+    pg.ports = (uint16_t *) ports;
+    pg.n_ports = n;
+    return do_ioctl(dpif_, ODP_PORT_GROUP_SET, &pg);
+}
+
+static int
+dpif_linux_flow_get(const struct dpif *dpif_, struct odp_flow flows[], int n)
+{
+    struct odp_flowvec fv;
+    fv.flows = flows;
+    fv.n_flows = n;
+    return do_ioctl(dpif_, ODP_FLOW_GET, &fv);
+}
+
+static int
+dpif_linux_flow_put(struct dpif *dpif_, struct odp_flow_put *put)
+{
+    return do_ioctl(dpif_, ODP_FLOW_PUT, put);
+}
+
+static int
+dpif_linux_flow_del(struct dpif *dpif_, struct odp_flow *flow)
+{
+    return do_ioctl(dpif_, ODP_FLOW_DEL, flow);
+}
+
+static int
+dpif_linux_flow_list(const struct dpif *dpif_, struct odp_flow flows[], int n)
+{
+    struct odp_flowvec fv;
+    int error;
+
+    fv.flows = flows;
+    fv.n_flows = n;
+    error = do_ioctl(dpif_, ODP_FLOW_LIST, &fv);
+    return error ? -error : fv.n_flows;
+}
+
+static int
+dpif_linux_execute(struct dpif *dpif_, uint16_t in_port,
+                   const union odp_action actions[], int n_actions,
+                   const struct ofpbuf *buf)
+{
+    struct odp_execute execute;
+    memset(&execute, 0, sizeof execute);
+    execute.in_port = in_port;
+    execute.actions = (union odp_action *) actions;
+    execute.n_actions = n_actions;
+    execute.data = buf->data;
+    execute.length = buf->size;
+    return do_ioctl(dpif_, ODP_EXECUTE, &execute);
+}
+
+static int
+dpif_linux_recv_get_mask(const struct dpif *dpif_, int *listen_mask)
+{
+    return do_ioctl(dpif_, ODP_GET_LISTEN_MASK, listen_mask);
+}
+
+static int
+dpif_linux_recv_set_mask(struct dpif *dpif_, int listen_mask)
+{
+    return do_ioctl(dpif_, ODP_SET_LISTEN_MASK, &listen_mask);
+}
+
+static int
+dpif_linux_get_sflow_probability(const struct dpif *dpif_,
+                                 uint32_t *probability)
+{
+    return do_ioctl(dpif_, ODP_GET_SFLOW_PROBABILITY, probability);
+}
+
+static int
+dpif_linux_set_sflow_probability(struct dpif *dpif_, uint32_t probability)
+{
+    return do_ioctl(dpif_, ODP_SET_SFLOW_PROBABILITY, &probability);
+}
+
+static int
+dpif_linux_recv(struct dpif *dpif_, struct ofpbuf **bufp)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    struct ofpbuf *buf;
+    int retval;
+    int error;
+
+    buf = ofpbuf_new(65536);
+    retval = read(dpif->fd, ofpbuf_tail(buf), ofpbuf_tailroom(buf));
+    if (retval < 0) {
+        error = errno;
+        if (error != EAGAIN) {
+            VLOG_WARN_RL(&error_rl, "%s: read failed: %s",
+                         dpif_name(dpif_), strerror(error));
+        }
+    } else if (retval >= sizeof(struct odp_msg)) {
+        struct odp_msg *msg = buf->data;
+        if (msg->length <= retval) {
+            buf->size += retval;
+            *bufp = buf;
+            return 0;
+        } else {
+            VLOG_WARN_RL(&error_rl, "%s: discarding message truncated "
+                         "from %"PRIu32" bytes to %d",
+                         dpif_name(dpif_), msg->length, retval);
+            error = ERANGE;
+        }
+    } else if (!retval) {
+        VLOG_WARN_RL(&error_rl, "%s: unexpected end of file", dpif_name(dpif_));
+        error = EPROTO;
+    } else {
+        VLOG_WARN_RL(&error_rl,
+                     "%s: discarding too-short message (%d bytes)",
+                     dpif_name(dpif_), retval);
+        error = ERANGE;
+    }
+
+    *bufp = NULL;
+    ofpbuf_delete(buf);
+    return error;
+}
+
+static void
+dpif_linux_recv_wait(struct dpif *dpif_)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    poll_fd_wait(dpif->fd, POLLIN);
+}
+
+const struct dpif_class dpif_linux_class = {
+    "system",
+    NULL,
+    NULL,
+    dpif_linux_enumerate,
+    dpif_linux_open,
+    dpif_linux_close,
+    dpif_linux_get_all_names,
+    dpif_linux_destroy,
+    dpif_linux_get_stats,
+    dpif_linux_get_drop_frags,
+    dpif_linux_set_drop_frags,
+    dpif_linux_port_add,
+    dpif_linux_port_del,
+    dpif_linux_port_query_by_number,
+    dpif_linux_port_query_by_name,
+    dpif_linux_port_list,
+    dpif_linux_port_poll,
+    dpif_linux_port_poll_wait,
+    dpif_linux_port_group_get,
+    dpif_linux_port_group_set,
+    dpif_linux_flow_get,
+    dpif_linux_flow_put,
+    dpif_linux_flow_del,
+    dpif_linux_flow_flush,
+    dpif_linux_flow_list,
+    dpif_linux_execute,
+    dpif_linux_recv_get_mask,
+    dpif_linux_recv_set_mask,
+    dpif_linux_get_sflow_probability,
+    dpif_linux_set_sflow_probability,
+    dpif_linux_recv,
+    dpif_linux_recv_wait,
+};
+\f
+static int get_openvswitch_major(void);
+static int get_major(const char *target);
+
+static int
+do_ioctl(const struct dpif *dpif_, int cmd, const void *arg)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    return ioctl(dpif->fd, cmd, arg) ? errno : 0;
+}
+
+static int
+lookup_minor(const char *name, int *minorp)
+{
+    struct ethtool_drvinfo drvinfo;
+    int minor, port_no;
+    struct ifreq ifr;
+    int error;
+    int sock;
+
+    sock = socket(AF_INET, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        VLOG_WARN("socket(AF_INET) failed: %s", strerror(errno));
+        error = errno;
+        goto error;
+    }
+
+    memset(&ifr, 0, sizeof ifr);
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+    ifr.ifr_data = (caddr_t) &drvinfo;
+
+    memset(&drvinfo, 0, sizeof drvinfo);
+    drvinfo.cmd = ETHTOOL_GDRVINFO;
+    if (ioctl(sock, SIOCETHTOOL, &ifr)) {
+        VLOG_WARN("ioctl(SIOCETHTOOL) failed: %s", strerror(errno));
+        error = errno;
+        goto error_close_sock;
+    }
+
+    if (strcmp(drvinfo.driver, "openvswitch")) {
+        VLOG_WARN("%s is not an openvswitch device", name);
+        error = EOPNOTSUPP;
+        goto error_close_sock;
+    }
+
+    if (sscanf(drvinfo.bus_info, "%d.%d", &minor, &port_no) != 2) {
+        VLOG_WARN("%s ethtool bus_info has unexpected format", name);
+        error = EPROTOTYPE;
+        goto error_close_sock;
+    } else if (port_no != ODPP_LOCAL) {
+        /* This is an Open vSwitch device but not the local port.  We
+         * intentionally support only using the name of the local port as the
+         * name of a datapath; otherwise, it would be too difficult to
+         * enumerate all the names of a datapath. */
+        error = EOPNOTSUPP;
+        goto error_close_sock;
+    }
+
+    *minorp = minor;
+    close(sock);
+    return 0;
+
+error_close_sock:
+    close(sock);
+error:
+    return error;
+}
+
+static int
+make_openvswitch_device(int minor, char **fnp)
+{
+    const char dirname[] = "/dev/net";
+    int major;
+    dev_t dev;
+    struct stat s;
+    char fn[128];
+
+    *fnp = NULL;
+
+    major = get_openvswitch_major();
+    if (major < 0) {
+        return -major;
+    }
+    dev = makedev(major, minor);
+
+    sprintf(fn, "%s/dp%d", dirname, minor);
+    if (!stat(fn, &s)) {
+        if (!S_ISCHR(s.st_mode)) {
+            VLOG_WARN_RL(&error_rl, "%s is not a character device, fixing",
+                         fn);
+        } else if (s.st_rdev != dev) {
+            VLOG_WARN_RL(&error_rl,
+                         "%s is device %u:%u but should be %u:%u, fixing",
+                         fn, major(s.st_rdev), minor(s.st_rdev),
+                         major(dev), minor(dev));
+        } else {
+            goto success;
+        }
+        if (unlink(fn)) {
+            VLOG_WARN_RL(&error_rl, "%s: unlink failed (%s)",
+                         fn, strerror(errno));
+            return errno;
+        }
+    } else if (errno == ENOENT) {
+        if (stat(dirname, &s)) {
+            if (errno == ENOENT) {
+                if (mkdir(dirname, 0755)) {
+                    VLOG_WARN_RL(&error_rl, "%s: mkdir failed (%s)",
+                                 dirname, strerror(errno));
+                    return errno;
+                }
+            } else {
+                VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)",
+                             dirname, strerror(errno));
+                return errno;
+            }
+        }
+    } else {
+        VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)", fn, strerror(errno));
+        return errno;
+    }
+
+    /* The device needs to be created. */
+    if (mknod(fn, S_IFCHR | 0700, dev)) {
+        VLOG_WARN_RL(&error_rl,
+                     "%s: creating character device %u:%u failed (%s)",
+                     fn, major(dev), minor(dev), strerror(errno));
+        return errno;
+    }
+
+success:
+    *fnp = xstrdup(fn);
+    return 0;
+}
+
+/* Return the major device number of the Open vSwitch device.  If it
+ * cannot be determined, a negative errno is returned. */
+static int
+get_openvswitch_major(void)
+{
+    static int openvswitch_major = -1;
+    if (openvswitch_major < 0) {
+        openvswitch_major = get_major("openvswitch");
+    }
+    return openvswitch_major;
+}
+
+static int
+get_major(const char *target)
+{
+    const char fn[] = "/proc/devices";
+    char line[128];
+    FILE *file;
+    int ln;
+
+    file = fopen(fn, "r");
+    if (!file) {
+        VLOG_ERR("opening %s failed (%s)", fn, strerror(errno));
+        return -errno;
+    }
+
+    for (ln = 1; fgets(line, sizeof line, file); ln++) {
+        char name[64];
+        int major;
+
+        if (!strncmp(line, "Character", 9) || line[0] == '\0') {
+            /* Nothing to do. */
+        } else if (!strncmp(line, "Block", 5)) {
+            /* We only want character devices, so skip the rest of the file. */
+            break;
+        } else if (sscanf(line, "%d %63s", &major, name)) {
+            if (!strcmp(name, target)) {
+                fclose(file);
+                return major;
+            }
+        } else {
+            static bool warned;
+            if (!warned) {
+                VLOG_WARN("%s:%d: syntax error", fn, ln);
+            }
+            warned = true;
+        }
+    }
+
+    fclose(file);
+
+    VLOG_ERR("%s: %s major not found (is the module loaded?)", fn, target);
+    return -ENODEV;
+}
+
+static int
+finish_open(struct dpif *dpif_, const char *local_ifname)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    dpif->local_ifname = xstrdup(local_ifname);
+    dpif->local_ifindex = if_nametoindex(local_ifname);
+    if (!dpif->local_ifindex) {
+        int error = errno;
+        dpif_uninit(dpif_, true);
+        VLOG_WARN("could not get ifindex of %s device: %s",
+                  local_ifname, strerror(errno));
+        return error;
+    }
+    return 0;
+}
+
+static int
+create_minor(const char *name, int minor, struct dpif **dpifp)
+{
+    int error = open_minor(minor, dpifp);
+    if (!error) {
+        error = do_ioctl(*dpifp, ODP_DP_CREATE, name);
+        if (!error) {
+            error = finish_open(*dpifp, name);
+        } else {
+            dpif_uninit(*dpifp, true);
+        }
+    }
+    return error;
+}
+
+static int
+open_minor(int minor, struct dpif **dpifp)
+{
+    int error;
+    char *fn;
+    int fd;
+
+    error = make_openvswitch_device(minor, &fn);
+    if (error) {
+        return error;
+    }
+
+    fd = open(fn, O_RDONLY | O_NONBLOCK);
+    if (fd >= 0) {
+        struct dpif_linux *dpif = xmalloc(sizeof *dpif);
+        error = rtnetlink_notifier_register(&dpif->port_notifier,
+                                           dpif_linux_port_changed, dpif);
+        if (!error) {
+            char *name;
+
+            name = xasprintf("dp%d", minor);
+            dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor);
+            free(name);
+
+            dpif->fd = fd;
+            dpif->local_ifname = NULL;
+            dpif->minor = minor;
+            dpif->local_ifindex = 0;
+            svec_init(&dpif->changed_ports);
+            dpif->change_error = false;
+            *dpifp = &dpif->dpif;
+        } else {
+            free(dpif);
+        }
+    } else {
+        error = errno;
+        VLOG_WARN("%s: open failed (%s)", fn, strerror(error));
+    }
+    free(fn);
+
+    return error;
+}
+
+static void
+dpif_linux_port_changed(const struct rtnetlink_change *change, void *dpif_)
+{
+    struct dpif_linux *dpif = dpif_;
+
+    if (change) {
+        if (change->master_ifindex == dpif->local_ifindex
+            && (change->nlmsg_type == RTM_NEWLINK
+                || change->nlmsg_type == RTM_DELLINK))
+        {
+            /* Our datapath changed, either adding a new port or deleting an
+             * existing one. */
+            if (!svec_contains(&dpif->changed_ports, change->ifname)) {
+                svec_add(&dpif->changed_ports, change->ifname);
+                svec_sort(&dpif->changed_ports);
+            }
+        }
+    } else {
+        dpif->change_error = true;
+    }
+}
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
new file mode 100644 (file)
index 0000000..1cc4ed4
--- /dev/null
@@ -0,0 +1,1393 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "dpif.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "csum.h"
+#include "dpif-provider.h"
+#include "flow.h"
+#include "hmap.h"
+#include "list.h"
+#include "netdev.h"
+#include "odp-util.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "queue.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_dpif_netdev
+
+/* Configuration parameters. */
+enum { N_QUEUES = 2 };          /* Number of queues for dpif_recv(). */
+enum { MAX_QUEUE_LEN = 100 };   /* Maximum number of packets per queue. */
+enum { N_GROUPS = 16 };         /* Number of port groups. */
+enum { MAX_PORTS = 256 };       /* Maximum number of ports. */
+enum { MAX_FLOWS = 65536 };     /* Maximum number of flows in flow table. */
+
+/* Enough headroom to add a vlan tag, plus an extra 2 bytes to allow IP
+ * headers to be aligned on a 4-byte boundary.  */
+enum { DP_NETDEV_HEADROOM = 2 + VLAN_HEADER_LEN };
+
+/* Datapath based on the network device interface from netdev.h. */
+struct dp_netdev {
+    struct list node;
+    int dp_idx;
+    int open_cnt;
+    bool destroyed;
+
+    bool drop_frags;            /* Drop all IP fragments, if true. */
+    struct ovs_queue queues[N_QUEUES]; /* Messages queued for dpif_recv(). */
+    struct hmap flow_table;     /* Flow table. */
+    struct odp_port_group groups[N_GROUPS];
+
+    /* Statistics. */
+    long long int n_frags;      /* Number of dropped IP fragments. */
+    long long int n_hit;        /* Number of flow table matches. */
+    long long int n_missed;     /* Number of flow table misses. */
+    long long int n_lost;       /* Number of misses not passed to client. */
+
+    /* Ports. */
+    int n_ports;
+    struct dp_netdev_port *ports[MAX_PORTS];
+    struct list port_list;
+    unsigned int serial;
+};
+
+/* A port in a netdev-based datapath. */
+struct dp_netdev_port {
+    int port_no;                /* Index into dp_netdev's 'ports'. */
+    struct list node;           /* Element in dp_netdev's 'port_list'. */
+    struct netdev *netdev;
+    bool internal;              /* Internal port (as ODP_PORT_INTERNAL)? */
+};
+
+/* A flow in dp_netdev's 'flow_table'. */
+struct dp_netdev_flow {
+    struct hmap_node node;      /* Element in dp_netdev's 'flow_table'. */
+    flow_t key;
+
+    /* Statistics. */
+       struct timeval used;        /* Last used time, in milliseconds. */
+       long long int packet_count; /* Number of packets matched. */
+       long long int byte_count;   /* Number of bytes matched. */
+       uint8_t ip_tos;             /* IP TOS value. */
+       uint16_t tcp_ctl;           /* Bitwise-OR of seen tcp_ctl values. */
+
+    /* Actions. */
+    union odp_action *actions;
+    unsigned int n_actions;
+};
+
+/* Interface to netdev-based datapath. */
+struct dpif_netdev {
+    struct dpif dpif;
+    struct dp_netdev *dp;
+    int listen_mask;
+    unsigned int dp_serial;
+};
+
+/* All netdev-based datapaths. */
+static struct dp_netdev *dp_netdevs[256];
+struct list dp_netdev_list = LIST_INITIALIZER(&dp_netdev_list);
+enum { N_DP_NETDEVS = ARRAY_SIZE(dp_netdevs) };
+
+/* Maximum port MTU seen so far. */
+static int max_mtu = ETH_PAYLOAD_MAX;
+
+static int get_port_by_number(struct dp_netdev *, uint16_t port_no,
+                              struct dp_netdev_port **portp);
+static int get_port_by_name(struct dp_netdev *, const char *devname,
+                            struct dp_netdev_port **portp);
+static void dp_netdev_free(struct dp_netdev *);
+static void dp_netdev_flow_flush(struct dp_netdev *);
+static int do_add_port(struct dp_netdev *, const char *devname, uint16_t flags,
+                       uint16_t port_no);
+static int do_del_port(struct dp_netdev *, uint16_t port_no);
+static int dp_netdev_output_control(struct dp_netdev *, const struct ofpbuf *,
+                                    int queue_no, int port_no, uint32_t arg);
+static int dp_netdev_execute_actions(struct dp_netdev *,
+                                     struct ofpbuf *, flow_t *,
+                                     const union odp_action *, int n);
+
+static struct dpif_netdev *
+dpif_netdev_cast(const struct dpif *dpif)
+{
+    dpif_assert_class(dpif, &dpif_netdev_class);
+    return CONTAINER_OF(dpif, struct dpif_netdev, dpif);
+}
+
+static struct dp_netdev *
+get_dp_netdev(const struct dpif *dpif)
+{
+    return dpif_netdev_cast(dpif)->dp;
+}
+
+static int
+name_to_dp_idx(const char *name)
+{
+    if (!strncmp(name, "dp", 2) && isdigit((unsigned char)name[2])) {
+        int dp_idx = atoi(name + 2);
+        if (dp_idx >= 0 && dp_idx < N_DP_NETDEVS) {
+            return dp_idx;
+        }
+    }
+    return -1;
+}
+
+static struct dp_netdev *
+find_dp_netdev(const char *name)
+{
+    int dp_idx;
+    size_t i;
+
+    dp_idx = name_to_dp_idx(name);
+    if (dp_idx >= 0) {
+        return dp_netdevs[dp_idx];
+    }
+
+    for (i = 0; i < N_DP_NETDEVS; i++) {
+        struct dp_netdev *dp = dp_netdevs[i];
+        if (dp) {
+            struct dp_netdev_port *port;
+            if (!get_port_by_name(dp, name, &port)) {
+                return dp;
+            }
+        }
+    }
+    return NULL;
+}
+
+static struct dpif *
+create_dpif_netdev(struct dp_netdev *dp)
+{
+    struct dpif_netdev *dpif;
+    char *dpname;
+
+    dp->open_cnt++;
+
+    dpname = xasprintf("dp%d", dp->dp_idx);
+    dpif = xmalloc(sizeof *dpif);
+    dpif_init(&dpif->dpif, &dpif_netdev_class, dpname, dp->dp_idx, dp->dp_idx);
+    dpif->dp = dp;
+    dpif->listen_mask = 0;
+    dpif->dp_serial = dp->serial;
+    free(dpname);
+
+    return &dpif->dpif;
+}
+
+static int
+create_dp_netdev(const char *name, int dp_idx, struct dpif **dpifp)
+{
+    struct dp_netdev *dp;
+    int error;
+    int i;
+
+    if (dp_netdevs[dp_idx]) {
+        return EBUSY;
+    }
+
+    /* Create datapath. */
+    dp_netdevs[dp_idx] = dp = xzalloc(sizeof *dp);
+    list_push_back(&dp_netdev_list, &dp->node);
+    dp->dp_idx = dp_idx;
+    dp->open_cnt = 0;
+    dp->drop_frags = false;
+    for (i = 0; i < N_QUEUES; i++) {
+        queue_init(&dp->queues[i]);
+    }
+    hmap_init(&dp->flow_table);
+    for (i = 0; i < N_GROUPS; i++) {
+        dp->groups[i].ports = NULL;
+        dp->groups[i].n_ports = 0;
+        dp->groups[i].group = i;
+    }
+    list_init(&dp->port_list);
+    error = do_add_port(dp, name, ODP_PORT_INTERNAL, ODPP_LOCAL);
+    if (error) {
+        dp_netdev_free(dp);
+        return ENODEV;
+    }
+
+    *dpifp = create_dpif_netdev(dp);
+    return 0;
+}
+
+static int
+dpif_netdev_open(const char *name, const char *type OVS_UNUSED, bool create,
+                 struct dpif **dpifp)
+{
+    if (create) {
+        if (find_dp_netdev(name)) {
+            return EEXIST;
+        } else {
+            int dp_idx = name_to_dp_idx(name);
+            if (dp_idx >= 0) {
+                return create_dp_netdev(name, dp_idx, dpifp);
+            } else {
+                /* Scan for unused dp_idx number. */
+                for (dp_idx = 0; dp_idx < N_DP_NETDEVS; dp_idx++) {
+                    int error = create_dp_netdev(name, dp_idx, dpifp);
+                    if (error != EBUSY) {
+                        return error;
+                    }
+                }
+
+                /* All datapath numbers in use. */
+                return ENOBUFS;
+            }
+        }
+    } else {
+        struct dp_netdev *dp = find_dp_netdev(name);
+        if (dp) {
+            *dpifp = create_dpif_netdev(dp);
+            return 0;
+        } else {
+            return ENODEV;
+        }
+    }
+}
+
+static void
+dp_netdev_free(struct dp_netdev *dp)
+{
+    int i;
+
+    dp_netdev_flow_flush(dp);
+    while (dp->n_ports > 0) {
+        struct dp_netdev_port *port = CONTAINER_OF(
+            dp->port_list.next, struct dp_netdev_port, node);
+        do_del_port(dp, port->port_no);
+    }
+    for (i = 0; i < N_QUEUES; i++) {
+        queue_destroy(&dp->queues[i]);
+    }
+    hmap_destroy(&dp->flow_table);
+    for (i = 0; i < N_GROUPS; i++) {
+        free(dp->groups[i].ports);
+    }
+    dp_netdevs[dp->dp_idx] = NULL;
+    list_remove(&dp->node);
+    free(dp);
+}
+
+static void
+dpif_netdev_close(struct dpif *dpif)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    assert(dp->open_cnt > 0);
+    if (--dp->open_cnt == 0 && dp->destroyed) {
+        dp_netdev_free(dp);
+    }
+    free(dpif);
+}
+
+static int
+dpif_netdev_destroy(struct dpif *dpif)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    dp->destroyed = true;
+    return 0;
+}
+
+static int
+dpif_netdev_get_stats(const struct dpif *dpif, struct odp_stats *stats)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    memset(stats, 0, sizeof *stats);
+    stats->n_flows = hmap_count(&dp->flow_table);
+    stats->cur_capacity = hmap_capacity(&dp->flow_table);
+    stats->max_capacity = MAX_FLOWS;
+    stats->n_ports = dp->n_ports;
+    stats->max_ports = MAX_PORTS;
+    stats->max_groups = N_GROUPS;
+    stats->n_frags = dp->n_frags;
+    stats->n_hit = dp->n_hit;
+    stats->n_missed = dp->n_missed;
+    stats->n_lost = dp->n_lost;
+    stats->max_miss_queue = MAX_QUEUE_LEN;
+    stats->max_action_queue = MAX_QUEUE_LEN;
+    return 0;
+}
+
+static int
+dpif_netdev_get_drop_frags(const struct dpif *dpif, bool *drop_fragsp)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    *drop_fragsp = dp->drop_frags;
+    return 0;
+}
+
+static int
+dpif_netdev_set_drop_frags(struct dpif *dpif, bool drop_frags)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    dp->drop_frags = drop_frags;
+    return 0;
+}
+
+static int
+do_add_port(struct dp_netdev *dp, const char *devname, uint16_t flags,
+            uint16_t port_no)
+{
+    bool internal = (flags & ODP_PORT_INTERNAL) != 0;
+    struct dp_netdev_port *port;
+    struct netdev_options netdev_options;
+    struct netdev *netdev;
+    int mtu;
+    int error;
+
+    /* XXX reject devices already in some dp_netdev. */
+
+    /* Open and validate network device. */
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = devname;
+    netdev_options.ethertype = NETDEV_ETH_TYPE_ANY;
+    netdev_options.may_create = true;
+    if (internal) {
+        netdev_options.type = "tap";
+    } else {
+        netdev_options.may_open = true;
+    }
+
+    error = netdev_open(&netdev_options, &netdev);
+    if (error) {
+        return error;
+    }
+    /* XXX reject loopback devices */
+    /* XXX reject non-Ethernet devices */
+
+    error = netdev_turn_flags_on(netdev, NETDEV_PROMISC, false);
+    if (error) {
+        netdev_close(netdev);
+        return error;
+    }
+
+    port = xmalloc(sizeof *port);
+    port->port_no = port_no;
+    port->netdev = netdev;
+    port->internal = internal;
+
+    netdev_get_mtu(netdev, &mtu);
+    if (mtu > max_mtu) {
+        max_mtu = mtu;
+    }
+
+    list_push_back(&dp->port_list, &port->node);
+    dp->ports[port_no] = port;
+    dp->n_ports++;
+    dp->serial++;
+
+    return 0;
+}
+
+static int
+dpif_netdev_port_add(struct dpif *dpif, const char *devname, uint16_t flags,
+                     uint16_t *port_nop)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    int port_no;
+
+    for (port_no = 0; port_no < MAX_PORTS; port_no++) {
+        if (!dp->ports[port_no]) {
+            *port_nop = port_no;
+            return do_add_port(dp, devname, flags, port_no);
+        }
+    }
+    return EFBIG;
+}
+
+static int
+dpif_netdev_port_del(struct dpif *dpif, uint16_t port_no)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    return port_no == ODPP_LOCAL ? EINVAL : do_del_port(dp, port_no);
+}
+
+static bool
+is_valid_port_number(uint16_t port_no)
+{
+    return port_no < MAX_PORTS;
+}
+
+static int
+get_port_by_number(struct dp_netdev *dp,
+                   uint16_t port_no, struct dp_netdev_port **portp)
+{
+    if (!is_valid_port_number(port_no)) {
+        *portp = NULL;
+        return EINVAL;
+    } else {
+        *portp = dp->ports[port_no];
+        return *portp ? 0 : ENOENT;
+    }
+}
+
+static int
+get_port_by_name(struct dp_netdev *dp,
+                 const char *devname, struct dp_netdev_port **portp)
+{
+    struct dp_netdev_port *port;
+
+    LIST_FOR_EACH (port, struct dp_netdev_port, node, &dp->port_list) {
+        if (!strcmp(netdev_get_name(port->netdev), devname)) {
+            *portp = port;
+            return 0;
+        }
+    }
+    return ENOENT;
+}
+
+static int
+do_del_port(struct dp_netdev *dp, uint16_t port_no)
+{
+    struct dp_netdev_port *port;
+    char *name;
+    int error;
+
+    error = get_port_by_number(dp, port_no, &port);
+    if (error) {
+        return error;
+    }
+
+    list_remove(&port->node);
+    dp->ports[port->port_no] = NULL;
+    dp->n_ports--;
+    dp->serial++;
+
+    name = xstrdup(netdev_get_name(port->netdev));
+    netdev_close(port->netdev);
+
+    free(name);
+    free(port);
+
+    return 0;
+}
+
+static void
+answer_port_query(const struct dp_netdev_port *port, struct odp_port *odp_port)
+{
+    memset(odp_port, 0, sizeof *odp_port);
+    ovs_strlcpy(odp_port->devname, netdev_get_name(port->netdev),
+                sizeof odp_port->devname);
+    odp_port->port = port->port_no;
+    odp_port->flags = port->internal ? ODP_PORT_INTERNAL : 0;
+}
+
+static int
+dpif_netdev_port_query_by_number(const struct dpif *dpif, uint16_t port_no,
+                                 struct odp_port *odp_port)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_port *port;
+    int error;
+
+    error = get_port_by_number(dp, port_no, &port);
+    if (!error) {
+        answer_port_query(port, odp_port);
+    }
+    return error;
+}
+
+static int
+dpif_netdev_port_query_by_name(const struct dpif *dpif, const char *devname,
+                               struct odp_port *odp_port)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_port *port;
+    int error;
+
+    error = get_port_by_name(dp, devname, &port);
+    if (!error) {
+        answer_port_query(port, odp_port);
+    }
+    return error;
+}
+
+static void
+dp_netdev_free_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
+{
+    hmap_remove(&dp->flow_table, &flow->node);
+    free(flow->actions);
+    free(flow);
+}
+
+static void
+dp_netdev_flow_flush(struct dp_netdev *dp)
+{
+    struct dp_netdev_flow *flow, *next;
+
+    HMAP_FOR_EACH_SAFE (flow, next, struct dp_netdev_flow, node,
+                        &dp->flow_table) {
+        dp_netdev_free_flow(dp, flow);
+    }
+}
+
+static int
+dpif_netdev_flow_flush(struct dpif *dpif)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    dp_netdev_flow_flush(dp);
+    return 0;
+}
+
+static int
+dpif_netdev_port_list(const struct dpif *dpif, struct odp_port *ports, int n)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_port *port;
+    int i;
+
+    i = 0;
+    LIST_FOR_EACH (port, struct dp_netdev_port, node, &dp->port_list) {
+        struct odp_port *odp_port = &ports[i];
+        if (i >= n) {
+            break;
+        }
+        answer_port_query(port, odp_port);
+        i++;
+    }
+    return dp->n_ports;
+}
+
+static int
+dpif_netdev_port_poll(const struct dpif *dpif_, char **devnamep OVS_UNUSED)
+{
+    struct dpif_netdev *dpif = dpif_netdev_cast(dpif_);
+    if (dpif->dp_serial != dpif->dp->serial) {
+        dpif->dp_serial = dpif->dp->serial;
+        return ENOBUFS;
+    } else {
+        return EAGAIN;
+    }
+}
+
+static void
+dpif_netdev_port_poll_wait(const struct dpif *dpif_)
+{
+    struct dpif_netdev *dpif = dpif_netdev_cast(dpif_);
+    if (dpif->dp_serial != dpif->dp->serial) {
+        poll_immediate_wake();
+    }
+}
+
+static int
+get_port_group(const struct dpif *dpif, int group_no,
+               struct odp_port_group **groupp)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+
+    if (group_no >= 0 && group_no < N_GROUPS) {
+        *groupp = &dp->groups[group_no];
+        return 0;
+    } else {
+        *groupp = NULL;
+        return EINVAL;
+    }
+}
+
+static int
+dpif_netdev_port_group_get(const struct dpif *dpif, int group_no,
+                           uint16_t ports[], int n)
+{
+    struct odp_port_group *group;
+    int error;
+
+    if (n < 0) {
+        return -EINVAL;
+    }
+
+    error = get_port_group(dpif, group_no, &group);
+    if (!error) {
+        memcpy(ports, group->ports, MIN(n, group->n_ports) * sizeof *ports);
+        return group->n_ports;
+    } else {
+        return -error;
+    }
+}
+
+static int
+dpif_netdev_port_group_set(struct dpif *dpif, int group_no,
+                           const uint16_t ports[], int n)
+{
+    struct odp_port_group *group;
+    int error;
+
+    if (n < 0 || n > MAX_PORTS) {
+        return EINVAL;
+    }
+
+    error = get_port_group(dpif, group_no, &group);
+    if (!error) {
+        free(group->ports);
+        group->ports = xmemdup(ports, n * sizeof *group->ports);
+        group->n_ports = n;
+        group->group = group_no;
+    }
+    return error;
+}
+
+static struct dp_netdev_flow *
+dp_netdev_lookup_flow(const struct dp_netdev *dp, const flow_t *key)
+{
+    struct dp_netdev_flow *flow;
+
+    assert(!key->reserved[0] && !key->reserved[1] && !key->reserved[2]);
+    HMAP_FOR_EACH_WITH_HASH (flow, struct dp_netdev_flow, node,
+                             flow_hash(key, 0), &dp->flow_table) {
+        if (flow_equal(&flow->key, key)) {
+            return flow;
+        }
+    }
+    return NULL;
+}
+
+static void
+answer_flow_query(struct dp_netdev_flow *flow, uint32_t query_flags,
+                  struct odp_flow *odp_flow)
+{
+    if (flow) {
+        odp_flow->key = flow->key;
+        odp_flow->stats.n_packets = flow->packet_count;
+        odp_flow->stats.n_bytes = flow->byte_count;
+        odp_flow->stats.used_sec = flow->used.tv_sec;
+        odp_flow->stats.used_nsec = flow->used.tv_usec * 1000;
+        odp_flow->stats.tcp_flags = TCP_FLAGS(flow->tcp_ctl);
+        odp_flow->stats.ip_tos = flow->ip_tos;
+        odp_flow->stats.error = 0;
+        if (odp_flow->n_actions > 0) {
+            unsigned int n = MIN(odp_flow->n_actions, flow->n_actions);
+            memcpy(odp_flow->actions, flow->actions,
+                   n * sizeof *odp_flow->actions);
+            odp_flow->n_actions = flow->n_actions;
+        }
+
+        if (query_flags & ODPFF_ZERO_TCP_FLAGS) {
+            flow->tcp_ctl = 0;
+        }
+
+    } else {
+        odp_flow->stats.error = ENOENT;
+    }
+}
+
+static int
+dpif_netdev_flow_get(const struct dpif *dpif, struct odp_flow flows[], int n)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    int i;
+
+    for (i = 0; i < n; i++) {
+        struct odp_flow *odp_flow = &flows[i];
+        answer_flow_query(dp_netdev_lookup_flow(dp, &odp_flow->key),
+                          odp_flow->flags, odp_flow);
+    }
+    return 0;
+}
+
+static int
+dpif_netdev_validate_actions(const union odp_action *actions, int n_actions,
+                             bool *mutates)
+{
+    unsigned int i;
+
+    *mutates = false;
+    for (i = 0; i < n_actions; i++) {
+        const union odp_action *a = &actions[i];
+        switch (a->type) {
+        case ODPAT_OUTPUT:
+            if (a->output.port >= MAX_PORTS) {
+                return EINVAL;
+            }
+            break;
+
+        case ODPAT_OUTPUT_GROUP:
+            *mutates = true;
+            if (a->output_group.group >= N_GROUPS) {
+                return EINVAL;
+            }
+            break;
+
+        case ODPAT_CONTROLLER:
+            break;
+
+        case ODPAT_SET_VLAN_VID:
+            *mutates = true;
+            if (a->vlan_vid.vlan_vid & htons(~VLAN_VID_MASK)) {
+                return EINVAL;
+            }
+            break;
+
+        case ODPAT_SET_VLAN_PCP:
+            *mutates = true;
+            if (a->vlan_pcp.vlan_pcp & ~(VLAN_PCP_MASK >> VLAN_PCP_SHIFT)) {
+                return EINVAL;
+            }
+            break;
+
+        case ODPAT_SET_NW_TOS:
+            *mutates = true;
+            if (a->nw_tos.nw_tos & IP_ECN_MASK) {
+                return EINVAL;
+            }
+            break;
+
+        case ODPAT_STRIP_VLAN:
+        case ODPAT_SET_DL_SRC:
+        case ODPAT_SET_DL_DST:
+        case ODPAT_SET_NW_SRC:
+        case ODPAT_SET_NW_DST:
+        case ODPAT_SET_TP_SRC:
+        case ODPAT_SET_TP_DST:
+            *mutates = true;
+            break;
+
+        default:
+            return EOPNOTSUPP;
+        }
+    }
+    return 0;
+}
+
+static int
+set_flow_actions(struct dp_netdev_flow *flow, struct odp_flow *odp_flow)
+{
+    size_t n_bytes;
+    bool mutates;
+    int error;
+
+    if (odp_flow->n_actions >= 4096 / sizeof *odp_flow->actions) {
+        return EINVAL;
+    }
+    error = dpif_netdev_validate_actions(odp_flow->actions,
+                                         odp_flow->n_actions, &mutates);
+    if (error) {
+        return error;
+    }
+
+    n_bytes = odp_flow->n_actions * sizeof *flow->actions;
+    flow->actions = xrealloc(flow->actions, n_bytes);
+    flow->n_actions = odp_flow->n_actions;
+    memcpy(flow->actions, odp_flow->actions, n_bytes);
+    return 0;
+}
+
+static int
+add_flow(struct dpif *dpif, struct odp_flow *odp_flow)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_flow *flow;
+    int error;
+
+    flow = xzalloc(sizeof *flow);
+    flow->key = odp_flow->key;
+    memset(flow->key.reserved, 0, sizeof flow->key.reserved);
+
+    error = set_flow_actions(flow, odp_flow);
+    if (error) {
+        free(flow);
+        return error;
+    }
+
+    hmap_insert(&dp->flow_table, &flow->node, flow_hash(&flow->key, 0));
+    return 0;
+}
+
+static void
+clear_stats(struct dp_netdev_flow *flow)
+{
+    flow->used.tv_sec = 0;
+    flow->used.tv_usec = 0;
+    flow->packet_count = 0;
+    flow->byte_count = 0;
+    flow->ip_tos = 0;
+    flow->tcp_ctl = 0;
+}
+
+static int
+dpif_netdev_flow_put(struct dpif *dpif, struct odp_flow_put *put)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_flow *flow;
+
+    flow = dp_netdev_lookup_flow(dp, &put->flow.key);
+    if (!flow) {
+        if (put->flags & ODPPF_CREATE) {
+            if (hmap_count(&dp->flow_table) < MAX_FLOWS) {
+                return add_flow(dpif, &put->flow);
+            } else {
+                return EFBIG;
+            }
+        } else {
+            return ENOENT;
+        }
+    } else {
+        if (put->flags & ODPPF_MODIFY) {
+            int error = set_flow_actions(flow, &put->flow);
+            if (!error && put->flags & ODPPF_ZERO_STATS) {
+                clear_stats(flow);
+            }
+            return error;
+        } else {
+            return EEXIST;
+        }
+    }
+}
+
+
+static int
+dpif_netdev_flow_del(struct dpif *dpif, struct odp_flow *odp_flow)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_flow *flow;
+
+    flow = dp_netdev_lookup_flow(dp, &odp_flow->key);
+    if (flow) {
+        answer_flow_query(flow, 0, odp_flow);
+        dp_netdev_free_flow(dp, flow);
+        return 0;
+    } else {
+        return ENOENT;
+    }
+}
+
+static int
+dpif_netdev_flow_list(const struct dpif *dpif, struct odp_flow flows[], int n)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct dp_netdev_flow *flow;
+    int i;
+
+    i = 0;
+    HMAP_FOR_EACH (flow, struct dp_netdev_flow, node, &dp->flow_table) {
+        if (i >= n) {
+            break;
+        }
+        answer_flow_query(flow, 0, &flows[i++]);
+    }
+    return hmap_count(&dp->flow_table);
+}
+
+static int
+dpif_netdev_execute(struct dpif *dpif, uint16_t in_port,
+                    const union odp_action actions[], int n_actions,
+                    const struct ofpbuf *packet)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    struct ofpbuf copy;
+    bool mutates;
+    flow_t flow;
+    int error;
+
+    if (packet->size < ETH_HEADER_LEN || packet->size > UINT16_MAX) {
+        return EINVAL;
+    }
+
+    error = dpif_netdev_validate_actions(actions, n_actions, &mutates);
+    if (error) {
+        return error;
+    }
+
+    if (mutates) {
+        /* We need a deep copy of 'packet' since we're going to modify its
+         * data. */
+        ofpbuf_init(&copy, DP_NETDEV_HEADROOM + packet->size);
+        copy.data = (char*)copy.base + DP_NETDEV_HEADROOM;
+        ofpbuf_put(&copy, packet->data, packet->size);
+    } else {
+        /* We still need a shallow copy of 'packet', even though we won't
+         * modify its data, because flow_extract() modifies packet->l2, etc.
+         * We could probably get away with modifying those but it's more polite
+         * if we don't. */
+        copy = *packet;
+    }
+    flow_extract(&copy, in_port, &flow);
+    error = dp_netdev_execute_actions(dp, &copy, &flow, actions, n_actions);
+    if (mutates) {
+        ofpbuf_uninit(&copy);
+    }
+    return error;
+}
+
+static int
+dpif_netdev_recv_get_mask(const struct dpif *dpif, int *listen_mask)
+{
+    struct dpif_netdev *dpif_netdev = dpif_netdev_cast(dpif);
+    *listen_mask = dpif_netdev->listen_mask;
+    return 0;
+}
+
+static int
+dpif_netdev_recv_set_mask(struct dpif *dpif, int listen_mask)
+{
+    struct dpif_netdev *dpif_netdev = dpif_netdev_cast(dpif);
+    if (!(listen_mask & ~ODPL_ALL)) {
+        dpif_netdev->listen_mask = listen_mask;
+        return 0;
+    } else {
+        return EINVAL;
+    }
+}
+
+static struct ovs_queue *
+find_nonempty_queue(struct dpif *dpif)
+{
+    struct dpif_netdev *dpif_netdev = dpif_netdev_cast(dpif);
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    int mask = dpif_netdev->listen_mask;
+    int i;
+
+    for (i = 0; i < N_QUEUES; i++) {
+        struct ovs_queue *q = &dp->queues[i];
+        if (q->n && mask & (1u << i)) {
+            return q;
+        }
+    }
+    return NULL;
+}
+
+static int
+dpif_netdev_recv(struct dpif *dpif, struct ofpbuf **bufp)
+{
+    struct ovs_queue *q = find_nonempty_queue(dpif);
+    if (q) {
+        *bufp = queue_pop_head(q);
+        return 0;
+    } else {
+        return EAGAIN;
+    }
+}
+
+static void
+dpif_netdev_recv_wait(struct dpif *dpif)
+{
+    struct ovs_queue *q = find_nonempty_queue(dpif);
+    if (q) {
+        poll_immediate_wake();
+    } else {
+        /* No messages ready to be received, and dp_wait() will ensure that we
+         * wake up to queue new messages, so there is nothing to do. */
+    }
+}
+\f
+static void
+dp_netdev_flow_used(struct dp_netdev_flow *flow, const flow_t *key,
+                    const struct ofpbuf *packet)
+{
+    time_timeval(&flow->used);
+    flow->packet_count++;
+    flow->byte_count += packet->size;
+    if (key->dl_type == htons(ETH_TYPE_IP)) {
+        struct ip_header *nh = packet->l3;
+        flow->ip_tos = nh->ip_tos;
+
+        if (key->nw_proto == IPPROTO_TCP) {
+            struct tcp_header *th = packet->l4;
+            flow->tcp_ctl |= th->tcp_ctl;
+        }
+    }
+}
+
+static void
+dp_netdev_port_input(struct dp_netdev *dp, struct dp_netdev_port *port,
+                     struct ofpbuf *packet)
+{
+    struct dp_netdev_flow *flow;
+    flow_t key;
+
+    if (flow_extract(packet, port->port_no, &key) && dp->drop_frags) {
+        dp->n_frags++;
+        return;
+    }
+
+    flow = dp_netdev_lookup_flow(dp, &key);
+    if (flow) {
+        dp_netdev_flow_used(flow, &key, packet);
+        dp_netdev_execute_actions(dp, packet, &key,
+                                  flow->actions, flow->n_actions);
+        dp->n_hit++;
+    } else {
+        dp->n_missed++;
+        dp_netdev_output_control(dp, packet, _ODPL_MISS_NR, port->port_no, 0);
+    }
+}
+
+static void
+dp_netdev_run(void)
+{
+    struct ofpbuf packet;
+    struct dp_netdev *dp;
+
+    ofpbuf_init(&packet, DP_NETDEV_HEADROOM + max_mtu);
+    LIST_FOR_EACH (dp, struct dp_netdev, node, &dp_netdev_list) {
+        struct dp_netdev_port *port;
+
+        LIST_FOR_EACH (port, struct dp_netdev_port, node, &dp->port_list) {
+            int error;
+
+            /* Reset packet contents. */
+            packet.data = (char*)packet.base + DP_NETDEV_HEADROOM;
+            packet.size = 0;
+
+            error = netdev_recv(port->netdev, &packet);
+            if (!error) {
+                dp_netdev_port_input(dp, port, &packet);
+            } else if (error != EAGAIN) {
+                struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_ERR_RL(&rl, "error receiving data from %s: %s",
+                            netdev_get_name(port->netdev), strerror(error));
+            }
+        }
+    }
+    ofpbuf_uninit(&packet);
+}
+
+static void
+dp_netdev_wait(void)
+{
+    struct dp_netdev *dp;
+
+    LIST_FOR_EACH (dp, struct dp_netdev, node, &dp_netdev_list) {
+        struct dp_netdev_port *port;
+        LIST_FOR_EACH (port, struct dp_netdev_port, node, &dp->port_list) {
+            netdev_recv_wait(port->netdev);
+        }
+    }
+}
+
+
+/* Modify the TCI field of 'packet'.  If a VLAN tag is not present, one
+ * is added with the TCI field set to 'tci'.  If a VLAN tag is present, 
+ * then 'mask' bits are cleared before 'tci' is logically OR'd into the
+ * TCI field.
+ *
+ * Note that the function does not ensure that 'tci' does not affect
+ * bits outside of 'mask'.
+ */
+static void
+dp_netdev_modify_vlan_tci(struct ofpbuf *packet, flow_t *key,
+                          uint16_t tci, uint16_t mask)
+{
+    struct vlan_eth_header *veh;
+
+    if (key->dl_vlan != htons(ODP_VLAN_NONE)) {
+        /* Clear 'mask' bits, but maintain other TCI bits. */
+        veh = packet->l2;
+        veh->veth_tci &= ~htons(mask);
+        veh->veth_tci |= htons(tci);
+    } else {
+        /* Insert new 802.1Q header. */
+        struct eth_header *eh = packet->l2;
+        struct vlan_eth_header tmp;
+        memcpy(tmp.veth_dst, eh->eth_dst, ETH_ADDR_LEN);
+        memcpy(tmp.veth_src, eh->eth_src, ETH_ADDR_LEN);
+        tmp.veth_type = htons(ETH_TYPE_VLAN);
+        tmp.veth_tci = htons(tci);
+        tmp.veth_next_type = eh->eth_type;
+
+        veh = ofpbuf_push_uninit(packet, VLAN_HEADER_LEN);
+        memcpy(veh, &tmp, sizeof tmp);
+        packet->l2 = (char*)packet->l2 - VLAN_HEADER_LEN;
+    }
+
+    key->dl_vlan = veh->veth_tci & htons(VLAN_VID_MASK);
+}
+
+static void
+dp_netdev_strip_vlan(struct ofpbuf *packet, flow_t *key)
+{
+    struct vlan_eth_header *veh = packet->l2;
+    if (veh->veth_type == htons(ETH_TYPE_VLAN)) {
+        struct eth_header tmp;
+
+        memcpy(tmp.eth_dst, veh->veth_dst, ETH_ADDR_LEN);
+        memcpy(tmp.eth_src, veh->veth_src, ETH_ADDR_LEN);
+        tmp.eth_type = veh->veth_next_type;
+
+        packet->size -= VLAN_HEADER_LEN;
+        packet->data = (char*)packet->data + VLAN_HEADER_LEN;
+        packet->l2 = (char*)packet->l2 + VLAN_HEADER_LEN;
+        memcpy(packet->data, &tmp, sizeof tmp);
+
+        key->dl_vlan = htons(ODP_VLAN_NONE);
+    }
+}
+
+static void
+dp_netdev_set_dl_src(struct ofpbuf *packet, flow_t *key,
+                     const uint8_t dl_addr[ETH_ADDR_LEN])
+{
+    struct eth_header *eh = packet->l2;
+    memcpy(eh->eth_src, dl_addr, sizeof eh->eth_src);
+    memcpy(key->dl_src, dl_addr, sizeof key->dl_src);
+}
+
+static void
+dp_netdev_set_dl_dst(struct ofpbuf *packet, flow_t *key,
+                     const uint8_t dl_addr[ETH_ADDR_LEN])
+{
+    struct eth_header *eh = packet->l2;
+    memcpy(eh->eth_dst, dl_addr, sizeof eh->eth_dst);
+    memcpy(key->dl_dst, dl_addr, sizeof key->dl_dst);
+}
+
+static void
+dp_netdev_set_nw_addr(struct ofpbuf *packet, flow_t *key,
+                      const struct odp_action_nw_addr *a)
+{
+    if (key->dl_type == htons(ETH_TYPE_IP)) {
+        struct ip_header *nh = packet->l3;
+        uint32_t *field;
+
+        field = a->type == ODPAT_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
+        if (key->nw_proto == IP_TYPE_TCP) {
+            struct tcp_header *th = packet->l4;
+            th->tcp_csum = recalc_csum32(th->tcp_csum, *field, a->nw_addr);
+        } else if (key->nw_proto == IP_TYPE_UDP) {
+            struct udp_header *uh = packet->l4;
+            if (uh->udp_csum) {
+                uh->udp_csum = recalc_csum32(uh->udp_csum, *field, a->nw_addr);
+                if (!uh->udp_csum) {
+                    uh->udp_csum = 0xffff;
+                }
+            }
+        }
+        nh->ip_csum = recalc_csum32(nh->ip_csum, *field, a->nw_addr);
+        *field = a->nw_addr;
+
+        if (a->type == ODPAT_SET_NW_SRC) {
+            key->nw_src = a->type;
+        } else {
+            key->nw_dst = a->type;
+        }
+    }
+}
+
+static void
+dp_netdev_set_nw_tos(struct ofpbuf *packet, flow_t *key,
+                     const struct odp_action_nw_tos *a)
+{
+    if (key->dl_type == htons(ETH_TYPE_IP)) {
+        struct ip_header *nh = packet->l3;
+        uint8_t *field = &nh->ip_tos;
+
+        /* Set the DSCP bits and preserve the ECN bits. */
+        uint8_t new = a->nw_tos | (nh->ip_tos & IP_ECN_MASK);
+
+        nh->ip_csum = recalc_csum16(nh->ip_csum, htons((uint16_t)*field),
+                htons((uint16_t)a->nw_tos));
+        *field = new;
+        key->nw_tos = a->nw_tos;
+    }
+}
+
+static void
+dp_netdev_set_tp_port(struct ofpbuf *packet, flow_t *key,
+                      const struct odp_action_tp_port *a)
+{
+       if (key->dl_type == htons(ETH_TYPE_IP)) {
+        uint16_t *field;
+        if (key->nw_proto == IPPROTO_TCP) {
+            struct tcp_header *th = packet->l4;
+            field = a->type == ODPAT_SET_TP_SRC ? &th->tcp_src : &th->tcp_dst;
+            th->tcp_csum = recalc_csum16(th->tcp_csum, *field, a->tp_port);
+            *field = a->tp_port;
+        } else if (key->nw_proto == IPPROTO_UDP) {
+            struct udp_header *uh = packet->l4;
+            field = a->type == ODPAT_SET_TP_SRC ? &uh->udp_src : &uh->udp_dst;
+            uh->udp_csum = recalc_csum16(uh->udp_csum, *field, a->tp_port);
+            *field = a->tp_port;
+        } else {
+            return;
+        }
+
+        if (a->type == ODPAT_SET_TP_SRC) {
+            key->tp_src = a->tp_port;
+        } else {
+            key->tp_dst = a->tp_port;
+        }
+    }
+}
+
+static void
+dp_netdev_output_port(struct dp_netdev *dp, struct ofpbuf *packet,
+                      uint16_t out_port)
+{
+       struct dp_netdev_port *p = dp->ports[out_port];
+    if (p) {
+        netdev_send(p->netdev, packet);
+    }
+}
+
+static void
+dp_netdev_output_group(struct dp_netdev *dp, uint16_t group, uint16_t in_port,
+                       struct ofpbuf *packet)
+{
+       struct odp_port_group *g = &dp->groups[group];
+       int i;
+
+       for (i = 0; i < g->n_ports; i++) {
+        uint16_t out_port = g->ports[i];
+        if (out_port != in_port) {
+            dp_netdev_output_port(dp, packet, out_port);
+        }
+       }
+}
+
+static int
+dp_netdev_output_control(struct dp_netdev *dp, const struct ofpbuf *packet,
+                         int queue_no, int port_no, uint32_t arg)
+{
+    struct ovs_queue *q = &dp->queues[queue_no];
+    struct odp_msg *header;
+    struct ofpbuf *msg;
+    size_t msg_size;
+
+    if (q->n >= MAX_QUEUE_LEN) {
+        dp->n_lost++;
+        return ENOBUFS;
+    }
+
+    msg_size = sizeof *header + packet->size;
+    msg = ofpbuf_new(msg_size);
+    header = ofpbuf_put_uninit(msg, sizeof *header);
+    header->type = queue_no;
+    header->length = msg_size;
+    header->port = port_no;
+    header->arg = arg;
+    ofpbuf_put(msg, packet->data, packet->size);
+    queue_push_tail(q, msg);
+
+    return 0;
+}
+
+static int
+dp_netdev_execute_actions(struct dp_netdev *dp,
+                          struct ofpbuf *packet, flow_t *key,
+                          const union odp_action *actions, int n_actions)
+{
+    int i;
+    for (i = 0; i < n_actions; i++) {
+        const union odp_action *a = &actions[i];
+
+               switch (a->type) {
+               case ODPAT_OUTPUT:
+            dp_netdev_output_port(dp, packet, a->output.port);
+                       break;
+
+               case ODPAT_OUTPUT_GROUP:
+                       dp_netdev_output_group(dp, a->output_group.group, key->in_port,
+                                   packet);
+                       break;
+
+               case ODPAT_CONTROLLER:
+            dp_netdev_output_control(dp, packet, _ODPL_ACTION_NR,
+                                     key->in_port, a->controller.arg);
+                       break;
+
+               case ODPAT_SET_VLAN_VID:
+                       dp_netdev_modify_vlan_tci(packet, key, ntohs(a->vlan_vid.vlan_vid),
+                                      VLAN_VID_MASK);
+            break;
+
+               case ODPAT_SET_VLAN_PCP:
+                       dp_netdev_modify_vlan_tci(
+                packet, key, a->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT,
+                VLAN_PCP_MASK);
+            break;
+
+               case ODPAT_STRIP_VLAN:
+                       dp_netdev_strip_vlan(packet, key);
+                       break;
+
+               case ODPAT_SET_DL_SRC:
+            dp_netdev_set_dl_src(packet, key, a->dl_addr.dl_addr);
+                       break;
+
+               case ODPAT_SET_DL_DST:
+            dp_netdev_set_dl_dst(packet, key, a->dl_addr.dl_addr);
+                       break;
+
+               case ODPAT_SET_NW_SRC:
+               case ODPAT_SET_NW_DST:
+                       dp_netdev_set_nw_addr(packet, key, &a->nw_addr);
+                       break;
+
+               case ODPAT_SET_NW_TOS:
+                       dp_netdev_set_nw_tos(packet, key, &a->nw_tos);
+                       break;
+
+               case ODPAT_SET_TP_SRC:
+               case ODPAT_SET_TP_DST:
+                       dp_netdev_set_tp_port(packet, key, &a->tp_port);
+                       break;
+               }
+       }
+    return 0;
+}
+
+const struct dpif_class dpif_netdev_class = {
+    "netdev",
+    dp_netdev_run,
+    dp_netdev_wait,
+    NULL,                       /* enumerate */
+    dpif_netdev_open,
+    dpif_netdev_close,
+    NULL,                       /* get_all_names */
+    dpif_netdev_destroy,
+    dpif_netdev_get_stats,
+    dpif_netdev_get_drop_frags,
+    dpif_netdev_set_drop_frags,
+    dpif_netdev_port_add,
+    dpif_netdev_port_del,
+    dpif_netdev_port_query_by_number,
+    dpif_netdev_port_query_by_name,
+    dpif_netdev_port_list,
+    dpif_netdev_port_poll,
+    dpif_netdev_port_poll_wait,
+    dpif_netdev_port_group_get,
+    dpif_netdev_port_group_set,
+    dpif_netdev_flow_get,
+    dpif_netdev_flow_put,
+    dpif_netdev_flow_del,
+    dpif_netdev_flow_flush,
+    dpif_netdev_flow_list,
+    dpif_netdev_execute,
+    dpif_netdev_recv_get_mask,
+    dpif_netdev_recv_set_mask,
+    NULL,                       /* get_sflow_probability */
+    NULL,                       /* set_sflow_probability */
+    dpif_netdev_recv,
+    dpif_netdev_recv_wait,
+};
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
new file mode 100644 (file)
index 0000000..fddc8ea
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DPIF_PROVIDER_H
+#define DPIF_PROVIDER_H 1
+
+/* Provider interface to dpifs, which provide an interface to an Open vSwitch
+ * datapath. */
+
+#include <assert.h>
+#include "dpif.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/* Open vSwitch datapath interface.
+ *
+ * This structure should be treated as opaque by dpif implementations. */
+struct dpif {
+    const struct dpif_class *dpif_class;
+    char *base_name;
+    char *full_name;
+    uint8_t netflow_engine_type;
+    uint8_t netflow_engine_id;
+};
+
+void dpif_init(struct dpif *, const struct dpif_class *, const char *name,
+               uint8_t netflow_engine_type, uint8_t netflow_engine_id);
+void dpif_uninit(struct dpif *dpif, bool close);
+
+static inline void dpif_assert_class(const struct dpif *dpif,
+                                     const struct dpif_class *dpif_class)
+{
+    assert(dpif->dpif_class == dpif_class);
+}
+
+/* Datapath interface class structure, to be defined by each implementation of
+ * a datapath interface.
+ *
+ * These functions return 0 if successful or a positive errno value on failure,
+ * except where otherwise noted.
+ *
+ * These functions are expected to execute synchronously, that is, to block as
+ * necessary to obtain a result.  Thus, they may not return EAGAIN or
+ * EWOULDBLOCK or EINPROGRESS.  We may relax this requirement in the future if
+ * and when we encounter performance problems. */
+struct dpif_class {
+    /* Type of dpif in this class, e.g. "system", "netdev", etc.
+     *
+     * One of the providers should supply a "system" type, since this is
+     * the type assumed if no type is specified when opening a dpif. */
+    const char *type;
+
+    /* Performs periodic work needed by dpifs of this class, if any is
+     * necessary. */
+    void (*run)(void);
+
+    /* Arranges for poll_block() to wake up if the "run" member function needs
+     * to be called. */
+    void (*wait)(void);
+
+    /* Enumerates the names of all known created datapaths, if possible, into
+     * 'all_dps'.  The caller has already initialized 'all_dps' and other dpif
+     * classes might already have added names to it.
+     *
+     * This is used by the vswitch at startup, so that it can delete any
+     * datapaths that are not configured.
+     *
+     * Some kinds of datapaths might not be practically enumerable, in which
+     * case this function may be a null pointer. */
+    int (*enumerate)(struct svec *all_dps);
+
+    /* Attempts to open an existing dpif called 'name', if 'create' is false,
+     * or to open an existing dpif or create a new one, if 'create' is true.
+     * 'type' corresponds to the 'type' field used in the dpif_class
+     * structure.
+     *
+     * If successful, stores a pointer to the new dpif in '*dpifp'.  On failure
+     * there are no requirements on what is stored in '*dpifp'. */
+    int (*open)(const char *name, const char *type, bool create,
+                struct dpif **dpifp);
+
+    /* Closes 'dpif' and frees associated memory. */
+    void (*close)(struct dpif *dpif);
+
+    /* Enumerates all names that may be used to open 'dpif' into 'all_names'.
+     * The Linux datapath, for example, supports opening a datapath both by
+     * number, e.g. "dp0", and by the name of the datapath's local port.  For
+     * some datapaths, this might be an infinite set (e.g. in a file name,
+     * slashes may be duplicated any number of times), in which case only the
+     * names most likely to be used should be enumerated.
+     *
+     * The caller has already initialized 'all_names' and might already have
+     * added some names to it.  This function should not disturb any existing
+     * names in 'all_names'.
+     *
+     * If a datapath class does not support multiple names for a datapath, this
+     * function may be a null pointer.
+     *
+     * This is used by the vswitch at startup, */
+    int (*get_all_names)(const struct dpif *dpif, struct svec *all_names);
+
+    /* Attempts to destroy the dpif underlying 'dpif'.
+     *
+     * If successful, 'dpif' will not be used again except as an argument for
+     * the 'close' member function. */
+    int (*destroy)(struct dpif *dpif);
+
+    /* Retrieves statistics for 'dpif' into 'stats'. */
+    int (*get_stats)(const struct dpif *dpif, struct odp_stats *stats);
+
+    /* Retrieves 'dpif''s current treatment of IP fragments into '*drop_frags':
+     * true indicates that fragments are dropped, false indicates that
+     * fragments are treated in the same way as other IP packets (except that
+     * the L4 header cannot be read). */
+    int (*get_drop_frags)(const struct dpif *dpif, bool *drop_frags);
+
+    /* Changes 'dpif''s treatment of IP fragments to 'drop_frags', whose
+     * meaning is the same as for the get_drop_frags member function. */
+    int (*set_drop_frags)(struct dpif *dpif, bool drop_frags);
+
+    /* Creates a new port in 'dpif' connected to network device 'devname'.
+     * 'flags' is a set of ODP_PORT_* flags.  If successful, sets '*port_no'
+     * to the new port's port number. */
+    int (*port_add)(struct dpif *dpif, const char *devname, uint16_t flags,
+                    uint16_t *port_no);
+
+    /* Removes port numbered 'port_no' from 'dpif'. */
+    int (*port_del)(struct dpif *dpif, uint16_t port_no);
+
+    /* Queries 'dpif' for a port with the given 'port_no' or 'devname'.  Stores
+     * information about the port into '*port' if successful. */
+    int (*port_query_by_number)(const struct dpif *dpif, uint16_t port_no,
+                                struct odp_port *port);
+    int (*port_query_by_name)(const struct dpif *dpif, const char *devname,
+                              struct odp_port *port);
+
+    /* Stores in 'ports' information about up to 'n' ports attached to 'dpif',
+     * in no particular order.  Returns the number of ports attached to 'dpif'
+     * (not the number stored), if successful, otherwise a negative errno
+     * value. */
+    int (*port_list)(const struct dpif *dpif, struct odp_port *ports, int n);
+
+    /* Polls for changes in the set of ports in 'dpif'.  If the set of ports in
+     * 'dpif' has changed, then this function should do one of the
+     * following:
+     *
+     * - Preferably: store the name of the device that was added to or deleted
+     *   from 'dpif' in '*devnamep' and return 0.  The caller is responsible
+     *   for freeing '*devnamep' (with free()) when it no longer needs it.
+     *
+     * - Alternatively: return ENOBUFS, without indicating the device that was
+     *   added or deleted.
+     *
+     * Occasional 'false positives', in which the function returns 0 while
+     * indicating a device that was not actually added or deleted or returns
+     * ENOBUFS without any change, are acceptable.
+     *
+     * If the set of ports in 'dpif' has not changed, returns EAGAIN.  May also
+     * return other positive errno values to indicate that something has gone
+     * wrong. */
+    int (*port_poll)(const struct dpif *dpif, char **devnamep);
+
+    /* Arranges for the poll loop to wake up when 'port_poll' will return a
+     * value other than EAGAIN. */
+    void (*port_poll_wait)(const struct dpif *dpif);
+
+    /* Stores in 'ports' the port numbers of up to 'n' ports that belong to
+     * 'group' in 'dpif'.  Returns the number of ports in 'group' (not the
+     * number stored), if successful, otherwise a negative errno value. */
+    int (*port_group_get)(const struct dpif *dpif, int group,
+                          uint16_t ports[], int n);
+
+    /* Changes port group 'group' in 'dpif' to consist of the 'n' ports whose
+     * numbers are given in 'ports'.
+     *
+     * Use the get_stats member function to obtain the number of supported port
+     * groups. */
+    int (*port_group_set)(struct dpif *dpif, int group,
+                          const uint16_t ports[], int n);
+
+    /* For each flow 'flow' in the 'n' flows in 'flows':
+     *
+     * - If a flow matching 'flow->key' exists in 'dpif':
+     *
+     *     Stores 0 into 'flow->stats.error' and stores statistics for the flow
+     *     into 'flow->stats'.
+     *
+     *     If 'flow->n_actions' is zero, then 'flow->actions' is ignored.  If
+     *     'flow->n_actions' is nonzero, then 'flow->actions' should point to
+     *     an array of the specified number of actions.  At most that many of
+     *     the flow's actions will be copied into that array.
+     *     'flow->n_actions' will be updated to the number of actions actually
+     *     present in the flow, which may be greater than the number stored if
+     *     the flow has more actions than space available in the array.
+     *
+     * - Flow-specific errors are indicated by a positive errno value in
+     *   'flow->stats.error'.  In particular, ENOENT indicates that no flow
+     *   matching 'flow->key' exists in 'dpif'.  When an error value is stored,
+     *   the contents of 'flow->key' are preserved but other members of 'flow'
+     *   should be treated as indeterminate.
+     *
+     * Returns 0 if all 'n' flows in 'flows' were updated (whether they were
+     * individually successful or not is indicated by 'flow->stats.error',
+     * however).  Returns a positive errno value if an error that prevented
+     * this update occurred, in which the caller must not depend on any
+     * elements in 'flows' being updated or not updated.
+     */
+    int (*flow_get)(const struct dpif *dpif, struct odp_flow flows[], int n);
+
+    /* Adds or modifies a flow in 'dpif' as specified in 'put':
+     *
+     * - If the flow specified in 'put->flow' does not exist in 'dpif', then
+     *   behavior depends on whether ODPPF_CREATE is specified in 'put->flags':
+     *   if it is, the flow will be added, otherwise the operation will fail
+     *   with ENOENT.
+     *
+     * - Otherwise, the flow specified in 'put->flow' does exist in 'dpif'.
+     *   Behavior in this case depends on whether ODPPF_MODIFY is specified in
+     *   'put->flags': if it is, the flow's actions will be updated, otherwise
+     *   the operation will fail with EEXIST.  If the flow's actions are
+     *   updated, then its statistics will be zeroed if ODPPF_ZERO_STATS is set
+     *   in 'put->flags', left as-is otherwise.
+     */
+    int (*flow_put)(struct dpif *dpif, struct odp_flow_put *put);
+
+    /* Deletes a flow matching 'flow->key' from 'dpif' or returns ENOENT if
+     * 'dpif' does not contain such a flow.
+     *
+     * If successful, updates 'flow->stats', 'flow->n_actions', and
+     * 'flow->actions' as described in more detail under the flow_get member
+     * function below. */
+    int (*flow_del)(struct dpif *dpif, struct odp_flow *flow);
+
+    /* Deletes all flows from 'dpif' and clears all of its queues of received
+     * packets. */
+    int (*flow_flush)(struct dpif *dpif);
+
+    /* Stores up to 'n' flows in 'dpif' into 'flows', updating their statistics
+     * and actions as described under the flow_get member function.  If
+     * successful, returns the number of flows actually present in 'dpif',
+     * which might be greater than the number stored (if 'dpif' has more than
+     * 'n' flows).  On failure, returns a negative errno value. */
+    int (*flow_list)(const struct dpif *dpif, struct odp_flow flows[], int n);
+
+    /* Performs the 'n_actions' actions in 'actions' on the Ethernet frame
+     * specified in 'packet'.
+     *
+     * Pretends that the frame was originally received on the port numbered
+     * 'in_port'.  This affects only ODPAT_OUTPUT_GROUP actions, which will not
+     * send a packet out their input port.  Specify the number of an unused
+     * port (e.g. UINT16_MAX is currently always unused) to avoid this
+     * behavior. */
+    int (*execute)(struct dpif *dpif, uint16_t in_port,
+                   const union odp_action actions[], int n_actions,
+                   const struct ofpbuf *packet);
+
+    /* Retrieves 'dpif''s "listen mask" into '*listen_mask'.  Each ODPL_* bit
+     * set in '*listen_mask' indicates the 'dpif' will receive messages of the
+     * corresponding type when it calls the recv member function. */
+    int (*recv_get_mask)(const struct dpif *dpif, int *listen_mask);
+
+    /* Sets 'dpif''s "listen mask" to 'listen_mask'.  Each ODPL_* bit set in
+     * 'listen_mask' indicates the 'dpif' will receive messages of the
+     * corresponding type when it calls the recv member function. */
+    int (*recv_set_mask)(struct dpif *dpif, int listen_mask);
+
+    /* Retrieves 'dpif''s sFlow sampling probability into '*probability'.
+     * Return value is 0 or a positive errno value.  EOPNOTSUPP indicates that
+     * the datapath does not support sFlow, as does a null pointer.
+     *
+     * '*probability' is expressed as the number of packets out of UINT_MAX to
+     * sample, e.g. probability/UINT_MAX is the probability of sampling a given
+     * packet. */
+    int (*get_sflow_probability)(const struct dpif *dpif,
+                                 uint32_t *probability);
+
+    /* Sets 'dpif''s sFlow sampling probability to 'probability'.  Return value
+     * is 0 or a positive errno value.  EOPNOTSUPP indicates that the datapath
+     * does not support sFlow, as does a null pointer.
+     *
+     * 'probability' is expressed as the number of packets out of UINT_MAX to
+     * sample, e.g. probability/UINT_MAX is the probability of sampling a given
+     * packet. */
+    int (*set_sflow_probability)(struct dpif *dpif, uint32_t probability);
+
+    /* Attempts to receive a message from 'dpif'.  If successful, stores the
+     * message into '*packetp'.  The message, if one is received, must begin
+     * with 'struct odp_msg' as a header.  Only messages of the types selected
+     * with the set_listen_mask member function should be received.
+     *
+     * This function must not block.  If no message is ready to be received
+     * when it is called, it should return EAGAIN without blocking. */
+    int (*recv)(struct dpif *dpif, struct ofpbuf **packetp);
+
+    /* Arranges for the poll loop to wake up when 'dpif' has a message queued
+     * to be received with the recv member function. */
+    void (*recv_wait)(struct dpif *dpif);
+};
+
+extern const struct dpif_class dpif_linux_class;
+extern const struct dpif_class dpif_netdev_class;
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* dpif-provider.h */
index 78e8ec3..315f11f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #include <config.h>
-#include "dpif.h"
+#include "dpif-provider.h"
 
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <inttypes.h>
-#include <net/if.h>
-#include <linux/rtnetlink.h>
-#include <linux/ethtool.h>
-#include <linux/sockios.h>
-#include <netinet/in.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <unistd.h>
 
 #include "coverage.h"
 #include "dynamic-string.h"
@@ -43,6 +33,7 @@
 #include "ofpbuf.h"
 #include "packets.h"
 #include "poll-loop.h"
+#include "shash.h"
 #include "svec.h"
 #include "util.h"
 #include "valgrind.h"
 #include "vlog.h"
 #define THIS_MODULE VLM_dpif
 
+static const struct dpif_class *base_dpif_classes[] = {
+    &dpif_linux_class,
+    &dpif_netdev_class,
+};
+
+struct registered_dpif_class {
+    struct dpif_class dpif_class;
+    int refcount;
+};
+static struct shash dpif_classes = SHASH_INITIALIZER(&dpif_classes);
+
 /* Rate limit for individual messages going to or from the datapath, output at
  * DBG level.  This is very high because, if these are enabled, it is because
  * we really need to see them. */
@@ -58,298 +60,455 @@ static struct vlog_rate_limit dpmsg_rl = VLOG_RATE_LIMIT_INIT(600, 600);
 /* Not really much point in logging many dpif errors. */
 static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(9999, 5);
 
-static int get_minor_from_name(const char *name, unsigned int *minor);
-static int name_to_minor(const char *name, unsigned int *minor);
-static int lookup_minor(const char *name, unsigned int *minor);
-static int open_by_minor(unsigned int minor, struct dpif *);
-static int make_openvswitch_device(unsigned int minor, char **fnp);
+static void log_operation(const struct dpif *, const char *operation,
+                          int error);
+static void log_flow_operation(const struct dpif *, const char *operation,
+                               int error, struct odp_flow *flow);
+static void log_flow_put(struct dpif *, int error,
+                         const struct odp_flow_put *);
+static bool should_log_flow_message(int error);
 static void check_rw_odp_flow(struct odp_flow *);
 
-
-/* Clears 'all_dps' and enumerates the names of all known created
- * datapaths into it.  Returns 0 if successful, otherwise a positive 
- * errno value. */
-int
-dp_enumerate(struct svec *all_dps)
+static void
+dp_initialize(void)
 {
-    int error;
-    int i;
+    static int status = -1;
 
-    svec_clear(all_dps);
-    error = 0;
-    for (i = 0; i < ODP_MAX; i++) {
-        struct dpif dpif;
-        char devname[16];
-        int retval;
+    if (status < 0) {
+        int i;
 
-        sprintf(devname, "dp%d", i);
-        retval = dpif_open(devname, &dpif);
-        if (!retval) {
-            svec_add(all_dps, devname);
-            dpif_close(&dpif);
-        } else if (retval != ENODEV && !error) {
-            error = retval;
+        status = 0;
+        for (i = 0; i < ARRAY_SIZE(base_dpif_classes); i++) {
+            dp_register_provider(base_dpif_classes[i]);
         }
     }
-    return error;
 }
 
-int
-dpif_open(const char *name, struct dpif *dpif)
+/* Performs periodic work needed by all the various kinds of dpifs.
+ *
+ * If your program opens any dpifs, it must call both this function and
+ * netdev_run() within its main poll loop. */
+void
+dp_run(void)
 {
-    int listen_mask;
-    int error;
-
-    dpif->fd = -1;
-
-    error = name_to_minor(name, &dpif->minor);
-    if (error) {
-        return error;
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        if (registered_class->dpif_class.run) {
+            registered_class->dpif_class.run();
+        }
     }
+}
 
-    error = open_by_minor(dpif->minor, dpif);
-    if (error) {
-        return error;
+/* Arranges for poll_block() to wake up when dp_run() needs to be called.
+ *
+ * If your program opens any dpifs, it must call both this function and
+ * netdev_wait() within its main poll loop. */
+void
+dp_wait(void)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        if (registered_class->dpif_class.wait) {
+            registered_class->dpif_class.wait();
+        }
     }
+}
 
-    /* We can open the device, but that doesn't mean that it's been created.
-     * If it hasn't been, then any command other than ODP_DP_CREATE will
-     * return ENODEV.  Try something innocuous. */
-    listen_mask = 0;            /* Make Valgrind happy. */
-    if (ioctl(dpif->fd, ODP_GET_LISTEN_MASK, &listen_mask)) {
-        error = errno;
-        if (error != ENODEV) {
-            VLOG_WARN("dp%u: probe returned unexpected error: %s",
-                      dpif->minor, strerror(error));
-        }
-        dpif_close(dpif);
-        return error;
+/* Registers a new datapath provider.  After successful registration, new
+ * datapaths of that type can be opened using dpif_open(). */
+int
+dp_register_provider(const struct dpif_class *new_class)
+{
+    struct registered_dpif_class *registered_class;
+
+    if (shash_find(&dpif_classes, new_class->type)) {
+        VLOG_WARN("attempted to register duplicate datapath provider: %s",
+                  new_class->type);
+        return EEXIST;
     }
+
+    registered_class = xmalloc(sizeof *registered_class);
+    memcpy(&registered_class->dpif_class, new_class,
+           sizeof registered_class->dpif_class);
+    registered_class->refcount = 0;
+
+    shash_add(&dpif_classes, new_class->type, registered_class);
+
     return 0;
 }
 
-void
-dpif_close(struct dpif *dpif)
+/* Unregisters a datapath provider.  'type' must have been previously
+ * registered and not currently be in use by any dpifs.  After unregistration
+ * new datapaths of that type cannot be opened using dpif_open(). */
+int
+dp_unregister_provider(const char *type)
 {
-    if (dpif) {
-        close(dpif->fd);
-        dpif->fd = -1;
+    struct shash_node *node;
+    struct registered_dpif_class *registered_class;
+
+    node = shash_find(&dpif_classes, type);
+    if (!node) {
+        VLOG_WARN("attempted to unregister a datapath provider that is not "
+                  "registered: %s", type);
+        return EAFNOSUPPORT;
+    }
+
+    registered_class = node->data;
+    if (registered_class->refcount) {
+        VLOG_WARN("attempted to unregister in use datapath provider: %s", type);
+        return EBUSY;
     }
+
+    shash_delete(&dpif_classes, node);
+    free(registered_class);
+
+    return 0;
 }
 
-static int
-do_ioctl(const struct dpif *dpif, int cmd, const char *cmd_name,
-         const void *arg)
+/* Clears 'types' and enumerates the types of all currently registered datapath
+ * providers into it.  The caller must first initialize the svec. */
+void
+dp_enumerate_types(struct svec *types)
 {
-    int error = ioctl(dpif->fd, cmd, arg) ? errno : 0;
-    if (cmd_name) {
-        if (error) {
-            VLOG_WARN_RL(&error_rl, "dp%u: ioctl(%s) failed (%s)",
-                         dpif->minor, cmd_name, strerror(error));
-        } else {
-            VLOG_DBG_RL(&dpmsg_rl, "dp%u: ioctl(%s): success",
-                        dpif->minor, cmd_name);
-        }
+    struct shash_node *node;
+
+    dp_initialize();
+    svec_clear(types);
+
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        svec_add(types, registered_class->dpif_class.type);
     }
-    return error;
 }
 
+/* Clears 'names' and enumerates the names of all known created datapaths with
+ * the given 'type'.  The caller must first initialize the svec. Returns 0 if
+ * successful, otherwise a positive errno value.
+ *
+ * Some kinds of datapaths might not be practically enumerable.  This is not
+ * considered an error. */
 int
-dpif_create(const char *name, struct dpif *dpif)
+dp_enumerate_names(const char *type, struct svec *names)
 {
-    unsigned int minor;
+    const struct registered_dpif_class *registered_class;
+    const struct dpif_class *dpif_class;
     int error;
 
-    if (!get_minor_from_name(name, &minor)) {
-        /* Minor was specified in 'name', go ahead and create it. */
-        error = open_by_minor(minor, dpif);
-        if (error) {
-            return error;
-        }
+    dp_initialize();
+    svec_clear(names);
 
-        if (!strncmp(name, "nl:", 3)) {
-            char devname[128];
-            sprintf(devname, "of%u", minor);
-            error = ioctl(dpif->fd, ODP_DP_CREATE, devname) < 0 ? errno : 0;
-        } else {
-            error = ioctl(dpif->fd, ODP_DP_CREATE, name) < 0 ? errno : 0;
-        }
-        if (error) {
-            dpif_close(dpif);
-        }
-        return error;
+    registered_class = shash_find_data(&dpif_classes, type);
+    if (!registered_class) {
+        VLOG_WARN("could not enumerate unknown type: %s", type);
+        return EAFNOSUPPORT;
+    }
+
+    dpif_class = &registered_class->dpif_class;
+    error = dpif_class->enumerate ? dpif_class->enumerate(names) : 0;
+
+    if (error) {
+        VLOG_WARN("failed to enumerate %s datapaths: %s", dpif_class->type,
+                   strerror(error));
+    }
+
+    return error;
+}
+
+/* Parses 'datapath name', which is of the form type@name into its
+ * component pieces.  'name' and 'type' must be freed by the caller. */
+void
+dp_parse_name(const char *datapath_name_, char **name, char **type)
+{
+    char *datapath_name = xstrdup(datapath_name_);
+    char *separator;
+
+    separator = strchr(datapath_name, '@');
+    if (separator) {
+        *separator = '\0';
+        *type = datapath_name;
+        *name = xstrdup(separator + 1);
     } else {
-        for (minor = 0; minor < ODP_MAX; minor++) {
-            error = open_by_minor(minor, dpif);
-            if (error) {
-                return error;
-            }
-
-            error = ioctl(dpif->fd, ODP_DP_CREATE, name) < 0 ? errno : 0;
-            if (!error) {
-                return 0;
-            }
-            dpif_close(dpif);
-            if (error != EBUSY) {
-                return error;
-            }
-        }
-        return ENOBUFS;
+        *name = datapath_name;
+        *type = NULL;
     }
 }
 
-int
-dpif_get_name(struct dpif *dpif, char *name, size_t name_size)
+static int
+do_open(const char *name, const char *type, bool create, struct dpif **dpifp)
 {
-    struct odp_port port;
+    struct dpif *dpif = NULL;
     int error;
+    struct registered_dpif_class *registered_class;
 
-    assert(name_size > 0);
-    *name = '\0';
+    dp_initialize();
+
+    if (!type || *type == '\0') {
+        type = "system";
+    }
+
+    registered_class = shash_find_data(&dpif_classes, type);
+    if (!registered_class) {
+        VLOG_WARN("could not create datapath %s of unknown type %s", name,
+                  type);
+        error = EAFNOSUPPORT;
+        goto exit;
+    }
 
-    error = dpif_port_query_by_number(dpif, ODPP_LOCAL, &port);
+    error = registered_class->dpif_class.open(name, type, create, &dpif);
     if (!error) {
-        ovs_strlcpy(name, port.devname, name_size);
+        registered_class->refcount++;
     }
+
+exit:
+    *dpifp = error ? NULL : dpif;
     return error;
 }
 
+/* Tries to open an existing datapath named 'name' and type 'type'.  Will fail
+ * if no datapath with 'name' and 'type' exists.  'type' may be either NULL or
+ * the empty string to specify the default system type.  Returns 0 if
+ * successful, otherwise a positive errno value.  On success stores a pointer
+ * to the datapath in '*dpifp', otherwise a null pointer. */
 int
-dpif_delete(struct dpif *dpif)
+dpif_open(const char *name, const char *type, struct dpif **dpifp)
 {
-    COVERAGE_INC(dpif_destroy);
-    return do_ioctl(dpif, ODP_DP_DESTROY, "ODP_DP_DESTROY", NULL);
+    return do_open(name, type, false, dpifp);
 }
 
+/* Tries to create and open a new datapath with the given 'name' and 'type'.
+ * 'type' may be either NULL or the empty string to specify the default system
+ * type.  Will fail if a datapath with 'name' and 'type' already exists.
+ * Returns 0 if successful, otherwise a positive errno value.  On success
+ * stores a pointer to the datapath in '*dpifp', otherwise a null pointer. */
 int
-dpif_get_dp_stats(const struct dpif *dpif, struct odp_stats *stats)
+dpif_create(const char *name, const char *type, struct dpif **dpifp)
 {
-    memset(stats, 0, sizeof *stats);
-    return do_ioctl(dpif, ODP_DP_STATS, "ODP_DP_STATS", stats);
+    return do_open(name, type, true, dpifp);
 }
 
+/* Tries to open a datapath with the given 'name' and 'type', creating it if it
+ * does not exist.  'type' may be either NULL or the empty string to specify
+ * the default system type.  Returns 0 if successful, otherwise a positive
+ * errno value. On success stores a pointer to the datapath in '*dpifp',
+ * otherwise a null pointer. */
 int
-dpif_get_drop_frags(const struct dpif *dpif, bool *drop_frags)
+dpif_create_and_open(const char *name, const char *type, struct dpif **dpifp)
 {
-    int tmp;
-    int error = do_ioctl(dpif, ODP_GET_DROP_FRAGS, "ODP_GET_DROP_FRAGS", &tmp);
-    *drop_frags = error ? tmp & 1 : false;
+    int error;
+
+    error = dpif_create(name, type, dpifp);
+    if (error == EEXIST || error == EBUSY) {
+        error = dpif_open(name, type, dpifp);
+        if (error) {
+            VLOG_WARN("datapath %s already exists but cannot be opened: %s",
+                      name, strerror(error));
+        }
+    } else if (error) {
+        VLOG_WARN("failed to create datapath %s: %s", name, strerror(error));
+    }
     return error;
 }
 
-int
-dpif_set_drop_frags(struct dpif *dpif, bool drop_frags)
+/* Closes and frees the connection to 'dpif'.  Does not destroy the datapath
+ * itself; call dpif_delete() first, instead, if that is desirable. */
+void
+dpif_close(struct dpif *dpif)
 {
-    int tmp = drop_frags;
-    return do_ioctl(dpif, ODP_SET_DROP_FRAGS, "ODP_SET_DROP_FRAGS", &tmp);
+    if (dpif) {
+        struct registered_dpif_class *registered_class;
+
+        registered_class = shash_find_data(&dpif_classes, 
+                dpif->dpif_class->type);
+        assert(registered_class);
+        assert(registered_class->refcount);
+
+        registered_class->refcount--;
+        dpif_uninit(dpif, true);
+    }
 }
 
-int
-dpif_get_listen_mask(const struct dpif *dpif, int *listen_mask)
+/* Returns the name of datapath 'dpif' prefixed with the type
+ * (for use in log messages). */
+const char *
+dpif_name(const struct dpif *dpif)
 {
-    int error = do_ioctl(dpif, ODP_GET_LISTEN_MASK, "ODP_GET_LISTEN_MASK",
-                         listen_mask);
-    if (error) {
-        *listen_mask = 0;
-    }
-    return error;
+    return dpif->full_name;
 }
 
+/* Returns the name of datapath 'dpif' without the type
+ * (for use in device names). */
+const char *
+dpif_base_name(const struct dpif *dpif)
+{
+    return dpif->base_name;
+}
+
+/* Enumerates all names that may be used to open 'dpif' into 'all_names'.  The
+ * Linux datapath, for example, supports opening a datapath both by number,
+ * e.g. "dp0", and by the name of the datapath's local port.  For some
+ * datapaths, this might be an infinite set (e.g. in a file name, slashes may
+ * be duplicated any number of times), in which case only the names most likely
+ * to be used will be enumerated.
+ *
+ * The caller must already have initialized 'all_names'.  Any existing names in
+ * 'all_names' will not be disturbed. */
 int
-dpif_set_listen_mask(struct dpif *dpif, int listen_mask)
+dpif_get_all_names(const struct dpif *dpif, struct svec *all_names)
 {
-    return do_ioctl(dpif, ODP_SET_LISTEN_MASK, "ODP_SET_LISTEN_MASK",
-                    &listen_mask);
+    if (dpif->dpif_class->get_all_names) {
+        int error = dpif->dpif_class->get_all_names(dpif, all_names);
+        if (error) {
+            VLOG_WARN_RL(&error_rl,
+                         "failed to retrieve names for datpath %s: %s",
+                         dpif_name(dpif), strerror(error));
+        }
+        return error;
+    } else {
+        svec_add(all_names, dpif_base_name(dpif));
+        return 0;
+    }
 }
 
+/* Destroys the datapath that 'dpif' is connected to, first removing all of its
+ * ports.  After calling this function, it does not make sense to pass 'dpif'
+ * to any functions other than dpif_name() or dpif_close(). */
 int
-dpif_purge(struct dpif *dpif)
+dpif_delete(struct dpif *dpif)
 {
-    struct odp_stats stats;
-    unsigned int i;
     int error;
 
-    COVERAGE_INC(dpif_purge);
+    COVERAGE_INC(dpif_destroy);
 
-    error = dpif_get_dp_stats(dpif, &stats);
+    error = dpif->dpif_class->destroy(dpif);
+    log_operation(dpif, "delete", error);
+    return error;
+}
+
+/* Retrieves statistics for 'dpif' into 'stats'.  Returns 0 if successful,
+ * otherwise a positive errno value. */
+int
+dpif_get_dp_stats(const struct dpif *dpif, struct odp_stats *stats)
+{
+    int error = dpif->dpif_class->get_stats(dpif, stats);
     if (error) {
-        return error;
+        memset(stats, 0, sizeof *stats);
     }
+    log_operation(dpif, "get_stats", error);
+    return error;
+}
 
-    for (i = 0; i < stats.max_miss_queue + stats.max_action_queue; i++) {
-        struct ofpbuf *buf;
-        error = dpif_recv(dpif, &buf);
-        if (error) {
-            return error == EAGAIN ? 0 : error;
-        }
-        ofpbuf_delete(buf);
+/* Retrieves the current IP fragment handling policy for 'dpif' into
+ * '*drop_frags': true indicates that fragments are dropped, false indicates
+ * that fragments are treated in the same way as other IP packets (except that
+ * the L4 header cannot be read).  Returns 0 if successful, otherwise a
+ * positive errno value. */
+int
+dpif_get_drop_frags(const struct dpif *dpif, bool *drop_frags)
+{
+    int error = dpif->dpif_class->get_drop_frags(dpif, drop_frags);
+    if (error) {
+        *drop_frags = false;
     }
-    return 0;
+    log_operation(dpif, "get_drop_frags", error);
+    return error;
 }
 
+/* Changes 'dpif''s treatment of IP fragments to 'drop_frags', whose meaning is
+ * the same as for the get_drop_frags member function.  Returns 0 if
+ * successful, otherwise a positive errno value. */
 int
-dpif_port_add(struct dpif *dpif, const char *devname, uint16_t port_no,
-              uint16_t flags)
+dpif_set_drop_frags(struct dpif *dpif, bool drop_frags)
 {
-    struct odp_port port;
+    int error = dpif->dpif_class->set_drop_frags(dpif, drop_frags);
+    log_operation(dpif, "set_drop_frags", error);
+    return error;
+}
+
+/* Attempts to add 'devname' as a port on 'dpif', given the combination of
+ * ODP_PORT_* flags in 'flags'.  If successful, returns 0 and sets '*port_nop'
+ * to the new port's port number (if 'port_nop' is non-null).  On failure,
+ * returns a positive errno value and sets '*port_nop' to UINT16_MAX (if
+ * 'port_nop' is non-null). */
+int
+dpif_port_add(struct dpif *dpif, const char *devname, uint16_t flags,
+              uint16_t *port_nop)
+{
+    uint16_t port_no;
+    int error;
 
     COVERAGE_INC(dpif_port_add);
-    memset(&port, 0, sizeof port);
-    strncpy(port.devname, devname, sizeof port.devname);
-    port.port = port_no;
-    port.flags = flags;
-    if (!ioctl(dpif->fd, ODP_PORT_ADD, &port)) {
-        VLOG_DBG_RL(&dpmsg_rl, "dp%u: added %s as port %"PRIu16,
-                    dpif->minor, devname, port_no);
-        return 0;
+
+    error = dpif->dpif_class->port_add(dpif, devname, flags, &port_no);
+    if (!error) {
+        VLOG_DBG_RL(&dpmsg_rl, "%s: added %s as port %"PRIu16,
+                    dpif_name(dpif), devname, port_no);
     } else {
-        VLOG_WARN_RL(&error_rl, "dp%u: failed to add %s as port "
-                     "%"PRIu16": %s", dpif->minor, devname, port_no,
-                     strerror(errno));
-        return errno;
+        VLOG_WARN_RL(&error_rl, "%s: failed to add %s as port: %s",
+                     dpif_name(dpif), devname, strerror(error));
+        port_no = UINT16_MAX;
     }
+    if (port_nop) {
+        *port_nop = port_no;
+    }
+    return error;
 }
 
+/* Attempts to remove 'dpif''s port number 'port_no'.  Returns 0 if successful,
+ * otherwise a positive errno value. */
 int
 dpif_port_del(struct dpif *dpif, uint16_t port_no)
 {
-    int tmp = port_no;
+    int error;
+
     COVERAGE_INC(dpif_port_del);
-    return do_ioctl(dpif, ODP_PORT_DEL, "ODP_PORT_DEL", &tmp);
+
+    error = dpif->dpif_class->port_del(dpif, port_no);
+    log_operation(dpif, "port_del", error);
+    return error;
 }
 
+/* Looks up port number 'port_no' in 'dpif'.  On success, returns 0 and
+ * initializes '*port' appropriately; on failure, returns a positive errno
+ * value. */
 int
 dpif_port_query_by_number(const struct dpif *dpif, uint16_t port_no,
                           struct odp_port *port)
 {
-    memset(port, 0, sizeof *port);
-    port->port = port_no;
-    if (!ioctl(dpif->fd, ODP_PORT_QUERY, port)) {
-        VLOG_DBG_RL(&dpmsg_rl, "dp%u: port %"PRIu16" is device %s",
-                    dpif->minor, port_no, port->devname);
-        return 0;
+    int error = dpif->dpif_class->port_query_by_number(dpif, port_no, port);
+    if (!error) {
+        VLOG_DBG_RL(&dpmsg_rl, "%s: port %"PRIu16" is device %s",
+                    dpif_name(dpif), port_no, port->devname);
     } else {
-        VLOG_WARN_RL(&error_rl, "dp%u: failed to query port %"PRIu16": %s",
-                     dpif->minor, port_no, strerror(errno));
-        return errno;
+        memset(port, 0, sizeof *port);
+        VLOG_WARN_RL(&error_rl, "%s: failed to query port %"PRIu16": %s",
+                     dpif_name(dpif), port_no, strerror(error));
     }
+    return error;
 }
 
+/* Looks up port named 'devname' in 'dpif'.  On success, returns 0 and
+ * initializes '*port' appropriately; on failure, returns a positive errno
+ * value. */
 int
 dpif_port_query_by_name(const struct dpif *dpif, const char *devname,
                         struct odp_port *port)
 {
-    memset(port, 0, sizeof *port);
-    strncpy(port->devname, devname, sizeof port->devname);
-    if (!ioctl(dpif->fd, ODP_PORT_QUERY, port)) {
-        VLOG_DBG_RL(&dpmsg_rl, "dp%u: device %s is on port %"PRIu16,
-                    dpif->minor, devname, port->port);
-        return 0;
+    int error = dpif->dpif_class->port_query_by_name(dpif, devname, port);
+    if (!error) {
+        VLOG_DBG_RL(&dpmsg_rl, "%s: device %s is on port %"PRIu16,
+                    dpif_name(dpif), devname, port->port);
     } else {
-        VLOG_WARN_RL(&error_rl, "dp%u: failed to query port %s: %s",
-                     dpif->minor, devname, strerror(errno));
-        return errno;
+        memset(port, 0, sizeof *port);
+
+        /* Log level is DBG here because all the current callers are interested
+         * in whether 'dpif' actually has a port 'devname', so that it's not an
+         * issue worth logging if it doesn't. */
+        VLOG_DBG_RL(&error_rl, "%s: failed to query port %s: %s",
+                    dpif_name(dpif), devname, strerror(error));
     }
+    return error;
 }
 
 /* Looks up port number 'port_no' in 'dpif'.  On success, returns 0 and copies
@@ -374,209 +533,314 @@ dpif_port_get_name(struct dpif *dpif, uint16_t port_no,
     return error;
 }
 
+/* Obtains a list of all the ports in 'dpif'.
+ *
+ * If successful, returns 0 and sets '*portsp' to point to an array of
+ * appropriately initialized port structures and '*n_portsp' to the number of
+ * ports in the array.  The caller is responsible for freeing '*portp' by
+ * calling free().
+ *
+ * On failure, returns a positive errno value and sets '*portsp' to NULL and
+ * '*n_portsp' to 0. */
 int
 dpif_port_list(const struct dpif *dpif,
-               struct odp_port **ports, size_t *n_ports)
+               struct odp_port **portsp, size_t *n_portsp)
 {
-    struct odp_portvec pv;
-    struct odp_stats stats;
+    struct odp_port *ports;
+    size_t n_ports = 0;
     int error;
 
-    do {
+    for (;;) {
+        struct odp_stats stats;
+        int retval;
+
         error = dpif_get_dp_stats(dpif, &stats);
         if (error) {
-            goto error;
+            goto exit;
         }
 
-        *ports = xcalloc(1, stats.n_ports * sizeof **ports);
-        pv.ports = *ports;
-        pv.n_ports = stats.n_ports;
-        error = do_ioctl(dpif, ODP_PORT_LIST, "ODP_PORT_LIST", &pv);
-        if (error) {
-            free(*ports);
-            goto error;
+        ports = xcalloc(stats.n_ports, sizeof *ports);
+        retval = dpif->dpif_class->port_list(dpif, ports, stats.n_ports);
+        if (retval < 0) {
+            /* Hard error. */
+            error = -retval;
+            free(ports);
+            goto exit;
+        } else if (retval <= stats.n_ports) {
+            /* Success. */
+            error = 0;
+            n_ports = retval;
+            goto exit;
+        } else {
+            /* Soft error: port count increased behind our back.  Try again. */
+            free(ports);
         }
-    } while (pv.n_ports != stats.n_ports);
-    *n_ports = pv.n_ports;
-    return 0;
+    }
 
-error:
-    *ports = NULL;
-    *n_ports = 0;
+exit:
+    if (error) {
+        *portsp = NULL;
+        *n_portsp = 0;
+    } else {
+        *portsp = ports;
+        *n_portsp = n_ports;
+    }
+    log_operation(dpif, "port_list", error);
     return error;
 }
 
+/* Polls for changes in the set of ports in 'dpif'.  If the set of ports in
+ * 'dpif' has changed, this function does one of the following:
+ *
+ * - Stores the name of the device that was added to or deleted from 'dpif' in
+ *   '*devnamep' and returns 0.  The caller is responsible for freeing
+ *   '*devnamep' (with free()) when it no longer needs it.
+ *
+ * - Returns ENOBUFS and sets '*devnamep' to NULL.
+ *
+ * This function may also return 'false positives', where it returns 0 and
+ * '*devnamep' names a device that was not actually added or deleted or it
+ * returns ENOBUFS without any change.
+ *
+ * Returns EAGAIN if the set of ports in 'dpif' has not changed.  May also
+ * return other positive errno values to indicate that something has gone
+ * wrong. */
 int
-dpif_port_group_set(struct dpif *dpif, uint16_t group,
-                    const uint16_t ports[], size_t n_ports)
+dpif_port_poll(const struct dpif *dpif, char **devnamep)
 {
-    struct odp_port_group pg;
+    int error = dpif->dpif_class->port_poll(dpif, devnamep);
+    if (error) {
+        *devnamep = NULL;
+    }
+    return error;
+}
 
-    COVERAGE_INC(dpif_port_group_set);
-    assert(n_ports <= UINT16_MAX);
-    pg.group = group;
-    pg.ports = (uint16_t *) ports;
-    pg.n_ports = n_ports;
-    return do_ioctl(dpif, ODP_PORT_GROUP_SET, "ODP_PORT_GROUP_SET", &pg);
+/* Arranges for the poll loop to wake up when port_poll(dpif) will return a
+ * value other than EAGAIN. */
+void
+dpif_port_poll_wait(const struct dpif *dpif)
+{
+    dpif->dpif_class->port_poll_wait(dpif);
 }
 
-/* Careful: '*n_out' can be greater than 'n_ports' on return, if 'n_ports' is
- * less than the number of ports in 'group'. */
+/* Retrieves a list of the port numbers in port group 'group' in 'dpif'.
+ *
+ * On success, returns 0 and points '*ports' to a newly allocated array of
+ * integers, each of which is a 'dpif' port number for a port in
+ * 'group'.  Stores the number of elements in the array in '*n_ports'.  The
+ * caller is responsible for freeing '*ports' by calling free().
+ *
+ * On failure, returns a positive errno value and sets '*ports' to NULL and
+ * '*n_ports' to 0. */
 int
 dpif_port_group_get(const struct dpif *dpif, uint16_t group,
-                    uint16_t ports[], size_t n_ports, size_t *n_out)
+                    uint16_t **ports, size_t *n_ports)
 {
-    struct odp_port_group pg;
     int error;
 
-    assert(n_ports <= UINT16_MAX);
-    pg.group = group;
-    pg.ports = ports;
-    pg.n_ports = n_ports;
-    error = do_ioctl(dpif, ODP_PORT_GROUP_GET, "ODP_PORT_GROUP_GET", &pg);
-    *n_out = error ? 0 : pg.n_ports;
+    *ports = NULL;
+    *n_ports = 0;
+    for (;;) {
+        int retval = dpif->dpif_class->port_group_get(dpif, group,
+                                                      *ports, *n_ports);
+        if (retval < 0) {
+            /* Hard error. */
+            error = -retval;
+            free(*ports);
+            *ports = NULL;
+            *n_ports = 0;
+            break;
+        } else if (retval <= *n_ports) {
+            /* Success. */
+            error = 0;
+            *n_ports = retval;
+            break;
+        } else {
+            /* Soft error: there were more ports than we expected in the
+             * group.  Try again. */
+            free(*ports);
+            *ports = xcalloc(retval, sizeof **ports);
+            *n_ports = retval;
+        }
+    }
+    log_operation(dpif, "port_group_get", error);
     return error;
 }
 
+/* Updates port group 'group' in 'dpif', making it contain the 'n_ports' ports
+ * whose 'dpif' port numbers are given in 'n_ports'.  Returns 0 if
+ * successful, otherwise a positive errno value.
+ *
+ * Behavior is undefined if the values in ports[] are not unique. */
 int
-dpif_flow_flush(struct dpif *dpif)
+dpif_port_group_set(struct dpif *dpif, uint16_t group,
+                    const uint16_t ports[], size_t n_ports)
 {
-    COVERAGE_INC(dpif_flow_flush);
-    return do_ioctl(dpif, ODP_FLOW_FLUSH, "ODP_FLOW_FLUSH", NULL);
-}
+    int error;
 
-static enum vlog_level
-flow_message_log_level(int error)
-{
-    return error ? VLL_WARN : VLL_DBG;
+    COVERAGE_INC(dpif_port_group_set);
+
+    error = dpif->dpif_class->port_group_set(dpif, group, ports, n_ports);
+    log_operation(dpif, "port_group_set", error);
+    return error;
 }
 
-static bool
-should_log_flow_message(int error)
+/* Deletes all flows from 'dpif'.  Returns 0 if successful, otherwise a
+ * positive errno value.  */
+int
+dpif_flow_flush(struct dpif *dpif)
 {
-    return !vlog_should_drop(THIS_MODULE, flow_message_log_level(error),
-                             error ? &error_rl : &dpmsg_rl);
+    int error;
+
+    COVERAGE_INC(dpif_flow_flush);
+
+    error = dpif->dpif_class->flow_flush(dpif);
+    log_operation(dpif, "flow_flush", error);
+    return error;
 }
 
-static void
-log_flow_message(const struct dpif *dpif, int error,
-                 const char *operation,
-                 const flow_t *flow, const struct odp_flow_stats *stats,
-                 const union odp_action *actions, size_t n_actions)
+/* Queries 'dpif' for a flow entry matching 'flow->key'.
+ *
+ * If a flow matching 'flow->key' exists in 'dpif', stores statistics for the
+ * flow into 'flow->stats'.  If 'flow->n_actions' is zero, then 'flow->actions'
+ * is ignored.  If 'flow->n_actions' is nonzero, then 'flow->actions' should
+ * point to an array of the specified number of actions.  At most that many of
+ * the flow's actions will be copied into that array.  'flow->n_actions' will
+ * be updated to the number of actions actually present in the flow, which may
+ * be greater than the number stored if the flow has more actions than space
+ * available in the array.
+ *
+ * If no flow matching 'flow->key' exists in 'dpif', returns ENOENT.  On other
+ * failure, returns a positive errno value. */
+int
+dpif_flow_get(const struct dpif *dpif, struct odp_flow *flow)
 {
-    struct ds ds = DS_EMPTY_INITIALIZER;
-    ds_put_format(&ds, "dp%u: ", dpif->minor);
-    if (error) {
-        ds_put_cstr(&ds, "failed to ");
-    }
-    ds_put_format(&ds, "%s ", operation);
-    if (error) {
-        ds_put_format(&ds, "(%s) ", strerror(error));
-    }
-    flow_format(&ds, flow);
-    if (stats) {
-        ds_put_cstr(&ds, ", ");
-        format_odp_flow_stats(&ds, stats);
+    int error;
+
+    COVERAGE_INC(dpif_flow_get);
+
+    check_rw_odp_flow(flow);
+    error = dpif->dpif_class->flow_get(dpif, flow, 1);
+    if (!error) {
+        error = flow->stats.error;
     }
-    if (actions || n_actions) {
-        ds_put_cstr(&ds, ", actions:");
-        format_odp_actions(&ds, actions, n_actions);
+    if (should_log_flow_message(error)) {
+        log_flow_operation(dpif, "flow_get", error, flow);
     }
-    vlog(THIS_MODULE, flow_message_log_level(error), "%s", ds_cstr(&ds));
-    ds_destroy(&ds);
+    return error;
 }
 
-static int
-do_flow_ioctl(const struct dpif *dpif, int cmd, struct odp_flow *flow,
-              const char *operation, bool show_stats)
+/* For each flow 'flow' in the 'n' flows in 'flows':
+ *
+ * - If a flow matching 'flow->key' exists in 'dpif':
+ *
+ *     Stores 0 into 'flow->stats.error' and stores statistics for the flow
+ *     into 'flow->stats'.
+ *
+ *     If 'flow->n_actions' is zero, then 'flow->actions' is ignored.  If
+ *     'flow->n_actions' is nonzero, then 'flow->actions' should point to an
+ *     array of the specified number of actions.  At most that many of the
+ *     flow's actions will be copied into that array.  'flow->n_actions' will
+ *     be updated to the number of actions actually present in the flow, which
+ *     may be greater than the number stored if the flow has more actions than
+ *     space available in the array.
+ *
+ * - Flow-specific errors are indicated by a positive errno value in
+ *   'flow->stats.error'.  In particular, ENOENT indicates that no flow
+ *   matching 'flow->key' exists in 'dpif'.  When an error value is stored, the
+ *   contents of 'flow->key' are preserved but other members of 'flow' should
+ *   be treated as indeterminate.
+ *
+ * Returns 0 if all 'n' flows in 'flows' were updated (whether they were
+ * individually successful or not is indicated by 'flow->stats.error',
+ * however).  Returns a positive errno value if an error that prevented this
+ * update occurred, in which the caller must not depend on any elements in
+ * 'flows' being updated or not updated.
+ */
+int
+dpif_flow_get_multiple(const struct dpif *dpif,
+                       struct odp_flow flows[], size_t n)
 {
-    int error = do_ioctl(dpif, cmd, NULL, flow);
-    if (error && show_stats) {
-        flow->n_actions = 0;
-    }
-    if (should_log_flow_message(error)) {
-        log_flow_message(dpif, error, operation, &flow->key,
-                         show_stats && !error ? &flow->stats : NULL,
-                         flow->actions, flow->n_actions);
+    int error;
+    size_t i;
+
+    COVERAGE_ADD(dpif_flow_get, n);
+
+    for (i = 0; i < n; i++) {
+        check_rw_odp_flow(&flows[i]);
     }
+
+    error = dpif->dpif_class->flow_get(dpif, flows, n);
+    log_operation(dpif, "flow_get_multiple", error);
     return error;
 }
 
+/* Adds or modifies a flow in 'dpif' as specified in 'put':
+ *
+ * - If the flow specified in 'put->flow' does not exist in 'dpif', then
+ *   behavior depends on whether ODPPF_CREATE is specified in 'put->flags': if
+ *   it is, the flow will be added, otherwise the operation will fail with
+ *   ENOENT.
+ *
+ * - Otherwise, the flow specified in 'put->flow' does exist in 'dpif'.
+ *   Behavior in this case depends on whether ODPPF_MODIFY is specified in
+ *   'put->flags': if it is, the flow's actions will be updated, otherwise the
+ *   operation will fail with EEXIST.  If the flow's actions are updated, then
+ *   its statistics will be zeroed if ODPPF_ZERO_STATS is set in 'put->flags',
+ *   left as-is otherwise.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
 int
 dpif_flow_put(struct dpif *dpif, struct odp_flow_put *put)
 {
-    int error = do_ioctl(dpif, ODP_FLOW_PUT, NULL, put);
+    int error;
+
     COVERAGE_INC(dpif_flow_put);
+
+    error = dpif->dpif_class->flow_put(dpif, put);
     if (should_log_flow_message(error)) {
-        struct ds operation = DS_EMPTY_INITIALIZER;
-        ds_put_cstr(&operation, "put");
-        if (put->flags & ODPPF_CREATE) {
-            ds_put_cstr(&operation, "[create]");
-        }
-        if (put->flags & ODPPF_MODIFY) {
-            ds_put_cstr(&operation, "[modify]");
-        }
-        if (put->flags & ODPPF_ZERO_STATS) {
-            ds_put_cstr(&operation, "[zero]");
-        }
-#define ODPPF_ALL (ODPPF_CREATE | ODPPF_MODIFY | ODPPF_ZERO_STATS)
-        if (put->flags & ~ODPPF_ALL) {
-            ds_put_format(&operation, "[%x]", put->flags & ~ODPPF_ALL);
-        }
-        log_flow_message(dpif, error, ds_cstr(&operation), &put->flow.key,
-                         !error ? &put->flow.stats : NULL,
-                         put->flow.actions, put->flow.n_actions);
-        ds_destroy(&operation);
+        log_flow_put(dpif, error, put);
     }
     return error;
 }
 
+/* Deletes a flow matching 'flow->key' from 'dpif' or returns ENOENT if 'dpif'
+ * does not contain such a flow.
+ *
+ * If successful, updates 'flow->stats', 'flow->n_actions', and 'flow->actions'
+ * as described for dpif_flow_get(). */
 int
 dpif_flow_del(struct dpif *dpif, struct odp_flow *flow)
 {
+    int error;
+
     COVERAGE_INC(dpif_flow_del);
-    check_rw_odp_flow(flow);
-    memset(&flow->stats, 0, sizeof flow->stats);
-    return do_flow_ioctl(dpif, ODP_FLOW_DEL, flow, "delete flow", true);
-}
 
-int
-dpif_flow_get(const struct dpif *dpif, struct odp_flow *flow)
-{
-    COVERAGE_INC(dpif_flow_query);
     check_rw_odp_flow(flow);
     memset(&flow->stats, 0, sizeof flow->stats);
-    return do_flow_ioctl(dpif, ODP_FLOW_GET, flow, "get flow", true);
-}
 
-int
-dpif_flow_get_multiple(const struct dpif *dpif,
-                       struct odp_flow flows[], size_t n)
-{
-    struct odp_flowvec fv;
-    size_t i;
-
-    COVERAGE_ADD(dpif_flow_query_multiple, n);
-    fv.flows = flows;
-    fv.n_flows = n;
-    for (i = 0; i < n; i++) {
-        check_rw_odp_flow(&flows[i]);
+    error = dpif->dpif_class->flow_del(dpif, flow);
+    if (should_log_flow_message(error)) {
+        log_flow_operation(dpif, "delete flow", error, flow);
     }
-    return do_ioctl(dpif, ODP_FLOW_GET_MULTIPLE, "ODP_FLOW_GET_MULTIPLE",
-                    &fv);
+    return error;
 }
 
+/* Stores up to 'n' flows in 'dpif' into 'flows', including their statistics
+ * but not including any information about their actions.  If successful,
+ * returns 0 and sets '*n_out' to the number of flows actually present in
+ * 'dpif', which might be greater than the number stored (if 'dpif' has more
+ * than 'n' flows).  On failure, returns a negative errno value and sets
+ * '*n_out' to 0. */
 int
 dpif_flow_list(const struct dpif *dpif, struct odp_flow flows[], size_t n,
                size_t *n_out)
 {
-    struct odp_flowvec fv;
     uint32_t i;
-    int error;
+    int retval;
 
     COVERAGE_INC(dpif_flow_query_list);
-    fv.flows = flows;
-    fv.n_flows = n;
     if (RUNNING_ON_VALGRIND) {
         memset(flows, 0, n * sizeof *flows);
     } else {
@@ -585,19 +849,31 @@ dpif_flow_list(const struct dpif *dpif, struct odp_flow flows[], size_t n,
             flows[i].n_actions = 0;
         }
     }
-    error = do_ioctl(dpif, ODP_FLOW_LIST, NULL, &fv);
-    if (error) {
+    retval = dpif->dpif_class->flow_list(dpif, flows, n);
+    if (retval < 0) {
         *n_out = 0;
-        VLOG_WARN_RL(&error_rl, "dp%u: flow list failed (%s)",
-                     dpif->minor, strerror(error));
+        VLOG_WARN_RL(&error_rl, "%s: flow list failed (%s)",
+                     dpif_name(dpif), strerror(-retval));
+        return -retval;
     } else {
-        COVERAGE_ADD(dpif_flow_query_list_n, fv.n_flows);
-        *n_out = fv.n_flows;
-        VLOG_DBG_RL(&dpmsg_rl, "dp%u: listed %zu flows", dpif->minor, *n_out);
+        COVERAGE_ADD(dpif_flow_query_list_n, retval);
+        *n_out = MIN(n, retval);
+        VLOG_DBG_RL(&dpmsg_rl, "%s: listed %zu flows (of %d)",
+                    dpif_name(dpif), *n_out, retval);
+        return 0;
     }
-    return error;
 }
 
+/* Retrieves all of the flows in 'dpif'.
+ *
+ * If successful, returns 0 and stores in '*flowsp' a pointer to a newly
+ * allocated array of flows, including their statistics but not including any
+ * information about their actions, and sets '*np' to the number of flows in
+ * '*flowsp'.  The caller is responsible for freeing '*flowsp' by calling
+ * free().
+ *
+ * On failure, returns a positive errno value and sets '*flowsp' to NULL and
+ * '*np' to 0. */
 int
 dpif_flow_list_all(const struct dpif *dpif,
                    struct odp_flow **flowsp, size_t *np)
@@ -623,15 +899,24 @@ dpif_flow_list_all(const struct dpif *dpif,
     }
 
     if (stats.n_flows != n_flows) {
-        VLOG_WARN_RL(&error_rl, "dp%u: datapath stats reported %"PRIu32" "
+        VLOG_WARN_RL(&error_rl, "%s: datapath stats reported %"PRIu32" "
                      "flows but flow listing reported %zu",
-                     dpif->minor, stats.n_flows, n_flows);
+                     dpif_name(dpif), stats.n_flows, n_flows);
     }
     *flowsp = flows;
     *np = n_flows;
     return 0;
 }
 
+/* Causes 'dpif' to perform the 'n_actions' actions in 'actions' on the
+ * Ethernet frame specified in 'packet'.
+ *
+ * Pretends that the frame was originally received on the port numbered
+ * 'in_port'.  This affects only ODPAT_OUTPUT_GROUP actions, which will not
+ * send a packet out their input port.  Specify the number of an unused port
+ * (e.g. UINT16_MAX is currently always unused) to avoid this behavior.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
 int
 dpif_execute(struct dpif *dpif, uint16_t in_port,
              const union odp_action actions[], size_t n_actions,
@@ -641,14 +926,8 @@ dpif_execute(struct dpif *dpif, uint16_t in_port,
 
     COVERAGE_INC(dpif_execute);
     if (n_actions > 0) {
-        struct odp_execute execute;
-        memset(&execute, 0, sizeof execute);
-        execute.in_port = in_port;
-        execute.actions = (union odp_action *) actions;
-        execute.n_actions = n_actions;
-        execute.data = buf->data;
-        execute.length = buf->size;
-        error = do_ioctl(dpif, ODP_EXECUTE, NULL, &execute);
+        error = dpif->dpif_class->execute(dpif, in_port, actions,
+                                          n_actions, buf);
     } else {
         error = 0;
     }
@@ -656,7 +935,7 @@ dpif_execute(struct dpif *dpif, uint16_t in_port,
     if (!(error ? VLOG_DROP_WARN(&error_rl) : VLOG_DROP_DBG(&dpmsg_rl))) {
         struct ds ds = DS_EMPTY_INITIALIZER;
         char *packet = ofp_packet_to_string(buf->data, buf->size, buf->size);
-        ds_put_format(&ds, "dp%u: execute ", dpif->minor);
+        ds_put_format(&ds, "%s: execute ", dpif_name(dpif));
         format_odp_actions(&ds, actions, n_actions);
         if (error) {
             ds_put_format(&ds, " failed (%s)", strerror(error));
@@ -669,424 +948,266 @@ dpif_execute(struct dpif *dpif, uint16_t in_port,
     return error;
 }
 
+/* Retrieves 'dpif''s "listen mask" into '*listen_mask'.  Each ODPL_* bit set
+ * in '*listen_mask' indicates that dpif_recv() will receive messages of that
+ * type.  Returns 0 if successful, otherwise a positive errno value. */
 int
-dpif_recv(struct dpif *dpif, struct ofpbuf **bufp)
+dpif_recv_get_mask(const struct dpif *dpif, int *listen_mask)
 {
-    struct ofpbuf *buf;
-    int retval;
-    int error;
-
-    buf = ofpbuf_new(65536);
-    retval = read(dpif->fd, ofpbuf_tail(buf), ofpbuf_tailroom(buf));
-    if (retval < 0) {
-        error = errno;
-        if (error != EAGAIN) {
-            VLOG_WARN_RL(&error_rl, "dp%u: read failed: %s",
-                         dpif->minor, strerror(error));
-        }
-    } else if (retval >= sizeof(struct odp_msg)) {
-        struct odp_msg *msg = buf->data;
-        if (msg->length <= retval) {
-            buf->size += retval;
-            if (VLOG_IS_DBG_ENABLED()) {
-                void *payload = msg + 1;
-                size_t length = buf->size - sizeof *msg;
-                char *s = ofp_packet_to_string(payload, length, length);
-                VLOG_DBG_RL(&dpmsg_rl, "dp%u: received %s message of length "
-                            "%zu on port %"PRIu16": %s", dpif->minor,
-                            (msg->type == _ODPL_MISS_NR ? "miss"
-                             : msg->type == _ODPL_ACTION_NR ? "action"
-                             : "<unknown>"),
-                            msg->length - sizeof(struct odp_msg),
-                            msg->port, s);
-                free(s);
-            }
-            *bufp = buf;
-            COVERAGE_INC(dpif_recv);
-            return 0;
-        } else {
-            VLOG_WARN_RL(&error_rl, "dp%u: discarding message truncated "
-                         "from %"PRIu32" bytes to %d",
-                         dpif->minor, msg->length, retval);
-            error = ERANGE;
-        }
-    } else if (!retval) {
-        VLOG_WARN_RL(&error_rl, "dp%u: unexpected end of file", dpif->minor);
-        error = EPROTO;
-    } else {
-        VLOG_WARN_RL(&error_rl,
-                     "dp%u: discarding too-short message (%d bytes)",
-                     dpif->minor, retval);
-        error = ERANGE;
+    int error = dpif->dpif_class->recv_get_mask(dpif, listen_mask);
+    if (error) {
+        *listen_mask = 0;
     }
-
-    *bufp = NULL;
-    ofpbuf_delete(buf);
+    log_operation(dpif, "recv_get_mask", error);
     return error;
 }
 
-void
-dpif_recv_wait(struct dpif *dpif)
+/* Sets 'dpif''s "listen mask" to 'listen_mask'.  Each ODPL_* bit set in
+ * '*listen_mask' requests that dpif_recv() receive messages of that type.
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+dpif_recv_set_mask(struct dpif *dpif, int listen_mask)
 {
-    poll_fd_wait(dpif->fd, POLLIN);
+    int error = dpif->dpif_class->recv_set_mask(dpif, listen_mask);
+    log_operation(dpif, "recv_set_mask", error);
+    return error;
 }
-\f
-struct dpifmon {
-    struct dpif dpif;
-    struct nl_sock *sock;
-    int local_ifindex;
-};
 
+/* Retrieve the sFlow sampling probability.  '*probability' is expressed as the
+ * number of packets out of UINT_MAX to sample, e.g. probability/UINT_MAX is
+ * the probability of sampling a given packet.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  EOPNOTSUPP
+ * indicates that 'dpif' does not support sFlow sampling. */
 int
-dpifmon_create(const char *datapath_name, struct dpifmon **monp)
+dpif_get_sflow_probability(const struct dpif *dpif, uint32_t *probability)
 {
-    struct dpifmon *mon;
-    char local_name[IFNAMSIZ];
-    int error;
-
-    mon = *monp = xmalloc(sizeof *mon);
-
-    error = dpif_open(datapath_name, &mon->dpif);
+    int error = (dpif->dpif_class->get_sflow_probability
+                 ? dpif->dpif_class->get_sflow_probability(dpif, probability)
+                 : EOPNOTSUPP);
     if (error) {
-        goto error;
+        *probability = 0;
     }
-    error = dpif_get_name(&mon->dpif, local_name, sizeof local_name);
-    if (error) {
-        goto error_close_dpif;
-    }
-
-    mon->local_ifindex = if_nametoindex(local_name);
-    if (!mon->local_ifindex) {
-        error = errno;
-        VLOG_WARN("could not get ifindex of %s device: %s",
-                  local_name, strerror(errno));
-        goto error_close_dpif;
-    }
-
-    error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0, &mon->sock);
-    if (error) {
-        VLOG_WARN("could not create rtnetlink socket: %s", strerror(error));
-        goto error_close_dpif;
-    }
-
-    return 0;
+    log_operation(dpif, "get_sflow_probability", error);
+    return error;
+}
 
-error_close_dpif:
-    dpif_close(&mon->dpif);
-error:
-    free(mon);
-    *monp = NULL;
+/* Set the sFlow sampling probability.  'probability' is expressed as the
+ * number of packets out of UINT_MAX to sample, e.g. probability/UINT_MAX is
+ * the probability of sampling a given packet.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  EOPNOTSUPP
+ * indicates that 'dpif' does not support sFlow sampling. */
+int
+dpif_set_sflow_probability(struct dpif *dpif, uint32_t probability)
+{
+    int error = (dpif->dpif_class->set_sflow_probability
+                 ? dpif->dpif_class->set_sflow_probability(dpif, probability)
+                 : EOPNOTSUPP);
+    log_operation(dpif, "set_sflow_probability", error);
     return error;
 }
 
-void
-dpifmon_destroy(struct dpifmon *mon)
+/* Attempts to receive a message from 'dpif'.  If successful, stores the
+ * message into '*packetp'.  The message, if one is received, will begin with
+ * 'struct odp_msg' as a header.  Only messages of the types selected with
+ * dpif_set_listen_mask() will ordinarily be received (but if a message type is
+ * enabled and then later disabled, some stragglers might pop up).
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  Returns EAGAIN
+ * if no message is immediately available. */
+int
+dpif_recv(struct dpif *dpif, struct ofpbuf **packetp)
 {
-    if (mon) {
-        dpif_close(&mon->dpif);
-        nl_sock_destroy(mon->sock);
+    int error = dpif->dpif_class->recv(dpif, packetp);
+    if (!error) {
+        if (VLOG_IS_DBG_ENABLED()) {
+            struct ofpbuf *buf = *packetp;
+            struct odp_msg *msg = buf->data;
+            void *payload = msg + 1;
+            size_t payload_len = buf->size - sizeof *msg;
+            char *s = ofp_packet_to_string(payload, payload_len, payload_len);
+            VLOG_DBG_RL(&dpmsg_rl, "%s: received %s message of length "
+                        "%zu on port %"PRIu16": %s", dpif_name(dpif),
+                        (msg->type == _ODPL_MISS_NR ? "miss"
+                         : msg->type == _ODPL_ACTION_NR ? "action"
+                         : msg->type == _ODPL_SFLOW_NR ? "sFlow"
+                         : "<unknown>"),
+                        payload_len, msg->port, s);
+            free(s);
+        }
+    } else {
+        *packetp = NULL;
     }
+    return error;
 }
 
+/* Discards all messages that would otherwise be received by dpif_recv() on
+ * 'dpif'.  Returns 0 if successful, otherwise a positive errno value. */
 int
-dpifmon_poll(struct dpifmon *mon, char **devnamep)
+dpif_recv_purge(struct dpif *dpif)
 {
-    static struct vlog_rate_limit slow_rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    static const struct nl_policy rtnlgrp_link_policy[] = {
-        [IFLA_IFNAME] = { .type = NL_A_STRING },
-        [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
-    };
-    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
-    struct ofpbuf *buf;
+    struct odp_stats stats;
+    unsigned int i;
     int error;
 
-    *devnamep = NULL;
-again:
-    error = nl_sock_recv(mon->sock, &buf, false);
-    switch (error) {
-    case 0:
-        if (!nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
-                             rtnlgrp_link_policy,
-                             attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
-            VLOG_WARN_RL(&slow_rl, "received bad rtnl message");
-            error = ENOBUFS;
-        } else {
-            const char *devname = nl_attr_get_string(attrs[IFLA_IFNAME]);
-            bool for_us;
-
-            if (attrs[IFLA_MASTER]) {
-                uint32_t master_ifindex = nl_attr_get_u32(attrs[IFLA_MASTER]);
-                for_us = master_ifindex == mon->local_ifindex;
-            } else {
-                /* It's for us if that device is one of our ports.  This is
-                 * open-coded instead of using dpif_port_query_by_name() to
-                 * avoid logging a warning on failure. */
-                struct odp_port port;
-                memset(&port, 0, sizeof port);
-                strncpy(port.devname, devname, sizeof port.devname);
-                for_us = !ioctl(mon->dpif.fd, ODP_PORT_QUERY, &port);
-            }
-
-            if (!for_us) {
-                /* Not for us, try again. */
-                ofpbuf_delete(buf);
-                COVERAGE_INC(dpifmon_poll_false_wakeup);
-                goto again;
-            }
-            COVERAGE_INC(dpifmon_poll_changed);
-            *devnamep = xstrdup(devname);
-        }
-        ofpbuf_delete(buf);
-        break;
-
-    case EAGAIN:
-        /* Nothing to do. */
-        break;
+    COVERAGE_INC(dpif_purge);
 
-    case ENOBUFS:
-        VLOG_WARN_RL(&slow_rl, "dpifmon socket overflowed");
-        break;
+    error = dpif_get_dp_stats(dpif, &stats);
+    if (error) {
+        return error;
+    }
 
-    default:
-        VLOG_WARN_RL(&slow_rl, "error on dpifmon socket: %s", strerror(error));
-        break;
+    for (i = 0; i < stats.max_miss_queue + stats.max_action_queue + stats.max_sflow_queue; i++) {
+        struct ofpbuf *buf;
+        error = dpif_recv(dpif, &buf);
+        if (error) {
+            return error == EAGAIN ? 0 : error;
+        }
+        ofpbuf_delete(buf);
     }
-    return error;
+    return 0;
 }
 
+/* Arranges for the poll loop to wake up when 'dpif' has a message queued to be
+ * received with dpif_recv(). */
 void
-dpifmon_run(struct dpifmon *mon UNUSED)
+dpif_recv_wait(struct dpif *dpif)
 {
-    /* Nothing to do in this implementation. */
+    dpif->dpif_class->recv_wait(dpif);
 }
 
+/* Obtains the NetFlow engine type and engine ID for 'dpif' into '*engine_type'
+ * and '*engine_id', respectively. */
 void
-dpifmon_wait(struct dpifmon *mon)
+dpif_get_netflow_ids(const struct dpif *dpif,
+                     uint8_t *engine_type, uint8_t *engine_id)
 {
-    nl_sock_wait(mon->sock, POLLIN);
+    *engine_type = dpif->netflow_engine_type;
+    *engine_id = dpif->netflow_engine_id;
 }
 \f
-static int get_openvswitch_major(void);
-static int get_major(const char *target, int default_major);
-
-static int
-lookup_minor(const char *name, unsigned int *minor)
+void
+dpif_init(struct dpif *dpif, const struct dpif_class *dpif_class,
+          const char *name,
+          uint8_t netflow_engine_type, uint8_t netflow_engine_id)
 {
-    struct ethtool_drvinfo drvinfo;
-    struct ifreq ifr;
-    int error;
-    int sock;
-
-    *minor = -1;
-    sock = socket(AF_INET, SOCK_DGRAM, 0);
-    if (sock < 0) {
-        VLOG_WARN("socket(AF_INET) failed: %s", strerror(errno));
-        error = errno;
-        goto error;
-    }
-
-    memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-    ifr.ifr_data = (caddr_t) &drvinfo;
-
-    memset(&drvinfo, 0, sizeof drvinfo);
-    drvinfo.cmd = ETHTOOL_GDRVINFO;
-    if (ioctl(sock, SIOCETHTOOL, &ifr)) {
-        VLOG_WARN("ioctl(SIOCETHTOOL) failed: %s", strerror(errno));
-        error = errno;
-        goto error_close_sock;
-    }
+    dpif->dpif_class = dpif_class;
+    dpif->base_name = xstrdup(name);
+    dpif->full_name = xasprintf("%s@%s", dpif_class->type, name);
+    dpif->netflow_engine_type = netflow_engine_type;
+    dpif->netflow_engine_id = netflow_engine_id;
+}
 
-    if (strcmp(drvinfo.driver, "openvswitch")) {
-        VLOG_WARN("%s is not an openvswitch device", name);
-        error = EOPNOTSUPP;
-        goto error_close_sock;
-    }
+/* Undoes the results of initialization.
+ *
+ * Normally this function only needs to be called from dpif_close().
+ * However, it may be called by providers due to an error on opening
+ * that occurs after initialization.  It this case dpif_close() would
+ * never be called. */
+void
+dpif_uninit(struct dpif *dpif, bool close)
+{
+    char *base_name = dpif->base_name;
+    char *full_name = dpif->full_name;
 
-    if (!isdigit(drvinfo.bus_info[0])) {
-        VLOG_WARN("%s ethtool info does not contain an openvswitch minor",
-                  name);
-        error = EPROTOTYPE;
-        goto error_close_sock;
+    if (close) {
+        dpif->dpif_class->close(dpif);
     }
 
-    *minor = atoi(drvinfo.bus_info);
-    close(sock);
-    return 0;
-
-error_close_sock:
-    close(sock);
-error:
-    return error;
+    free(base_name);
+    free(full_name);
 }
-
-static int
-make_openvswitch_device(unsigned int minor, char **fnp)
+\f
+static void
+log_operation(const struct dpif *dpif, const char *operation, int error)
 {
-    dev_t dev = makedev(get_openvswitch_major(), minor);
-    const char dirname[] = "/dev/net";
-    struct stat s;
-    char fn[128];
-
-    *fnp = NULL;
-    sprintf(fn, "%s/dp%d", dirname, minor);
-    if (!stat(fn, &s)) {
-        if (!S_ISCHR(s.st_mode)) {
-            VLOG_WARN_RL(&error_rl, "%s is not a character device, fixing",
-                         fn);
-        } else if (s.st_rdev != dev) {
-            VLOG_WARN_RL(&error_rl,
-                         "%s is device %u:%u instead of %u:%u, fixing",
-                         fn, major(s.st_rdev), minor(s.st_rdev),
-                         major(dev), minor(dev));
-        } else {
-            goto success;
-        }
-        if (unlink(fn)) {
-            VLOG_WARN_RL(&error_rl, "%s: unlink failed (%s)",
-                         fn, strerror(errno));
-            return errno;
-        }
-    } else if (errno == ENOENT) {
-        if (stat(dirname, &s)) {
-            if (errno == ENOENT) {
-                if (mkdir(dirname, 0755)) {
-                    VLOG_WARN_RL(&error_rl, "%s: mkdir failed (%s)",
-                                 dirname, strerror(errno));
-                    return errno;
-                }
-            } else {
-                VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)",
-                             dirname, strerror(errno));
-                return errno;
-            }
-        }
+    if (!error) {
+        VLOG_DBG_RL(&dpmsg_rl, "%s: %s success", dpif_name(dpif), operation);
     } else {
-        VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)", fn, strerror(errno));
-        return errno;
+        VLOG_WARN_RL(&error_rl, "%s: %s failed (%s)",
+                     dpif_name(dpif), operation, strerror(error));
     }
-
-    /* The device needs to be created. */
-    if (mknod(fn, S_IFCHR | 0700, dev)) {
-        VLOG_WARN_RL(&error_rl,
-                     "%s: creating character device %u:%u failed (%s)",
-                     fn, major(dev), minor(dev), strerror(errno));
-        return errno;
-    }
-
-success:
-    *fnp = xstrdup(fn);
-    return 0;
 }
 
-
-static int
-get_openvswitch_major(void)
+static enum vlog_level
+flow_message_log_level(int error)
 {
-    static unsigned int openvswitch_major;
-    if (!openvswitch_major) {
-        enum { DEFAULT_MAJOR = 248 };
-        openvswitch_major = get_major("openvswitch", DEFAULT_MAJOR);
-    }
-    return openvswitch_major;
+    return error ? VLL_WARN : VLL_DBG;
 }
 
-static int
-get_major(const char *target, int default_major)
+static bool
+should_log_flow_message(int error)
 {
-    const char fn[] = "/proc/devices";
-    char line[128];
-    FILE *file;
-    int ln;
-
-    file = fopen(fn, "r");
-    if (!file) {
-        VLOG_ERR("opening %s failed (%s)", fn, strerror(errno));
-        goto error;
-    }
-
-    for (ln = 1; fgets(line, sizeof line, file); ln++) {
-        char name[64];
-        int major;
-
-        if (!strncmp(line, "Character", 9) || line[0] == '\0') {
-            /* Nothing to do. */
-        } else if (!strncmp(line, "Block", 5)) {
-            /* We only want character devices, so skip the rest of the file. */
-            break;
-        } else if (sscanf(line, "%d %63s", &major, name)) {
-            if (!strcmp(name, target)) {
-                fclose(file);
-                return major;
-            }
-        } else {
-            static bool warned;
-            if (!warned) {
-                VLOG_WARN("%s:%d: syntax error", fn, ln);
-            }
-            warned = true;
-        }
-    }
-
-    VLOG_ERR("%s: %s major not found (is the module loaded?), using "
-             "default major %d", fn, target, default_major);
-error:
-    VLOG_INFO("using default major %d for %s", default_major, target);
-    return default_major;
+    return !vlog_should_drop(THIS_MODULE, flow_message_log_level(error),
+                             error ? &error_rl : &dpmsg_rl);
 }
 
-static int
-name_to_minor(const char *name, unsigned int *minor)
+static void
+log_flow_message(const struct dpif *dpif, int error, const char *operation,
+                 const flow_t *flow, const struct odp_flow_stats *stats,
+                 const union odp_action *actions, size_t n_actions)
 {
-    if (!get_minor_from_name(name, minor)) {
-        return 0;
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    ds_put_format(&ds, "%s: ", dpif_name(dpif));
+    if (error) {
+        ds_put_cstr(&ds, "failed to ");
     }
-    return lookup_minor(name, minor);
+    ds_put_format(&ds, "%s ", operation);
+    if (error) {
+        ds_put_format(&ds, "(%s) ", strerror(error));
+    }
+    flow_format(&ds, flow);
+    if (stats) {
+        ds_put_cstr(&ds, ", ");
+        format_odp_flow_stats(&ds, stats);
+    }
+    if (actions || n_actions) {
+        ds_put_cstr(&ds, ", actions:");
+        format_odp_actions(&ds, actions, n_actions);
+    }
+    vlog(THIS_MODULE, flow_message_log_level(error), "%s", ds_cstr(&ds));
+    ds_destroy(&ds);
 }
 
-static int
-get_minor_from_name(const char *name, unsigned int *minor)
+static void
+log_flow_operation(const struct dpif *dpif, const char *operation, int error,
+                   struct odp_flow *flow)
 {
-    if (!strncmp(name, "dp", 2) && isdigit(name[2])) {
-        *minor = atoi(name + 2);
-        return 0;
-    } else if (!strncmp(name, "nl:", 3) && isdigit(name[3])) {
-        /* This is for compatibility only and will be dropped. */
-        *minor = atoi(name + 3);
-        return 0;
-    } else {
-        return EINVAL;
+    if (error) {
+        flow->n_actions = 0;
     }
+    log_flow_message(dpif, error, operation, &flow->key,
+                     !error ? &flow->stats : NULL,
+                     flow->actions, flow->n_actions);
 }
 
-static int
-open_by_minor(unsigned int minor, struct dpif *dpif)
+static void
+log_flow_put(struct dpif *dpif, int error, const struct odp_flow_put *put)
 {
-    int error;
-    char *fn;
-    int fd;
+    enum { ODPPF_ALL = ODPPF_CREATE | ODPPF_MODIFY | ODPPF_ZERO_STATS };
+    struct ds s;
 
-    dpif->minor = -1;
-    dpif->fd = -1;
-    error = make_openvswitch_device(minor, &fn);
-    if (error) {
-        return error;
+    ds_init(&s);
+    ds_put_cstr(&s, "put");
+    if (put->flags & ODPPF_CREATE) {
+        ds_put_cstr(&s, "[create]");
     }
-
-    fd = open(fn, O_RDONLY | O_NONBLOCK);
-    if (fd < 0) {
-        error = errno;
-        VLOG_WARN("%s: open failed (%s)", fn, strerror(error));
-        free(fn);
-        return error;
+    if (put->flags & ODPPF_MODIFY) {
+        ds_put_cstr(&s, "[modify]");
     }
-
-    free(fn);
-    dpif->minor = minor;
-    dpif->fd = fd;
-    return 0;
+    if (put->flags & ODPPF_ZERO_STATS) {
+        ds_put_cstr(&s, "[zero]");
+    }
+    if (put->flags & ~ODPPF_ALL) {
+        ds_put_format(&s, "[%x]", put->flags & ~ODPPF_ALL);
+    }
+    log_flow_message(dpif, error, ds_cstr(&s), &put->flow.key,
+                     !error ? &put->flow.stats : NULL,
+                     put->flow.actions, put->flow.n_actions);
+    ds_destroy(&s);
 }
-\f
+
 /* There is a tendency to construct odp_flow objects on the stack and to
  * forget to properly initialize their "actions" and "n_actions" members.
  * When this happens, we get memory corruption because the kernel
index f31fb3b..4789284 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef DPIF_H
 #define DPIF_H 1
 
-/* Operations for the datapath running in the local kernel.  The interface can
- * generalize to multiple types of local datapaths, but the implementation only
- * supports the openflow kernel module. */
-
 #include "openvswitch/datapath-protocol.h"
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+struct dpif;
 struct ofpbuf;
 struct svec;
+struct dpif_class;
+
+void dp_run(void);
+void dp_wait(void);
 
-/* A datapath interface.  Opaque. */
-struct dpif {
-    unsigned int minor;         /* For use in error messages. */
-    int fd;
-};
+int dp_register_provider(const struct dpif_class *);
+int dp_unregister_provider(const char *type);
+void dp_enumerate_types(struct svec *types);
 
-int dp_enumerate(struct svec *);
+int dp_enumerate_names(const char *type, struct svec *names);
+void dp_parse_name(const char *datapath_name, char **name, char **type);
 
-int dpif_open(const char *name, struct dpif *);
-int dpif_create(const char *name, struct dpif *);
+int dpif_open(const char *name, const char *type, struct dpif **);
+int dpif_create(const char *name, const char *type, struct dpif **);
+int dpif_create_and_open(const char *name, const char *type, struct dpif **);
 void dpif_close(struct dpif *);
 
-static inline unsigned int dpif_id(const struct dpif *dpif);
-int dpif_get_name(struct dpif *, char *name, size_t name_size);
+const char *dpif_name(const struct dpif *);
+const char *dpif_base_name(const struct dpif *);
+int dpif_get_all_names(const struct dpif *, struct svec *);
 
 int dpif_delete(struct dpif *);
 
@@ -51,25 +57,24 @@ int dpif_get_dp_stats(const struct dpif *, struct odp_stats *);
 int dpif_get_drop_frags(const struct dpif *, bool *drop_frags);
 int dpif_set_drop_frags(struct dpif *, bool drop_frags);
 
-int dpif_get_listen_mask(const struct dpif *, int *listen_mask);
-int dpif_set_listen_mask(struct dpif *, int listen_mask);
-int dpif_purge(struct dpif *);
-
-int dpif_port_add(struct dpif *, const char *devname, uint16_t port_no,
-                  uint16_t flags);
+int dpif_port_add(struct dpif *, const char *devname, uint16_t flags,
+                  uint16_t *port_no);
 int dpif_port_del(struct dpif *, uint16_t port_no);
 int dpif_port_query_by_number(const struct dpif *, uint16_t port_no,
                               struct odp_port *);
 int dpif_port_query_by_name(const struct dpif *, const char *devname,
                             struct odp_port *);
-int dpif_port_get_name(struct dpif *dpif, uint16_t port_no,
+int dpif_port_get_name(struct dpif *, uint16_t port_no,
                        char *name, size_t name_size);
 int dpif_port_list(const struct dpif *, struct odp_port **, size_t *n_ports);
 
+int dpif_port_poll(const struct dpif *, char **devnamep);
+void dpif_port_poll_wait(const struct dpif *);
+
+int dpif_port_group_get(const struct dpif *, uint16_t group,
+                        uint16_t **ports, size_t *n_ports);
 int dpif_port_group_set(struct dpif *, uint16_t group,
                         const uint16_t ports[], size_t n_ports);
-int dpif_port_group_get(const struct dpif *, uint16_t group,
-                        uint16_t ports[], size_t n_ports, size_t *n_out);
 
 int dpif_flow_flush(struct dpif *);
 int dpif_flow_put(struct dpif *, struct odp_flow_put *);
@@ -85,23 +90,19 @@ int dpif_execute(struct dpif *, uint16_t in_port,
                  const union odp_action[], size_t n_actions,
                  const struct ofpbuf *);
 
+int dpif_recv_get_mask(const struct dpif *, int *listen_mask);
+int dpif_recv_set_mask(struct dpif *, int listen_mask);
+int dpif_get_sflow_probability(const struct dpif *, uint32_t *probability);
+int dpif_set_sflow_probability(struct dpif *, uint32_t probability);
 int dpif_recv(struct dpif *, struct ofpbuf **);
+int dpif_recv_purge(struct dpif *);
 void dpif_recv_wait(struct dpif *);
 
-static inline unsigned int
-dpif_id(const struct dpif *dpif)
-{
-    return dpif->minor;
-}
-\f
-struct dpifmon;
-
-int dpifmon_create(const char *datapath_name, struct dpifmon **);
-void dpifmon_destroy(struct dpifmon *);
+void dpif_get_netflow_ids(const struct dpif *,
+                          uint8_t *engine_type, uint8_t *engine_id);
 
-int dpifmon_poll(struct dpifmon *, char **devnamep);
-
-void dpifmon_run(struct dpifmon *);
-void dpifmon_wait(struct dpifmon *);
+#ifdef  __cplusplus
+}
+#endif
 
 #endif /* dpif.h */
index 72175b0..775ec58 100644 (file)
@@ -1,16 +1,14 @@
 .RS
 .TP
-\fBdp\fIN\fR
+[\fItype\fB@\fR]\fBdp\fIN\fR
 Datapath number \fIN\fR, where \fIN\fR is a number between 0 and 255,
-inclusive.
-
+inclusive.  If \fItype\fR is given, it specifies the datapath provider of
+\fBdp\fIN\fR, otherwise the default provider \fBsystem\fR is assumed.
+.
 .TP
-\fIname\fR
+[\fItype\fB@\fR]\fIname\fR
 The name of the network device associated with the datapath's local
 port.  (\fB\*(PN\fR internally converts this into a datapath number,
-as above.)
-
-.TP
-\fBnl:\fIN\fR
-This is an obsolete synonym for \fBdp\fIN\fR.
+as above.)  If \fItype\fR is given, it specifies the datapath provider of
+\fIname\fR, otherwise the default provider \fBsystem\fR is assumed.
 .RE
index 9684ffa..c333818 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
     *ds_put_uninit(ds, 1) = c;
 }
 
+/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
+void
+ds_put_utf8(struct ds *ds, int uc)
+{
+    if (uc <= 0x7f) {
+        ds_put_char(ds, uc);
+    } else if (uc <= 0x7ff) {
+        ds_put_char(ds, 0xc0 | (uc >> 6));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0xffff) {
+        ds_put_char(ds, 0xe0 | (uc >> 12));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0x10ffff) {
+        ds_put_char(ds, 0xf0 | (uc >> 18));
+        ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else {
+        /* Invalid code point.  Insert the Unicode general substitute
+         * REPLACEMENT CHARACTER. */
+        ds_put_utf8(ds, 0xfffd);
+    }
+}
+
 void
 ds_put_char_multiple(struct ds *ds, char c, size_t n)
 {
@@ -90,6 +115,13 @@ ds_put_cstr(struct ds *ds, const char *s)
     memcpy(ds_put_uninit(ds, s_len), s, s_len);
 }
 
+void
+ds_put_and_free_cstr(struct ds *ds, char *s)
+{
+    ds_put_cstr(ds, s);
+    free(s);
+}
+
 void
 ds_put_format(struct ds *ds, const char *format, ...)
 {
@@ -187,6 +219,12 @@ ds_cstr(struct ds *ds)
     return ds->string;
 }
 
+const char *
+ds_cstr_ro(const struct ds *ds)
+{
+    return ds_cstr((struct ds *) ds);
+}
+
 /* Returns a null-terminated string representing the current contents of 'ds',
  * which the caller is expected to free with free(), then clears the contents
  * of 'ds'. */
@@ -204,6 +242,15 @@ ds_destroy(struct ds *ds)
     free(ds->string);
 }
 
+/* Swaps the content of 'a' and 'b'. */
+void
+ds_swap(struct ds *a, struct ds *b)
+{
+    struct ds temp = *a;
+    *a = *b;
+    *b = temp;
+}
+
 /* Writes the 'size' bytes in 'buf' to 'string' as hex bytes arranged 16 per
  * line.  Numeric offsets are also included, starting at 'ofs' for the first
  * byte in 'buf'.  If 'ascii' is true then the corresponding ASCII characters
index a44e0b3..eebbdbf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,9 +40,11 @@ void ds_truncate(struct ds *, size_t new_length);
 void ds_reserve(struct ds *, size_t min_length);
 char *ds_put_uninit(struct ds *, size_t n);
 void ds_put_char(struct ds *, char);
+void ds_put_utf8(struct ds *, int uc);
 void ds_put_char_multiple(struct ds *, char, size_t n);
 void ds_put_buffer(struct ds *, const char *, size_t n);
 void ds_put_cstr(struct ds *, const char *);
+void ds_put_and_free_cstr(struct ds *, char *);
 void ds_put_format(struct ds *, const char *, ...) PRINTF_FORMAT(2, 3);
 void ds_put_format_valist(struct ds *, const char *, va_list)
     PRINTF_FORMAT(2, 0);
@@ -54,8 +56,10 @@ void ds_put_hex_dump(struct ds *ds, const void *buf_, size_t size,
 int ds_get_line(struct ds *, FILE *);
 
 char *ds_cstr(struct ds *);
+const char *ds_cstr_ro(const struct ds *);
 char *ds_steal_cstr(struct ds *);
 void ds_destroy(struct ds *);
+void ds_swap(struct ds *, struct ds *);
 
 int ds_last(const struct ds *);
 void ds_chomp(struct ds *, int c);
index fc126ca..f6f913e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include "poll-loop.h"
+#include "shash.h"
+#include "socket-util.h"
 #include "util.h"
 
+#define THIS_MODULE VLM_fatal_signal
+#include "vlog.h"
+
 /* Signals to catch. */
 static const int fatal_signals[] = { SIGTERM, SIGINT, SIGHUP, SIGALRM };
 
@@ -33,7 +40,8 @@ static sigset_t fatal_signal_set;
 
 /* Hooks to call upon catching a signal */
 struct hook {
-    void (*func)(void *aux);
+    void (*hook_cb)(void *aux);
+    void (*cancel_cb)(void *aux);
     void *aux;
     bool run_at_exit;
 };
@@ -41,49 +49,29 @@ struct hook {
 static struct hook hooks[MAX_HOOKS];
 static size_t n_hooks;
 
-/* Number of nesting signal blockers. */
-static int block_level = 0;
-
-/* Signal mask saved by outermost signal blocker. */
-static sigset_t saved_signal_mask;
+static int signal_fds[2];
+static volatile sig_atomic_t stored_sig_nr = SIG_ATOMIC_MAX;
 
-/* Disabled by fatal_signal_fork()? */
-static bool disabled;
-
-static void call_sigprocmask(int how, sigset_t* new_set, sigset_t* old_set);
+static void fatal_signal_init(void);
 static void atexit_handler(void);
 static void call_hooks(int sig_nr);
 
-/* Registers 'hook' to be called when a process termination signal is raised.
- * If 'run_at_exit' is true, 'hook' is also called during normal process
- * termination, e.g. when exit() is called or when main() returns. */
-void
-fatal_signal_add_hook(void (*func)(void *aux), void *aux, bool run_at_exit)
-{
-    fatal_signal_block();
-    assert(n_hooks < MAX_HOOKS);
-    hooks[n_hooks].func = func;
-    hooks[n_hooks].aux = aux;
-    hooks[n_hooks].run_at_exit = run_at_exit;
-    n_hooks++;
-    fatal_signal_unblock();
-}
-
-/* Blocks program termination signals until fatal_signal_unblock() is called.
- * May be called multiple times with nesting; if so, fatal_signal_unblock()
- * must be called the same number of times to unblock signals.
- *
- * This is needed while adjusting a data structure that will be accessed by a
- * fatal signal hook, so that the hook is not invoked while the data structure
- * is in an inconsistent state. */
-void
-fatal_signal_block(void)
+static void
+fatal_signal_init(void)
 {
     static bool inited = false;
+
     if (!inited) {
         size_t i;
 
         inited = true;
+
+        if (pipe(signal_fds)) {
+            ovs_fatal(errno, "could not create pipe");
+        }
+        set_nonblocking(signal_fds[0]);
+        set_nonblocking(signal_fds[1]);
+
         sigemptyset(&fatal_signal_set);
         for (i = 0; i < ARRAY_SIZE(fatal_signals); i++) {
             int sig_nr = fatal_signals[i];
@@ -100,23 +88,34 @@ fatal_signal_block(void)
         }
         atexit(atexit_handler);
     }
-
-    if (++block_level == 1) {
-        call_sigprocmask(SIG_BLOCK, &fatal_signal_set, &saved_signal_mask);
-    }
 }
 
-/* Unblocks program termination signals blocked by fatal_signal_block() is
- * called.  If multiple calls to fatal_signal_block() are nested,
- * fatal_signal_unblock() must be called the same number of times to unblock
- * signals. */
+/* Registers 'hook_cb' to be called when a process termination signal is
+ * raised.  If 'run_at_exit' is true, 'hook_cb' is also called during normal
+ * process termination, e.g. when exit() is called or when main() returns.
+ *
+ * 'hook_cb' is not called immediately from the signal handler but rather the
+ * next time the poll loop iterates, so it is freed from the usual restrictions
+ * on signal handler functions.
+ *
+ * If the current process forks, fatal_signal_fork() may be called to clear the
+ * parent process's fatal signal hooks, so that 'hook_cb' is only called when
+ * the child terminates, not when the parent does.  When fatal_signal_fork() is
+ * called, it calls the 'cancel_cb' function if it is nonnull, passing 'aux',
+ * to notify that the hook has been canceled.  This allows the hook to free
+ * memory, etc. */
 void
-fatal_signal_unblock(void)
+fatal_signal_add_hook(void (*hook_cb)(void *aux), void (*cancel_cb)(void *aux),
+                      void *aux, bool run_at_exit)
 {
-    assert(block_level > 0);
-    if (--block_level == 0) {
-        call_sigprocmask(SIG_SETMASK, &saved_signal_mask, NULL);
-    }
+    fatal_signal_init();
+
+    assert(n_hooks < MAX_HOOKS);
+    hooks[n_hooks].hook_cb = hook_cb;
+    hooks[n_hooks].cancel_cb = cancel_cb;
+    hooks[n_hooks].aux = aux;
+    hooks[n_hooks].run_at_exit = run_at_exit;
+    n_hooks++;
 }
 
 /* Handles fatal signal number 'sig_nr'.
@@ -131,20 +130,39 @@ fatal_signal_unblock(void)
 void
 fatal_signal_handler(int sig_nr)
 {
-    call_hooks(sig_nr);
+    ignore(write(signal_fds[1], "", 1));
+    stored_sig_nr = sig_nr;
+}
+
+void
+fatal_signal_run(void)
+{
+    int sig_nr;
 
-    /* Re-raise the signal with the default handling so that the program
-     * termination status reflects that we were killed by this signal */
-    signal(sig_nr, SIG_DFL);
-    raise(sig_nr);
+    fatal_signal_init();
+
+    sig_nr = stored_sig_nr;
+    if (sig_nr != SIG_ATOMIC_MAX) {
+        call_hooks(sig_nr);
+
+        /* Re-raise the signal with the default handling so that the program
+         * termination status reflects that we were killed by this signal */
+        signal(sig_nr, SIG_DFL);
+        raise(sig_nr);
+    }
+}
+
+void
+fatal_signal_wait(void)
+{
+    fatal_signal_init();
+    poll_fd_wait(signal_fds[0], POLLIN);
 }
 
 static void
 atexit_handler(void)
 {
-    if (!disabled) {
-        call_hooks(0);
-    }
+    call_hooks(0);
 }
 
 static void
@@ -159,16 +177,21 @@ call_hooks(int sig_nr)
         for (i = 0; i < n_hooks; i++) {
             struct hook *h = &hooks[i];
             if (sig_nr || h->run_at_exit) {
-                h->func(h->aux);
+                h->hook_cb(h->aux);
             }
         }
     }
 }
 \f
-static char **files;
-static size_t n_files, max_files;
+/* Files to delete on exit.  (The 'data' member of each node is unused.) */
+static struct shash files = SHASH_INITIALIZER(&files);
+
+/* Has a hook function been registered with fatal_signal_add_hook() (and not
+ * cleared by fatal_signal_fork())? */
+static bool added_hook;
 
 static void unlink_files(void *aux);
+static void cancel_files(void *aux);
 static void do_unlink_files(void);
 
 /* Registers 'file' to be unlinked when the program terminates via exit() or a
@@ -176,18 +199,14 @@ static void do_unlink_files(void);
 void
 fatal_signal_add_file_to_unlink(const char *file)
 {
-    static bool added_hook = false;
     if (!added_hook) {
         added_hook = true;
-        fatal_signal_add_hook(unlink_files, NULL, true);
+        fatal_signal_add_hook(unlink_files, cancel_files, NULL, true);
     }
 
-    fatal_signal_block();
-    if (n_files >= max_files) {
-        files = x2nrealloc(files, &max_files, sizeof *files);
+    if (!shash_find(&files, file)) {
+        shash_add(&files, file, NULL);
     }
-    files[n_files++] = xstrdup(file);
-    fatal_signal_unblock();
 }
 
 /* Unregisters 'file' from being unlinked when the program terminates via
@@ -195,59 +214,76 @@ fatal_signal_add_file_to_unlink(const char *file)
 void
 fatal_signal_remove_file_to_unlink(const char *file)
 {
-    size_t i;
+    struct shash_node *node;
 
-    fatal_signal_block();
-    for (i = 0; i < n_files; i++) {
-        if (!strcmp(files[i], file)) {
-            free(files[i]);
-            files[i] = files[--n_files];
-            break;
-        }
+    node = shash_find(&files, file);
+    if (node) {
+        shash_delete(&files, node);
     }
-    fatal_signal_unblock();
+}
+
+/* Like fatal_signal_remove_file_to_unlink(), but also unlinks 'file'.
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+fatal_signal_unlink_file_now(const char *file)
+{
+    int error = unlink(file) ? errno : 0;
+    if (error) {
+        VLOG_WARN("could not unlink \"%s\" (%s)", file, strerror(error));
+    }
+
+    fatal_signal_remove_file_to_unlink(file);
+
+    return error;
 }
 
 static void
-unlink_files(void *aux UNUSED)
+unlink_files(void *aux OVS_UNUSED)
 {
     do_unlink_files(); 
 }
 
+static void
+cancel_files(void *aux OVS_UNUSED)
+{
+    shash_clear(&files);
+    added_hook = false;
+}
+
 static void
 do_unlink_files(void)
 {
-    size_t i;
+    struct shash_node *node;
 
-    for (i = 0; i < n_files; i++) {
-        unlink(files[i]);
+    SHASH_FOR_EACH (node, &files) {
+        unlink(node->name);
     }
 }
 \f
-/* Disables the fatal signal hook mechanism.  Following a fork, one of the
- * resulting processes can call this function to allow it to terminate without
- * triggering fatal signal processing or removing files.  Fatal signal
- * processing is still enabled in the other process. */
+/* Clears all of the fatal signal hooks without executing them.  If any of the
+ * hooks passed a 'cancel_cb' function to fatal_signal_add_hook(), then those
+ * functions will be called, allowing them to free resources, etc.
+ *
+ * Following a fork, one of the resulting processes can call this function to
+ * allow it to terminate without calling the hooks registered before calling
+ * this function.  New hooks registered after calling this function will take
+ * effect normally. */
 void
 fatal_signal_fork(void)
 {
     size_t i;
 
-    disabled = true;
-
-    for (i = 0; i < ARRAY_SIZE(fatal_signals); i++) {
-        int sig_nr = fatal_signals[i];
-        if (signal(sig_nr, SIG_DFL) == SIG_IGN) {
-            signal(sig_nr, SIG_IGN);
+    for (i = 0; i < n_hooks; i++) {
+        struct hook *h = &hooks[i];
+        if (h->cancel_cb) {
+            h->cancel_cb(h->aux);
         }
     }
-}
-\f
-static void
-call_sigprocmask(int how, sigset_t* new_set, sigset_t* old_set)
-{
-    int error = sigprocmask(how, new_set, old_set);
-    if (error) {
-        fprintf(stderr, "sigprocmask: %s\n", strerror(errno));
+    n_hooks = 0;
+
+    /* Raise any signals that we have already received with the default
+     * handler. */
+    if (stored_sig_nr != SIG_ATOMIC_MAX) {
+        raise(stored_sig_nr);
     }
 }
index 0bce1e2..94a1f1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stdbool.h>
 
 /* Basic interface. */
-void fatal_signal_add_hook(void (*)(void *aux), void *aux, bool run_at_exit);
-void fatal_signal_block(void);
-void fatal_signal_unblock(void);
+void fatal_signal_add_hook(void (*hook_cb)(void *aux),
+                           void (*cancel_cb)(void *aux), void *aux,
+                           bool run_at_exit);
 void fatal_signal_fork(void);
+void fatal_signal_run(void);
+void fatal_signal_wait(void);
 
 /* Convenience functions for unlinking files upon termination.
  *
@@ -31,6 +33,7 @@ void fatal_signal_fork(void);
  * exit(). */
 void fatal_signal_add_file_to_unlink(const char *);
 void fatal_signal_remove_file_to_unlink(const char *);
+int fatal_signal_unlink_file_now(const char *);
 
 /* Interface for other code that catches one of our signals and needs to pass
  * it through. */
diff --git a/lib/fault.c b/lib/fault.c
deleted file mode 100644 (file)
index 14e229e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "fault.h"
-#include <dlfcn.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include "util.h"
-
-#include "vlog.h"
-#define THIS_MODULE VLM_fault
-
-static void
-fault_handler(int sig_nr)
-{
-    VLOG_EMER("Caught signal %d.", sig_nr);
-    log_backtrace();
-    fflush(stdout);
-    fflush(stderr);
-
-    signal(sig_nr, SIG_DFL);
-    raise(sig_nr);
-}
-
-void
-log_backtrace(void)
-{
-    /* During the loop:
-
-       frame[0] points to the next frame.
-       frame[1] points to the return address. */
-    void **frame;
-    for (frame = __builtin_frame_address(0);
-         frame != NULL && frame[0] != NULL;
-         frame = frame[0]) {
-        Dl_info addrinfo;
-        if (!dladdr(frame[1], &addrinfo) || !addrinfo.dli_sname) {
-            fprintf(stderr, "  0x%08"PRIxPTR"\n", (uintptr_t) frame[1]);
-        } else {
-            fprintf(stderr, "  0x%08"PRIxPTR" (%s+0x%tx)\n",
-                    (uintptr_t) frame[1], addrinfo.dli_sname,
-                    (char *) frame[1] - (char *) addrinfo.dli_saddr); 
-        }
-    }
-    fflush(stderr);
-}
-
-void
-register_fault_handlers(void)
-{
-    signal(SIGABRT, fault_handler);
-    signal(SIGBUS, fault_handler);
-    signal(SIGFPE, fault_handler);
-    signal(SIGILL, fault_handler);
-    signal(SIGSEGV, fault_handler);
-}
index 7d4a1bd..7d368bb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -139,6 +139,7 @@ flow_extract(struct ofpbuf *packet, uint16_t in_port, flow_t *flow)
             if (vh) {
                 flow->dl_type = vh->vlan_next_type;
                 flow->dl_vlan = vh->vlan_tci & htons(VLAN_VID_MASK);
+                flow->dl_vlan_pcp = (ntohs(vh->vlan_tci) & 0xe000) >> 13;
             }
         }
         memcpy(flow->dl_src, eth->eth_src, ETH_ADDR_LEN);
@@ -150,6 +151,7 @@ flow_extract(struct ofpbuf *packet, uint16_t in_port, flow_t *flow)
             if (nh) {
                 flow->nw_src = nh->ip_src;
                 flow->nw_dst = nh->ip_dst;
+                flow->nw_tos = nh->ip_tos & IP_DSCP_MASK;
                 flow->nw_proto = nh->ip_proto;
                 packet->l4 = b.data;
                 if (!IP_IS_FRAGMENT(nh->ip_frag_off)) {
@@ -235,48 +237,29 @@ flow_extract_stats(const flow_t *flow, struct ofpbuf *packet,
     stats->n_packets = 1;
 }
 
-/* The Open vSwitch datapath supports matching on ARP payloads, which 
- * OpenFlow does not.  This function is identical to 'flow_to_match',
- * but does not hide the datapath's ability to match on ARP. */
+/* Extract 'flow' with 'wildcards' into the OpenFlow match structure
+ * 'match'. */
 void
-flow_to_ovs_match(const flow_t *flow, uint32_t wildcards, 
-                  struct ofp_match *match)
+flow_to_match(const flow_t *flow, uint32_t wildcards, struct ofp_match *match)
 {
     match->wildcards = htonl(wildcards);
     match->in_port = htons(flow->in_port == ODPP_LOCAL ? OFPP_LOCAL
                            : flow->in_port);
     match->dl_vlan = flow->dl_vlan;
+    match->dl_vlan_pcp = flow->dl_vlan_pcp;
     memcpy(match->dl_src, flow->dl_src, ETH_ADDR_LEN);
     memcpy(match->dl_dst, flow->dl_dst, ETH_ADDR_LEN);
     match->dl_type = flow->dl_type;
     match->nw_src = flow->nw_src;
     match->nw_dst = flow->nw_dst;
+    match->nw_tos = flow->nw_tos;
     match->nw_proto = flow->nw_proto;
     match->tp_src = flow->tp_src;
     match->tp_dst = flow->tp_dst;
-    match->pad = 0;
-}
-
-/* Extract 'flow' with 'wildcards' into the OpenFlow match structure
- * 'match'. */
-void
-flow_to_match(const flow_t *flow, uint32_t wildcards, struct ofp_match *match)
-{
-    flow_to_ovs_match(flow, wildcards, match);
-
-    /* The datapath supports matching on an ARP's opcode and IP addresses, 
-     * but OpenFlow does not.  We wildcard and zero out the appropriate
-     * fields so that OpenFlow is unaware of our trickery. */
-    if (flow->dl_type == htons(ETH_TYPE_ARP)) {
-        wildcards |= (OFPFW_NW_PROTO | OFPFW_NW_SRC_ALL | OFPFW_NW_DST_ALL);
-        match->nw_src = 0;
-        match->nw_dst = 0;
-        match->nw_proto = 0;
-    }
-    match->wildcards = htonl(wildcards);
+    memset(match->pad1, '\0', sizeof match->pad1);
+    memset(match->pad2, '\0', sizeof match->pad2);
 }
 
-
 void
 flow_from_match(flow_t *flow, uint32_t *wildcards,
                 const struct ofp_match *match)
@@ -284,26 +267,20 @@ flow_from_match(flow_t *flow, uint32_t *wildcards,
     if (wildcards) {
         *wildcards = ntohl(match->wildcards);
     }
-    /* The datapath supports matching on an ARP's opcode and IP addresses, 
-     * but OpenFlow does not.  In case the controller hasn't, we need to 
-     * set the appropriate wildcard bits so that we're externally 
-     * OpenFlow-compliant. */
-    if (match->dl_type == htons(ETH_TYPE_ARP)) {
-        *wildcards |= (OFPFW_NW_PROTO | OFPFW_NW_SRC_ALL | OFPFW_NW_DST_ALL);
-    }
-
     flow->nw_src = match->nw_src;
     flow->nw_dst = match->nw_dst;
     flow->in_port = (match->in_port == htons(OFPP_LOCAL) ? ODPP_LOCAL
                      : ntohs(match->in_port));
     flow->dl_vlan = match->dl_vlan;
+    flow->dl_vlan_pcp = match->dl_vlan_pcp;
     flow->dl_type = match->dl_type;
     flow->tp_src = match->tp_src;
     flow->tp_dst = match->tp_dst;
     memcpy(flow->dl_src, match->dl_src, ETH_ADDR_LEN);
     memcpy(flow->dl_dst, match->dl_dst, ETH_ADDR_LEN);
+    flow->nw_tos = match->nw_tos;
     flow->nw_proto = match->nw_proto;
-    flow->reserved = 0;
+    memset(flow->reserved, 0, sizeof flow->reserved);
 }
 
 char *
@@ -317,11 +294,12 @@ flow_to_string(const flow_t *flow)
 void
 flow_format(struct ds *ds, const flow_t *flow)
 {
-    ds_put_format(ds, "in_port%04x:vlan%d mac"ETH_ADDR_FMT"->"ETH_ADDR_FMT" "
-                  "type%04x proto%"PRId8" ip"IP_FMT"->"IP_FMT" port%d->%d",
-                  flow->in_port, ntohs(flow->dl_vlan),
+    ds_put_format(ds, "in_port%04x:vlan%d:pcp%d mac"ETH_ADDR_FMT
+                  "->"ETH_ADDR_FMT" type%04x proto%"PRId8" tos%"PRIu8
+                  " ip"IP_FMT"->"IP_FMT" port%d->%d",
+                  flow->in_port, ntohs(flow->dl_vlan), flow->dl_vlan_pcp,
                   ETH_ADDR_ARGS(flow->dl_src), ETH_ADDR_ARGS(flow->dl_dst),
-                  ntohs(flow->dl_type), flow->nw_proto,
+                  ntohs(flow->dl_type), flow->nw_proto, flow->nw_tos,
                   IP_ARGS(&flow->nw_src), IP_ARGS(&flow->nw_dst),
                   ntohs(flow->tp_src), ntohs(flow->tp_dst));
 }
index cb20109..ca140af 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 #ifndef FLOW_H
 #define FLOW_H 1
 
+#include <sys/types.h>
 #include <netinet/in.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -36,7 +37,6 @@ int flow_extract(struct ofpbuf *, uint16_t in_port, flow_t *);
 void flow_extract_stats(const flow_t *flow, struct ofpbuf *packet, 
         struct odp_flow_stats *stats);
 void flow_to_match(const flow_t *, uint32_t wildcards, struct ofp_match *);
-void flow_to_ovs_match(const flow_t *, uint32_t wildcards, struct ofp_match *);
 void flow_from_match(flow_t *, uint32_t *wildcards, const struct ofp_match *);
 char *flow_to_string(const flow_t *);
 void flow_format(struct ds *, const flow_t *);
index f1daa6b..63b4784 100644 (file)
@@ -52,6 +52,21 @@ hash_words(const uint32_t *p, size_t n, uint32_t basis)
     return c;
 }
 
+/* Returns the hash of the pair of aligned 32-bit words at 'p', starting from
+ * 'basis'. */
+uint32_t
+hash_2words(const uint32_t *p, uint32_t basis)
+{
+    uint32_t a, b, c;
+
+    a = b = c = 0xdeadbeef + (2 << 2) + basis;
+    b += p[1];
+    a += p[0];
+    HASH_FINAL(a, b, c);
+
+    return c;
+}
+
 /* Returns the hash of the 'n' bytes at 'p', starting from 'basis'. */
 uint32_t
 hash_bytes(const void *p_, size_t n, uint32_t basis)
index 5485f8c..5f6409c 100644 (file)
 #ifndef HASH_H
 #define HASH_H 1
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
+#include "util.h"
 
 /* This is the public domain lookup3 hash by Bob Jenkins from
  * http://burtleburtle.net/bob/c/lookup3.c, modified for style. */
@@ -47,6 +49,7 @@
     } while (0)
 
 uint32_t hash_words(const uint32_t *, size_t n_word, uint32_t basis);
+uint32_t hash_2words(const uint32_t *, uint32_t basis);
 uint32_t hash_bytes(const void *, size_t n_bytes, uint32_t basis);
 
 static inline uint32_t hash_string(const char *s, uint32_t basis)
@@ -62,10 +65,42 @@ static inline uint32_t hash_int(uint32_t x, uint32_t basis)
     x ^= x >> 17;
     x -= x << 9;
     x ^= x << 4;
+    x += basis;
     x -= x << 3;
     x ^= x << 10;
     x ^= x >> 15;
-    return x + basis;
+    return x;
+}
+
+/* An attempt at a useful 1-bit hash function.  Has not been analyzed for
+ * quality. */
+static inline uint32_t hash_boolean(bool x, uint32_t basis)
+{
+    enum { P0 = 0xc2b73583 };   /* This is hash_int(1, 0). */
+    enum { P1 = 0xe90f1258 };   /* This is hash_int(2, 0). */
+    return (x ? P0 : P1) ^ HASH_ROT(basis, 1);
+}
+
+static inline uint32_t hash_double(double x, uint32_t basis)
+{
+    uint32_t value[2];
+    BUILD_ASSERT_DECL(sizeof x == sizeof value);
+
+    memcpy(value, &x, sizeof value);
+    return hash_2words(value, basis);
+}
+
+static inline uint32_t hash_pointer(const void *p, uint32_t basis)
+{
+    /* Often pointers are hashed simply by casting to integer type, but that
+     * has pitfalls since the lower bits of a pointer are often all 0 for
+     * alignment reasons.  It's hard to guess where the entropy really is, so
+     * we give up here and just use a high-quality hash function.
+     *
+     * The double cast suppresses a warning on 64-bit systems about casting to
+     * an integer to different size.  That's OK in this case, since most of the
+     * entropy in the pointer is almost certainly in the lower 32 bits. */
+    return hash_int((uint32_t) (uintptr_t) p, basis);
 }
 
 #endif /* hash.h */
index f75fc07..71943a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -48,11 +48,17 @@ hmap_swap(struct hmap *a, struct hmap *b)
     struct hmap tmp = *a;
     *a = *b;
     *b = tmp;
-    if (a->buckets == &b->one) {
-        a->buckets = &a->one;
-    }
-    if (b->buckets == &a->one) {
-        b->buckets = &b->one;
+    hmap_moved(a);
+    hmap_moved(b);
+}
+
+/* Adjusts 'hmap' to compensate for having moved position in memory (e.g. due
+ * to realloc()). */
+void
+hmap_moved(struct hmap *hmap)
+{
+    if (!hmap->mask) {
+        hmap->buckets = &hmap->one;
     }
 }
 
@@ -143,3 +149,17 @@ hmap_reserve(struct hmap *hmap, size_t n)
         resize(hmap, new_mask);
     }
 }
+
+/* Adjusts 'hmap' to compensate for 'old_node' having moved position in memory
+ * to 'node' (e.g. due to realloc()). */
+void
+hmap_node_moved(struct hmap *hmap,
+                struct hmap_node *old_node, struct hmap_node *node)
+{
+    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
+    while (*bucket != old_node) {
+        bucket = &(*bucket)->next;
+    }
+    *bucket = node;
+}
+
index 67c8e70..2f4a302 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stdlib.h>
 #include "util.h"
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 /* A hash map node, to be embedded inside the data structure being mapped. */
 struct hmap_node {
     size_t hash;                /* Hash value. */
@@ -33,9 +37,27 @@ static inline size_t hmap_node_hash(const struct hmap_node *node)
     return node->hash;
 }
 
+#define HMAP_NODE_NULL ((struct hmap_node *) 1)
+
+/* Returns true if 'node' has been set to null by hmap_node_nullify() and has
+ * not been un-nullified by being inserted into an hmap. */
+static inline bool
+hmap_node_is_null(const struct hmap_node *node)
+{
+    return node->next == HMAP_NODE_NULL;
+}
+
+/* Marks 'node' with a distinctive value that can be tested with
+ * hmap_node_is_null().  */
+static inline void
+hmap_node_nullify(struct hmap_node *node)
+{
+    node->next = HMAP_NODE_NULL;
+}
+
 /* A hash map. */
 struct hmap {
-    struct hmap_node **buckets;
+    struct hmap_node **buckets; /* Must point to 'one' iff 'mask' == 0. */
     struct hmap_node *one;
     size_t mask;
     size_t n;
@@ -48,6 +70,7 @@ struct hmap {
 void hmap_init(struct hmap *);
 void hmap_destroy(struct hmap *);
 void hmap_swap(struct hmap *a, struct hmap *b);
+void hmap_moved(struct hmap *hmap);
 static inline size_t hmap_count(const struct hmap *);
 static inline bool hmap_is_empty(const struct hmap *);
 
@@ -61,22 +84,51 @@ static inline void hmap_insert_fast(struct hmap *,
                                     struct hmap_node *, size_t hash);
 static inline void hmap_insert(struct hmap *, struct hmap_node *, size_t hash);
 static inline void hmap_remove(struct hmap *, struct hmap_node *);
-static inline void hmap_moved(struct hmap *,
-                              struct hmap_node *, struct hmap_node *);
+
+void hmap_node_moved(struct hmap *, struct hmap_node *, struct hmap_node *);
 static inline void hmap_replace(struct hmap *, const struct hmap_node *old,
                                 struct hmap_node *new);
 
-/* Search. */
+/* Search.
+ *
+ * HMAP_FOR_EACH_WITH_HASH iterates NODE over all of the nodes in HMAP that
+ * have hash value equal to HASH.  HMAP_FOR_EACH_IN_BUCKET iterates NODE over
+ * all of the nodes in HMAP that would fall in the same bucket as HASH.  STRUCT
+ * and MEMBER must be the name of the struct that contains the 'struct
+ * hmap_node' and the name of the 'struct hmap_node' member, respectively.
+ *
+ * These macros may be used interchangeably to search for a particular value in
+ * an hmap, see, e.g. shash_find() for an example.  Usually, using
+ * HMAP_FOR_EACH_WITH_HASH provides an optimization, because comparing a hash
+ * value is usually cheaper than comparing an entire hash map key.  But for
+ * simple hash map keys, it makes sense to use HMAP_FOR_EACH_IN_BUCKET because
+ * it avoids doing two comparisons when a single simple comparison suffices.
+ *
+ * The loop should not change NODE to point to a different node or insert or
+ * delete nodes in HMAP (unless it "break"s out of the loop to terminate
+ * iteration).
+ *
+ * HASH is only evaluated once.
+ */
 #define HMAP_FOR_EACH_WITH_HASH(NODE, STRUCT, MEMBER, HASH, HMAP)       \
     for ((NODE) = CONTAINER_OF(hmap_first_with_hash(HMAP, HASH),        \
                                STRUCT, MEMBER);                         \
          &(NODE)->MEMBER != NULL;                                       \
          (NODE) = CONTAINER_OF(hmap_next_with_hash(&(NODE)->MEMBER),    \
                                STRUCT, MEMBER))
+#define HMAP_FOR_EACH_IN_BUCKET(NODE, STRUCT, MEMBER, HASH, HMAP)       \
+    for ((NODE) = CONTAINER_OF(hmap_first_in_bucket(HMAP, HASH),        \
+                               STRUCT, MEMBER);                         \
+         &(NODE)->MEMBER != NULL;                                       \
+         (NODE) = CONTAINER_OF(hmap_next_in_bucket(&(NODE)->MEMBER),    \
+                               STRUCT, MEMBER))
 
 static inline struct hmap_node *hmap_first_with_hash(const struct hmap *,
                                                      size_t hash);
 static inline struct hmap_node *hmap_next_with_hash(const struct hmap_node *);
+static inline struct hmap_node *hmap_first_in_bucket(const struct hmap *,
+                                                     size_t hash);
+static inline struct hmap_node *hmap_next_in_bucket(const struct hmap_node *);
 
 /* Iteration.
  *
@@ -108,6 +160,14 @@ hmap_count(const struct hmap *hmap)
     return hmap->n;
 }
 
+/* Returns the maximum number of nodes that 'hmap' may hold before it should be
+ * rehashed. */
+static inline size_t
+hmap_capacity(const struct hmap *hmap)
+{
+    return hmap->mask * 2 + 1;
+}
+
 /* Returns true if 'hmap' currently contains no nodes,
  * false otherwise. */
 static inline bool
@@ -152,36 +212,24 @@ hmap_remove(struct hmap *hmap, struct hmap_node *node)
     hmap->n--;
 }
 
-/* Adjusts 'hmap' to compensate for 'old_node' having moved position in memory
- * to 'node' (e.g. due to realloc()). */
-static inline void
-hmap_moved(struct hmap *hmap,
-           struct hmap_node *old_node, struct hmap_node *node)
-{
-    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
-    while (*bucket != old_node) {
-        bucket = &(*bucket)->next;
-    }
-    *bucket = node;
-}
-
-/* Puts 'new' in the position in 'hmap' currently occupied by 'old'.  The 'new'
- * node must hash to the same value as 'old'.  The client is responsible for
- * ensuring that the replacement does not violate any client-imposed
- * invariants (e.g. uniqueness of keys within a map).
+/* Puts 'new_node' in the position in 'hmap' currently occupied by 'old_node'.
+ * The 'new_node' must hash to the same value as 'old_node'.  The client is
+ * responsible for ensuring that the replacement does not violate any
+ * client-imposed invariants (e.g. uniqueness of keys within a map).
  *
- * Afterward, 'old' is not part of 'hmap', and the client is responsible for
- * freeing it (if this is desirable). */
+ * Afterward, 'old_node' is not part of 'hmap', and the client is responsible
+ * for freeing it (if this is desirable). */
 static inline void
 hmap_replace(struct hmap *hmap,
-             const struct hmap_node *old, struct hmap_node *new)
+             const struct hmap_node *old_node, struct hmap_node *new_node)
 {
-    struct hmap_node **bucket = &hmap->buckets[old->hash & hmap->mask];
-    while (*bucket != old) {
+    struct hmap_node **bucket = &hmap->buckets[old_node->hash & hmap->mask];
+    while (*bucket != old_node) {
         bucket = &(*bucket)->next;
     }
-    *bucket = new;
-    new->hash = old->hash;
+    *bucket = new_node;
+    new_node->hash = old_node->hash;
+    new_node->next = old_node->next;
 }
 
 static inline struct hmap_node *
@@ -201,6 +249,28 @@ hmap_first_with_hash(const struct hmap *hmap, size_t hash)
     return hmap_next_with_hash__(hmap->buckets[hash & hmap->mask], hash);
 }
 
+/* Returns the first node in 'hmap' in the bucket in which the given 'hash'
+ * would land, or a null pointer if that bucket is empty. */
+static inline struct hmap_node *
+hmap_first_in_bucket(const struct hmap *hmap, size_t hash)
+{
+    return hmap->buckets[hash & hmap->mask];
+}
+
+/* Returns the next node in the same bucket as 'node', or a null pointer if
+ * there are no more nodes in that bucket.
+ *
+ * If the hash map has been reallocated since 'node' was visited, some nodes
+ * may be skipped; if new nodes with the same hash value have been added, they
+ * will be skipped.  (Removing 'node' from the hash map does not prevent
+ * calling this function, since node->next is preserved, although freeing
+ * 'node' of course does.) */
+static inline struct hmap_node *
+hmap_next_in_bucket(const struct hmap_node *node)
+{
+    return node->next;
+}
+
 /* Returns the next node in the same hash map as 'node' with the same hash
  * value, or a null pointer if no more nodes have that hash value.
  *
@@ -251,4 +321,8 @@ hmap_next(const struct hmap *hmap, const struct hmap_node *node)
             : hmap_next__(hmap, (node->hash & hmap->mask) + 1));
 }
 
+#ifdef  __cplusplus
+}
+#endif
+
 #endif /* hmap.h */
diff --git a/lib/json.c b/lib/json.c
new file mode 100644 (file)
index 0000000..10fa3c1
--- /dev/null
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "shash.h"
+#include "unicode.h"
+#include "util.h"
+
+/* The type of a JSON token. */
+enum json_token_type {
+    T_EOF = 0,
+    T_BEGIN_ARRAY = '[',
+    T_END_ARRAY = ']',
+    T_BEGIN_OBJECT = '{',
+    T_END_OBJECT = '}',
+    T_NAME_SEPARATOR = ':',
+    T_VALUE_SEPARATOR = ',',
+    T_FALSE = UCHAR_MAX + 1,
+    T_NULL,
+    T_TRUE,
+    T_INTEGER,
+    T_REAL,
+    T_STRING
+};
+
+/* A JSON token.
+ *
+ * RFC 4627 doesn't define a lexical structure for JSON but I believe this to
+ * be compliant with the standard.
+ */
+struct json_token {
+    enum json_token_type type;
+    union {
+        double real;
+        long long int integer;
+        const char *string;
+    } u;
+};
+
+enum json_lex_state {
+    JSON_LEX_START,             /* Not inside a token. */
+    JSON_LEX_NUMBER,            /* Reading a number. */
+    JSON_LEX_KEYWORD,           /* Reading a keyword. */
+    JSON_LEX_STRING,            /* Reading a quoted string. */
+    JSON_LEX_ESCAPE             /* In a quoted string just after a "\". */
+};
+
+enum json_parse_state {
+    JSON_PARSE_START,           /* Beginning of input. */
+    JSON_PARSE_END,             /* End of input. */
+
+    /* Objects. */
+    JSON_PARSE_OBJECT_INIT,     /* Expecting '}' or an object name. */
+    JSON_PARSE_OBJECT_NAME,     /* Expecting an object name. */
+    JSON_PARSE_OBJECT_COLON,    /* Expecting ':'. */
+    JSON_PARSE_OBJECT_VALUE,    /* Expecting an object value. */
+    JSON_PARSE_OBJECT_NEXT,     /* Expecting ',' or '}'. */
+
+    /* Arrays. */
+    JSON_PARSE_ARRAY_INIT,      /* Expecting ']' or a value. */
+    JSON_PARSE_ARRAY_VALUE,     /* Expecting a value. */
+    JSON_PARSE_ARRAY_NEXT       /* Expecting ',' or ']'. */
+};
+
+struct json_parser_node {
+    struct json *json;
+};
+
+/* A JSON parser. */
+struct json_parser {
+    int flags;
+
+    /* Lexical analysis. */
+    enum json_lex_state lex_state;
+    struct ds buffer;           /* Buffer for accumulating token text. */
+    int line_number;
+    int column_number;
+    int byte_number;
+
+    /* Parsing. */
+    enum json_parse_state parse_state;
+#define JSON_MAX_HEIGHT 1000
+    struct json_parser_node *stack;
+    size_t height, allocated_height;
+    char *member_name;
+
+    /* Parse status. */
+    bool done;
+    char *error;                /* Error message, if any, null if none yet. */
+};
+
+static struct json *json_create(enum json_type type);
+static void json_parser_input(struct json_parser *, struct json_token *);
+
+static void json_error(struct json_parser *p, const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+\f
+const char *
+json_type_to_string(enum json_type type)
+{
+    switch (type) {
+    case JSON_NULL:
+        return "null";
+
+    case JSON_FALSE:
+        return "false";
+
+    case JSON_TRUE:
+        return "true";
+
+    case JSON_OBJECT:
+        return "object";
+
+    case JSON_ARRAY:
+        return "array";
+
+    case JSON_INTEGER:
+    case JSON_REAL:
+        return "number";
+
+    case JSON_STRING:
+        return "string";
+
+    case JSON_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+\f
+/* Functions for manipulating struct json. */
+
+struct json *
+json_null_create(void)
+{
+    return json_create(JSON_NULL);
+}
+
+struct json *
+json_boolean_create(bool b)
+{
+    return json_create(b ? JSON_TRUE : JSON_FALSE);
+}
+
+struct json *
+json_string_create_nocopy(char *s)
+{
+    struct json *json = json_create(JSON_STRING);
+    json->u.string = s;
+    return json;
+}
+
+struct json *
+json_string_create(const char *s)
+{
+    return json_string_create_nocopy(xstrdup(s));
+}
+
+struct json *
+json_array_create_empty(void)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = NULL;
+    json->u.array.n = 0;
+    json->u.array.n_allocated = 0;
+    return json;
+}
+
+void
+json_array_add(struct json *array_, struct json *element)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n >= array->n_allocated) {
+        array->elems = x2nrealloc(array->elems, &array->n_allocated,
+                                  sizeof *array->elems);
+    }
+    array->elems[array->n++] = element;
+}
+
+void
+json_array_trim(struct json *array_)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n < array->n_allocated){
+        array->n_allocated = array->n;
+        array->elems = xrealloc(array->elems, array->n * sizeof *array->elems);
+    }
+}
+
+struct json *
+json_array_create(struct json **elements, size_t n)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = elements;
+    json->u.array.n = n;
+    json->u.array.n_allocated = n;
+    return json;
+}
+
+struct json *
+json_array_create_1(struct json *elem0)
+{
+    struct json **elems = xmalloc(sizeof *elems);
+    elems[0] = elem0;
+    return json_array_create(elems, 1);
+}
+
+struct json *
+json_array_create_2(struct json *elem0, struct json *elem1)
+{
+    struct json **elems = xmalloc(2 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    return json_array_create(elems, 2);
+}
+
+struct json *
+json_array_create_3(struct json *elem0, struct json *elem1, struct json *elem2)
+{
+    struct json **elems = xmalloc(3 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    elems[2] = elem2;
+    return json_array_create(elems, 3);
+}
+
+struct json *
+json_object_create(void)
+{
+    struct json *json = json_create(JSON_OBJECT);
+    json->u.object = xmalloc(sizeof *json->u.object);
+    shash_init(json->u.object);
+    return json;
+}
+
+struct json *
+json_integer_create(long long int integer)
+{
+    struct json *json = json_create(JSON_INTEGER);
+    json->u.integer = integer;
+    return json;
+}
+
+struct json *
+json_real_create(double real)
+{
+    struct json *json = json_create(JSON_REAL);
+    json->u.real = real;
+    return json;
+}
+
+void
+json_object_put(struct json *json, const char *name, struct json *value)
+{
+    shash_add(json->u.object, name, value);
+}
+
+void
+json_object_put_string(struct json *json, const char *name, const char *value)
+{
+    json_object_put(json, name, json_string_create(value));
+}
+
+const char *
+json_string(const struct json *json)
+{
+    assert(json->type == JSON_STRING);
+    return json->u.string;
+}
+
+struct json_array *
+json_array(const struct json *json)
+{
+    assert(json->type == JSON_ARRAY);
+    return (struct json_array *) &json->u.array;
+}
+
+struct shash *
+json_object(const struct json *json)
+{
+    assert(json->type == JSON_OBJECT);
+    return (struct shash *) json->u.object;
+}
+
+bool
+json_boolean(const struct json *json)
+{
+    assert(json->type == JSON_TRUE || json->type == JSON_FALSE);
+    return json->type == JSON_TRUE;
+}
+
+double
+json_real(const struct json *json)
+{
+    assert(json->type == JSON_REAL || json->type == JSON_INTEGER);
+    return json->type == JSON_REAL ? json->u.real : json->u.integer;
+}
+
+int64_t
+json_integer(const struct json *json)
+{
+    assert(json->type == JSON_INTEGER);
+    return json->u.integer;
+}
+\f
+static void json_destroy_object(struct shash *object);
+static void json_destroy_array(struct json_array *array);
+
+/* Frees 'json' and everything it points to, recursively. */
+void
+json_destroy(struct json *json)
+{
+    if (json) {
+        switch (json->type) {
+        case JSON_OBJECT:
+            json_destroy_object(json->u.object);
+            break;
+
+        case JSON_ARRAY:
+            json_destroy_array(&json->u.array);
+            break;
+
+        case JSON_STRING:
+            free(json->u.string);
+            break;
+
+        case JSON_NULL:
+        case JSON_FALSE:
+        case JSON_TRUE:
+        case JSON_INTEGER:
+        case JSON_REAL:
+            break;
+
+        case JSON_N_TYPES:
+            NOT_REACHED();
+        }
+        free(json);
+    }
+}
+
+static void
+json_destroy_object(struct shash *object)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, object) {
+        struct json *value = node->data;
+
+        json_destroy(value);
+        shash_delete(object, node);
+    }
+    shash_destroy(object);
+    free(object);
+}
+
+static void
+json_destroy_array(struct json_array *array)
+{
+    size_t i;
+
+    for (i = 0; i < array->n; i++) {
+        json_destroy(array->elems[i]);
+    }
+    free(array->elems);
+}
+\f
+static struct json *json_clone_object(const struct shash *object);
+static struct json *json_clone_array(const struct json_array *array);
+
+/* Returns a deep copy of 'json'. */
+struct json *
+json_clone(const struct json *json)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_clone_object(json->u.object);
+
+    case JSON_ARRAY:
+        return json_clone_array(&json->u.array);
+
+    case JSON_STRING:
+        return json_string_create(json->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return json_create(json->type);
+
+    case JSON_INTEGER:
+        return json_integer_create(json->u.integer);
+
+    case JSON_REAL:
+        return json_real_create(json->u.real);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct json *
+json_clone_object(const struct shash *object)
+{
+    struct shash_node *node;
+    struct json *json;
+
+    json = json_object_create();
+    SHASH_FOR_EACH (node, object) {
+        struct json *value = node->data;
+        json_object_put(json, node->name, json_clone(value));
+    }
+    return json;
+}
+
+static struct json *
+json_clone_array(const struct json_array *array)
+{
+    struct json **elems;
+    size_t i;
+
+    elems = xmalloc(array->n * sizeof *elems);
+    for (i = 0; i < array->n; i++) {
+        elems[i] = json_clone(array->elems[i]);
+    }
+    return json_array_create(elems, array->n);
+}
+\f
+static size_t
+json_hash_object(const struct shash *object, size_t basis)
+{
+    const struct shash_node **nodes;
+    size_t n, i;
+
+    nodes = shash_sort(object);
+    n = shash_count(object);
+    for (i = 0; i < n; i++) {
+        const struct shash_node *node = nodes[i];
+        basis = hash_string(node->name, basis);
+        basis = json_hash(node->data, basis);
+    }
+    return basis;
+}
+
+static size_t
+json_hash_array(const struct json_array *array, size_t basis)
+{
+    size_t i;
+
+    basis = hash_int(array->n, basis);
+    for (i = 0; i < array->n; i++) {
+        basis = json_hash(array->elems[i], basis);
+    }
+    return basis;
+}
+
+size_t
+json_hash(const struct json *json, size_t basis)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_hash_object(json->u.object, basis);
+
+    case JSON_ARRAY:
+        return json_hash_array(&json->u.array, basis);
+
+    case JSON_STRING:
+        return hash_string(json->u.string, basis);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return hash_int(json->type << 8, basis);
+
+    case JSON_INTEGER:
+        return hash_int(json->u.integer, basis);
+
+    case JSON_REAL:
+        return hash_double(json->u.real, basis);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static bool
+json_equal_object(const struct shash *a, const struct shash *b)
+{
+    struct shash_node *a_node;
+
+    if (shash_count(a) != shash_count(b)) {
+        return false;
+    }
+
+    SHASH_FOR_EACH (a_node, a) {
+        struct shash_node *b_node = shash_find(b, a_node->name);
+        if (!b_node || !json_equal(a_node->data, b_node->data)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+json_equal_array(const struct json_array *a, const struct json_array *b)
+{
+    size_t i;
+
+    if (a->n != b->n) {
+        return false;
+    }
+
+    for (i = 0; i < a->n; i++) {
+        if (!json_equal(a->elems[i], b->elems[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+json_equal(const struct json *a, const struct json *b)
+{
+    if (a->type != b->type) {
+        return false;
+    }
+
+    switch (a->type) {
+    case JSON_OBJECT:
+        return json_equal_object(a->u.object, b->u.object);
+
+    case JSON_ARRAY:
+        return json_equal_array(&a->u.array, &b->u.array);
+
+    case JSON_STRING:
+        return !strcmp(a->u.string, b->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return true;
+
+    case JSON_INTEGER:
+        return a->u.integer == b->u.integer;
+
+    case JSON_REAL:
+        return a->u.real == b->u.real;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+/* Lexical analysis. */
+
+static void
+json_lex_keyword(struct json_parser *p)
+{
+    struct json_token token;
+    const char *s;
+
+    s = ds_cstr(&p->buffer);
+    if (!strcmp(s, "false")) {
+        token.type = T_FALSE;
+    } else if (!strcmp(s, "true")) {
+        token.type = T_TRUE;
+    } else if (!strcmp(s, "null")) {
+        token.type = T_NULL;
+    } else {
+        json_error(p, "invalid keyword '%s'", s);
+        return;
+    }
+    json_parser_input(p, &token);
+}
+
+static void
+json_lex_number(struct json_parser *p)
+{
+    const char *cp = ds_cstr(&p->buffer);
+    unsigned long long int significand = 0;
+    struct json_token token;
+    int sig_digits = 0;
+    bool imprecise = false;
+    bool negative = false;
+    int pow10 = 0;
+
+    /* Leading minus sign. */
+    if (*cp == '-') {
+        negative = true;
+        cp++;
+    }
+
+    /* At least one integer digit, but 0 may not be used as a leading digit for
+     * a longer number. */
+    significand = 0;
+    sig_digits = 0;
+    if (*cp == '0') {
+        cp++;
+        if (isdigit(*cp)) {
+            json_error(p, "leading zeros not allowed");
+            return;
+        }
+    } else if (isdigit(*cp)) {
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+            } else {
+                pow10++;
+                if (*cp != '0') {
+                    imprecise = true;
+                }
+            }
+            cp++;
+        } while (isdigit(*cp));
+    } else {
+        json_error(p, "'-' must be followed by digit");
+        return;
+    }
+
+    /* Optional fraction. */
+    if (*cp == '.') {
+        cp++;
+        if (!isdigit(*cp)) {
+            json_error(p, "decimal point must be followed by digit");
+            return;
+        }
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+                pow10--;
+            } else if (*cp != '0') {
+                imprecise = true;
+            }
+            cp++;
+        } while (isdigit(*cp));
+    }
+
+    /* Optional exponent. */
+    if (*cp == 'e' || *cp == 'E') {
+        bool negative_exponent = false;
+        int exponent;
+
+        cp++;
+        if (*cp == '+') {
+            cp++;
+        } else if (*cp == '-') {
+            negative_exponent = true;
+            cp++;
+        }
+
+        if (!isdigit(*cp)) {
+            json_error(p, "exponent must contain at least one digit");
+            return;
+        }
+
+        exponent = 0;
+        do {
+            if (exponent >= INT_MAX / 10) {
+                json_error(p, "exponent outside valid range");
+                return;
+            }
+            exponent = exponent * 10 + (*cp - '0');
+            cp++;
+        } while (isdigit(*cp));
+
+        if (negative_exponent) {
+            pow10 -= exponent;
+        } else {
+            pow10 += exponent;
+        }
+    }
+
+    if (*cp != '\0') {
+        json_error(p, "syntax error in number");
+        return;
+    }
+
+    /* Figure out number.
+     *
+     * We suppress negative zeros as a matter of policy. */
+    if (!significand) {
+        struct json_token token;
+        token.type = T_INTEGER;
+        token.u.integer = 0;
+        json_parser_input(p, &token);
+        return;
+    }
+
+    if (!imprecise) {
+        while (pow10 > 0 && significand < ULLONG_MAX / 10) {
+            significand *= 10;
+            sig_digits++;
+            pow10--;
+        }
+        while (pow10 < 0 && significand % 10 == 0) {
+            significand /= 10;
+            sig_digits--;
+            pow10++;
+        }
+        if (pow10 == 0
+            && significand <= (negative
+                               ? (unsigned long long int) LLONG_MAX + 1
+                               : LLONG_MAX)) {
+            token.type = T_INTEGER;
+            token.u.integer = negative ? -significand : significand;
+            json_parser_input(p, &token);
+            return;
+        }
+    }
+
+    token.type = T_REAL;
+    if (!str_to_double(ds_cstr(&p->buffer), &token.u.real)) {
+        json_error(p, "number outside valid range");
+        return;
+    }
+    /* Suppress negative zero. */
+    if (token.u.real == 0) {
+        token.u.real = 0;
+    }
+    json_parser_input(p, &token);
+}
+
+static const char *
+json_lex_4hex(const char *cp, const char *end, int *valuep)
+{
+    int value, i;
+
+    if (cp + 4 > end) {
+        return "quoted string ends within \\u escape";
+    }
+
+    value = 0;
+    for (i = 0; i < 4; i++) {
+        unsigned char c = *cp++;
+        if (!isxdigit(c)) {
+            return "malformed \\u escape";
+        }
+        value = (value << 4) | hexit_value(c);
+    }
+    if (!value) {
+        return "null bytes not supported in quoted strings";
+    }
+    *valuep = value;
+    return NULL;
+}
+
+static const char *
+json_lex_unicode(const char *cp, const char *end, struct ds *out)
+{
+    const char *error;
+    int c0, c1;
+
+    error = json_lex_4hex(cp, end, &c0);
+    if (error) {
+        ds_clear(out);
+        ds_put_cstr(out, error);
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_leading_surrogate(c0)) {
+        ds_put_utf8(out, c0);
+        return cp;
+    }
+
+    if (cp + 2 > end || *cp++ != '\\' || *cp++ != 'u') {
+        ds_clear(out);
+        ds_put_cstr(out, "malformed escaped surrogate pair");
+        return NULL;
+    }
+
+    error = json_lex_4hex(cp, end, &c1);
+    if (error) {
+        ds_clear(out);
+        ds_put_cstr(out, error);
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_trailing_surrogate(c1)) {
+        ds_clear(out);
+        ds_put_cstr(out, "second half of escaped surrogate pair is not "
+                    "trailing surrogate");
+        return NULL;
+    }
+
+    ds_put_utf8(out, utf16_decode_surrogate_pair(c0, c1));
+    return cp;
+}
+
+bool
+json_string_unescape(const char *in, size_t in_len, char **outp)
+{
+    const char *end = in + in_len;
+    bool ok = false;
+    struct ds out;
+
+    ds_init(&out);
+    ds_reserve(&out, in_len);
+    if (in_len > 0 && in[in_len - 1] == '\\') {
+        ds_put_cstr(&out, "quoted string may not end with backslash");
+        goto exit;
+    }
+    while (in < end) {
+        if (*in == '"') {
+            ds_clear(&out);
+            ds_put_cstr(&out, "quoted string may not include unescaped \"");
+            goto exit;
+        }
+        if (*in != '\\') {
+            ds_put_char(&out, *in++);
+            continue;
+        }
+
+        in++;
+        switch (*in++) {
+        case '"': case '\\': case '/':
+            ds_put_char(&out, in[-1]);
+            break;
+
+        case 'b':
+            ds_put_char(&out, '\b');
+            break;
+
+        case 'f':
+            ds_put_char(&out, '\f');
+            break;
+
+        case 'n':
+            ds_put_char(&out, '\n');
+            break;
+
+        case 'r':
+            ds_put_char(&out, '\r');
+            break;
+
+        case 't':
+            ds_put_char(&out, '\t');
+            break;
+
+        case 'u':
+            in = json_lex_unicode(in, end, &out);
+            if (!in) {
+                goto exit;
+            }
+            break;
+
+        default:
+            ds_clear(&out);
+            ds_put_format(&out, "bad escape \\%c", in[-1]);
+            goto exit;
+        }
+    }
+    ok = true;
+
+exit:
+    *outp = ds_cstr(&out);
+    return ok;
+}
+
+static void
+json_parser_input_string(struct json_parser *p, const char *s)
+{
+    struct json_token token;
+
+    token.type = T_STRING;
+    token.u.string = s;
+    json_parser_input(p, &token);
+}
+
+static void
+json_lex_string(struct json_parser *p)
+{
+    const char *raw = ds_cstr(&p->buffer);
+    if (!strchr(raw, '\\')) {
+        json_parser_input_string(p, raw);
+    } else {
+        char *cooked;
+
+        if (json_string_unescape(raw, strlen(raw), &cooked)) {
+            json_parser_input_string(p, cooked);
+        } else {
+            json_error(p, "%s", cooked);
+        }
+
+        free(cooked);
+    }
+}
+
+static bool
+json_lex_input(struct json_parser *p, unsigned char c)
+{
+    struct json_token token;
+
+    p->byte_number++;
+    if (c == '\n') {
+        p->column_number = 0;
+        p->line_number++;
+    } else {
+        p->column_number++;
+    }
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        switch (c) {
+        case ' ': case '\t': case '\n': case '\r':
+            /* Nothing to do. */
+            return true;
+
+        case 'a': case 'b': case 'c': case 'd': case 'e':
+        case 'f': case 'g': case 'h': case 'i': case 'j':
+        case 'k': case 'l': case 'm': case 'n': case 'o':
+        case 'p': case 'q': case 'r': case 's': case 't':
+        case 'u': case 'v': case 'w': case 'x': case 'y':
+        case 'z':
+            p->lex_state = JSON_LEX_KEYWORD;
+            break;
+
+        case '[': case '{': case ']': case '}': case ':': case ',':
+            token.type = c;
+            json_parser_input(p, &token);
+            return true;
+
+        case '-':
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            p->lex_state = JSON_LEX_NUMBER;
+            break;
+
+        case '"':
+            p->lex_state = JSON_LEX_STRING;
+            return true;
+
+        default:
+            if (isprint(c)) {
+                json_error(p, "invalid character '%c'", c);
+            } else {
+                json_error(p, "invalid character U+%04x", c);
+            }
+            return true;
+        }
+        break;
+
+    case JSON_LEX_KEYWORD:
+        if (!isalpha((unsigned char) c)) {
+            json_lex_keyword(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_NUMBER:
+        if (!strchr(".0123456789eE-+", c)) {
+            json_lex_number(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_STRING:
+        if (c == '\\') {
+            p->lex_state = JSON_LEX_ESCAPE;
+        } else if (c == '"') {
+            json_lex_string(p);
+            return true;
+        } else if (c < 0x20) {
+            json_error(p, "U+%04X must be escaped in quoted string", c);
+            return true;
+        }
+        break;
+
+    case JSON_LEX_ESCAPE:
+        p->lex_state = JSON_LEX_STRING;
+        break;
+
+    default:
+        abort();
+    }
+    ds_put_char(&p->buffer, c);
+    return true;
+}
+\f
+/* Parsing. */
+
+/* Parses 'string' as a JSON object or array and returns a newly allocated
+ * 'struct json'.  The caller must free the returned structure with
+ * json_destroy() when it is no longer needed.
+ *
+ * 'string' must be encoded in UTF-8.
+ *
+ * If 'string' is valid JSON, then the returned 'struct json' will be either an
+ * object (JSON_OBJECT) or an array (JSON_ARRAY).
+ *
+ * If 'string' is not valid JSON, then the returned 'struct json' will be a
+ * string (JSON_STRING) that describes the particular error encountered during
+ * parsing.  (This is an acceptable means of error reporting because at its top
+ * level JSON must be either an object or an array; a bare string is not
+ * valid.) */
+struct json *
+json_from_string(const char *string)
+{
+    struct json_parser *p = json_parser_create(JSPF_TRAILER);
+    json_parser_feed(p, string, strlen(string));
+    return json_parser_finish(p);
+}
+
+/* Reads the file named 'file_name', parses its contents as a JSON object or
+ * array, and returns a newly allocated 'struct json'.  The caller must free
+ * the returned structure with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_file(const char *file_name)
+{
+    struct json *json;
+    FILE *stream;
+
+    stream = fopen(file_name, "r");
+    if (!stream) {
+        return json_string_create_nocopy(
+            xasprintf("error opening \"%s\": %s", file_name, strerror(errno)));
+    }
+    json = json_from_stream(stream);
+    fclose(stream);
+
+    return json;
+}
+
+/* Parses the contents of 'stream' as a JSON object or array, and returns a
+ * newly allocated 'struct json'.  The caller must free the returned structure
+ * with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_stream(FILE *stream)
+{
+    struct json_parser *p;
+    struct json *json;
+
+    p = json_parser_create(JSPF_TRAILER);
+    for (;;) {
+        char buffer[BUFSIZ];
+        size_t n;
+
+        n = fread(buffer, 1, sizeof buffer, stream);
+        if (!n || json_parser_feed(p, buffer, n) != n) {
+            break;
+        }
+    }
+    json = json_parser_finish(p);
+
+    if (ferror(stream)) {
+        json_destroy(json);
+        json = json_string_create_nocopy(
+            xasprintf("error reading JSON stream: %s", strerror(errno)));
+    }
+
+    return json;
+}
+
+struct json_parser *
+json_parser_create(int flags)
+{
+    struct json_parser *p = xzalloc(sizeof *p);
+    p->flags = flags;
+    return p;
+}
+
+size_t
+json_parser_feed(struct json_parser *p, const char *input, size_t n)
+{
+    size_t i;
+    for (i = 0; !p->done && i < n; ) {
+        if (json_lex_input(p, input[i])) {
+            i++;
+        }
+    }
+    return i;
+}
+
+bool
+json_parser_is_done(const struct json_parser *p)
+{
+    return p->done;
+}
+
+struct json *
+json_parser_finish(struct json_parser *p)
+{
+    struct json *json;
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        break;
+
+    case JSON_LEX_STRING:
+    case JSON_LEX_ESCAPE:
+        json_error(p, "unexpected end of input in quoted string");
+        break;
+
+    case JSON_LEX_NUMBER:
+    case JSON_LEX_KEYWORD:
+        json_lex_input(p, ' ');
+        break;
+    }
+
+    if (p->parse_state == JSON_PARSE_START) {
+        json_error(p, "empty input stream");
+    } else if (p->parse_state != JSON_PARSE_END) {
+        json_error(p, "unexpected end of input");
+    }
+
+    if (!p->error) {
+        assert(p->height == 1);
+        assert(p->stack[0].json != NULL);
+        json = p->stack[--p->height].json;
+    } else {
+        json = json_string_create_nocopy(p->error);
+        p->error = NULL;
+    }
+
+    json_parser_abort(p);
+
+    return json;
+}
+
+void
+json_parser_abort(struct json_parser *p)
+{
+    if (p) {
+        ds_destroy(&p->buffer);
+        if (p->height) {
+            json_destroy(p->stack[0].json);
+        }
+        free(p->stack);
+        free(p->member_name);
+        free(p->error);
+        free(p);
+    }
+}
+
+static struct json_parser_node *
+json_parser_top(struct json_parser *p)
+{
+    return &p->stack[p->height - 1];
+}
+
+static void
+json_parser_put_value(struct json_parser *p, struct json *value)
+{
+    struct json_parser_node *node = json_parser_top(p);
+    if (node->json->type == JSON_OBJECT) {
+        json_object_put(node->json, p->member_name, value);
+        free(p->member_name);
+        p->member_name = NULL;
+    } else if (node->json->type == JSON_ARRAY) {
+        json_array_add(node->json, value);
+    } else {
+        NOT_REACHED();
+    }
+}
+
+static struct json_parser_node *
+json_parser_push(struct json_parser *p,
+                 struct json *new_json, enum json_parse_state new_state)
+{
+    if (p->height < JSON_MAX_HEIGHT) {
+        struct json_parser_node *node;
+
+        if (p->height >= p->allocated_height) {
+            p->stack = x2nrealloc(p->stack, &p->allocated_height,
+                                  sizeof *p->stack);
+        }
+
+        if (p->height > 0) {
+            json_parser_put_value(p, new_json);
+        }
+
+        node = &p->stack[p->height++];
+        node->json = new_json;
+        p->parse_state = new_state;
+        return node;
+    } else {
+        json_destroy(new_json);
+        json_error(p, "input exceeds maximum nesting depth %d",
+                   JSON_MAX_HEIGHT);
+        return NULL;
+    }
+}
+
+static void
+json_parser_push_object(struct json_parser *p)
+{
+    json_parser_push(p, json_object_create(), JSON_PARSE_OBJECT_INIT);
+}
+
+static void
+json_parser_push_array(struct json_parser *p)
+{
+    json_parser_push(p, json_array_create_empty(), JSON_PARSE_ARRAY_INIT);
+}
+
+static void
+json_parse_value(struct json_parser *p, struct json_token *token,
+                 enum json_parse_state next_state)
+{
+    struct json *value;
+
+    switch (token->type) {
+    case T_FALSE:
+        value = json_boolean_create(false);
+        break;
+
+    case T_NULL:
+        value = json_null_create();
+        break;
+
+    case T_TRUE:
+        value = json_boolean_create(true);
+        break;
+
+    case '{':
+        json_parser_push_object(p);
+        return;
+
+    case '[':
+        json_parser_push_array(p);
+        return;
+
+    case T_INTEGER:
+        value = json_integer_create(token->u.integer);
+        break;
+
+    case T_REAL:
+        value = json_real_create(token->u.real);
+        break;
+
+    case T_STRING:
+        value = json_string_create(token->u.string);
+        break;
+
+    case T_EOF:
+    case '}':
+    case ']':
+    case ':':
+    case ',':
+    default:
+        json_error(p, "syntax error expecting value");
+        return;
+    }
+
+    json_parser_put_value(p, value);
+    p->parse_state = next_state;
+}
+
+static void
+json_parser_pop(struct json_parser *p)
+{
+    struct json_parser_node *node;
+
+    /* Conserve memory. */
+    node = json_parser_top(p);
+    if (node->json->type == JSON_ARRAY) {
+        json_array_trim(node->json);
+    }
+
+    /* Pop off the top-of-stack. */
+    if (p->height == 1) {
+        p->parse_state = JSON_PARSE_END;
+        if (!(p->flags & JSPF_TRAILER)) {
+            p->done = true;
+        }
+    } else {
+        p->height--;
+        node = json_parser_top(p);
+        if (node->json->type == JSON_ARRAY) {
+            p->parse_state = JSON_PARSE_ARRAY_NEXT;
+        } else if (node->json->type == JSON_OBJECT) {
+            p->parse_state = JSON_PARSE_OBJECT_NEXT;
+        } else {
+            NOT_REACHED();
+        }
+    }
+}
+
+static void
+json_parser_input(struct json_parser *p, struct json_token *token)
+{
+    switch (p->parse_state) {
+    case JSON_PARSE_START:
+        if (token->type == '{') {
+            json_parser_push_object(p);
+        } else if (token->type == '[') {
+            json_parser_push_array(p);
+        } else {
+            json_error(p, "syntax error at beginning of input");
+        }
+        break;
+
+    case JSON_PARSE_END:
+        json_error(p, "trailing garbage at end of input");
+        break;
+
+    case JSON_PARSE_OBJECT_INIT:
+        if (token->type == '}') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_OBJECT_NAME:
+        if (token->type == T_STRING) {
+            p->member_name = xstrdup(token->u.string);
+            p->parse_state = JSON_PARSE_OBJECT_COLON;
+        } else {
+            json_error(p, "syntax error parsing object expecting string");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_COLON:
+        if (token->type == ':') {
+            p->parse_state = JSON_PARSE_OBJECT_VALUE;
+        } else {
+            json_error(p, "syntax error parsing object expecting ':'");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_VALUE:
+        json_parse_value(p, token, JSON_PARSE_OBJECT_NEXT);
+        break;
+
+    case JSON_PARSE_OBJECT_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_OBJECT_NAME;
+        } else if (token->type == '}') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting '}' or ','");
+        }
+        break;
+
+    case JSON_PARSE_ARRAY_INIT:
+        if (token->type == ']') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_ARRAY_VALUE:
+        json_parse_value(p, token, JSON_PARSE_ARRAY_NEXT);
+        break;
+
+    case JSON_PARSE_ARRAY_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_ARRAY_VALUE;
+        } else if (token->type == ']') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting ']' or ','");
+        }
+        break;
+
+    default:
+        abort();
+    }
+
+    p->lex_state = JSON_LEX_START;
+    ds_clear(&p->buffer);
+}
+
+static struct json *
+json_create(enum json_type type)
+{
+    struct json *json = xmalloc(sizeof *json);
+    json->type = type;
+    return json;
+}
+
+static void
+json_error(struct json_parser *p, const char *format, ...)
+{
+    if (!p->error) {
+        struct ds msg;
+        va_list args;
+
+        ds_init(&msg);
+        ds_put_format(&msg, "line %d, column %d, byte %d: ",
+                      p->line_number, p->column_number, p->byte_number);
+        va_start(args, format);
+        ds_put_format_valist(&msg, format, args);
+        va_end(args);
+
+        p->error = ds_steal_cstr(&msg);
+
+        p->done = true;
+    }
+}
+\f
+#define SPACES_PER_LEVEL 2
+
+struct json_serializer {
+    struct ds *ds;
+    int depth;
+    int flags;
+};
+
+static void json_serialize(const struct json *, struct json_serializer *);
+static void json_serialize_object(const struct shash *object,
+                                  struct json_serializer *);
+static void json_serialize_array(const struct json_array *,
+                                 struct json_serializer *);
+static void json_serialize_string(const char *, struct ds *);
+
+/* Converts 'json' to a string in JSON format, encoded in UTF-8, and returns
+ * that string.  The caller is responsible for freeing the returned string,
+ * with free(), when it is no longer needed.
+ *
+ * If 'flags' contains JSSF_PRETTY, the output is pretty-printed with each
+ * nesting level introducing an additional indentation.  Otherwise, the
+ * returned string does not contain any new-line characters.
+ *
+ * If 'flags' contains JSSF_SORT, members of objects in the output are sorted
+ * in bytewise lexicographic order for reproducibility.  Otherwise, members of
+ * objects are output in an indeterminate order.
+ *
+ * The returned string is valid JSON only if 'json' represents an array or an
+ * object, since a bare literal does not satisfy the JSON grammar. */
+char *
+json_to_string(const struct json *json, int flags)
+{
+    struct ds ds;
+
+    ds_init(&ds);
+    json_to_ds(json, flags, &ds);
+    return ds_steal_cstr(&ds);
+}
+
+/* Same as json_to_string(), but the output is appended to 'ds'. */
+void
+json_to_ds(const struct json *json, int flags, struct ds *ds)
+{
+    struct json_serializer s;
+
+    s.ds = ds;
+    s.depth = 0;
+    s.flags = flags;
+    json_serialize(json, &s);
+}
+
+static void
+json_serialize(const struct json *json, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    switch (json->type) {
+    case JSON_NULL:
+        ds_put_cstr(ds, "null");
+        break;
+
+    case JSON_FALSE:
+        ds_put_cstr(ds, "false");
+        break;
+
+    case JSON_TRUE:
+        ds_put_cstr(ds, "true");
+        break;
+
+    case JSON_OBJECT:
+        json_serialize_object(json->u.object, s);
+        break;
+
+    case JSON_ARRAY:
+        json_serialize_array(&json->u.array, s);
+        break;
+
+    case JSON_INTEGER:
+        ds_put_format(ds, "%lld", json->u.integer);
+        break;
+
+    case JSON_REAL:
+        ds_put_format(ds, "%.*g", DBL_DIG, json->u.real);
+        break;
+
+    case JSON_STRING:
+        json_serialize_string(json->u.string, ds);
+        break;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static void
+indent_line(struct json_serializer *s)
+{
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(s->ds, '\n');
+        ds_put_char_multiple(s->ds, ' ', SPACES_PER_LEVEL * s->depth);
+    }
+}
+
+static void
+json_serialize_object_member(size_t i, const struct shash_node *node,
+                             struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    if (i) {
+        ds_put_char(ds, ',');
+        indent_line(s);
+    }
+
+    json_serialize_string(node->name, ds);
+    ds_put_char(ds, ':');
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(ds, ' ');
+    }
+    json_serialize(node->data, s);
+}
+
+static void
+json_serialize_object(const struct shash *object, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    ds_put_char(ds, '{');
+
+    s->depth++;
+    indent_line(s);
+
+    if (s->flags & JSSF_SORT) {
+        const struct shash_node **nodes;
+        size_t n, i;
+
+        nodes = shash_sort(object);
+        n = shash_count(object);
+        for (i = 0; i < n; i++) {
+            json_serialize_object_member(i, nodes[i], s);
+        }
+        free(nodes);
+    } else {
+        struct shash_node *node;
+        size_t i;
+
+        i = 0;
+        SHASH_FOR_EACH (node, object) {
+            json_serialize_object_member(i++, node, s);
+        }
+    }
+
+    ds_put_char(ds, '}');
+    s->depth--;
+}
+
+static void
+json_serialize_array(const struct json_array *array, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+    size_t i;
+
+    ds_put_char(ds, '[');
+    s->depth++;
+
+    if (array->n > 0) {
+        indent_line(s);
+
+        for (i = 0; i < array->n; i++) {
+            if (i) {
+                ds_put_char(ds, ',');
+                indent_line(s);
+            }
+            json_serialize(array->elems[i], s);
+        }
+    }
+
+    s->depth--;
+    ds_put_char(ds, ']');
+}
+
+static void
+json_serialize_string(const char *string, struct ds *ds)
+{
+    uint8_t c;
+
+    ds_put_char(ds, '"');
+    while ((c = *string++) != '\0') {
+        switch (c) {
+        case '"':
+            ds_put_cstr(ds, "\\\"");
+            break;
+
+        case '\\':
+            ds_put_cstr(ds, "\\\\");
+            break;
+
+        case '\b':
+            ds_put_cstr(ds, "\\b");
+            break;
+
+        case '\f':
+            ds_put_cstr(ds, "\\f");
+            break;
+
+        case '\n':
+            ds_put_cstr(ds, "\\n");
+            break;
+
+        case '\r':
+            ds_put_cstr(ds, "\\r");
+            break;
+
+        case '\t':
+            ds_put_cstr(ds, "\\t");
+            break;
+
+        default:
+            if (c >= 32) {
+                ds_put_char(ds, c);
+            } else {
+                ds_put_format(ds, "\\u%04x", c);
+            }
+            break;
+        }
+    }
+    ds_put_char(ds, '"');
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644 (file)
index 0000000..41257b0
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+/* This is an implementation of JavaScript Object Notation (JSON) as specified
+ * by RFC 4627.  It is intended to fully comply with RFC 4627, with the
+ * following known exceptions and clarifications:
+ *
+ *      - Null bytes (\u0000) are not allowed in strings.
+ *
+ *      - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
+ *        encodings).
+ *
+ *      - Names within an object must be unique (RFC 4627 says that they
+ *        "should" be unique).
+ */
+
+#include "shash.h"
+
+struct ds;
+
+/* Type of a JSON value. */
+enum json_type {
+    JSON_NULL,                  /* null */
+    JSON_FALSE,                 /* false */
+    JSON_TRUE,                  /* true */
+    JSON_OBJECT,                /* {"a": b, "c": d, ...} */
+    JSON_ARRAY,                 /* [1, 2, 3, ...] */
+    JSON_INTEGER,               /* 123. */
+    JSON_REAL,                  /* 123.456. */
+    JSON_STRING,                /* "..." */
+    JSON_N_TYPES
+};
+
+const char *json_type_to_string(enum json_type);
+
+/* A JSON array. */
+struct json_array {
+    size_t n, n_allocated;
+    struct json **elems;
+};
+
+/* A JSON value. */
+struct json {
+    enum json_type type;
+    union {
+        struct shash *object;   /* Contains "struct json *"s. */
+        struct json_array array;
+        long long int integer;
+        double real;
+        char *string;
+    } u;
+};
+
+struct json *json_null_create(void);
+struct json *json_boolean_create(bool);
+struct json *json_string_create(const char *);
+struct json *json_string_create_nocopy(char *);
+struct json *json_integer_create(long long int);
+struct json *json_real_create(double);
+
+struct json *json_array_create_empty(void);
+void json_array_add(struct json *, struct json *element);
+void json_array_trim(struct json *);
+struct json *json_array_create(struct json **, size_t n);
+struct json *json_array_create_1(struct json *);
+struct json *json_array_create_2(struct json *, struct json *);
+struct json *json_array_create_3(struct json *, struct json *, struct json *);
+
+struct json *json_object_create(void);
+void json_object_put(struct json *, const char *name, struct json *value);
+void json_object_put_string(struct json *,
+                            const char *name, const char *value);
+
+const char *json_string(const struct json *);
+struct json_array *json_array(const struct json *);
+struct shash *json_object(const struct json *);
+bool json_boolean(const struct json *);
+double json_real(const struct json *);
+int64_t json_integer(const struct json *);
+
+struct json *json_clone(const struct json *);
+void json_destroy(struct json *);
+
+size_t json_hash(const struct json *, size_t basis);
+bool json_equal(const struct json *, const struct json *);
+\f
+/* Parsing JSON. */
+enum {
+    JSPF_TRAILER = 1 << 0       /* Check for garbage following input.  */
+};
+
+struct json_parser *json_parser_create(int flags);
+size_t json_parser_feed(struct json_parser *, const char *, size_t);
+bool json_parser_is_done(const struct json_parser *);
+struct json *json_parser_finish(struct json_parser *);
+void json_parser_abort(struct json_parser *);
+
+struct json *json_from_string(const char *string);
+struct json *json_from_file(const char *file_name);
+struct json *json_from_stream(FILE *stream);
+\f
+/* Serializing JSON. */
+
+enum {
+    JSSF_PRETTY = 1 << 0,       /* Multiple lines with indentation, if true. */
+    JSSF_SORT = 1 << 1          /* Object members in sorted order, if true. */
+};
+char *json_to_string(const struct json *, int flags);
+void json_to_ds(const struct json *, int flags, struct ds *);
+\f
+/* JSON string formatting operations. */
+
+bool json_string_unescape(const char *in, size_t in_len, char **outp);
+
+#endif /* json.h */
diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c
new file mode 100644 (file)
index 0000000..5e05480
--- /dev/null
@@ -0,0 +1,868 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include "byteq.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "list.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "queue.h"
+#include "reconnect.h"
+#include "stream.h"
+#include "timeval.h"
+
+#define THIS_MODULE VLM_jsonrpc
+#include "vlog.h"
+\f
+struct jsonrpc {
+    struct stream *stream;
+    char *name;
+    int status;
+
+    /* Input. */
+    struct byteq input;
+    struct json_parser *parser;
+    struct jsonrpc_msg *received;
+
+    /* Output. */
+    struct ovs_queue output;
+    size_t backlog;
+};
+
+/* Rate limit for error messages. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+
+static void jsonrpc_received(struct jsonrpc *);
+static void jsonrpc_cleanup(struct jsonrpc *);
+
+struct jsonrpc *
+jsonrpc_open(struct stream *stream)
+{
+    struct jsonrpc *rpc;
+
+    assert(stream != NULL);
+
+    rpc = xzalloc(sizeof *rpc);
+    rpc->name = xstrdup(stream_get_name(stream));
+    rpc->stream = stream;
+    byteq_init(&rpc->input);
+    queue_init(&rpc->output);
+
+    return rpc;
+}
+
+void
+jsonrpc_close(struct jsonrpc *rpc)
+{
+    if (rpc) {
+        jsonrpc_cleanup(rpc);
+        free(rpc->name);
+        free(rpc);
+    }
+}
+
+void
+jsonrpc_run(struct jsonrpc *rpc)
+{
+    if (rpc->status) {
+        return;
+    }
+
+    stream_run(rpc->stream);
+    while (!queue_is_empty(&rpc->output)) {
+        struct ofpbuf *buf = rpc->output.head;
+        int retval;
+
+        retval = stream_send(rpc->stream, buf->data, buf->size);
+        if (retval >= 0) {
+            rpc->backlog -= retval;
+            ofpbuf_pull(buf, retval);
+            if (!buf->size) {
+                ofpbuf_delete(queue_pop_head(&rpc->output));
+            }
+        } else {
+            if (retval != -EAGAIN) {
+                VLOG_WARN_RL(&rl, "%s: send error: %s",
+                             rpc->name, strerror(-retval));
+                jsonrpc_error(rpc, -retval);
+            }
+            break;
+        }
+    }
+}
+
+void
+jsonrpc_wait(struct jsonrpc *rpc)
+{
+    if (!rpc->status) {
+        stream_run_wait(rpc->stream);
+        if (!queue_is_empty(&rpc->output)) {
+            stream_send_wait(rpc->stream);
+        }
+    }
+}
+
+int
+jsonrpc_get_status(const struct jsonrpc *rpc)
+{
+    return rpc->status;
+}
+
+size_t
+jsonrpc_get_backlog(const struct jsonrpc *rpc)
+{
+    return rpc->status ? 0 : rpc->backlog;
+}
+
+const char *
+jsonrpc_get_name(const struct jsonrpc *rpc)
+{
+    return rpc->name;
+}
+
+static void
+jsonrpc_log_msg(const struct jsonrpc *rpc, const char *title,
+                const struct jsonrpc_msg *msg)
+{
+    if (VLOG_IS_DBG_ENABLED()) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        if (msg->method) {
+            ds_put_format(&s, ", method=\"%s\"", msg->method);
+        }
+        if (msg->params) {
+            ds_put_cstr(&s, ", params=");
+            json_to_ds(msg->params, 0, &s);
+        }
+        if (msg->result) {
+            ds_put_cstr(&s, ", result=");
+            json_to_ds(msg->result, 0, &s);
+        }
+        if (msg->error) {
+            ds_put_cstr(&s, ", error=");
+            json_to_ds(msg->error, 0, &s);
+        }
+        if (msg->id) {
+            ds_put_cstr(&s, ", id=");
+            json_to_ds(msg->id, 0, &s);
+        }
+        VLOG_DBG("%s: %s %s%s", rpc->name, title,
+                 jsonrpc_msg_type_to_string(msg->type), ds_cstr(&s));
+        ds_destroy(&s);
+    }
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_send(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    struct ofpbuf *buf;
+    struct json *json;
+    size_t length;
+    char *s;
+
+    if (rpc->status) {
+        jsonrpc_msg_destroy(msg);
+        return rpc->status;
+    }
+
+    jsonrpc_log_msg(rpc, "send", msg);
+
+    json = jsonrpc_msg_to_json(msg);
+    s = json_to_string(json, 0);
+    length = strlen(s);
+    json_destroy(json);
+
+    buf = xmalloc(sizeof *buf);
+    ofpbuf_use(buf, s, length);
+    buf->size = length;
+    queue_push_tail(&rpc->output, buf);
+    rpc->backlog += length;
+
+    if (rpc->output.n == 1) {
+        jsonrpc_run(rpc);
+    }
+    return rpc->status;
+}
+
+int
+jsonrpc_recv(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    *msgp = NULL;
+    if (rpc->status) {
+        return rpc->status;
+    }
+
+    while (!rpc->received) {
+        if (byteq_is_empty(&rpc->input)) {
+            size_t chunk;
+            int retval;
+
+            chunk = byteq_headroom(&rpc->input);
+            retval = stream_recv(rpc->stream, byteq_head(&rpc->input), chunk);
+            if (retval < 0) {
+                if (retval == -EAGAIN) {
+                    return EAGAIN;
+                } else {
+                    VLOG_WARN_RL(&rl, "%s: receive error: %s",
+                                 rpc->name, strerror(-retval));
+                    jsonrpc_error(rpc, -retval);
+                    return rpc->status;
+                }
+            } else if (retval == 0) {
+                VLOG_INFO_RL(&rl, "%s: connection closed", rpc->name);
+                jsonrpc_error(rpc, EOF);
+                return EOF;
+            }
+            byteq_advance_head(&rpc->input, retval);
+        } else {
+            size_t n, used;
+
+            if (!rpc->parser) {
+                rpc->parser = json_parser_create(0);
+            }
+            n = byteq_tailroom(&rpc->input);
+            used = json_parser_feed(rpc->parser,
+                                    (char *) byteq_tail(&rpc->input), n);
+            byteq_advance_tail(&rpc->input, used);
+            if (json_parser_is_done(rpc->parser)) {
+                jsonrpc_received(rpc);
+                if (rpc->status) {
+                    return rpc->status;
+                }
+            }
+        }
+    }
+
+    *msgp = rpc->received;
+    rpc->received = NULL;
+    return 0;
+}
+
+void
+jsonrpc_recv_wait(struct jsonrpc *rpc)
+{
+    if (rpc->status || rpc->received || !byteq_is_empty(&rpc->input)) {
+        poll_immediate_wake();
+    } else {
+        stream_recv_wait(rpc->stream);
+    }
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_send_block(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    int error;
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        return error;
+    }
+
+    for (;;) {
+        jsonrpc_run(rpc);
+        if (queue_is_empty(&rpc->output) || rpc->status) {
+            return rpc->status;
+        }
+        jsonrpc_wait(rpc);
+        poll_block();
+    }
+}
+
+int
+jsonrpc_recv_block(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    for (;;) {
+        int error = jsonrpc_recv(rpc, msgp);
+        if (error != EAGAIN) {
+            return error;
+        }
+
+        jsonrpc_run(rpc);
+        jsonrpc_wait(rpc);
+        jsonrpc_recv_wait(rpc);
+        poll_block();
+    }
+}
+
+/* Always takes ownership of 'request', regardless of success. */
+int
+jsonrpc_transact_block(struct jsonrpc *rpc, struct jsonrpc_msg *request,
+                       struct jsonrpc_msg **replyp)
+{
+    struct jsonrpc_msg *reply = NULL;
+    struct json *id;
+    int error;
+
+    id = json_clone(request->id);
+    error = jsonrpc_send_block(rpc, request);
+    if (!error) {
+        for (;;) {
+            error = jsonrpc_recv_block(rpc, &reply);
+            if (error
+                || (reply->type == JSONRPC_REPLY
+                    && json_equal(id, reply->id))) {
+                break;
+            }
+            jsonrpc_msg_destroy(reply);
+        }
+    }
+    *replyp = error ? NULL : reply;
+    json_destroy(id);
+    return error;
+}
+
+static void
+jsonrpc_received(struct jsonrpc *rpc)
+{
+    struct jsonrpc_msg *msg;
+    struct json *json;
+    char *error;
+
+    json = json_parser_finish(rpc->parser);
+    rpc->parser = NULL;
+    if (json->type == JSON_STRING) {
+        VLOG_WARN_RL(&rl, "%s: error parsing stream: %s",
+                     rpc->name, json_string(json));
+        jsonrpc_error(rpc, EPROTO);
+        json_destroy(json);
+        return;
+    }
+
+    error = jsonrpc_msg_from_json(json, &msg);
+    if (error) {
+        VLOG_WARN_RL(&rl, "%s: received bad JSON-RPC message: %s",
+                     rpc->name, error);
+        free(error);
+        jsonrpc_error(rpc, EPROTO);
+        return;
+    }
+
+    jsonrpc_log_msg(rpc, "received", msg);
+    rpc->received = msg;
+}
+
+void
+jsonrpc_error(struct jsonrpc *rpc, int error)
+{
+    assert(error);
+    if (!rpc->status) {
+        rpc->status = error;
+        jsonrpc_cleanup(rpc);
+    }
+}
+
+static void
+jsonrpc_cleanup(struct jsonrpc *rpc)
+{
+    stream_close(rpc->stream);
+    rpc->stream = NULL;
+
+    json_parser_abort(rpc->parser);
+    rpc->parser = NULL;
+
+    jsonrpc_msg_destroy(rpc->received);
+    rpc->received = NULL;
+
+    queue_clear(&rpc->output);
+    rpc->backlog = 0;
+}
+\f
+static struct jsonrpc_msg *
+jsonrpc_create(enum jsonrpc_msg_type type, const char *method,
+                struct json *params, struct json *result, struct json *error,
+                struct json *id)
+{
+    struct jsonrpc_msg *msg = xmalloc(sizeof *msg);
+    msg->type = type;
+    msg->method = method ? xstrdup(method) : NULL;
+    msg->params = params;
+    msg->result = result;
+    msg->error = error;
+    msg->id = id;
+    return msg;
+}
+
+static struct json *
+jsonrpc_create_id(void)
+{
+    static unsigned int id;
+    return json_integer_create(id++);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_request(const char *method, struct json *params,
+                       struct json **idp)
+{
+    struct json *id = jsonrpc_create_id();
+    if (idp) {
+        *idp = json_clone(id);
+    }
+    return jsonrpc_create(JSONRPC_REQUEST, method, params, NULL, NULL, id);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_notify(const char *method, struct json *params)
+{
+    return jsonrpc_create(JSONRPC_NOTIFY, method, params, NULL, NULL, NULL);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_reply(struct json *result, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, result, NULL,
+                           json_clone(id));
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_error(struct json *error, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, NULL, error,
+                           json_clone(id));
+}
+
+const char *
+jsonrpc_msg_type_to_string(enum jsonrpc_msg_type type)
+{
+    switch (type) {
+    case JSONRPC_REQUEST:
+        return "request";
+
+    case JSONRPC_NOTIFY:
+        return "notification";
+
+    case JSONRPC_REPLY:
+        return "reply";
+
+    case JSONRPC_ERROR:
+        return "error";
+    }
+    return "(null)";
+}
+
+char *
+jsonrpc_msg_is_valid(const struct jsonrpc_msg *m)
+{
+    const char *type_name;
+    unsigned int pattern;
+
+    if (m->params && m->params->type != JSON_ARRAY) {
+        return xstrdup("\"params\" must be JSON array");
+    }
+
+    switch (m->type) {
+    case JSONRPC_REQUEST:
+        pattern = 0x11001;
+        break;
+
+    case JSONRPC_NOTIFY:
+        pattern = 0x11000;
+        break;
+
+    case JSONRPC_REPLY:
+        pattern = 0x00101;
+        break;
+
+    case JSONRPC_ERROR:
+        pattern = 0x00011;
+        break;
+
+    default:
+        return xasprintf("invalid JSON-RPC message type %d", m->type);
+    }
+
+    type_name = jsonrpc_msg_type_to_string(m->type);
+    if ((m->method != NULL) != ((pattern & 0x10000) != 0)) {
+        return xasprintf("%s must%s have \"method\"",
+                         type_name, (pattern & 0x10000) ? "" : " not");
+
+    }
+    if ((m->params != NULL) != ((pattern & 0x1000) != 0)) {
+        return xasprintf("%s must%s have \"params\"",
+                         type_name, (pattern & 0x1000) ? "" : " not");
+
+    }
+    if ((m->result != NULL) != ((pattern & 0x100) != 0)) {
+        return xasprintf("%s must%s have \"result\"",
+                         type_name, (pattern & 0x100) ? "" : " not");
+
+    }
+    if ((m->error != NULL) != ((pattern & 0x10) != 0)) {
+        return xasprintf("%s must%s have \"error\"",
+                         type_name, (pattern & 0x10) ? "" : " not");
+
+    }
+    if ((m->id != NULL) != ((pattern & 0x1) != 0)) {
+        return xasprintf("%s must%s have \"id\"",
+                         type_name, (pattern & 0x1) ? "" : " not");
+
+    }
+    return NULL;
+}
+
+void
+jsonrpc_msg_destroy(struct jsonrpc_msg *m)
+{
+    if (m) {
+        free(m->method);
+        json_destroy(m->params);
+        json_destroy(m->result);
+        json_destroy(m->error);
+        json_destroy(m->id);
+        free(m);
+    }
+}
+
+static struct json *
+null_from_json_null(struct json *json)
+{
+    if (json && json->type == JSON_NULL) {
+        json_destroy(json);
+        return NULL;
+    }
+    return json;
+}
+
+char *
+jsonrpc_msg_from_json(struct json *json, struct jsonrpc_msg **msgp)
+{
+    struct json *method = NULL;
+    struct jsonrpc_msg *msg = NULL;
+    struct shash *object;
+    char *error;
+
+    if (json->type != JSON_OBJECT) {
+        error = xstrdup("message is not a JSON object");
+        goto exit;
+    }
+    object = json_object(json);
+
+    method = shash_find_and_delete(object, "method");
+    if (method && method->type != JSON_STRING) {
+        error = xstrdup("method is not a JSON string");
+        goto exit;
+    }
+
+    msg = xzalloc(sizeof *msg);
+    msg->method = method ? xstrdup(method->u.string) : NULL;
+    msg->params = null_from_json_null(shash_find_and_delete(object, "params"));
+    msg->result = null_from_json_null(shash_find_and_delete(object, "result"));
+    msg->error = null_from_json_null(shash_find_and_delete(object, "error"));
+    msg->id = null_from_json_null(shash_find_and_delete(object, "id"));
+    msg->type = (msg->result ? JSONRPC_REPLY
+                 : msg->error ? JSONRPC_ERROR
+                 : msg->id ? JSONRPC_REQUEST
+                 : JSONRPC_NOTIFY);
+    if (!shash_is_empty(object)) {
+        error = xasprintf("message has unexpected member \"%s\"",
+                          shash_first(object)->name);
+        goto exit;
+    }
+    error = jsonrpc_msg_is_valid(msg);
+    if (error) {
+        goto exit;
+    }
+
+exit:
+    json_destroy(method);
+    json_destroy(json);
+    if (error) {
+        jsonrpc_msg_destroy(msg);
+        msg = NULL;
+    }
+    *msgp = msg;
+    return error;
+}
+
+struct json *
+jsonrpc_msg_to_json(struct jsonrpc_msg *m)
+{
+    struct json *json = json_object_create();
+
+    if (m->method) {
+        json_object_put(json, "method", json_string_create_nocopy(m->method));
+    }
+
+    if (m->params) {
+        json_object_put(json, "params", m->params);
+    }
+
+    if (m->result) {
+        json_object_put(json, "result", m->result);
+    } else if (m->type == JSONRPC_ERROR) {
+        json_object_put(json, "result", json_null_create());
+    }
+
+    if (m->error) {
+        json_object_put(json, "error", m->error);
+    } else if (m->type == JSONRPC_REPLY) {
+        json_object_put(json, "error", json_null_create());
+    }
+
+    if (m->id) {
+        json_object_put(json, "id", m->id);
+    } else if (m->type == JSONRPC_NOTIFY) {
+        json_object_put(json, "id", json_null_create());
+    }
+
+    free(m);
+
+    return json;
+}
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session {
+    struct reconnect *reconnect;
+    struct jsonrpc *rpc;
+    struct stream *stream;
+    unsigned int seqno;
+};
+
+/* Creates and returns a jsonrpc_session that connects and reconnects, with
+ * back-off, to 'name', which should be a string acceptable to
+ * stream_open(). */
+struct jsonrpc_session *
+jsonrpc_session_open(const char *name)
+{
+    struct jsonrpc_session *s;
+
+    s = xmalloc(sizeof *s);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, name);
+    reconnect_enable(s->reconnect, time_msec());
+    s->rpc = NULL;
+    s->stream = NULL;
+    s->seqno = 0;
+
+    return s;
+}
+
+/* Creates and returns a jsonrpc_session that is initially connected to
+ * 'jsonrpc'.  If the connection is dropped, it will not be reconnected. */
+struct jsonrpc_session *
+jsonrpc_session_open_unreliably(struct jsonrpc *jsonrpc)
+{
+    struct jsonrpc_session *s;
+
+    s = xmalloc(sizeof *s);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, jsonrpc_get_name(jsonrpc));
+    reconnect_set_max_tries(s->reconnect, 0);
+    reconnect_connected(s->reconnect, time_msec());
+    s->rpc = jsonrpc;
+    s->stream = NULL;
+    s->seqno = 0;
+
+    return s;
+}
+
+void
+jsonrpc_session_close(struct jsonrpc_session *s)
+{
+    if (s) {
+        jsonrpc_close(s->rpc);
+        reconnect_destroy(s->reconnect);
+        stream_close(s->stream);
+        free(s);
+    }
+}
+
+static void
+jsonrpc_session_disconnect(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_error(s->rpc, EOF);
+        jsonrpc_close(s->rpc);
+        s->rpc = NULL;
+        s->seqno++;
+    } else if (s->stream) {
+        stream_close(s->stream);
+        s->stream = NULL;
+        s->seqno++;
+    }
+}
+
+static void
+jsonrpc_session_connect(struct jsonrpc_session *s)
+{
+    int error;
+
+    jsonrpc_session_disconnect(s);
+    error = stream_open(reconnect_get_name(s->reconnect), &s->stream);
+    if (error) {
+        reconnect_connect_failed(s->reconnect, time_msec(), error);
+    } else {
+        reconnect_connecting(s->reconnect, time_msec());
+    }
+    s->seqno++;
+}
+
+void
+jsonrpc_session_run(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        int error;
+
+        jsonrpc_run(s->rpc);
+        error = jsonrpc_get_status(s->rpc);
+        if (error) {
+            reconnect_disconnected(s->reconnect, time_msec(), 0);
+            jsonrpc_session_disconnect(s);
+        }
+    } else if (s->stream) {
+        int error;
+
+        stream_run(s->stream);
+        error = stream_connect(s->stream);
+        if (!error) {
+            reconnect_connected(s->reconnect, time_msec());
+            s->rpc = jsonrpc_open(s->stream);
+            s->stream = NULL;
+        } else if (error != EAGAIN) {
+            reconnect_connect_failed(s->reconnect, time_msec(), error);
+            stream_close(s->stream);
+            s->stream = NULL;
+        }
+    }
+
+    switch (reconnect_run(s->reconnect, time_msec())) {
+    case RECONNECT_CONNECT:
+        jsonrpc_session_connect(s);
+        break;
+
+    case RECONNECT_DISCONNECT:
+        reconnect_disconnected(s->reconnect, time_msec(), 0);
+        jsonrpc_session_disconnect(s);
+        break;
+
+    case RECONNECT_PROBE:
+        if (s->rpc) {
+            struct json *params;
+            struct jsonrpc_msg *request;
+
+            params = json_array_create_empty();
+            request = jsonrpc_create_request("echo", params, NULL);
+            json_destroy(request->id);
+            request->id = json_string_create("echo");
+            jsonrpc_send(s->rpc, request);
+        }
+        break;
+    }
+}
+
+void
+jsonrpc_session_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_wait(s->rpc);
+    } else if (s->stream) {
+        stream_run_wait(s->stream);
+        stream_connect_wait(s->stream);
+    }
+    reconnect_wait(s->reconnect, time_msec());
+}
+
+size_t
+jsonrpc_session_get_backlog(const struct jsonrpc_session *s)
+{
+    return s->rpc ? jsonrpc_get_backlog(s->rpc) : 0;
+}
+
+const char *
+jsonrpc_session_get_name(const struct jsonrpc_session *s)
+{
+    return reconnect_get_name(s->reconnect);
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg)
+{
+    if (s->rpc) {
+        return jsonrpc_send(s->rpc, msg);
+    } else {
+        jsonrpc_msg_destroy(msg);
+        return ENOTCONN;
+    }
+}
+
+struct jsonrpc_msg *
+jsonrpc_session_recv(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        struct jsonrpc_msg *msg;
+        jsonrpc_recv(s->rpc, &msg);
+        if (msg) {
+            reconnect_received(s->reconnect, time_msec());
+            if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+                /* Echo request.  Send reply. */
+                struct jsonrpc_msg *reply;
+
+                reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+                jsonrpc_session_send(s, reply);
+            } else if (msg->type == JSONRPC_REPLY
+                && msg->id && msg->id->type == JSON_STRING
+                && !strcmp(msg->id->u.string, "echo")) {
+                /* It's a reply to our echo request.  Suppress it. */
+            } else {
+                return msg;
+            }
+            jsonrpc_msg_destroy(msg);
+        }
+    }
+    return NULL;
+}
+
+void
+jsonrpc_session_recv_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_recv_wait(s->rpc);
+    }
+}
+
+bool
+jsonrpc_session_is_alive(const struct jsonrpc_session *s)
+{
+    return s->rpc || s->stream || reconnect_get_max_tries(s->reconnect);
+}
+
+bool
+jsonrpc_session_is_connected(const struct jsonrpc_session *s)
+{
+    return s->rpc != NULL;
+}
+
+unsigned int
+jsonrpc_session_get_seqno(const struct jsonrpc_session *s)
+{
+    return s->seqno;
+}
+
+void
+jsonrpc_session_force_reconnect(struct jsonrpc_session *s)
+{
+    reconnect_force_reconnect(s->reconnect, time_msec());
+}
diff --git a/lib/jsonrpc.h b/lib/jsonrpc.h
new file mode 100644 (file)
index 0000000..ae8b9de
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSONRPC_H
+#define JSONRPC_H 1
+
+/* This is an implementation of the JSON-RPC 1.0 specification defined at
+ * http://json-rpc.org/wiki/specification. */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct json;
+struct jsonrpc_msg;
+struct stream;
+\f
+/* API for a JSON-RPC stream. */
+
+struct jsonrpc *jsonrpc_open(struct stream *);
+void jsonrpc_close(struct jsonrpc *);
+
+void jsonrpc_run(struct jsonrpc *);
+void jsonrpc_wait(struct jsonrpc *);
+
+void jsonrpc_error(struct jsonrpc *, int error);
+int jsonrpc_get_status(const struct jsonrpc *);
+size_t jsonrpc_get_backlog(const struct jsonrpc *);
+const char *jsonrpc_get_name(const struct jsonrpc *);
+
+int jsonrpc_send(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv(struct jsonrpc *, struct jsonrpc_msg **);
+void jsonrpc_recv_wait(struct jsonrpc *);
+
+int jsonrpc_send_block(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv_block(struct jsonrpc *, struct jsonrpc_msg **);
+int jsonrpc_transact_block(struct jsonrpc *, struct jsonrpc_msg *,
+                           struct jsonrpc_msg **);
+
+/* Messages. */
+enum jsonrpc_msg_type {
+    JSONRPC_REQUEST,           /* Request. */
+    JSONRPC_NOTIFY,            /* Notification. */
+    JSONRPC_REPLY,             /* Successful reply. */
+    JSONRPC_ERROR              /* Error reply. */
+};
+
+struct jsonrpc_msg {
+    enum jsonrpc_msg_type type;
+    char *method;               /* Request or notification only. */
+    struct json *params;        /* Request or notification only. */
+    struct json *result;        /* Successful reply only. */
+    struct json *error;         /* Error reply only. */
+    struct json *id;            /* Request or reply only. */
+};
+
+struct jsonrpc_msg *jsonrpc_create_request(const char *method,
+                                           struct json *params,
+                                           struct json **idp);
+struct jsonrpc_msg *jsonrpc_create_notify(const char *method,
+                                          struct json *params);
+struct jsonrpc_msg *jsonrpc_create_reply(struct json *result,
+                                         const struct json *id);
+struct jsonrpc_msg *jsonrpc_create_error(struct json *error,
+                                         const struct json *id);
+
+const char *jsonrpc_msg_type_to_string(enum jsonrpc_msg_type);
+char *jsonrpc_msg_is_valid(const struct jsonrpc_msg *);
+void jsonrpc_msg_destroy(struct jsonrpc_msg *);
+
+char *jsonrpc_msg_from_json(struct json *, struct jsonrpc_msg **);
+struct json *jsonrpc_msg_to_json(struct jsonrpc_msg *);
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session *jsonrpc_session_open(const char *name);
+struct jsonrpc_session *jsonrpc_session_open_unreliably(struct jsonrpc *);
+void jsonrpc_session_close(struct jsonrpc_session *);
+
+void jsonrpc_session_run(struct jsonrpc_session *);
+void jsonrpc_session_wait(struct jsonrpc_session *);
+
+size_t jsonrpc_session_get_backlog(const struct jsonrpc_session *);
+const char *jsonrpc_session_get_name(const struct jsonrpc_session *);
+
+int jsonrpc_session_send(struct jsonrpc_session *, struct jsonrpc_msg *);
+struct jsonrpc_msg *jsonrpc_session_recv(struct jsonrpc_session *);
+void jsonrpc_session_recv_wait(struct jsonrpc_session *);
+
+bool jsonrpc_session_is_alive(const struct jsonrpc_session *);
+bool jsonrpc_session_is_connected(const struct jsonrpc_session *);
+unsigned int jsonrpc_session_get_seqno(const struct jsonrpc_session *);
+void jsonrpc_session_force_reconnect(struct jsonrpc_session *);
+
+#endif /* jsonrpc.h */
index 8d256bc..4ab55e5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #ifndef HAVE_MALLOC_HOOKS
 void
-leak_checker_start(const char *file_name UNUSED)
+leak_checker_start(const char *file_name OVS_UNUSED)
 {
     VLOG_WARN("not enabling leak checker because the libc in use does not "
               "have the required hooks");
 }
 
 void
-leak_checker_set_limit(off_t max_size UNUSED)
+leak_checker_set_limit(off_t max_size OVS_UNUSED)
 {
 }
 
 void
-leak_checker_claim(const void *p UNUSED)
+leak_checker_claim(const void *p OVS_UNUSED)
 {
 }
 
@@ -180,7 +180,7 @@ reset_hooks(void)
 }
 
 static void *
-hook_malloc(size_t size, const void *caller UNUSED)
+hook_malloc(size_t size, const void *caller OVS_UNUSED)
 {
     void *p;
 
@@ -209,7 +209,7 @@ leak_checker_claim(const void *p)
 }
 
 static void
-hook_free(void *p, const void *caller UNUSED)
+hook_free(void *p, const void *caller OVS_UNUSED)
 {
     if (!p) {
         return;
@@ -225,7 +225,7 @@ hook_free(void *p, const void *caller UNUSED)
 }
 
 static void *
-hook_realloc(void *p, size_t size, const void *caller UNUSED)
+hook_realloc(void *p, size_t size, const void *caller OVS_UNUSED)
 {
     void *q;
 
index 73464c6..092274c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -57,6 +57,8 @@ struct lswitch {
     uint32_t capabilities;
     time_t last_features_request;
     struct mac_learning *ml;    /* NULL to act as hub instead of switch. */
+    bool exact_flows;           /* Use exact-match flows? */
+    bool action_normal;         /* Use OFPP_NORMAL? */
 
     /* Number of outgoing queued packets on the rconn. */
     struct rconn_packet_counter *queued;
@@ -105,16 +107,19 @@ static packet_handler_func process_stats_reply;
  *
  * 'rconn' is used to send out an OpenFlow features request. */
 struct lswitch *
-lswitch_create(struct rconn *rconn, bool learn_macs, int max_idle)
+lswitch_create(struct rconn *rconn, bool learn_macs,
+              bool exact_flows, int max_idle, bool action_normal)
 {
     struct lswitch *sw;
     size_t i;
 
-    sw = xcalloc(1, sizeof *sw);
+    sw = xzalloc(sizeof *sw);
     sw->max_idle = max_idle;
     sw->datapath_id = 0;
     sw->last_features_request = time_now() - 1;
     sw->ml = learn_macs ? mac_learning_create() : NULL;
+    sw->action_normal = action_normal;
+    sw->exact_flows = exact_flows;
     sw->queued = rconn_packet_counter_create();
     sw->next_query = LLONG_MIN;
     sw->last_query = LLONG_MIN;
@@ -151,7 +156,7 @@ lswitch_run(struct lswitch *sw, struct rconn *rconn)
     /* If we're waiting for more replies, keeping waiting for up to 10 s. */
     if (sw->last_reply != LLONG_MIN) {
         if (now - sw->last_reply > 10000) {
-            VLOG_ERR_RL(&rl, "%012llx: No more flow stat replies last 10 s",
+            VLOG_ERR_RL(&rl, "%016llx: No more flow stat replies last 10 s",
                         sw->datapath_id);
             sw->last_reply = LLONG_MIN;
             sw->last_query = LLONG_MIN;
@@ -164,7 +169,7 @@ lswitch_run(struct lswitch *sw, struct rconn *rconn)
     /* If we're waiting for any reply at all, keep waiting for up to 10 s. */
     if (sw->last_query != LLONG_MIN) {
         if (now - sw->last_query > 10000) {
-            VLOG_ERR_RL(&rl, "%012llx: No flow stat replies in last 10 s",
+            VLOG_ERR_RL(&rl, "%016llx: No flow stat replies in last 10 s",
                         sw->datapath_id);
             sw->last_query = LLONG_MIN;
             schedule_query(sw, 0);
@@ -184,7 +189,7 @@ lswitch_run(struct lswitch *sw, struct rconn *rconn)
             struct ofpbuf *b;
             int error;
 
-            VLOG_DBG("%012llx: Sending flow stats request to implement STP",
+            VLOG_DBG("%016llx: Sending flow stats request to implement STP",
                      sw->datapath_id);
 
             sw->last_query = now;
@@ -203,7 +208,7 @@ lswitch_run(struct lswitch *sw, struct rconn *rconn)
 
             error = rconn_send(rconn, b, NULL);
             if (error) {
-                VLOG_WARN_RL(&rl, "%012llx: sending flow stats request "
+                VLOG_WARN_RL(&rl, "%016llx: sending flow stats request "
                              "failed: %s", sw->datapath_id, strerror(error));
                 ofpbuf_delete(b);
                 schedule_query(sw, 1000);
@@ -278,8 +283,8 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
             process_stats_reply
         },
         {
-            OFPT_FLOW_EXPIRED,
-            sizeof(struct ofp_flow_expired),
+            OFPT_FLOW_REMOVED,
+            sizeof(struct ofp_flow_removed),
             NULL
         },
     };
@@ -298,7 +303,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     for (p = processors; p < &processors[n_processors]; p++) {
         if (oh->type == p->type) {
             if (msg->size < p->min_size) {
-                VLOG_WARN_RL(&rl, "%012llx: %s: too short (%zu bytes) for "
+                VLOG_WARN_RL(&rl, "%016llx: %s: too short (%zu bytes) for "
                              "type %"PRIu8" (min %zu)", sw->datapath_id,
                              rconn_get_name(rconn), msg->size, oh->type,
                              p->min_size);
@@ -312,7 +317,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     }
     if (VLOG_IS_DBG_ENABLED()) {
         char *p = ofp_to_string(msg->data, msg->size, 2);
-        VLOG_DBG_RL(&rl, "%012llx: OpenFlow packet ignored: %s",
+        VLOG_DBG_RL(&rl, "%016llx: OpenFlow packet ignored: %s",
                     sw->datapath_id, p);
         free(p);
     }
@@ -332,7 +337,6 @@ send_features_request(struct lswitch *sw, struct rconn *rconn)
 
         /* Send OFPT_SET_CONFIG. */
         osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &b);
-        osc->flags = htons(OFPC_SEND_FLOW_EXP);
         osc->miss_send_len = htons(OFP_DEFAULT_MISS_SEND_LEN);
         queue_tx(sw, rconn, b);
 
@@ -346,10 +350,10 @@ queue_tx(struct lswitch *sw, struct rconn *rconn, struct ofpbuf *b)
     int retval = rconn_send_with_limit(rconn, b, sw->queued, 10);
     if (retval && retval != ENOTCONN) {
         if (retval == EAGAIN) {
-            VLOG_INFO_RL(&rl, "%012llx: %s: tx queue overflow",
+            VLOG_INFO_RL(&rl, "%016llx: %s: tx queue overflow",
                          sw->datapath_id, rconn_get_name(rconn));
         } else {
-            VLOG_WARN_RL(&rl, "%012llx: %s: send: %s",
+            VLOG_WARN_RL(&rl, "%016llx: %s: send: %s",
                          sw->datapath_id, rconn_get_name(rconn),
                          strerror(retval));
         }
@@ -404,7 +408,7 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_)
 
     if (may_learn(sw, in_port) && sw->ml) {
         if (mac_learning_learn(sw->ml, flow.dl_src, 0, in_port)) {
-            VLOG_DBG_RL(&rl, "%012llx: learned that "ETH_ADDR_FMT" is on "
+            VLOG_DBG_RL(&rl, "%016llx: learned that "ETH_ADDR_FMT" is on "
                         "port %"PRIu16, sw->datapath_id,
                         ETH_ADDR_ARGS(flow.dl_src), in_port);
         }
@@ -430,10 +434,34 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_)
         /* Don't send out packets on their input ports. */
         goto drop_it;
     } else if (sw->max_idle >= 0 && (!sw->ml || out_port != OFPP_FLOOD)) {
+        struct ofpbuf *buffer;
+        struct ofp_flow_mod *ofm;
+        uint32_t wildcards;
+
+        /* Check if we need to wildcard the flows. */
+        if (!sw->exact_flows) {
+            /* We can not wildcard all fields.
+             * We need in_port to detect moves.
+             * We need both SA and DA to do learning. */
+            wildcards = (OFPFW_DL_TYPE | OFPFW_NW_SRC_MASK | OFPFW_NW_DST_MASK
+                         | OFPFW_NW_PROTO | OFPFW_TP_SRC | OFPFW_TP_DST);
+        } else {
+            /* Exact match */
+            wildcards = 0;
+        }
+
+        /* Check if we need to use "NORMAL" action. */
+        if (sw->action_normal && out_port != OFPP_FLOOD) {
+            out_port = OFPP_NORMAL;
+        }
+
         /* The output port is known, or we always flood everything, so add a
          * new flow. */
-        queue_tx(sw, rconn, make_add_simple_flow(&flow, ntohl(opi->buffer_id),
-                                                 out_port, sw->max_idle));
+        buffer = make_add_simple_flow(&flow, ntohl(opi->buffer_id),
+                                      out_port, sw->max_idle);
+        ofm = buffer->data;
+        ofm->match.wildcards = htonl(wildcards);
+        queue_tx(sw, rconn, buffer);
 
         /* If the switch didn't buffer the packet, we need to send a copy. */
         if (ntohl(opi->buffer_id) == UINT32_MAX) {
@@ -441,9 +469,15 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_)
                      make_unbuffered_packet_out(&pkt, in_port, out_port));
         }
     } else {
+        struct ofpbuf *b;
+
+        /* Check if we need to use "NORMAL" action. */
+        if (sw->action_normal && out_port != OFPP_FLOOD) {
+            out_port = OFPP_NORMAL;
+        }
+
         /* We don't know that MAC, or we don't set up flows.  Send along the
          * packet without setting up a flow. */
-        struct ofpbuf *b;
         if (ntohl(opi->buffer_id) == UINT32_MAX) {
             b = make_unbuffered_packet_out(&pkt, in_port, out_port);
         } else {
@@ -482,7 +516,8 @@ process_port_status(struct lswitch *sw, struct rconn *rconn, void *ops_)
 }
 
 static void
-process_phy_port(struct lswitch *sw, struct rconn *rconn UNUSED, void *opp_)
+process_phy_port(struct lswitch *sw, struct rconn *rconn OVS_UNUSED,
+                 void *opp_)
 {
     const struct ofp_phy_port *opp = opp_;
     uint16_t port_no = ntohs(opp->port_no);
@@ -579,12 +614,12 @@ process_flow_stats(struct lswitch *sw, struct rconn *rconn,
         for (a = ofs->actions; (char *) a < end; a += len / 8) {
             len = ntohs(a->len);
             if (len > end - (char *) a) {
-                VLOG_DBG_RL(&rl, "%012llx: action exceeds available space "
+                VLOG_DBG_RL(&rl, "%016llx: action exceeds available space "
                             "(%zu > %td)",
                             sw->datapath_id, len, end - (char *) a);
                 break;
             } else if (len % 8) {
-                VLOG_DBG_RL(&rl, "%012llx: action length (%zu) not multiple "
+                VLOG_DBG_RL(&rl, "%016llx: action length (%zu) not multiple "
                             "of 8 bytes", sw->datapath_id, len);
                 break;
             }
@@ -630,7 +665,7 @@ process_stats_reply(struct lswitch *sw, struct rconn *rconn, void *osr_)
         process_flow_stats(sw, rconn, fs);
     }
     if (!(osr->flags & htons(OFPSF_REPLY_MORE))) {
-        VLOG_DBG("%012llx: Deleted %d of %d received flows to "
+        VLOG_DBG("%016llx: Deleted %d of %d received flows to "
                  "implement STP, %d because of no-recv, %d because of "
                  "no-send", sw->datapath_id,
                  sw->n_no_recv + sw->n_no_send, sw->n_flows,
index 8837d64..2de862e 100644 (file)
@@ -22,7 +22,9 @@
 struct ofpbuf;
 struct rconn;
 
-struct lswitch *lswitch_create(struct rconn *, bool learn_macs, int max_idle);
+struct lswitch *lswitch_create(struct rconn *, bool learn_macs,
+                              bool exact_flows, int max_idle,
+                              bool action_normal);
 void lswitch_run(struct lswitch *, struct rconn *);
 void lswitch_wait(struct lswitch *);
 void lswitch_destroy(struct lswitch *);
index bc8ea14..845aab2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "util.h"
 
 /* Doubly linked list head or element. */
-struct list
-  {
+struct list {
     struct list *prev;     /* Previous list element. */
     struct list *next;     /* Next list element. */
-  };
+};
 
 #define LIST_INITIALIZER(LIST) { LIST, LIST }
 
diff --git a/lib/lockfile.c b/lib/lockfile.c
new file mode 100644 (file)
index 0000000..100440e
--- /dev/null
@@ -0,0 +1,286 @@
+ /* Copyright (c) 2008, 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "hash.h"
+#include "hmap.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_lockfile
+#include "vlog.h"
+
+struct lockfile {
+    struct hmap_node hmap_node;
+    char *name;
+    dev_t device;
+    ino_t inode;
+    int fd;
+};
+
+/* Lock table.
+ *
+ * We have to do this stupid dance because POSIX says that closing *any* file
+ * descriptor for a file on which a process holds a lock drops *all* locks on
+ * that file.  That means that we can't afford to open a lockfile more than
+ * once. */
+static struct hmap lock_table = HMAP_INITIALIZER(&lock_table);
+
+static void lockfile_unhash(struct lockfile *);
+static int lockfile_try_lock(const char *name, bool block,
+                             struct lockfile **lockfilep);
+
+/* Returns the name of the lockfile that would be created for locking a file
+ * named 'file_name'.  The caller is responsible for freeing the returned
+ * name, with free(), when it is no longer needed. */
+char *
+lockfile_name(const char *file_name)
+{
+    const char *slash = strrchr(file_name, '/');
+    return (slash
+            ? xasprintf("%.*s/.%s.~lock~",
+                        (int) (slash - file_name), file_name, slash + 1)
+            : xasprintf(".%s.~lock~", file_name));
+}
+
+/* Locks the configuration file against modification by other processes and
+ * re-reads it from disk.
+ *
+ * The 'timeout' specifies the maximum number of milliseconds to wait for the
+ * config file to become free.  Use 0 to avoid waiting or INT_MAX to wait
+ * forever.
+ *
+ * Returns 0 on success, otherwise a positive errno value.  On success,
+ * '*lockfilep' is set to point to a new "struct lockfile *" that may be
+ * unlocked with lockfile_unlock().  On failure, '*lockfilep' is set to
+ * NULL. */
+int
+lockfile_lock(const char *file, int timeout, struct lockfile **lockfilep)
+{
+    /* Only exclusive ("write") locks are supported.  This is not a problem
+     * because the Open vSwitch code that currently uses lock files does so in
+     * stylized ways such that any number of readers may access a file while it
+     * is being written. */
+    long long int warn_elapsed = 1000;
+    long long int start, elapsed;
+    char *lock_name;
+    int error;
+
+    COVERAGE_INC(lockfile_lock);
+
+    lock_name = lockfile_name(file);
+    time_refresh();
+    start = time_msec();
+
+    do {
+        error = lockfile_try_lock(lock_name, timeout > 0, lockfilep);
+        time_refresh();
+        elapsed = time_msec() - start;
+        if (elapsed > warn_elapsed) {
+            warn_elapsed *= 2;
+            VLOG_WARN("%s: waiting for lock file, %lld ms elapsed",
+                      lock_name, elapsed);
+        }
+    } while (error == EINTR && (timeout == INT_MAX || elapsed < timeout));
+
+    if (!error) {
+        if (elapsed) {
+            VLOG_WARN("%s: waited %lld ms for lock file",
+                      lock_name, elapsed);
+        }
+    } else if (error == EINTR) {
+        COVERAGE_INC(lockfile_timeout);
+        VLOG_WARN("%s: giving up on lock file after %lld ms",
+                  lock_name, elapsed);
+        error = ETIMEDOUT;
+    } else {
+        COVERAGE_INC(lockfile_error);
+        if (error == EACCES) {
+            error = EAGAIN;
+        }
+        VLOG_WARN("%s: failed to lock file "
+                  "(after %lld ms, with %d-ms timeout): %s",
+                  lock_name, elapsed, timeout, strerror(error));
+    }
+
+    free(lock_name);
+    return error;
+}
+
+/* Unlocks 'lockfile', which must have been created by a call to
+ * lockfile_lock(), and frees 'lockfile'. */
+void
+lockfile_unlock(struct lockfile *lockfile)
+{
+    if (lockfile) {
+        COVERAGE_INC(lockfile_unlock);
+        lockfile_unhash(lockfile);
+        free(lockfile->name);
+        free(lockfile);
+    }
+}
+
+/* Marks all the currently locked lockfiles as no longer locked.  It makes
+ * sense to call this function after fork(), because a child created by fork()
+ * does not hold its parents' locks. */
+void
+lockfile_postfork(void)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH (lockfile, struct lockfile, hmap_node, &lock_table) {
+        if (lockfile->fd >= 0) {
+            VLOG_WARN("%s: child does not inherit lock", lockfile->name);
+            lockfile_unhash(lockfile);
+        }
+    }
+}
+\f
+static uint32_t
+lockfile_hash(dev_t device, ino_t inode)
+{
+    return hash_bytes(&device, sizeof device,
+                      hash_bytes(&inode, sizeof inode, 0));
+}
+
+static struct lockfile *
+lockfile_find(dev_t device, ino_t inode)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH_WITH_HASH (lockfile, struct lockfile, hmap_node,
+                             lockfile_hash(device, inode), &lock_table) {
+        if (lockfile->device == device && lockfile->inode == inode) {
+            return lockfile;
+        }
+    }
+    return NULL;
+}
+
+static void
+lockfile_unhash(struct lockfile *lockfile)
+{
+    if (lockfile->fd >= 0) {
+        close(lockfile->fd);
+        lockfile->fd = -1;
+        hmap_remove(&lock_table, &lockfile->hmap_node);
+    }
+}
+
+static struct lockfile *
+lockfile_register(const char *name, dev_t device, ino_t inode, int fd)
+{
+    struct lockfile *lockfile;
+
+    lockfile = lockfile_find(device, inode);
+    if (lockfile) {
+        VLOG_ERR("%s: lock file disappeared and reappeared!", name);
+        lockfile_unhash(lockfile);
+    }
+
+    lockfile = xmalloc(sizeof *lockfile);
+    lockfile->name = xstrdup(name);
+    lockfile->device = device;
+    lockfile->inode = inode;
+    lockfile->fd = fd;
+    hmap_insert(&lock_table, &lockfile->hmap_node,
+                lockfile_hash(device, inode));
+    return lockfile;
+}
+
+static int
+lockfile_try_lock(const char *name, bool block, struct lockfile **lockfilep)
+{
+    struct flock l;
+    struct stat s;
+    int error;
+    int fd;
+
+    *lockfilep = NULL;
+
+    /* Open the lock file, first creating it if necessary. */
+    for (;;) {
+        /* Check whether we've already got a lock on that file. */
+        if (!stat(name, &s)) {
+            if (lockfile_find(s.st_dev, s.st_ino)) {
+                return EDEADLK;
+            }
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to stat lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to open an existing lock file. */
+        fd = open(name, O_RDWR);
+        if (fd >= 0) {
+            break;
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to open lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to create a new lock file. */
+        VLOG_INFO("%s: lock file does not exist, creating", name);
+        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+        if (fd >= 0) {
+            break;
+        } else if (errno != EEXIST) {
+            VLOG_WARN("%s: failed to create lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Someone else created the lock file.  Try again. */
+    }
+
+    /* Get the inode and device number for the lock table. */
+    if (fstat(fd, &s)) {
+        VLOG_ERR("%s: failed to fstat lock file: %s", name, strerror(errno));
+        close(fd);
+        return errno;
+    }
+
+    /* Try to lock the file. */
+    memset(&l, 0, sizeof l);
+    l.l_type = F_WRLCK;
+    l.l_whence = SEEK_SET;
+    l.l_start = 0;
+    l.l_len = 0;
+
+    time_disable_restart();
+    error = fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
+    time_enable_restart();
+
+    if (!error) {
+        *lockfilep = lockfile_register(name, s.st_dev, s.st_ino, fd);
+    } else {
+        close(fd);
+    }
+    return error;
+}
+
similarity index 64%
rename from vswitchd/mgmt.h
rename to lib/lockfile.h
index f05c916..c52fa21 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2008, 2009 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef VSWITCHD_MGMT_H
-#define VSWITCHD_MGMT_H 1
+#ifndef LOCKFILE_H
+#define LOCKFILE_H 1
 
-void mgmt_init(void);
-void mgmt_reconfigure(void);
-bool mgmt_run(void);
-void mgmt_wait(void);
-uint64_t mgmt_get_mgmt_id(void);
+struct lockfile;
 
-#endif /* mgmt.h */
+char *lockfile_name(const char *file);
+int lockfile_lock(const char *file, int timeout, struct lockfile **);
+void lockfile_unlock(struct lockfile *);
+void lockfile_postfork(void);
+
+#endif /* lib/lockfile.h */
index 3f1db14..a9d414d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,7 +130,7 @@ mac_learning_create(void)
         list_push_front(&ml->free, &s->lru_node);
     }
     ml->secret = random_uint32();
-    ml->non_learning_vlans = NULL;
+    ml->flood_vlans = NULL;
     return ml;
 }
 
@@ -139,24 +139,24 @@ void
 mac_learning_destroy(struct mac_learning *ml)
 {
     if (ml) {
-        bitmap_free(ml->non_learning_vlans);
+        bitmap_free(ml->flood_vlans);
     }
     free(ml);
 }
 
-/* Provides a bitmap of VLANs which have learning disabled.  It takes
- * ownership of the bitmap.  Returns true if the set has changed from
- * the previous value. */
+/* Provides a bitmap of VLANs which have learning disabled, that is, VLANs on
+ * which all packets are flooded.  It takes ownership of the bitmap.  Returns
+ * true if the set has changed from the previous value. */
 bool
-mac_learning_set_disabled_vlans(struct mac_learning *ml, unsigned long *bitmap)
+mac_learning_set_flood_vlans(struct mac_learning *ml, unsigned long *bitmap)
 {
     bool ret = (bitmap == NULL
-        ? ml->non_learning_vlans != NULL
-        : (ml->non_learning_vlans == NULL
-          || !bitmap_equal(bitmap, ml->non_learning_vlans, 4096)));
+                ? ml->flood_vlans != NULL
+                : (ml->flood_vlans == NULL
+                   || !bitmap_equal(bitmap, ml->flood_vlans, 4096)));
 
-    bitmap_free(ml->non_learning_vlans);
-    ml->non_learning_vlans = bitmap;
+    bitmap_free(ml->flood_vlans);
+    ml->flood_vlans = bitmap;
 
     return ret;
 }
@@ -164,8 +164,7 @@ mac_learning_set_disabled_vlans(struct mac_learning *ml, unsigned long *bitmap)
 static bool
 is_learning_vlan(const struct mac_learning *ml, uint16_t vlan)
 {
-    return !(ml->non_learning_vlans
-            && bitmap_is_set(ml->non_learning_vlans, vlan));
+    return !(ml->flood_vlans && bitmap_is_set(ml->flood_vlans, vlan));
 }
 
 /* Attempts to make 'ml' learn from the fact that a frame from 'src_mac' was
index ed843cd..c4a0e28 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,14 +51,14 @@ struct mac_learning {
                                    front, most recently used at the back. */
     struct list table[MAC_HASH_SIZE]; /* Hash table. */
     struct mac_entry entries[MAC_MAX]; /* All entries. */
-    uint32_t secret;            /* Secret for  */
-    unsigned long *non_learning_vlans; /* Bitmap of learning disabled VLANs. */
+    uint32_t secret;            /* Secret for randomizing hash table. */
+    unsigned long *flood_vlans; /* Bitmap of learning disabled VLANs. */
 };
 
 struct mac_learning *mac_learning_create(void);
 void mac_learning_destroy(struct mac_learning *);
-bool mac_learning_set_disabled_vlans(struct mac_learning *,
-                                     unsigned long *bitmap);
+bool mac_learning_set_flood_vlans(struct mac_learning *,
+                                  unsigned long *bitmap);
 tag_type mac_learning_learn(struct mac_learning *,
                             const uint8_t src[ETH_ADDR_LEN], uint16_t vlan,
                             uint16_t src_port);
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
new file mode 100644 (file)
index 0000000..736b588
--- /dev/null
@@ -0,0 +1,2397 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <linux/if_tun.h>
+#include <linux/ip.h>
+#include <linux/types.h>
+#include <linux/ethtool.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <linux/version.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <linux/if_tunnel.h>
+#include <net/if_arp.h>
+#include <net/if_packet.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "netdev-provider.h"
+#include "netlink.h"
+#include "ofpbuf.h"
+#include "openflow/openflow.h"
+#include "openvswitch/gre.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "rtnetlink.h"
+#include "socket-util.h"
+#include "shash.h"
+#include "svec.h"
+
+#ifndef GRE_IOCTL_ONLY
+#include <linux/if_link.h>
+#endif
+
+#define THIS_MODULE VLM_netdev_linux
+#include "vlog.h"
+\f
+/* These were introduced in Linux 2.6.14, so they might be missing if we have
+ * old headers. */
+#ifndef ADVERTISED_Pause
+#define ADVERTISED_Pause                (1 << 13)
+#endif
+#ifndef ADVERTISED_Asym_Pause
+#define ADVERTISED_Asym_Pause           (1 << 14)
+#endif
+
+static struct rtnetlink_notifier netdev_linux_cache_notifier;
+static int cache_notifier_refcount;
+
+enum {
+    VALID_IFINDEX = 1 << 0,
+    VALID_ETHERADDR = 1 << 1,
+    VALID_IN4 = 1 << 2,
+    VALID_IN6 = 1 << 3,
+    VALID_MTU = 1 << 4,
+    VALID_CARRIER = 1 << 5,
+    VALID_IS_INTERNAL = 1 << 6
+};
+
+struct tap_state {
+    int fd;
+};
+
+struct netdev_dev_linux {
+    struct netdev_dev netdev_dev;
+
+    struct shash_node *shash_node;
+    unsigned int cache_valid;
+
+    int ifindex;
+    uint8_t etheraddr[ETH_ADDR_LEN];
+    struct in_addr address, netmask;
+    struct in6_addr in6;
+    int mtu;
+    int carrier;
+    bool is_internal;
+
+    union {
+        struct tap_state tap;
+    } state;
+};
+
+struct netdev_linux {
+    struct netdev netdev;
+    int fd;
+};
+
+/* An AF_INET socket (used for ioctl operations). */
+static int af_inet_sock = -1;
+
+struct gre_config {
+    uint32_t local_ip;
+    uint32_t remote_ip;
+    uint32_t in_key;
+    uint32_t out_key;
+    uint8_t tos;
+    bool have_in_key;
+    bool have_out_key;
+    bool in_csum;
+    bool out_csum;
+    bool pmtud;
+};
+
+static struct {
+    union {
+        struct nl_sock *nl_sock;
+        int ioctl_fd;
+    };
+    bool use_ioctl;
+} gre_descriptors;
+
+struct netdev_linux_notifier {
+    struct netdev_notifier notifier;
+    struct list node;
+};
+
+static struct shash netdev_linux_notifiers =
+    SHASH_INITIALIZER(&netdev_linux_notifiers);
+static struct rtnetlink_notifier netdev_linux_poll_notifier;
+
+/* This is set pretty low because we probably won't learn anything from the
+ * additional log messages. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+static int destroy_gre(const char *name);
+static int netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *,
+                                   int cmd, const char *cmd_name);
+static int netdev_linux_do_ioctl(const char *name, struct ifreq *, int cmd,
+                                 const char *cmd_name);
+static int netdev_linux_get_ipv4(const struct netdev *, struct in_addr *,
+                                 int cmd, const char *cmd_name);
+static int get_flags(const struct netdev *, int *flagsp);
+static int set_flags(struct netdev *, int flags);
+static int do_get_ifindex(const char *netdev_name);
+static int get_ifindex(const struct netdev *, int *ifindexp);
+static int do_set_addr(struct netdev *netdev,
+                       int ioctl_nr, const char *ioctl_name,
+                       struct in_addr addr);
+static int get_etheraddr(const char *netdev_name, uint8_t ea[ETH_ADDR_LEN]);
+static int set_etheraddr(const char *netdev_name, int hwaddr_family,
+                         const uint8_t[ETH_ADDR_LEN]);
+static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats);
+static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats);
+
+static struct netdev_dev_linux *
+netdev_dev_linux_cast(const struct netdev_dev *netdev_dev)
+{
+    const char *type = netdev_dev_get_type(netdev_dev);
+    assert(!strcmp(type, "system") || !strcmp(type, "tap")
+            || !strcmp(type, "gre"));
+    return CONTAINER_OF(netdev_dev, struct netdev_dev_linux, netdev_dev);
+}
+
+static struct netdev_linux *
+netdev_linux_cast(const struct netdev *netdev)
+{
+    const char *type = netdev_get_type(netdev);
+    assert(!strcmp(type, "system") || !strcmp(type, "tap")
+            || !strcmp(type, "gre"));
+    return CONTAINER_OF(netdev, struct netdev_linux, netdev);
+}
+
+static int
+netdev_linux_init(void)
+{
+    static int status = -1;
+    if (status < 0) {
+        af_inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
+        status = af_inet_sock >= 0 ? 0 : errno;
+        if (status) {
+            VLOG_ERR("failed to create inet socket: %s", strerror(status));
+        }
+    }
+    return status;
+}
+
+static void
+netdev_linux_run(void)
+{
+    rtnetlink_notifier_run();
+}
+
+static void
+netdev_linux_wait(void)
+{
+    rtnetlink_notifier_wait();
+}
+
+static void
+netdev_linux_cache_cb(const struct rtnetlink_change *change,
+                      void *aux OVS_UNUSED)
+{
+    struct netdev_dev_linux *dev;
+    if (change) {
+        struct netdev_dev *base_dev = netdev_dev_from_name(change->ifname);
+        if (base_dev) {
+            dev = netdev_dev_linux_cast(base_dev);
+            dev->cache_valid = 0;
+        }
+    } else {
+        struct shash device_shash;
+        struct shash_node *node;
+
+        shash_init(&device_shash);
+        netdev_dev_get_devices(&netdev_linux_class, &device_shash);
+        SHASH_FOR_EACH (node, &device_shash) {
+            dev = node->data;
+            dev->cache_valid = 0;
+        }
+        shash_destroy(&device_shash);
+    }
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static int
+setup_gre_netlink(const char *name OVS_UNUSED,
+                  struct gre_config *config OVS_UNUSED, bool create OVS_UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return EOPNOTSUPP;
+#else
+    int error;
+    struct ofpbuf request, *reply;
+    unsigned int nl_flags;
+    struct ifinfomsg ifinfomsg;
+    struct nlattr *linkinfo_hdr;
+    struct nlattr *info_data_hdr;
+    uint16_t iflags = 0;
+    uint16_t oflags = 0;
+
+    VLOG_DBG("%s: attempting to create gre device using netlink", name);
+
+    if (!gre_descriptors.nl_sock) {
+        error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0,
+                               &gre_descriptors.nl_sock);
+        if (error) {
+            VLOG_WARN("couldn't create netlink socket: %s", strerror(error));
+            goto error;
+        }
+    }
+
+    ofpbuf_init(&request, 0);
+
+    nl_flags = NLM_F_REQUEST;
+    if (create) {
+        nl_flags |= NLM_F_CREATE|NLM_F_EXCL;
+    }
+
+    /* We over-reserve space, because we do some pointer arithmetic
+     * and don't want the buffer address shifting under us. */
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 2048, RTM_NEWLINK,
+                        nl_flags);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    linkinfo_hdr = ofpbuf_tail(&request);
+    nl_msg_put_unspec(&request, IFLA_LINKINFO, NULL, 0);
+
+    nl_msg_put_unspec(&request, IFLA_INFO_KIND, "gretap", 6);
+
+    info_data_hdr = ofpbuf_tail(&request);
+    nl_msg_put_unspec(&request, IFLA_INFO_DATA, NULL, 0);
+
+    /* Set flags */
+    if (config->have_in_key) {
+        iflags |= GRE_KEY;
+    }
+    if (config->have_out_key) {
+        oflags |= GRE_KEY;
+    }
+
+    if (config->in_csum) {
+        iflags |= GRE_CSUM;
+    }
+    if (config->out_csum) {
+        oflags |= GRE_CSUM;
+    }
+
+    /* Add options */
+    nl_msg_put_u32(&request, IFLA_GRE_IKEY, config->in_key);
+    nl_msg_put_u32(&request, IFLA_GRE_OKEY, config->out_key);
+    nl_msg_put_u16(&request, IFLA_GRE_IFLAGS, iflags);
+    nl_msg_put_u16(&request, IFLA_GRE_OFLAGS, oflags);
+    nl_msg_put_u32(&request, IFLA_GRE_LOCAL, config->local_ip);
+    nl_msg_put_u32(&request, IFLA_GRE_REMOTE, config->remote_ip);
+    nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, config->pmtud);
+    nl_msg_put_u8(&request, IFLA_GRE_TTL, IPDEFTTL);
+    nl_msg_put_u8(&request, IFLA_GRE_TOS, config->tos);
+
+    info_data_hdr->nla_len = (char *)ofpbuf_tail(&request)
+                                - (char *)info_data_hdr;
+    linkinfo_hdr->nla_len = (char *)ofpbuf_tail(&request)
+                                - (char *)linkinfo_hdr;
+
+    nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        goto error;
+    }
+    ofpbuf_delete(reply);
+
+error:
+    return error;
+#endif
+}
+
+static int
+setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
+{
+    struct ip_tunnel_parm p;
+    struct ifreq ifr;
+
+    VLOG_DBG("%s: attempting to create gre device using ioctl", name);
+
+    memset(&p, 0, sizeof p);
+
+    strncpy(p.name, name, IFNAMSIZ);
+
+    p.iph.version = 4;
+    p.iph.ihl = 5;
+    p.iph.protocol = IPPROTO_GRE;
+    p.iph.saddr = config->local_ip;
+    p.iph.daddr = config->remote_ip;
+    p.iph.ttl = IPDEFTTL;
+    p.iph.tos = config->tos;
+
+    if (config->have_in_key) {
+        p.i_flags |= GRE_KEY;
+        p.i_key = config->in_key;
+    }
+    if (config->have_out_key) {
+        p.o_flags |= GRE_KEY;
+        p.o_key = config->out_key;
+    }
+
+    if (config->in_csum) {
+        p.i_flags |= GRE_CSUM;
+    }
+    if (config->out_csum) {
+        p.o_flags |= GRE_CSUM;
+    }
+
+    if (config->pmtud) {
+        p.iph.frag_off = htons(IP_DONT_FRAGMENT);
+    }
+
+    strncpy(ifr.ifr_name, create ? GRE_IOCTL_DEVICE : name, IFNAMSIZ);
+    ifr.ifr_ifru.ifru_data = (void *)&p;
+
+    if (!gre_descriptors.ioctl_fd) {
+        gre_descriptors.ioctl_fd = socket(AF_INET, SOCK_DGRAM, 0);
+        if (gre_descriptors.ioctl_fd < 0) {
+            VLOG_WARN("couldn't create gre ioctl socket: %s", strerror(errno));
+            gre_descriptors.ioctl_fd = 0;
+            return errno;
+        }
+    }
+
+    if (ioctl(gre_descriptors.ioctl_fd, create ? SIOCADDGRETAP : SIOCCHGGRETAP,
+              &ifr) < 0) {
+        VLOG_WARN("couldn't do gre ioctl: %s", strerror(errno));
+        return errno;
+    }
+
+    return 0;
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static bool
+check_gre_device_netlink(const char *name OVS_UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return false;
+#else
+    static const struct nl_policy getlink_policy[] = {
+        [IFLA_LINKINFO] = { .type = NL_A_NESTED, .optional = false },
+    };
+
+    static const struct nl_policy linkinfo_policy[] = {
+        [IFLA_INFO_KIND] = { .type = NL_A_STRING, .optional = false },
+    };
+
+    int error;
+    bool ret = false;
+    struct ofpbuf request, *reply;
+    struct ifinfomsg ifinfomsg;
+    struct nlattr *getlink_attrs[ARRAY_SIZE(getlink_policy)];
+    struct nlattr *linkinfo_attrs[ARRAY_SIZE(linkinfo_policy)];
+    struct ofpbuf linkinfo;
+    const char *device_kind;
+
+    ofpbuf_init(&request, 0);
+
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock,
+                        NLMSG_LENGTH(sizeof ifinfomsg), RTM_GETLINK,
+                        NLM_F_REQUEST);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    ifinfomsg.ifi_index =  do_get_ifindex(name);
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        return false;
+    }
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                         getlink_policy, getlink_attrs,
+                         ARRAY_SIZE(getlink_policy))) {
+        VLOG_WARN("received bad rtnl message (getlink policy)");
+        goto error;
+    }
+
+    linkinfo.data = (void *)nl_attr_get(getlink_attrs[IFLA_LINKINFO]);
+    linkinfo.size = nl_attr_get_size(getlink_attrs[IFLA_LINKINFO]);
+    if (!nl_policy_parse(&linkinfo, 0, linkinfo_policy,
+                        linkinfo_attrs, ARRAY_SIZE(linkinfo_policy))) {
+        VLOG_WARN("received bad rtnl message (linkinfo policy)");
+        goto error;
+    }
+
+    device_kind = nl_attr_get_string(linkinfo_attrs[IFLA_INFO_KIND]);
+    ret = !strcmp(device_kind, "gretap");
+
+error:
+    ofpbuf_delete(reply);
+    return ret;
+#endif
+}
+
+static bool
+check_gre_device_ioctl(const char *name)
+{
+    struct ethtool_drvinfo drvinfo;
+    int error;
+
+    memset(&drvinfo, 0, sizeof drvinfo);
+    error = netdev_linux_do_ethtool(name, (struct ethtool_cmd *)&drvinfo,
+                                    ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO");
+
+    return !error && !strcmp(drvinfo.driver, "ip_gre")
+           && !strcmp(drvinfo.bus_info, "gretap");
+}
+
+static int
+setup_gre(const char *name, const struct shash *args, bool create)
+{
+    int error;
+    struct in_addr in_addr;
+    struct shash_node *node;
+    struct gre_config config;
+
+    memset(&config, 0, sizeof config);
+    config.in_csum = true;
+    config.out_csum = true;
+    config.pmtud = true;
+
+    SHASH_FOR_EACH (node, args) {
+        if (!strcmp(node->name, "remote_ip")) {
+            if (lookup_ip(node->data, &in_addr)) {
+                VLOG_WARN("bad 'remote_ip' for gre device %s ", name);
+            } else {
+                config.remote_ip = in_addr.s_addr;
+            }
+        } else if (!strcmp(node->name, "local_ip")) {
+            if (lookup_ip(node->data, &in_addr)) {
+                VLOG_WARN("bad 'local_ip' for gre device %s ", name);
+            } else {
+                config.local_ip = in_addr.s_addr;
+            }
+        } else if (!strcmp(node->name, "key")) {
+            config.have_in_key = true;
+            config.have_out_key = true;
+            config.in_key = htonl(atoi(node->data));
+            config.out_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "in_key")) {
+            config.have_in_key = true;
+            config.in_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "out_key")) {
+            config.have_out_key = true;
+            config.out_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "tos")) {
+            config.tos = atoi(node->data);
+        } else if (!strcmp(node->name, "csum")) {
+            if (!strcmp(node->data, "false")) {
+                config.in_csum = false;
+                config.out_csum = false;
+            }
+        } else if (!strcmp(node->name, "pmtud")) {
+            if (!strcmp(node->data, "false")) {
+                config.pmtud = false;
+            }
+        } else {
+            VLOG_WARN("unknown gre argument '%s'", node->name);
+        }
+    }
+
+    if (!config.remote_ip) {
+        VLOG_WARN("gre type requires valid 'remote_ip' argument");
+        error = EINVAL;
+        goto error;
+    }
+
+    if (!gre_descriptors.use_ioctl) {
+        error = setup_gre_netlink(name, &config, create);
+        if (error == EOPNOTSUPP) {
+            gre_descriptors.use_ioctl = true;
+        }
+    }
+    if (gre_descriptors.use_ioctl) {
+        error = setup_gre_ioctl(name, &config, create);
+    }
+
+    if (create && error == EEXIST) {
+        bool gre_device;
+
+        if (gre_descriptors.use_ioctl) {
+            gre_device = check_gre_device_ioctl(name);
+        } else {
+            gre_device = check_gre_device_netlink(name);
+        }
+
+        if (!gre_device) {
+            goto error;
+        }
+
+        VLOG_WARN("replacing existing gre device %s", name);
+        error = destroy_gre(name);
+        if (error) {
+            goto error;
+        }
+
+        if (gre_descriptors.use_ioctl) {
+            error = setup_gre_ioctl(name, &config, create);
+        } else {
+            error = setup_gre_netlink(name, &config, create);
+        }
+    }
+
+error:
+    return error;
+}
+
+/* Creates the netdev device of 'type' with 'name'. */
+static int
+netdev_linux_create_system(const char *name, const char *type OVS_UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
+{
+    struct netdev_dev_linux *netdev_dev;
+    int error;
+
+    if (!shash_is_empty(args)) {
+        VLOG_WARN("%s: arguments for system devices should be empty", name);
+    }
+
+    if (!cache_notifier_refcount) {
+        error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
+                                            netdev_linux_cache_cb, NULL);
+        if (error) {
+            return error;
+        }
+    }
+    cache_notifier_refcount++;
+
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class);
+
+    *netdev_devp = &netdev_dev->netdev_dev;
+    return 0;
+}
+
+/* For most types of netdevs we open the device for each call of
+ * netdev_open().  However, this is not the case with tap devices,
+ * since it is only possible to open the device once.  In this
+ * situation we share a single file descriptor, and consequently
+ * buffers, across all readers.  Therefore once data is read it will
+ * be unavailable to other reads for tap devices. */
+static int
+netdev_linux_create_tap(const char *name, const char *type OVS_UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
+{
+    struct netdev_dev_linux *netdev_dev;
+    struct tap_state *state;
+    static const char tap_dev[] = "/dev/net/tun";
+    struct ifreq ifr;
+    int error;
+
+    if (!shash_is_empty(args)) {
+        VLOG_WARN("%s: arguments for TAP devices should be empty", name);
+    }
+
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+    state = &netdev_dev->state.tap;
+
+    /* Open tap device. */
+    state->fd = open(tap_dev, O_RDWR);
+    if (state->fd < 0) {
+        error = errno;
+        VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
+        goto error;
+    }
+
+    /* Create tap device. */
+    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+    if (ioctl(state->fd, TUNSETIFF, &ifr) == -1) {
+        VLOG_WARN("%s: creating tap device failed: %s", name,
+                  strerror(errno));
+        error = errno;
+        goto error;
+    }
+
+    /* Make non-blocking. */
+    error = set_nonblocking(state->fd);
+    if (error) {
+        goto error;
+    }
+
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
+    *netdev_devp = &netdev_dev->netdev_dev;
+    return 0;
+
+error:
+    free(netdev_dev);
+    return error;
+}
+
+static int
+if_up(const char *name)
+{
+    struct ifreq ifr;
+
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+    ifr.ifr_flags = IFF_UP;
+
+    if (ioctl(af_inet_sock, SIOCSIFFLAGS, &ifr) == -1) {
+        VLOG_DBG_RL(&rl, "%s: failed to bring device up: %s",
+                    name, strerror(errno));
+        return errno;
+    }
+
+    return 0;
+}
+
+static int
+netdev_linux_create_gre(const char *name, const char *type OVS_UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
+{
+    struct netdev_dev_linux *netdev_dev;
+    int error;
+
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+
+    error = setup_gre(name, args, true);
+    if (error) {
+        goto error;
+    }
+
+    error = if_up(name);
+    if (error) {
+        goto error;
+    }
+
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_gre_class);
+    *netdev_devp = &netdev_dev->netdev_dev;
+    return 0;
+
+error:
+    free(netdev_dev);
+    return error;
+}
+
+static int
+netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_,
+                             const struct shash *args)
+{
+    const char *name = netdev_dev_get_name(netdev_dev_);
+
+    return setup_gre(name, args, false);
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static int
+destroy_gre_netlink(const char *name OVS_UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return EOPNOTSUPP;
+#else
+    int error;
+    struct ofpbuf request, *reply;
+    struct ifinfomsg ifinfomsg;
+    int ifindex;
+
+    ofpbuf_init(&request, 0);
+    
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 0, RTM_DELLINK,
+                        NLM_F_REQUEST);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    ifindex = do_get_ifindex(name);
+    nl_msg_put_u32(&request, IFLA_LINK, ifindex);
+
+    nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        goto error;
+    }
+    ofpbuf_delete(reply);
+
+error:
+    return 0;
+#endif
+}
+
+static int
+destroy_gre_ioctl(const char *name)
+{
+    struct ip_tunnel_parm p;
+    struct ifreq ifr;
+
+    memset(&p, 0, sizeof p);
+    strncpy(p.name, name, IFNAMSIZ);
+
+    strncpy(ifr.ifr_name, name, IFNAMSIZ);
+    ifr.ifr_ifru.ifru_data = (void *)&p;
+
+    if (ioctl(gre_descriptors.ioctl_fd, SIOCDELGRETAP, &ifr) < 0) {
+        VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno));
+        return errno;
+    }
+
+    return 0;
+}
+
+static void
+destroy_tap(struct netdev_dev_linux *netdev_dev)
+{
+    struct tap_state *state = &netdev_dev->state.tap;
+
+    if (state->fd >= 0) {
+        close(state->fd);
+    }
+}
+
+static int
+destroy_gre(const char *name)
+{
+    if (gre_descriptors.use_ioctl) {
+        return destroy_gre_ioctl(name);
+    } else {
+        return destroy_gre_netlink(name);
+    }
+}
+
+/* Destroys the netdev device 'netdev_dev_'. */
+static void
+netdev_linux_destroy(struct netdev_dev *netdev_dev_)
+{
+    struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+    const char *type = netdev_dev_get_type(netdev_dev_);
+
+    if (!strcmp(type, "system")) {
+        cache_notifier_refcount--;
+
+        if (!cache_notifier_refcount) {
+            rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
+        }
+    } else if (!strcmp(type, "tap")) {
+        destroy_tap(netdev_dev);
+    } else if (!strcmp(type, "gre")) {
+        destroy_gre(netdev_dev_get_name(&netdev_dev->netdev_dev));
+    }
+
+    free(netdev_dev_);
+}
+
+static int
+netdev_linux_open(struct netdev_dev *netdev_dev_, int ethertype,
+                  struct netdev **netdevp)
+{
+    struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+    struct netdev_linux *netdev;
+    enum netdev_flags flags;
+    int error;
+
+    /* Allocate network device. */
+    netdev = xzalloc(sizeof *netdev);
+    netdev->fd = -1;
+    netdev_init(&netdev->netdev, netdev_dev_);
+
+    error = netdev_get_flags(&netdev->netdev, &flags);
+    if (error == ENODEV) {
+        goto error;
+    }
+
+    if (!strcmp(netdev_dev_get_type(netdev_dev_), "tap")) {
+        netdev->fd = netdev_dev->state.tap.fd;
+    } else if (ethertype != NETDEV_ETH_TYPE_NONE) {
+        struct sockaddr_ll sll;
+        int protocol;
+        int ifindex;
+
+        /* Create file descriptor. */
+        protocol = (ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL
+                    : ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2
+                    : ethertype);
+        netdev->fd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
+        if (netdev->fd < 0) {
+            error = errno;
+            goto error;
+        }
+
+        /* Set non-blocking mode. */
+        error = set_nonblocking(netdev->fd);
+        if (error) {
+            goto error;
+        }
+
+        /* Get ethernet device index. */
+        error = get_ifindex(&netdev->netdev, &ifindex);
+        if (error) {
+            goto error;
+        }
+
+        /* Bind to specific ethernet device. */
+        memset(&sll, 0, sizeof sll);
+        sll.sll_family = AF_PACKET;
+        sll.sll_ifindex = ifindex;
+        if (bind(netdev->fd,
+                 (struct sockaddr *) &sll, sizeof sll) < 0) {
+            error = errno;
+            VLOG_ERR("bind to %s failed: %s", netdev_dev_get_name(netdev_dev_),
+                     strerror(error));
+            goto error;
+        }
+
+        /* Between the socket() and bind() calls above, the socket receives all
+         * packets of the requested type on all system interfaces.  We do not
+         * want to receive that data, but there is no way to avoid it.  So we
+         * must now drain out the receive queue. */
+        error = drain_rcvbuf(netdev->fd);
+        if (error) {
+            goto error;
+        }
+    }
+
+    *netdevp = &netdev->netdev;
+    return 0;
+
+error:
+    netdev_uninit(&netdev->netdev, true);
+    return error;
+}
+
+/* Closes and destroys 'netdev'. */
+static void
+netdev_linux_close(struct netdev *netdev_)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+
+    if (netdev->fd > 0 && strcmp(netdev_get_type(netdev_), "tap")) {
+        close(netdev->fd);
+    }
+    free(netdev);
+}
+
+/* Initializes 'svec' with a list of the names of all known network devices. */
+static int
+netdev_linux_enumerate(struct svec *svec)
+{
+    struct if_nameindex *names;
+
+    names = if_nameindex();
+    if (names) {
+        size_t i;
+
+        for (i = 0; names[i].if_name != NULL; i++) {
+            svec_add(svec, names[i].if_name);
+        }
+        if_freenameindex(names);
+        return 0;
+    } else {
+        VLOG_WARN("could not obtain list of network device names: %s",
+                  strerror(errno));
+        return errno;
+    }
+}
+
+static int
+netdev_linux_recv(struct netdev *netdev_, void *data, size_t size)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+
+    if (netdev->fd < 0) {
+        /* Device was opened with NETDEV_ETH_TYPE_NONE. */
+        return -EAGAIN;
+    }
+
+    for (;;) {
+        ssize_t retval = read(netdev->fd, data, size);
+        if (retval >= 0) {
+            return retval;
+        } else if (errno != EINTR) {
+            if (errno != EAGAIN) {
+                VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s",
+                             strerror(errno), netdev_get_name(netdev_));
+            }
+            return -errno;
+        }
+    }
+}
+
+/* Registers with the poll loop to wake up from the next call to poll_block()
+ * when a packet is ready to be received with netdev_recv() on 'netdev'. */
+static void
+netdev_linux_recv_wait(struct netdev *netdev_)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    if (netdev->fd >= 0) {
+        poll_fd_wait(netdev->fd, POLLIN);
+    }
+}
+
+/* Discards all packets waiting to be received from 'netdev'. */
+static int
+netdev_linux_drain(struct netdev *netdev_)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    if (netdev->fd < 0) {
+        return 0;
+    } else if (!strcmp(netdev_get_type(netdev_), "tap")) {
+        struct ifreq ifr;
+        int error = netdev_linux_do_ioctl(netdev_get_name(netdev_), &ifr,
+                                          SIOCGIFTXQLEN, "SIOCGIFTXQLEN");
+        if (error) {
+            return error;
+        }
+        drain_fd(netdev->fd, ifr.ifr_qlen);
+        return 0;
+    } else {
+        return drain_rcvbuf(netdev->fd);
+    }
+}
+
+/* Sends 'buffer' on 'netdev'.  Returns 0 if successful, otherwise a positive
+ * errno value.  Returns EAGAIN without blocking if the packet cannot be queued
+ * immediately.  Returns EMSGSIZE if a partial packet was transmitted or if
+ * the packet is too big or too small to transmit on the device.
+ *
+ * The caller retains ownership of 'buffer' in all cases.
+ *
+ * The kernel maintains a packet transmission queue, so the caller is not
+ * expected to do additional queuing of packets. */
+static int
+netdev_linux_send(struct netdev *netdev_, const void *data, size_t size)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+
+    /* XXX should support sending even if 'ethertype' was NETDEV_ETH_TYPE_NONE.
+     */
+    if (netdev->fd < 0) {
+        return EPIPE;
+    }
+
+    for (;;) {
+        ssize_t retval = write(netdev->fd, data, size);
+        if (retval < 0) {
+            /* The Linux AF_PACKET implementation never blocks waiting for room
+             * for packets, instead returning ENOBUFS.  Translate this into
+             * EAGAIN for the caller. */
+            if (errno == ENOBUFS) {
+                return EAGAIN;
+            } else if (errno == EINTR) {
+                continue;
+            } else if (errno != EAGAIN) {
+                VLOG_WARN_RL(&rl, "error sending Ethernet packet on %s: %s",
+                             netdev_get_name(netdev_), strerror(errno));
+            }
+            return errno;
+        } else if (retval != size) {
+            VLOG_WARN_RL(&rl, "sent partial Ethernet packet (%zd bytes of "
+                         "%zu) on %s", retval, size, netdev_get_name(netdev_));
+            return EMSGSIZE;
+        } else {
+            return 0;
+        }
+    }
+}
+
+/* Registers with the poll loop to wake up from the next call to poll_block()
+ * when the packet transmission queue has sufficient room to transmit a packet
+ * with netdev_send().
+ *
+ * The kernel maintains a packet transmission queue, so the client is not
+ * expected to do additional queuing of packets.  Thus, this function is
+ * unlikely to ever be used.  It is included for completeness. */
+static void
+netdev_linux_send_wait(struct netdev *netdev_)
+{
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    if (netdev->fd < 0) {
+        /* Nothing to do. */
+    } else if (strcmp(netdev_get_type(netdev_), "tap")) {
+        poll_fd_wait(netdev->fd, POLLOUT);
+    } else {
+        /* TAP device always accepts packets.*/
+        poll_immediate_wake();
+    }
+}
+
+/* Attempts to set 'netdev''s MAC address to 'mac'.  Returns 0 if successful,
+ * otherwise a positive errno value. */
+static int
+netdev_linux_set_etheraddr(struct netdev *netdev_,
+                           const uint8_t mac[ETH_ADDR_LEN])
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    int error;
+
+    if (!(netdev_dev->cache_valid & VALID_ETHERADDR)
+        || !eth_addr_equals(netdev_dev->etheraddr, mac)) {
+        error = set_etheraddr(netdev_get_name(netdev_), ARPHRD_ETHER, mac);
+        if (!error) {
+            netdev_dev->cache_valid |= VALID_ETHERADDR;
+            memcpy(netdev_dev->etheraddr, mac, ETH_ADDR_LEN);
+        }
+    } else {
+        error = 0;
+    }
+    return error;
+}
+
+/* Returns a pointer to 'netdev''s MAC address.  The caller must not modify or
+ * free the returned buffer. */
+static int
+netdev_linux_get_etheraddr(const struct netdev *netdev_,
+                           uint8_t mac[ETH_ADDR_LEN])
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_ETHERADDR)) {
+        int error = get_etheraddr(netdev_get_name(netdev_),
+                                  netdev_dev->etheraddr);
+        if (error) {
+            return error;
+        }
+        netdev_dev->cache_valid |= VALID_ETHERADDR;
+    }
+    memcpy(mac, netdev_dev->etheraddr, ETH_ADDR_LEN);
+    return 0;
+}
+
+/* Returns the maximum size of transmitted (and received) packets on 'netdev',
+ * in bytes, not including the hardware header; thus, this is typically 1500
+ * bytes for Ethernet devices. */
+static int
+netdev_linux_get_mtu(const struct netdev *netdev_, int *mtup)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_MTU)) {
+        struct ifreq ifr;
+        int error;
+
+        error = netdev_linux_do_ioctl(netdev_get_name(netdev_), &ifr,
+                                      SIOCGIFMTU, "SIOCGIFMTU");
+        if (error) {
+            return error;
+        }
+        netdev_dev->mtu = ifr.ifr_mtu;
+        netdev_dev->cache_valid |= VALID_MTU;
+    }
+    *mtup = netdev_dev->mtu;
+    return 0;
+}
+
+/* Returns the ifindex of 'netdev', if successful, as a positive number.
+ * On failure, returns a negative errno value. */
+static int
+netdev_linux_get_ifindex(const struct netdev *netdev)
+{
+    int ifindex, error;
+
+    error = get_ifindex(netdev, &ifindex);
+    return error ? -error : ifindex;
+}
+
+static int
+netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    int error = 0;
+    char *fn = NULL;
+    int fd = -1;
+
+    if (!(netdev_dev->cache_valid & VALID_CARRIER)) {
+        char line[8];
+        int retval;
+
+        fn = xasprintf("/sys/class/net/%s/carrier",
+                       netdev_get_name(netdev_));
+        fd = open(fn, O_RDONLY);
+        if (fd < 0) {
+            error = errno;
+            VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
+            goto exit;
+        }
+
+        retval = read(fd, line, sizeof line);
+        if (retval < 0) {
+            error = errno;
+            if (error == EINVAL) {
+                /* This is the normal return value when we try to check carrier
+                 * if the network device is not up. */
+            } else {
+                VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
+            }
+            goto exit;
+        } else if (retval == 0) {
+            error = EPROTO;
+            VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
+            goto exit;
+        }
+
+        if (line[0] != '0' && line[0] != '1') {
+            error = EPROTO;
+            VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)",
+                         fn, line[0]);
+            goto exit;
+        }
+        netdev_dev->carrier = line[0] != '0';
+        netdev_dev->cache_valid |= VALID_CARRIER;
+    }
+    *carrier = netdev_dev->carrier;
+    error = 0;
+
+exit:
+    if (fd >= 0) {
+        close(fd);
+    }
+    free(fn);
+    return error;
+}
+
+/* Check whether we can we use RTM_GETLINK to get network device statistics.
+ * In pre-2.6.19 kernels, this was only available if wireless extensions were
+ * enabled. */
+static bool
+check_for_working_netlink_stats(void)
+{
+    /* Decide on the netdev_get_stats() implementation to use.  Netlink is
+     * preferable, so if that works, we'll use it. */
+    int ifindex = do_get_ifindex("lo");
+    if (ifindex < 0) {
+        VLOG_WARN("failed to get ifindex for lo, "
+                  "obtaining netdev stats from proc");
+        return false;
+    } else {
+        struct netdev_stats stats;
+        int error = get_stats_via_netlink(ifindex, &stats);
+        if (!error) {
+            VLOG_DBG("obtaining netdev stats via rtnetlink");
+            return true;
+        } else {
+            VLOG_INFO("RTM_GETLINK failed (%s), obtaining netdev stats "
+                      "via proc (you are probably running a pre-2.6.19 "
+                      "kernel)", strerror(error));
+            return false;
+        }
+    }
+}
+
+/* Retrieves current device stats for 'netdev'.
+ *
+ * XXX All of the members of struct netdev_stats are 64 bits wide, but on
+ * 32-bit architectures the Linux network stats are only 32 bits. */
+static int
+netdev_linux_get_stats(const struct netdev *netdev_,
+                       struct netdev_stats *stats)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    static int use_netlink_stats = -1;
+    int error;
+    struct netdev_stats raw_stats;
+    struct netdev_stats *collect_stats = stats;
+
+    COVERAGE_INC(netdev_get_stats);
+
+    if (!(netdev_dev->cache_valid & VALID_IS_INTERNAL)) {
+        netdev_dev->is_internal = !strcmp(netdev_get_type(netdev_), "tap");
+        if (!netdev_dev->is_internal) {
+            struct ethtool_drvinfo drvinfo;
+
+            memset(&drvinfo, 0, sizeof drvinfo);
+            error = netdev_linux_do_ethtool(netdev_get_name(netdev_),
+                                            (struct ethtool_cmd *)&drvinfo,
+                                            ETHTOOL_GDRVINFO,
+                                            "ETHTOOL_GDRVINFO");
+
+            if (!error) {
+                netdev_dev->is_internal = !strcmp(drvinfo.driver,
+                                                        "openvswitch");
+            }
+        }
+
+        netdev_dev->cache_valid |= VALID_IS_INTERNAL;
+    }
+
+    if (netdev_dev->is_internal) {
+        collect_stats = &raw_stats;
+    }
+
+    if (use_netlink_stats < 0) {
+        use_netlink_stats = check_for_working_netlink_stats();
+    }
+    if (use_netlink_stats) {
+        int ifindex;
+
+        error = get_ifindex(netdev_, &ifindex);
+        if (!error) {
+            error = get_stats_via_netlink(ifindex, collect_stats);
+        }
+    } else {
+        error = get_stats_via_proc(netdev_get_name(netdev_), collect_stats);
+    }
+
+    /* If this port is an internal port then the transmit and receive stats
+     * will appear to be swapped relative to the other ports since we are the
+     * one sending the data, not a remote computer.  For consistency, we swap
+     * them back here. */
+    if (!error && netdev_dev->is_internal) {
+        stats->rx_packets = raw_stats.tx_packets;
+        stats->tx_packets = raw_stats.rx_packets;
+        stats->rx_bytes = raw_stats.tx_bytes;
+        stats->tx_bytes = raw_stats.rx_bytes;
+        stats->rx_errors = raw_stats.tx_errors;
+        stats->tx_errors = raw_stats.rx_errors;
+        stats->rx_dropped = raw_stats.tx_dropped;
+        stats->tx_dropped = raw_stats.rx_dropped;
+        stats->multicast = raw_stats.multicast;
+        stats->collisions = raw_stats.collisions;
+        stats->rx_length_errors = 0;
+        stats->rx_over_errors = 0;
+        stats->rx_crc_errors = 0;
+        stats->rx_frame_errors = 0;
+        stats->rx_fifo_errors = 0;
+        stats->rx_missed_errors = 0;
+        stats->tx_aborted_errors = 0;
+        stats->tx_carrier_errors = 0;
+        stats->tx_fifo_errors = 0;
+        stats->tx_heartbeat_errors = 0;
+        stats->tx_window_errors = 0;
+    }
+
+    return error;
+}
+
+/* Stores the features supported by 'netdev' into each of '*current',
+ * '*advertised', '*supported', and '*peer' that are non-null.  Each value is a
+ * bitmap of "enum ofp_port_features" bits, in host byte order.  Returns 0 if
+ * successful, otherwise a positive errno value. */
+static int
+netdev_linux_get_features(struct netdev *netdev,
+                          uint32_t *current, uint32_t *advertised,
+                          uint32_t *supported, uint32_t *peer)
+{
+    struct ethtool_cmd ecmd;
+    int error;
+
+    memset(&ecmd, 0, sizeof ecmd);
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+                                    ETHTOOL_GSET, "ETHTOOL_GSET");
+    if (error) {
+        return error;
+    }
+
+    /* Supported features. */
+    *supported = 0;
+    if (ecmd.supported & SUPPORTED_10baseT_Half) {
+        *supported |= OFPPF_10MB_HD;
+    }
+    if (ecmd.supported & SUPPORTED_10baseT_Full) {
+        *supported |= OFPPF_10MB_FD;
+    }
+    if (ecmd.supported & SUPPORTED_100baseT_Half)  {
+        *supported |= OFPPF_100MB_HD;
+    }
+    if (ecmd.supported & SUPPORTED_100baseT_Full) {
+        *supported |= OFPPF_100MB_FD;
+    }
+    if (ecmd.supported & SUPPORTED_1000baseT_Half) {
+        *supported |= OFPPF_1GB_HD;
+    }
+    if (ecmd.supported & SUPPORTED_1000baseT_Full) {
+        *supported |= OFPPF_1GB_FD;
+    }
+    if (ecmd.supported & SUPPORTED_10000baseT_Full) {
+        *supported |= OFPPF_10GB_FD;
+    }
+    if (ecmd.supported & SUPPORTED_TP) {
+        *supported |= OFPPF_COPPER;
+    }
+    if (ecmd.supported & SUPPORTED_FIBRE) {
+        *supported |= OFPPF_FIBER;
+    }
+    if (ecmd.supported & SUPPORTED_Autoneg) {
+        *supported |= OFPPF_AUTONEG;
+    }
+    if (ecmd.supported & SUPPORTED_Pause) {
+        *supported |= OFPPF_PAUSE;
+    }
+    if (ecmd.supported & SUPPORTED_Asym_Pause) {
+        *supported |= OFPPF_PAUSE_ASYM;
+    }
+
+    /* Advertised features. */
+    *advertised = 0;
+    if (ecmd.advertising & ADVERTISED_10baseT_Half) {
+        *advertised |= OFPPF_10MB_HD;
+    }
+    if (ecmd.advertising & ADVERTISED_10baseT_Full) {
+        *advertised |= OFPPF_10MB_FD;
+    }
+    if (ecmd.advertising & ADVERTISED_100baseT_Half) {
+        *advertised |= OFPPF_100MB_HD;
+    }
+    if (ecmd.advertising & ADVERTISED_100baseT_Full) {
+        *advertised |= OFPPF_100MB_FD;
+    }
+    if (ecmd.advertising & ADVERTISED_1000baseT_Half) {
+        *advertised |= OFPPF_1GB_HD;
+    }
+    if (ecmd.advertising & ADVERTISED_1000baseT_Full) {
+        *advertised |= OFPPF_1GB_FD;
+    }
+    if (ecmd.advertising & ADVERTISED_10000baseT_Full) {
+        *advertised |= OFPPF_10GB_FD;
+    }
+    if (ecmd.advertising & ADVERTISED_TP) {
+        *advertised |= OFPPF_COPPER;
+    }
+    if (ecmd.advertising & ADVERTISED_FIBRE) {
+        *advertised |= OFPPF_FIBER;
+    }
+    if (ecmd.advertising & ADVERTISED_Autoneg) {
+        *advertised |= OFPPF_AUTONEG;
+    }
+    if (ecmd.advertising & ADVERTISED_Pause) {
+        *advertised |= OFPPF_PAUSE;
+    }
+    if (ecmd.advertising & ADVERTISED_Asym_Pause) {
+        *advertised |= OFPPF_PAUSE_ASYM;
+    }
+
+    /* Current settings. */
+    if (ecmd.speed == SPEED_10) {
+        *current = ecmd.duplex ? OFPPF_10MB_FD : OFPPF_10MB_HD;
+    } else if (ecmd.speed == SPEED_100) {
+        *current = ecmd.duplex ? OFPPF_100MB_FD : OFPPF_100MB_HD;
+    } else if (ecmd.speed == SPEED_1000) {
+        *current = ecmd.duplex ? OFPPF_1GB_FD : OFPPF_1GB_HD;
+    } else if (ecmd.speed == SPEED_10000) {
+        *current = OFPPF_10GB_FD;
+    } else {
+        *current = 0;
+    }
+
+    if (ecmd.port == PORT_TP) {
+        *current |= OFPPF_COPPER;
+    } else if (ecmd.port == PORT_FIBRE) {
+        *current |= OFPPF_FIBER;
+    }
+
+    if (ecmd.autoneg) {
+        *current |= OFPPF_AUTONEG;
+    }
+
+    /* Peer advertisements. */
+    *peer = 0;                  /* XXX */
+
+    return 0;
+}
+
+/* Set the features advertised by 'netdev' to 'advertise'. */
+static int
+netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
+{
+    struct ethtool_cmd ecmd;
+    int error;
+
+    memset(&ecmd, 0, sizeof ecmd);
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+                                    ETHTOOL_GSET, "ETHTOOL_GSET");
+    if (error) {
+        return error;
+    }
+
+    ecmd.advertising = 0;
+    if (advertise & OFPPF_10MB_HD) {
+        ecmd.advertising |= ADVERTISED_10baseT_Half;
+    }
+    if (advertise & OFPPF_10MB_FD) {
+        ecmd.advertising |= ADVERTISED_10baseT_Full;
+    }
+    if (advertise & OFPPF_100MB_HD) {
+        ecmd.advertising |= ADVERTISED_100baseT_Half;
+    }
+    if (advertise & OFPPF_100MB_FD) {
+        ecmd.advertising |= ADVERTISED_100baseT_Full;
+    }
+    if (advertise & OFPPF_1GB_HD) {
+        ecmd.advertising |= ADVERTISED_1000baseT_Half;
+    }
+    if (advertise & OFPPF_1GB_FD) {
+        ecmd.advertising |= ADVERTISED_1000baseT_Full;
+    }
+    if (advertise & OFPPF_10GB_FD) {
+        ecmd.advertising |= ADVERTISED_10000baseT_Full;
+    }
+    if (advertise & OFPPF_COPPER) {
+        ecmd.advertising |= ADVERTISED_TP;
+    }
+    if (advertise & OFPPF_FIBER) {
+        ecmd.advertising |= ADVERTISED_FIBRE;
+    }
+    if (advertise & OFPPF_AUTONEG) {
+        ecmd.advertising |= ADVERTISED_Autoneg;
+    }
+    if (advertise & OFPPF_PAUSE) {
+        ecmd.advertising |= ADVERTISED_Pause;
+    }
+    if (advertise & OFPPF_PAUSE_ASYM) {
+        ecmd.advertising |= ADVERTISED_Asym_Pause;
+    }
+    return netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+                                   ETHTOOL_SSET, "ETHTOOL_SSET");
+}
+
+/* If 'netdev_name' is the name of a VLAN network device (e.g. one created with
+ * vconfig(8)), sets '*vlan_vid' to the VLAN VID associated with that device
+ * and returns 0.  Otherwise returns a errno value (specifically ENOENT if
+ * 'netdev_name' is the name of a network device that is not a VLAN device) and
+ * sets '*vlan_vid' to -1. */
+static int
+netdev_linux_get_vlan_vid(const struct netdev *netdev, int *vlan_vid)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    struct ds line = DS_EMPTY_INITIALIZER;
+    FILE *stream = NULL;
+    int error;
+    char *fn;
+
+    COVERAGE_INC(netdev_get_vlan_vid);
+    fn = xasprintf("/proc/net/vlan/%s", netdev_name);
+    stream = fopen(fn, "r");
+    if (!stream) {
+        error = errno;
+        goto done;
+    }
+
+    if (ds_get_line(&line, stream)) {
+        if (ferror(stream)) {
+            error = errno;
+            VLOG_ERR_RL(&rl, "error reading \"%s\": %s", fn, strerror(errno));
+        } else {
+            error = EPROTO;
+            VLOG_ERR_RL(&rl, "unexpected end of file reading \"%s\"", fn);
+        }
+        goto done;
+    }
+
+    if (!sscanf(ds_cstr(&line), "%*s VID: %d", vlan_vid)) {
+        error = EPROTO;
+        VLOG_ERR_RL(&rl, "parse error reading \"%s\" line 1: \"%s\"",
+                    fn, ds_cstr(&line));
+        goto done;
+    }
+
+    error = 0;
+
+done:
+    free(fn);
+    if (stream) {
+        fclose(stream);
+    }
+    ds_destroy(&line);
+    if (error) {
+        *vlan_vid = -1;
+    }
+    return error;
+}
+
+#define POLICE_ADD_CMD "/sbin/tc qdisc add dev %s handle ffff: ingress"
+#define POLICE_CONFIG_CMD "/sbin/tc filter add dev %s parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate %dkbit burst %dk mtu 65535 drop flowid :1"
+/* We redirect stderr to /dev/null because we often want to remove all
+ * traffic control configuration on a port so its in a known state.  If
+ * this done when there is no such configuration, tc complains, so we just
+ * always ignore it.
+ */
+#define POLICE_DEL_CMD "/sbin/tc qdisc del dev %s handle ffff: ingress 2>/dev/null"
+
+/* Attempts to set input rate limiting (policing) policy. */
+static int
+netdev_linux_set_policing(struct netdev *netdev,
+                          uint32_t kbits_rate, uint32_t kbits_burst)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    char command[1024];
+
+    COVERAGE_INC(netdev_set_policing);
+    if (kbits_rate) {
+        if (!kbits_burst) {
+            /* Default to 1000 kilobits if not specified. */
+            kbits_burst = 1000;
+        }
+
+        /* xxx This should be more careful about only adding if it
+         * xxx actually exists, as opposed to always deleting it. */
+        snprintf(command, sizeof(command), POLICE_DEL_CMD, netdev_name);
+        if (system(command) == -1) {
+            VLOG_WARN_RL(&rl, "%s: problem removing policing", netdev_name);
+        }
+
+        snprintf(command, sizeof(command), POLICE_ADD_CMD, netdev_name);
+        if (system(command) != 0) {
+            VLOG_WARN_RL(&rl, "%s: problem adding policing", netdev_name);
+            return -1;
+        }
+
+        snprintf(command, sizeof(command), POLICE_CONFIG_CMD, netdev_name,
+                kbits_rate, kbits_burst);
+        if (system(command) != 0) {
+            VLOG_WARN_RL(&rl, "%s: problem configuring policing",
+                    netdev_name);
+            return -1;
+        }
+    } else {
+        snprintf(command, sizeof(command), POLICE_DEL_CMD, netdev_name);
+        if (system(command) == -1) {
+            VLOG_WARN_RL(&rl, "%s: problem removing policing", netdev_name);
+        }
+    }
+
+    return 0;
+}
+
+static int
+netdev_linux_get_in4(const struct netdev *netdev_,
+                     struct in_addr *address, struct in_addr *netmask)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+
+    if (!(netdev_dev->cache_valid & VALID_IN4)) {
+        int error;
+
+        error = netdev_linux_get_ipv4(netdev_, &netdev_dev->address,
+                                      SIOCGIFADDR, "SIOCGIFADDR");
+        if (error) {
+            return error;
+        }
+
+        error = netdev_linux_get_ipv4(netdev_, &netdev_dev->netmask,
+                                      SIOCGIFNETMASK, "SIOCGIFNETMASK");
+        if (error) {
+            return error;
+        }
+
+        netdev_dev->cache_valid |= VALID_IN4;
+    }
+    *address = netdev_dev->address;
+    *netmask = netdev_dev->netmask;
+    return address->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
+}
+
+static int
+netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
+                     struct in_addr netmask)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    int error;
+
+    error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", address);
+    if (!error) {
+        netdev_dev->cache_valid |= VALID_IN4;
+        netdev_dev->address = address;
+        netdev_dev->netmask = netmask;
+        if (address.s_addr != INADDR_ANY) {
+            error = do_set_addr(netdev_, SIOCSIFNETMASK,
+                                "SIOCSIFNETMASK", netmask);
+        }
+    }
+    return error;
+}
+
+static bool
+parse_if_inet6_line(const char *line,
+                    struct in6_addr *in6, char ifname[16 + 1])
+{
+    uint8_t *s6 = in6->s6_addr;
+#define X8 "%2"SCNx8
+    return sscanf(line,
+                  " "X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8
+                  "%*x %*x %*x %*x %16s\n",
+                  &s6[0], &s6[1], &s6[2], &s6[3],
+                  &s6[4], &s6[5], &s6[6], &s6[7],
+                  &s6[8], &s6[9], &s6[10], &s6[11],
+                  &s6[12], &s6[13], &s6[14], &s6[15],
+                  ifname) == 17;
+}
+
+/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address (if
+ * 'in6' is non-null) and returns true.  Otherwise, returns false. */
+static int
+netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_IN6)) {
+        FILE *file;
+        char line[128];
+
+        netdev_dev->in6 = in6addr_any;
+
+        file = fopen("/proc/net/if_inet6", "r");
+        if (file != NULL) {
+            const char *name = netdev_get_name(netdev_);
+            while (fgets(line, sizeof line, file)) {
+                struct in6_addr in6;
+                char ifname[16 + 1];
+                if (parse_if_inet6_line(line, &in6, ifname)
+                    && !strcmp(name, ifname))
+                {
+                    netdev_dev->in6 = in6;
+                    break;
+                }
+            }
+            fclose(file);
+        }
+        netdev_dev->cache_valid |= VALID_IN6;
+    }
+    *in6 = netdev_dev->in6;
+    return 0;
+}
+
+static void
+make_in4_sockaddr(struct sockaddr *sa, struct in_addr addr)
+{
+    struct sockaddr_in sin;
+    memset(&sin, 0, sizeof sin);
+    sin.sin_family = AF_INET;
+    sin.sin_addr = addr;
+    sin.sin_port = 0;
+
+    memset(sa, 0, sizeof *sa);
+    memcpy(sa, &sin, sizeof sin);
+}
+
+static int
+do_set_addr(struct netdev *netdev,
+            int ioctl_nr, const char *ioctl_name, struct in_addr addr)
+{
+    struct ifreq ifr;
+    strncpy(ifr.ifr_name, netdev_get_name(netdev), sizeof ifr.ifr_name);
+    make_in4_sockaddr(&ifr.ifr_addr, addr);
+
+    return netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, ioctl_nr,
+                                 ioctl_name);
+}
+
+/* Adds 'router' as a default IP gateway. */
+static int
+netdev_linux_add_router(struct netdev *netdev OVS_UNUSED, struct in_addr router)
+{
+    struct in_addr any = { INADDR_ANY };
+    struct rtentry rt;
+    int error;
+
+    memset(&rt, 0, sizeof rt);
+    make_in4_sockaddr(&rt.rt_dst, any);
+    make_in4_sockaddr(&rt.rt_gateway, router);
+    make_in4_sockaddr(&rt.rt_genmask, any);
+    rt.rt_flags = RTF_UP | RTF_GATEWAY;
+    COVERAGE_INC(netdev_add_router);
+    error = ioctl(af_inet_sock, SIOCADDRT, &rt) < 0 ? errno : 0;
+    if (error) {
+        VLOG_WARN("ioctl(SIOCADDRT): %s", strerror(error));
+    }
+    return error;
+}
+
+static int
+netdev_linux_get_next_hop(const struct in_addr *host, struct in_addr *next_hop,
+                          char **netdev_name)
+{
+    static const char fn[] = "/proc/net/route";
+    FILE *stream;
+    char line[256];
+    int ln;
+
+    *netdev_name = NULL;
+    stream = fopen(fn, "r");
+    if (stream == NULL) {
+        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
+        return errno;
+    }
+
+    ln = 0;
+    while (fgets(line, sizeof line, stream)) {
+        if (++ln >= 2) {
+            char iface[17];
+            uint32_t dest, gateway, mask;
+            int refcnt, metric, mtu;
+            unsigned int flags, use, window, irtt;
+
+            if (sscanf(line,
+                       "%16s %"SCNx32" %"SCNx32" %04X %d %u %d %"SCNx32
+                       " %d %u %u\n",
+                       iface, &dest, &gateway, &flags, &refcnt,
+                       &use, &metric, &mask, &mtu, &window, &irtt) != 11) {
+
+                VLOG_WARN_RL(&rl, "%s: could not parse line %d: %s", 
+                        fn, ln, line);
+                continue;
+            }
+            if (!(flags & RTF_UP)) {
+                /* Skip routes that aren't up. */
+                continue;
+            }
+
+            /* The output of 'dest', 'mask', and 'gateway' were given in
+             * network byte order, so we don't need need any endian 
+             * conversions here. */
+            if ((dest & mask) == (host->s_addr & mask)) {
+                if (!gateway) {
+                    /* The host is directly reachable. */
+                    next_hop->s_addr = 0;
+                } else {
+                    /* To reach the host, we must go through a gateway. */
+                    next_hop->s_addr = gateway;
+                }
+                *netdev_name = xstrdup(iface);
+                fclose(stream);
+                return 0;
+            }
+        }
+    }
+
+    fclose(stream);
+    return ENXIO;
+}
+
+/* Looks up the ARP table entry for 'ip' on 'netdev'.  If one exists and can be
+ * successfully retrieved, it stores the corresponding MAC address in 'mac' and
+ * returns 0.  Otherwise, it returns a positive errno value; in particular,
+ * ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'. */
+static int
+netdev_linux_arp_lookup(const struct netdev *netdev,
+                        uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
+{
+    struct arpreq r;
+    struct sockaddr_in sin;
+    int retval;
+
+    memset(&r, 0, sizeof r);
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = ip;
+    sin.sin_port = 0;
+    memcpy(&r.arp_pa, &sin, sizeof sin);
+    r.arp_ha.sa_family = ARPHRD_ETHER;
+    r.arp_flags = 0;
+    strncpy(r.arp_dev, netdev_get_name(netdev), sizeof r.arp_dev);
+    COVERAGE_INC(netdev_arp_lookup);
+    retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
+    if (!retval) {
+        memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
+    } else if (retval != ENXIO) {
+        VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
+                     netdev_get_name(netdev), IP_ARGS(&ip), strerror(retval));
+    }
+    return retval;
+}
+
+static int
+nd_to_iff_flags(enum netdev_flags nd)
+{
+    int iff = 0;
+    if (nd & NETDEV_UP) {
+        iff |= IFF_UP;
+    }
+    if (nd & NETDEV_PROMISC) {
+        iff |= IFF_PROMISC;
+    }
+    return iff;
+}
+
+static int
+iff_to_nd_flags(int iff)
+{
+    enum netdev_flags nd = 0;
+    if (iff & IFF_UP) {
+        nd |= NETDEV_UP;
+    }
+    if (iff & IFF_PROMISC) {
+        nd |= NETDEV_PROMISC;
+    }
+    return nd;
+}
+
+static int
+netdev_linux_update_flags(struct netdev *netdev, enum netdev_flags off,
+                          enum netdev_flags on, enum netdev_flags *old_flagsp)
+{
+    int old_flags, new_flags;
+    int error;
+
+    error = get_flags(netdev, &old_flags);
+    if (!error) {
+        *old_flagsp = iff_to_nd_flags(old_flags);
+        new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
+        if (new_flags != old_flags) {
+            error = set_flags(netdev, new_flags);
+        }
+    }
+    return error;
+}
+
+static void
+poll_notify(struct list *list)
+{
+    struct netdev_linux_notifier *notifier;
+    LIST_FOR_EACH (notifier, struct netdev_linux_notifier, node, list) {
+        struct netdev_notifier *n = &notifier->notifier;
+        n->cb(n);
+    }
+}
+
+static void
+netdev_linux_poll_cb(const struct rtnetlink_change *change,
+                     void *aux OVS_UNUSED)
+{
+    if (change) {
+        struct list *list = shash_find_data(&netdev_linux_notifiers,
+                                            change->ifname);
+        if (list) {
+            poll_notify(list);
+        }
+    } else {
+        struct shash_node *node;
+        SHASH_FOR_EACH (node, &netdev_linux_notifiers) {
+            poll_notify(node->data);
+        }
+    }
+}
+
+static int
+netdev_linux_poll_add(struct netdev *netdev,
+                      void (*cb)(struct netdev_notifier *), void *aux,
+                      struct netdev_notifier **notifierp)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    struct netdev_linux_notifier *notifier;
+    struct list *list;
+
+    if (shash_is_empty(&netdev_linux_notifiers)) {
+        int error = rtnetlink_notifier_register(&netdev_linux_poll_notifier,
+                                                   netdev_linux_poll_cb, NULL);
+        if (error) {
+            return error;
+        }
+    }
+
+    list = shash_find_data(&netdev_linux_notifiers, netdev_name);
+    if (!list) {
+        list = xmalloc(sizeof *list);
+        list_init(list);
+        shash_add(&netdev_linux_notifiers, netdev_name, list);
+    }
+
+    notifier = xmalloc(sizeof *notifier);
+    netdev_notifier_init(&notifier->notifier, netdev, cb, aux);
+    list_push_back(list, &notifier->node);
+    *notifierp = &notifier->notifier;
+    return 0;
+}
+
+static void
+netdev_linux_poll_remove(struct netdev_notifier *notifier_)
+{
+    struct netdev_linux_notifier *notifier =
+        CONTAINER_OF(notifier_, struct netdev_linux_notifier, notifier);
+    struct list *list;
+
+    /* Remove 'notifier' from its list. */
+    list = list_remove(&notifier->node);
+    if (list_is_empty(list)) {
+        /* The list is now empty.  Remove it from the hash and free it. */
+        const char *netdev_name = netdev_get_name(notifier->notifier.netdev);
+        shash_delete(&netdev_linux_notifiers,
+                     shash_find(&netdev_linux_notifiers, netdev_name));
+        free(list);
+    }
+    free(notifier);
+
+    /* If that was the last notifier, unregister. */
+    if (shash_is_empty(&netdev_linux_notifiers)) {
+        rtnetlink_notifier_unregister(&netdev_linux_poll_notifier);
+    }
+}
+
+const struct netdev_class netdev_linux_class = {
+    "system",
+
+    netdev_linux_init,
+    netdev_linux_run,
+    netdev_linux_wait,
+
+    netdev_linux_create_system,
+    netdev_linux_destroy,
+    NULL,                       /* reconfigure */
+
+    netdev_linux_open,
+    netdev_linux_close,
+
+    netdev_linux_enumerate,
+
+    netdev_linux_recv,
+    netdev_linux_recv_wait,
+    netdev_linux_drain,
+
+    netdev_linux_send,
+    netdev_linux_send_wait,
+
+    netdev_linux_set_etheraddr,
+    netdev_linux_get_etheraddr,
+    netdev_linux_get_mtu,
+    netdev_linux_get_ifindex,
+    netdev_linux_get_carrier,
+    netdev_linux_get_stats,
+
+    netdev_linux_get_features,
+    netdev_linux_set_advertisements,
+    netdev_linux_get_vlan_vid,
+    netdev_linux_set_policing,
+
+    netdev_linux_get_in4,
+    netdev_linux_set_in4,
+    netdev_linux_get_in6,
+    netdev_linux_add_router,
+    netdev_linux_get_next_hop,
+    netdev_linux_arp_lookup,
+
+    netdev_linux_update_flags,
+
+    netdev_linux_poll_add,
+    netdev_linux_poll_remove,
+};
+
+const struct netdev_class netdev_tap_class = {
+    "tap",
+
+    netdev_linux_init,
+    netdev_linux_run,
+    netdev_linux_wait,
+
+    netdev_linux_create_tap,
+    netdev_linux_destroy,
+    NULL,                       /* reconfigure */
+
+    netdev_linux_open,
+    netdev_linux_close,
+
+    NULL,                       /* enumerate */
+
+    netdev_linux_recv,
+    netdev_linux_recv_wait,
+    netdev_linux_drain,
+
+    netdev_linux_send,
+    netdev_linux_send_wait,
+
+    netdev_linux_set_etheraddr,
+    netdev_linux_get_etheraddr,
+    netdev_linux_get_mtu,
+    netdev_linux_get_ifindex,
+    netdev_linux_get_carrier,
+    netdev_linux_get_stats,
+
+    netdev_linux_get_features,
+    netdev_linux_set_advertisements,
+    netdev_linux_get_vlan_vid,
+    netdev_linux_set_policing,
+
+    netdev_linux_get_in4,
+    netdev_linux_set_in4,
+    netdev_linux_get_in6,
+    netdev_linux_add_router,
+    netdev_linux_get_next_hop,
+    netdev_linux_arp_lookup,
+
+    netdev_linux_update_flags,
+
+    netdev_linux_poll_add,
+    netdev_linux_poll_remove,
+};
+
+const struct netdev_class netdev_gre_class = {
+    "gre",
+
+    netdev_linux_init,
+    netdev_linux_run,
+    netdev_linux_wait,
+
+    netdev_linux_create_gre,
+    netdev_linux_destroy,
+    netdev_linux_reconfigure_gre,
+
+    netdev_linux_open,
+    netdev_linux_close,
+
+    NULL,                       /* enumerate */
+
+    netdev_linux_recv,
+    netdev_linux_recv_wait,
+    netdev_linux_drain,
+
+    netdev_linux_send,
+    netdev_linux_send_wait,
+
+    netdev_linux_set_etheraddr,
+    netdev_linux_get_etheraddr,
+    netdev_linux_get_mtu,
+    netdev_linux_get_ifindex,
+    netdev_linux_get_carrier,
+    netdev_linux_get_stats,
+
+    netdev_linux_get_features,
+    netdev_linux_set_advertisements,
+    netdev_linux_get_vlan_vid,
+    netdev_linux_set_policing,
+
+    netdev_linux_get_in4,
+    netdev_linux_set_in4,
+    netdev_linux_get_in6,
+    netdev_linux_add_router,
+    netdev_linux_get_next_hop,
+    netdev_linux_arp_lookup,
+
+    netdev_linux_update_flags,
+
+    netdev_linux_poll_add,
+    netdev_linux_poll_remove,
+};
+\f
+static int
+get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
+{
+    /* Policy for RTNLGRP_LINK messages.
+     *
+     * There are *many* more fields in these messages, but currently we only
+     * care about these fields. */
+    static const struct nl_policy rtnlgrp_link_policy[] = {
+        [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+        [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
+                         .min_len = sizeof(struct rtnl_link_stats) },
+    };
+
+
+    static struct nl_sock *rtnl_sock;
+    struct ofpbuf request;
+    struct ofpbuf *reply;
+    struct ifinfomsg *ifi;
+    const struct rtnl_link_stats *rtnl_stats;
+    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
+    int error;
+
+    if (!rtnl_sock) {
+        error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, &rtnl_sock);
+        if (error) {
+            VLOG_ERR_RL(&rl, "failed to create rtnetlink socket: %s",
+                        strerror(error));
+            return error;
+        }
+    }
+
+    ofpbuf_init(&request, 0);
+    nl_msg_put_nlmsghdr(&request, rtnl_sock, sizeof *ifi,
+                        RTM_GETLINK, NLM_F_REQUEST);
+    ifi = ofpbuf_put_zeros(&request, sizeof *ifi);
+    ifi->ifi_family = PF_UNSPEC;
+    ifi->ifi_index = ifindex;
+    error = nl_sock_transact(rtnl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        return error;
+    }
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                         rtnlgrp_link_policy,
+                         attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
+        ofpbuf_delete(reply);
+        return EPROTO;
+    }
+
+    if (!attrs[IFLA_STATS]) {
+        VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
+        ofpbuf_delete(reply);
+        return EPROTO;
+    }
+
+    rtnl_stats = nl_attr_get(attrs[IFLA_STATS]);
+    stats->rx_packets = rtnl_stats->rx_packets;
+    stats->tx_packets = rtnl_stats->tx_packets;
+    stats->rx_bytes = rtnl_stats->rx_bytes;
+    stats->tx_bytes = rtnl_stats->tx_bytes;
+    stats->rx_errors = rtnl_stats->rx_errors;
+    stats->tx_errors = rtnl_stats->tx_errors;
+    stats->rx_dropped = rtnl_stats->rx_dropped;
+    stats->tx_dropped = rtnl_stats->tx_dropped;
+    stats->multicast = rtnl_stats->multicast;
+    stats->collisions = rtnl_stats->collisions;
+    stats->rx_length_errors = rtnl_stats->rx_length_errors;
+    stats->rx_over_errors = rtnl_stats->rx_over_errors;
+    stats->rx_crc_errors = rtnl_stats->rx_crc_errors;
+    stats->rx_frame_errors = rtnl_stats->rx_frame_errors;
+    stats->rx_fifo_errors = rtnl_stats->rx_fifo_errors;
+    stats->rx_missed_errors = rtnl_stats->rx_missed_errors;
+    stats->tx_aborted_errors = rtnl_stats->tx_aborted_errors;
+    stats->tx_carrier_errors = rtnl_stats->tx_carrier_errors;
+    stats->tx_fifo_errors = rtnl_stats->tx_fifo_errors;
+    stats->tx_heartbeat_errors = rtnl_stats->tx_heartbeat_errors;
+    stats->tx_window_errors = rtnl_stats->tx_window_errors;
+
+    ofpbuf_delete(reply);
+
+    return 0;
+}
+
+static int
+get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
+{
+    static const char fn[] = "/proc/net/dev";
+    char line[1024];
+    FILE *stream;
+    int ln;
+
+    stream = fopen(fn, "r");
+    if (!stream) {
+        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
+        return errno;
+    }
+
+    ln = 0;
+    while (fgets(line, sizeof line, stream)) {
+        if (++ln >= 3) {
+            char devname[16];
+#define X64 "%"SCNu64
+            if (sscanf(line,
+                       " %15[^:]:"
+                       X64 X64 X64 X64 X64 X64 X64 "%*u"
+                       X64 X64 X64 X64 X64 X64 X64 "%*u",
+                       devname,
+                       &stats->rx_bytes,
+                       &stats->rx_packets,
+                       &stats->rx_errors,
+                       &stats->rx_dropped,
+                       &stats->rx_fifo_errors,
+                       &stats->rx_frame_errors,
+                       &stats->multicast,
+                       &stats->tx_bytes,
+                       &stats->tx_packets,
+                       &stats->tx_errors,
+                       &stats->tx_dropped,
+                       &stats->tx_fifo_errors,
+                       &stats->collisions,
+                       &stats->tx_carrier_errors) != 15) {
+                VLOG_WARN_RL(&rl, "%s:%d: parse error", fn, ln);
+            } else if (!strcmp(devname, netdev_name)) {
+                stats->rx_length_errors = UINT64_MAX;
+                stats->rx_over_errors = UINT64_MAX;
+                stats->rx_crc_errors = UINT64_MAX;
+                stats->rx_missed_errors = UINT64_MAX;
+                stats->tx_aborted_errors = UINT64_MAX;
+                stats->tx_heartbeat_errors = UINT64_MAX;
+                stats->tx_window_errors = UINT64_MAX;
+                fclose(stream);
+                return 0;
+            }
+        }
+    }
+    VLOG_WARN_RL(&rl, "%s: no stats for %s", fn, netdev_name);
+    fclose(stream);
+    return ENODEV;
+}
+\f
+static int
+get_flags(const struct netdev *netdev, int *flags)
+{
+    struct ifreq ifr;
+    int error;
+
+    error = netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, SIOCGIFFLAGS,
+                                  "SIOCGIFFLAGS");
+    *flags = ifr.ifr_flags;
+    return error;
+}
+
+static int
+set_flags(struct netdev *netdev, int flags)
+{
+    struct ifreq ifr;
+
+    ifr.ifr_flags = flags;
+    return netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, SIOCSIFFLAGS,
+                                 "SIOCSIFFLAGS");
+}
+
+static int
+do_get_ifindex(const char *netdev_name)
+{
+    struct ifreq ifr;
+
+    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
+    COVERAGE_INC(netdev_get_ifindex);
+    if (ioctl(af_inet_sock, SIOCGIFINDEX, &ifr) < 0) {
+        VLOG_WARN_RL(&rl, "ioctl(SIOCGIFINDEX) on %s device failed: %s",
+                     netdev_name, strerror(errno));
+        return -errno;
+    }
+    return ifr.ifr_ifindex;
+}
+
+static int
+get_ifindex(const struct netdev *netdev_, int *ifindexp)
+{
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    *ifindexp = 0;
+    if (!(netdev_dev->cache_valid & VALID_IFINDEX)) {
+        int ifindex = do_get_ifindex(netdev_get_name(netdev_));
+        if (ifindex < 0) {
+            return -ifindex;
+        }
+        netdev_dev->cache_valid |= VALID_IFINDEX;
+        netdev_dev->ifindex = ifindex;
+    }
+    *ifindexp = netdev_dev->ifindex;
+    return 0;
+}
+
+static int
+get_etheraddr(const char *netdev_name, uint8_t ea[ETH_ADDR_LEN])
+{
+    struct ifreq ifr;
+    int hwaddr_family;
+
+    memset(&ifr, 0, sizeof ifr);
+    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
+    COVERAGE_INC(netdev_get_hwaddr);
+    if (ioctl(af_inet_sock, SIOCGIFHWADDR, &ifr) < 0) {
+        VLOG_ERR("ioctl(SIOCGIFHWADDR) on %s device failed: %s",
+                 netdev_name, strerror(errno));
+        return errno;
+    }
+    hwaddr_family = ifr.ifr_hwaddr.sa_family;
+    if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER) {
+        VLOG_WARN("%s device has unknown hardware address family %d",
+                  netdev_name, hwaddr_family);
+    }
+    memcpy(ea, ifr.ifr_hwaddr.sa_data, ETH_ADDR_LEN);
+    return 0;
+}
+
+static int
+set_etheraddr(const char *netdev_name, int hwaddr_family,
+              const uint8_t mac[ETH_ADDR_LEN])
+{
+    struct ifreq ifr;
+
+    memset(&ifr, 0, sizeof ifr);
+    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
+    ifr.ifr_hwaddr.sa_family = hwaddr_family;
+    memcpy(ifr.ifr_hwaddr.sa_data, mac, ETH_ADDR_LEN);
+    COVERAGE_INC(netdev_set_hwaddr);
+    if (ioctl(af_inet_sock, SIOCSIFHWADDR, &ifr) < 0) {
+        VLOG_ERR("ioctl(SIOCSIFHWADDR) on %s device failed: %s",
+                 netdev_name, strerror(errno));
+        return errno;
+    }
+    return 0;
+}
+
+static int
+netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *ecmd,
+                        int cmd, const char *cmd_name)
+{
+    struct ifreq ifr;
+
+    memset(&ifr, 0, sizeof ifr);
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+    ifr.ifr_data = (caddr_t) ecmd;
+
+    ecmd->cmd = cmd;
+    COVERAGE_INC(netdev_ethtool);
+    if (ioctl(af_inet_sock, SIOCETHTOOL, &ifr) == 0) {
+        return 0;
+    } else {
+        if (errno != EOPNOTSUPP) {
+            VLOG_WARN_RL(&rl, "ethtool command %s on network device %s "
+                         "failed: %s", cmd_name, name, strerror(errno));
+        } else {
+            /* The device doesn't support this operation.  That's pretty
+             * common, so there's no point in logging anything. */
+        }
+        return errno;
+    }
+}
+
+static int
+netdev_linux_do_ioctl(const char *name, struct ifreq *ifr, int cmd,
+                      const char *cmd_name)
+{
+    strncpy(ifr->ifr_name, name, sizeof ifr->ifr_name);
+    if (ioctl(af_inet_sock, cmd, ifr) == -1) {
+        VLOG_DBG_RL(&rl, "%s: ioctl(%s) failed: %s", name, cmd_name,
+                     strerror(errno));
+        return errno;
+    }
+    return 0;
+}
+
+static int
+netdev_linux_get_ipv4(const struct netdev *netdev, struct in_addr *ip,
+                      int cmd, const char *cmd_name)
+{
+    struct ifreq ifr;
+    int error;
+
+    ifr.ifr_addr.sa_family = AF_INET;
+    error = netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, cmd, cmd_name);
+    if (!error) {
+        const struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
+        *ip = sin->sin_addr;
+    }
+    return error;
+}
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
new file mode 100644 (file)
index 0000000..1eb1b1e
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETDEV_PROVIDER_H
+#define NETDEV_PROVIDER_H 1
+
+/* Generic interface to network devices. */
+
+#include <assert.h>
+
+#include "netdev.h"
+#include "list.h"
+#include "shash.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+struct arg {
+    char *key;
+    char *value;
+};
+
+/* A network device (e.g. an Ethernet device).
+ *
+ * This structure should be treated as opaque by network device
+ * implementations. */
+struct netdev_dev {
+    char *name;                         /* Name of network device. */
+    const struct netdev_class *netdev_class; /* Functions to control 
+                                                this device. */
+    int ref_cnt;                        /* Times this devices was opened. */
+    struct shash_node *node;            /* Pointer to element in global map. */
+    struct arg *args;                   /* Argument list from last config. */
+    int n_args;                         /* Number of arguments in 'args'. */
+};
+
+void netdev_dev_init(struct netdev_dev *, const char *name,
+                     const struct netdev_class *);
+void netdev_dev_uninit(struct netdev_dev *, bool destroy);
+const char *netdev_dev_get_type(const struct netdev_dev *);
+const char *netdev_dev_get_name(const struct netdev_dev *);
+struct netdev_dev *netdev_dev_from_name(const char *name);
+void netdev_dev_get_devices(const struct netdev_class *,
+                            struct shash *device_list);
+
+static inline void netdev_dev_assert_class(const struct netdev_dev *netdev_dev,
+                                           const struct netdev_class *class_)
+{
+    assert(netdev_dev->netdev_class == class_);
+}
+
+/* A instance of an open network device.
+ *
+ * This structure should be treated as opaque by network device
+ * implementations. */
+struct netdev {
+    struct netdev_dev *netdev_dev;   /* Parent netdev_dev. */
+    struct list node;                /* Element in global list. */
+
+    enum netdev_flags save_flags;    /* Initial device flags. */
+    enum netdev_flags changed_flags; /* Flags that we changed. */
+};
+
+void netdev_init(struct netdev *, struct netdev_dev *);
+void netdev_uninit(struct netdev *, bool close);
+struct netdev_dev *netdev_get_dev(const struct netdev *);
+
+static inline void netdev_assert_class(const struct netdev *netdev,
+                                       const struct netdev_class *netdev_class)
+{
+    netdev_dev_assert_class(netdev_get_dev(netdev), netdev_class);
+}
+
+/* A network device notifier.
+ *
+ * Network device implementations should use netdev_notifier_init() to
+ * initialize this structure, but they may freely read its members after
+ * initialization. */
+struct netdev_notifier {
+    struct netdev *netdev;
+    void (*cb)(struct netdev_notifier *);
+    void *aux;
+};
+void netdev_notifier_init(struct netdev_notifier *, struct netdev *,
+                          void (*cb)(struct netdev_notifier *), void *aux);
+
+/* Network device class structure, to be defined by each implementation of a
+ * network device.
+ *
+ * These functions return 0 if successful or a positive errno value on failure,
+ * except where otherwise noted. */
+struct netdev_class {
+    /* Type of netdevs in this class, e.g. "system", "tap", "gre", etc.
+     *
+     * One of the providers should supply a "system" type, since this is
+     * the type assumed if no type is specified when opening a netdev.
+     * The "system" type corresponds to an existing network device on
+     * the system. */
+    const char *type;
+
+    /* Called when the netdev provider is registered, typically at program
+     * startup.  Returning an error from this function will prevent any network
+     * device in this class from being opened.
+     *
+     * This function may be set to null if a network device class needs no
+     * initialization at registration time. */
+    int (*init)(void);
+
+    /* Performs periodic work needed by netdevs of this class.  May be null if
+     * no periodic work is necessary. */
+    void (*run)(void);
+
+    /* Arranges for poll_block() to wake up if the "run" member function needs
+     * to be called.  May be null if nothing is needed here. */
+    void (*wait)(void);
+
+    /* Attempts to create a network device of 'type' with 'name'.
+     * 'type' corresponds to the 'type' field used in the netdev_class
+     * structure. On success sets 'netdev_devp' to the newly created device. */
+    int (*create)(const char *name, const char *type, const struct shash *args,
+                  struct netdev_dev **netdev_devp);
+
+    /* Destroys 'netdev_dev'.
+     *
+     * Netdev devices maintain a reference count that is incremented on
+     * netdev_open() and decremented on netdev_close().  If 'netdev_dev'
+     * has a non-zero reference count, then this function will not be
+     * called. */
+    void (*destroy)(struct netdev_dev *netdev_dev);
+
+    /* Reconfigures the device 'netdev_dev' with 'args'.
+     *
+     * If this netdev class does not support reconfiguring a netdev
+     * device, this may be a null pointer.
+     */
+    int (*reconfigure)(struct netdev_dev *netdev_dev, const struct shash *args);
+
+    /* Attempts to open a network device.  On success, sets 'netdevp'
+     * to the new network device.
+     *
+     * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order
+     * to capture frames of that type received on the device.  It may also be
+     * one of the 'enum netdev_pseudo_ethertype' values to receive frames in
+     * one of those categories. */
+    int (*open)(struct netdev_dev *netdev_dev, int ethertype,
+                struct netdev **netdevp);
+
+    /* Closes 'netdev'. */
+    void (*close)(struct netdev *netdev);
+
+    /* Enumerates the names of all network devices of this class.
+     *
+     * The caller has already initialized 'all_names' and might already have
+     * added some names to it.  This function should not disturb any existing
+     * names in 'all_names'.
+     *
+     * If this netdev class does not support enumeration, this may be a null
+     * pointer. */
+    int (*enumerate)(struct svec *all_names);
+
+    /* Attempts to receive a packet from 'netdev' into the 'size' bytes in
+     * 'buffer'.  If successful, returns the number of bytes in the received
+     * packet, otherwise a negative errno value.  Returns -EAGAIN immediately
+     * if no packet is ready to be received. */
+    int (*recv)(struct netdev *netdev, void *buffer, size_t size);
+
+    /* Registers with the poll loop to wake up from the next call to
+     * poll_block() when a packet is ready to be received with netdev_recv() on
+     * 'netdev'. */
+    void (*recv_wait)(struct netdev *netdev);
+
+    /* Discards all packets waiting to be received from 'netdev'. */
+    int (*drain)(struct netdev *netdev);
+
+    /* Sends the 'size'-byte packet in 'buffer' on 'netdev'.  Returns 0 if
+     * successful, otherwise a positive errno value.  Returns EAGAIN without
+     * blocking if the packet cannot be queued immediately.  Returns EMSGSIZE
+     * if a partial packet was transmitted or if the packet is too big or too
+     * small to transmit on the device.
+     *
+     * The caller retains ownership of 'buffer' in all cases.
+     *
+     * The network device is expected to maintain a packet transmission queue,
+     * so that the caller does not ordinarily have to do additional queuing of
+     * packets. */
+    int (*send)(struct netdev *netdev, const void *buffer, size_t size);
+
+    /* Registers with the poll loop to wake up from the next call to
+     * poll_block() when the packet transmission queue for 'netdev' has
+     * sufficient room to transmit a packet with netdev_send().
+     *
+     * The network device is expected to maintain a packet transmission queue,
+     * so that the caller does not ordinarily have to do additional queuing of
+     * packets.  Thus, this function is unlikely to ever be useful. */
+    void (*send_wait)(struct netdev *netdev);
+
+    /* Sets 'netdev''s Ethernet address to 'mac' */
+    int (*set_etheraddr)(struct netdev *netdev, const uint8_t mac[6]);
+
+    /* Retrieves 'netdev''s Ethernet address into 'mac'. */
+    int (*get_etheraddr)(const struct netdev *netdev, uint8_t mac[6]);
+
+    /* Retrieves 'netdev''s MTU into '*mtup'.
+     *
+     * The MTU is the maximum size of transmitted (and received) packets, in
+     * bytes, not including the hardware header; thus, this is typically 1500
+     * bytes for Ethernet devices.*/
+    int (*get_mtu)(const struct netdev *netdev, int *mtup);
+
+    /* Returns the ifindex of 'netdev', if successful, as a positive number.
+     * On failure, returns a negative errno value.
+     *
+     * The desired semantics of the ifindex value are a combination of those
+     * specified by POSIX for if_nametoindex() and by SNMP for ifIndex.  An
+     * ifindex value should be unique within a host and remain stable at least
+     * until reboot.  SNMP says an ifindex "ranges between 1 and the value of
+     * ifNumber" but many systems do not follow this rule anyhow. */
+    int (*get_ifindex)(const struct netdev *netdev);
+
+    /* Sets 'carrier' to true if carrier is active (link light is on) on
+     * 'netdev'. */
+    int (*get_carrier)(const struct netdev *netdev, bool *carrier);
+
+    /* Retrieves current device stats for 'netdev' into 'stats'.
+     *
+     * A network device that supports some statistics but not others, it should
+     * set the values of the unsupported statistics to all-1-bits
+     * (UINT64_MAX). */
+    int (*get_stats)(const struct netdev *netdev, struct netdev_stats *);
+
+    /* Stores the features supported by 'netdev' into each of '*current',
+     * '*advertised', '*supported', and '*peer'.  Each value is a bitmap of
+     * "enum ofp_port_features" bits, in host byte order. */
+    int (*get_features)(struct netdev *netdev,
+                        uint32_t *current, uint32_t *advertised,
+                        uint32_t *supported, uint32_t *peer);
+
+    /* Set the features advertised by 'netdev' to 'advertise', which is a
+     * bitmap of "enum ofp_port_features" bits, in host byte order.
+     *
+     * This function may be set to null for a network device that does not
+     * support configuring advertisements. */
+    int (*set_advertisements)(struct netdev *netdev, uint32_t advertise);
+
+    /* If 'netdev' is a VLAN network device (e.g. one created with vconfig(8)),
+     * sets '*vlan_vid' to the VLAN VID associated with that device and returns
+     * 0.
+     *
+     * Returns ENOENT if 'netdev' is a network device that is not a
+     * VLAN device.
+     *
+     * This function should be set to null if it doesn't make any sense for
+     * your network device (it probably doesn't). */
+    int (*get_vlan_vid)(const struct netdev *netdev, int *vlan_vid);
+
+    /* Attempts to set input rate limiting (policing) policy, such that up to
+     * 'kbits_rate' kbps of traffic is accepted, with a maximum accumulative
+     * burst size of 'kbits' kb.
+     *
+     * This function may be set to null if policing is not supported. */
+    int (*set_policing)(struct netdev *netdev, unsigned int kbits_rate,
+                        unsigned int kbits_burst);
+
+    /* If 'netdev' has an assigned IPv4 address, sets '*address' to that
+     * address and '*netmask' to the associated netmask.
+     *
+     * The following error values have well-defined meanings:
+     *
+     *   - EADDRNOTAVAIL: 'netdev' has no assigned IPv4 address.
+     *
+     *   - EOPNOTSUPP: No IPv4 network stack attached to 'netdev'.
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*get_in4)(const struct netdev *netdev, struct in_addr *address,
+                   struct in_addr *netmask);
+
+    /* Assigns 'addr' as 'netdev''s IPv4 address and 'mask' as its netmask.  If
+     * 'addr' is INADDR_ANY, 'netdev''s IPv4 address is cleared.
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*set_in4)(struct netdev *netdev, struct in_addr addr,
+                   struct in_addr mask);
+
+    /* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address.
+     *
+     * The following error values have well-defined meanings:
+     *
+     *   - EADDRNOTAVAIL: 'netdev' has no assigned IPv6 address.
+     *
+     *   - EOPNOTSUPP: No IPv6 network stack attached to 'netdev'.
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*get_in6)(const struct netdev *netdev, struct in6_addr *in6);
+
+    /* Adds 'router' as a default IP gateway for the TCP/IP stack that
+     * corresponds to 'netdev'.
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*add_router)(struct netdev *netdev, struct in_addr router);
+
+    /* Looks up the next hop for 'host'.  If succesful, stores the next hop
+     * gateway's address (0 if 'host' is on a directly connected network) in
+     * '*next_hop' and a copy of the name of the device to reach 'host' in
+     * '*netdev_name', and returns 0.  The caller is responsible for freeing
+     * '*netdev_name' (by calling free()).
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*get_next_hop)(const struct in_addr *host, struct in_addr *next_hop,
+                        char **netdev_name);
+
+    /* Looks up the ARP table entry for 'ip' on 'netdev' and stores the
+     * corresponding MAC address in 'mac'.  A return value of ENXIO, in
+     * particular, indicates that there is no ARP table entry for 'ip' on
+     * 'netdev'.
+     *
+     * This function may be set to null if it would always return EOPNOTSUPP
+     * anyhow. */
+    int (*arp_lookup)(const struct netdev *netdev, uint32_t ip, uint8_t mac[6]);
+
+    /* Retrieves the current set of flags on 'netdev' into '*old_flags'.
+     * Then, turns off the flags that are set to 1 in 'off' and turns on the
+     * flags that are set to 1 in 'on'.  (No bit will be set to 1 in both 'off'
+     * and 'on'; that is, off & on == 0.)
+     *
+     * This function may be invoked from a signal handler.  Therefore, it
+     * should not do anything that is not signal-safe (such as logging). */
+    int (*update_flags)(struct netdev *netdev, enum netdev_flags off,
+                        enum netdev_flags on, enum netdev_flags *old_flags);
+
+    /* Arranges for 'cb' to be called whenever one of the attributes of
+     * 'netdev' changes and sets '*notifierp' to a newly created
+     * netdev_notifier that represents this arrangement.  The created notifier
+     * will have its 'netdev', 'cb', and 'aux' members set to the values of the
+     * corresponding parameters. */
+    int (*poll_add)(struct netdev *netdev,
+                    void (*cb)(struct netdev_notifier *notifier), void *aux,
+                    struct netdev_notifier **notifierp);
+
+    /* Cancels poll notification for 'notifier'. */
+    void (*poll_remove)(struct netdev_notifier *notifier);
+};
+
+extern const struct netdev_class netdev_linux_class;
+extern const struct netdev_class netdev_tap_class;
+extern const struct netdev_class netdev_gre_class;
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* netdev.h */
index bed480f..2c7b260 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <assert.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <arpa/inet.h>
 #include <inttypes.h>
-#include <linux/if_tun.h>
-#include <linux/types.h>
-#include <linux/ethtool.h>
-#include <linux/rtnetlink.h>
-#include <linux/sockios.h>
-#include <linux/version.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netpacket/packet.h>
-#include <net/ethernet.h>
-#include <net/if.h>
-#include <net/if_arp.h>
-#include <net/if_packet.h>
-#include <net/route.h>
 #include <netinet/in.h>
 #include <stdlib.h>
 #include <string.h>
 #include "coverage.h"
 #include "dynamic-string.h"
 #include "fatal-signal.h"
+#include "hash.h"
 #include "list.h"
-#include "netlink.h"
+#include "netdev-provider.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
-#include "socket-util.h"
+#include "shash.h"
 #include "svec.h"
 
-/* linux/if.h defines IFF_LOWER_UP, net/if.h doesn't.
- * net/if.h defines if_nameindex(), linux/if.h doesn't.
- * We can't include both headers, so define IFF_LOWER_UP ourselves. */
-#ifndef IFF_LOWER_UP
-#define IFF_LOWER_UP 0x10000
-#endif
-
-/* These were introduced in Linux 2.6.14, so they might be missing if we have
- * old headers. */
-#ifndef ADVERTISED_Pause
-#define ADVERTISED_Pause                (1 << 13)
-#endif
-#ifndef ADVERTISED_Asym_Pause
-#define ADVERTISED_Asym_Pause           (1 << 14)
-#endif
-
 #define THIS_MODULE VLM_netdev
 #include "vlog.h"
 
-struct netdev {
-    struct list node;
-    char *name;
-
-    /* File descriptors.  For ordinary network devices, the two fds below are
-     * the same; for tap devices, they differ. */
-    int netdev_fd;              /* Network device. */
-    int tap_fd;                 /* TAP character device, if any, otherwise the
-                                 * network device. */
-
-    /* Cached network device information. */
-    int ifindex;                /* -1 if not known. */
-    uint8_t etheraddr[ETH_ADDR_LEN];
-    struct in6_addr in6;
-    int speed;
-    int mtu;
-    int txqlen;
-    int hwaddr_family;
-
-    int save_flags;             /* Initial device flags. */
-    int changed_flags;          /* Flags that we changed. */
+static const struct netdev_class *base_netdev_classes[] = {
+    &netdev_linux_class,
+    &netdev_tap_class,
+    &netdev_gre_class,
 };
 
-/* Policy for RTNLGRP_LINK messages.
- *
- * There are *many* more fields in these messages, but currently we only care
- * about interface names. */
-static const struct nl_policy rtnlgrp_link_policy[] = {
-    [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
-    [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
-                     .min_len = sizeof(struct rtnl_link_stats) },
-};
+static struct shash netdev_classes = SHASH_INITIALIZER(&netdev_classes);
+
+/* All created network devices. */
+static struct shash netdev_dev_shash = SHASH_INITIALIZER(&netdev_dev_shash);
 
 /* All open network devices. */
 static struct list netdev_list = LIST_INITIALIZER(&netdev_list);
 
-/* An AF_INET socket (used for ioctl operations). */
-static int af_inet_sock = -1;
-
-/* NETLINK_ROUTE socket. */
-static struct nl_sock *rtnl_sock;
-
-/* Can we use RTM_GETLINK to get network device statistics?  (In pre-2.6.19
- * kernels, this was only available if wireless extensions were enabled.) */
-static bool use_netlink_stats;
-
 /* This is set pretty low because we probably won't learn anything from the
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
-static void init_netdev(void);
-static int do_open_netdev(const char *name, int ethertype, int tap_fd,
-                          struct netdev **netdev_);
+static void close_all_netdevs(void *aux OVS_UNUSED);
 static int restore_flags(struct netdev *netdev);
-static int get_flags(const char *netdev_name, int *flagsp);
-static int set_flags(const char *netdev_name, int flags);
-static int do_get_ifindex(const char *netdev_name);
-static int get_ifindex(const struct netdev *, int *ifindexp);
-static int get_etheraddr(const char *netdev_name, uint8_t ea[ETH_ADDR_LEN],
-                         int *hwaddr_familyp);
-static int set_etheraddr(const char *netdev_name, int hwaddr_family,
-                         const uint8_t[ETH_ADDR_LEN]);
-
-/* Obtains the IPv6 address for 'name' into 'in6'. */
+void update_device_args(struct netdev_dev *, const struct shash *args);
+
 static void
-get_ipv6_address(const char *name, struct in6_addr *in6)
+netdev_initialize(void)
 {
-    FILE *file;
-    char line[128];
+    static int status = -1;
 
-    file = fopen("/proc/net/if_inet6", "r");
-    if (file == NULL) {
-        /* This most likely indicates that the host doesn't have IPv6 support,
-         * so it's not really a failure condition.*/
-        *in6 = in6addr_any;
-        return;
+    if (status < 0) {
+        int i;
+
+        fatal_signal_add_hook(close_all_netdevs, NULL, NULL, true);
+
+        status = 0;
+        for (i = 0; i < ARRAY_SIZE(base_netdev_classes); i++) {
+            netdev_register_provider(base_netdev_classes[i]);
+        }
     }
+}
 
-    while (fgets(line, sizeof line, file)) {
-        uint8_t *s6 = in6->s6_addr;
-        char ifname[16 + 1];
-
-#define X8 "%2"SCNx8
-        if (sscanf(line, " "X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8
-                   "%*x %*x %*x %*x %16s\n",
-                   &s6[0], &s6[1], &s6[2], &s6[3],
-                   &s6[4], &s6[5], &s6[6], &s6[7],
-                   &s6[8], &s6[9], &s6[10], &s6[11],
-                   &s6[12], &s6[13], &s6[14], &s6[15],
-                   ifname) == 17
-            && !strcmp(name, ifname))
-        {
-            fclose(file);
-            return;
+/* Performs periodic work needed by all the various kinds of netdevs.
+ *
+ * If your program opens any netdevs, it must call this function within its
+ * main poll loop. */
+void
+netdev_run(void)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        if (netdev_class->run) {
+            netdev_class->run();
         }
     }
-    *in6 = in6addr_any;
+}
 
-    fclose(file);
+/* Arranges for poll_block() to wake up when netdev_run() needs to be called.
+ *
+ * If your program opens any netdevs, it must call this function within its
+ * main poll loop. */
+void
+netdev_wait(void)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        if (netdev_class->wait) {
+            netdev_class->wait();
+        }
+    }
 }
 
-static int
-do_ethtool(struct netdev *netdev, struct ethtool_cmd *ecmd,
-           int cmd, const char *cmd_name)
+/* Initializes and registers a new netdev provider.  After successful
+ * registration, new netdevs of that type can be opened using netdev_open(). */
+int
+netdev_register_provider(const struct netdev_class *new_class)
 {
-    struct ifreq ifr;
+    struct netdev_class *new_provider;
 
-    memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
-    ifr.ifr_data = (caddr_t) ecmd;
+    if (shash_find(&netdev_classes, new_class->type)) {
+        VLOG_WARN("attempted to register duplicate netdev provider: %s",
+                   new_class->type);
+        return EEXIST;
+    }
 
-    ecmd->cmd = cmd;
-    COVERAGE_INC(netdev_ethtool);
-    if (ioctl(netdev->netdev_fd, SIOCETHTOOL, &ifr) == 0) {
-        return 0;
-    } else {
-        if (errno != EOPNOTSUPP) {
-            VLOG_WARN_RL(&rl, "ethtool command %s on network device %s "
-                         "failed: %s", cmd_name, netdev->name,
-                         strerror(errno));
-        } else {
-            /* The device doesn't support this operation.  That's pretty
-             * common, so there's no point in logging anything. */
+    if (new_class->init) {
+        int error = new_class->init();
+        if (error) {
+            VLOG_ERR("failed to initialize %s network device class: %s",
+                     new_class->type, strerror(error));
+            return error;
         }
-        return errno;
     }
+
+    new_provider = xmalloc(sizeof *new_provider);
+    memcpy(new_provider, new_class, sizeof *new_provider);
+
+    shash_add(&netdev_classes, new_class->type, new_provider);
+
+    return 0;
 }
 
-static int
-do_get_features(struct netdev *netdev,
-                uint32_t *current, uint32_t *advertised,
-                uint32_t *supported, uint32_t *peer)
+/* Unregisters a netdev provider.  'type' must have been previously
+ * registered and not currently be in use by any netdevs.  After unregistration
+ * new netdevs of that type cannot be opened using netdev_open(). */
+int
+netdev_unregister_provider(const char *type)
 {
-    struct ethtool_cmd ecmd;
-    int error;
-
-    *current = 0;
-    *supported = 0;
-    *advertised = 0;
-    *peer = 0;
+    struct shash_node *del_node, *netdev_dev_node;
 
-    memset(&ecmd, 0, sizeof ecmd);
-    error = do_ethtool(netdev, &ecmd, ETHTOOL_GSET, "ETHTOOL_GSET");
-    if (error) {
-        return error;
+    del_node = shash_find(&netdev_classes, type);
+    if (!del_node) {
+        VLOG_WARN("attempted to unregister a netdev provider that is not "
+                  "registered: %s", type);
+        return EAFNOSUPPORT;
     }
 
-    if (ecmd.supported & SUPPORTED_10baseT_Half) {
-        *supported |= OFPPF_10MB_HD;
-    }
-    if (ecmd.supported & SUPPORTED_10baseT_Full) {
-        *supported |= OFPPF_10MB_FD;
-    }
-    if (ecmd.supported & SUPPORTED_100baseT_Half)  {
-        *supported |= OFPPF_100MB_HD;
-    }
-    if (ecmd.supported & SUPPORTED_100baseT_Full) {
-        *supported |= OFPPF_100MB_FD;
-    }
-    if (ecmd.supported & SUPPORTED_1000baseT_Half) {
-        *supported |= OFPPF_1GB_HD;
-    }
-    if (ecmd.supported & SUPPORTED_1000baseT_Full) {
-        *supported |= OFPPF_1GB_FD;
-    }
-    if (ecmd.supported & SUPPORTED_10000baseT_Full) {
-        *supported |= OFPPF_10GB_FD;
-    }
-    if (ecmd.supported & SUPPORTED_TP) {
-        *supported |= OFPPF_COPPER;
-    }
-    if (ecmd.supported & SUPPORTED_FIBRE) {
-        *supported |= OFPPF_FIBER;
-    }
-    if (ecmd.supported & SUPPORTED_Autoneg) {
-        *supported |= OFPPF_AUTONEG;
-    }
-    if (ecmd.supported & SUPPORTED_Pause) {
-        *supported |= OFPPF_PAUSE;
-    }
-    if (ecmd.supported & SUPPORTED_Asym_Pause) {
-        *supported |= OFPPF_PAUSE_ASYM;
+    SHASH_FOR_EACH(netdev_dev_node, &netdev_dev_shash) {
+        struct netdev_dev *netdev_dev = netdev_dev_node->data;
+        if (!strcmp(netdev_dev->netdev_class->type, type)) {
+            VLOG_WARN("attempted to unregister in use netdev provider: %s",
+                      type);
+            return EBUSY;
+        }
     }
 
-    /* Set the advertised features */
-    if (ecmd.advertising & ADVERTISED_10baseT_Half) {
-        *advertised |= OFPPF_10MB_HD;
-    }
-    if (ecmd.advertising & ADVERTISED_10baseT_Full) {
-        *advertised |= OFPPF_10MB_FD;
-    }
-    if (ecmd.advertising & ADVERTISED_100baseT_Half) {
-        *advertised |= OFPPF_100MB_HD;
-    }
-    if (ecmd.advertising & ADVERTISED_100baseT_Full) {
-        *advertised |= OFPPF_100MB_FD;
-    }
-    if (ecmd.advertising & ADVERTISED_1000baseT_Half) {
-        *advertised |= OFPPF_1GB_HD;
-    }
-    if (ecmd.advertising & ADVERTISED_1000baseT_Full) {
-        *advertised |= OFPPF_1GB_FD;
-    }
-    if (ecmd.advertising & ADVERTISED_10000baseT_Full) {
-        *advertised |= OFPPF_10GB_FD;
-    }
-    if (ecmd.advertising & ADVERTISED_TP) {
-        *advertised |= OFPPF_COPPER;
-    }
-    if (ecmd.advertising & ADVERTISED_FIBRE) {
-        *advertised |= OFPPF_FIBER;
-    }
-    if (ecmd.advertising & ADVERTISED_Autoneg) {
-        *advertised |= OFPPF_AUTONEG;
-    }
-    if (ecmd.advertising & ADVERTISED_Pause) {
-        *advertised |= OFPPF_PAUSE;
+    shash_delete(&netdev_classes, del_node);
+    free(del_node->data);
+
+    return 0;
+}
+
+/* Clears 'types' and enumerates the types of all currently registered netdev
+ * providers into it.  The caller must first initialize the svec. */
+void
+netdev_enumerate_types(struct svec *types)
+{
+    struct shash_node *node;
+
+    netdev_initialize();
+    svec_clear(types);
+
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        svec_add(types, netdev_class->type);
     }
-    if (ecmd.advertising & ADVERTISED_Asym_Pause) {
-        *advertised |= OFPPF_PAUSE_ASYM;
+}
+
+/* Compares 'args' to those used to those used by 'dev'.  Returns true
+ * if the arguments are the same, false otherwise.  Does not update the
+ * values stored in 'dev'. */
+static bool
+compare_device_args(const struct netdev_dev *dev, const struct shash *args)
+{
+    const struct shash_node **new_args;
+    bool result = true;
+    int i;
+
+    if (shash_count(args) != dev->n_args) {
+        return false;
     }
 
-    /* Set the current features */
-    if (ecmd.speed == SPEED_10) {
-        *current = (ecmd.duplex) ? OFPPF_10MB_FD : OFPPF_10MB_HD;
+    new_args = shash_sort(args);
+    for (i = 0; i < dev->n_args; i++) {
+        if (strcmp(dev->args[i].key, new_args[i]->name) || 
+            strcmp(dev->args[i].value, new_args[i]->data)) {
+            result = false;
+            goto finish;
+        }
     }
-    else if (ecmd.speed == SPEED_100) {
-        *current = (ecmd.duplex) ? OFPPF_100MB_FD : OFPPF_100MB_HD;
+
+finish:
+    free(new_args);
+    return result;
+}
+
+static int
+compare_args(const void *a_, const void *b_)
+{
+    const struct arg *a = a_;
+    const struct arg *b = b_;
+    return strcmp(a->key, b->key);
+}
+
+void
+update_device_args(struct netdev_dev *dev, const struct shash *args)
+{
+    struct shash_node *node;
+    int i;
+
+    if (dev->n_args) {
+        for (i = 0; i < dev->n_args; i++) {
+            free(dev->args[i].key);
+            free(dev->args[i].value);
+        }
+
+        free(dev->args);
+        dev->n_args = 0;
     }
-    else if (ecmd.speed == SPEED_1000) {
-        *current = (ecmd.duplex) ? OFPPF_1GB_FD : OFPPF_1GB_HD;
+
+    if (!args || shash_is_empty(args)) {
+        return;
     }
-    else if (ecmd.speed == SPEED_10000) {
-        *current = OFPPF_10GB_FD;
+
+    dev->n_args = shash_count(args);
+    dev->args = xmalloc(dev->n_args * sizeof *dev->args);
+
+    i = 0;
+    SHASH_FOR_EACH(node, args) {
+        dev->args[i].key = xstrdup(node->name);
+        dev->args[i].value = xstrdup(node->data);
+        i++;
     }
 
-    if (ecmd.port == PORT_TP) {
-        *current |= OFPPF_COPPER;
+    qsort(dev->args, dev->n_args, sizeof *dev->args, compare_args);
+}
+
+static int
+create_device(struct netdev_options *options, struct netdev_dev **netdev_devp)
+{
+    struct netdev_class *netdev_class;
+
+    if (!options->may_create) {
+        VLOG_WARN("attempted to create a device that may not be created: %s",
+                  options->name);
+        return ENODEV;
     }
-    else if (ecmd.port == PORT_FIBRE) {
-        *current |= OFPPF_FIBER;
+
+    if (!options->type || strlen(options->type) == 0) {
+        /* Default to system. */
+        options->type = "system";
     }
 
-    if (ecmd.autoneg) {
-        *current |= OFPPF_AUTONEG;
+    netdev_class = shash_find_data(&netdev_classes, options->type);
+    if (!netdev_class) {
+        VLOG_WARN("could not create netdev %s of unknown type %s",
+                  options->name, options->type);
+        return EAFNOSUPPORT;
     }
-    return 0;
+
+    return netdev_class->create(options->name, options->type, options->args,
+                                netdev_devp);
 }
 
 /* Opens the network device named 'name' (e.g. "eth0") and returns zero if
  * successful, otherwise a positive errno value.  On success, sets '*netdevp'
  * to the new network device, otherwise to null.
  *
+ * If this is the first time the device has been opened, then create is called
+ * before opening.  The device is  created using the given type and arguments.
+ *
  * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order to
  * capture frames of that type received on the device.  It may also be one of
  * the 'enum netdev_pseudo_ethertype' values to receive frames in one of those
- * categories. */
-int
-netdev_open(const char *name, int ethertype, struct netdev **netdevp) 
-{
-    if (!strncmp(name, "tap:", 4)) {
-        return netdev_open_tap(name + 4, netdevp);
-    } else {
-        return do_open_netdev(name, ethertype, -1, netdevp); 
-    }
-}
+ * categories.
+ *
+ * If the 'may_create' flag is set then this is allowed to be the first time
+ * the device is opened (i.e. the refcount will be 1 after this call).  It
+ * may be set to false if the device should have already been created.
+ *
+ * If the 'may_open' flag is set then the call will succeed even if another
+ * caller has already opened it.  It may be to false if the device should not
+ * currently be open. */
 
-/* Opens a TAP virtual network device.  If 'name' is a nonnull, non-empty
- * string, attempts to assign that name to the TAP device (failing if the name
- * is already in use); otherwise, a name is automatically assigned.  Returns
- * zero if successful, otherwise a positive errno value.  On success, sets
- * '*netdevp' to the new network device, otherwise to null.  */
 int
-netdev_open_tap(const char *name, struct netdev **netdevp)
+netdev_open(struct netdev_options *options, struct netdev **netdevp)
 {
-    static const char tap_dev[] = "/dev/net/tun";
-    struct ifreq ifr;
+    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
+    struct netdev_dev *netdev_dev;
     int error;
-    int tap_fd;
 
-    tap_fd = open(tap_dev, O_RDWR);
-    if (tap_fd < 0) {
-        ovs_error(errno, "opening \"%s\" failed", tap_dev);
-        return errno;
-    }
+    *netdevp = NULL;
+    netdev_initialize();
 
-    memset(&ifr, 0, sizeof ifr);
-    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
-    if (name) {
-        strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-    }
-    if (ioctl(tap_fd, TUNSETIFF, &ifr) < 0) {
-        int error = errno;
-        ovs_error(error, "ioctl(TUNSETIFF) on \"%s\" failed", tap_dev);
-        close(tap_fd);
-        return error;
+    if (!options->args) {
+        options->args = &empty_args;
     }
 
-    error = set_nonblocking(tap_fd);
-    if (error) {
-        ovs_error(error, "set_nonblocking on \"%s\" failed", tap_dev);
-        close(tap_fd);
-        return error;
-    }
+    netdev_dev = shash_find_data(&netdev_dev_shash, options->name);
 
-    error = do_open_netdev(ifr.ifr_name, NETDEV_ETH_TYPE_NONE, tap_fd,
-                           netdevp);
-    if (error) {
-        close(tap_fd);
-    }
-    return error;
-}
-
-static int
-do_open_netdev(const char *name, int ethertype, int tap_fd,
-               struct netdev **netdev_)
-{
-    int netdev_fd;
-    struct sockaddr_ll sll;
-    struct ifreq ifr;
-    int ifindex = -1;
-    uint8_t etheraddr[ETH_ADDR_LEN];
-    struct in6_addr in6;
-    int mtu;
-    int txqlen;
-    int hwaddr_family;
-    int error;
-    struct netdev *netdev;
-
-    init_netdev();
-    *netdev_ = NULL;
-    COVERAGE_INC(netdev_open);
-
-    /* Create raw socket. */
-    netdev_fd = socket(PF_PACKET, SOCK_RAW,
-                       htons(ethertype == NETDEV_ETH_TYPE_NONE ? 0
-                             : ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL
-                             : ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2
-                             : ethertype));
-    if (netdev_fd < 0) {
-        return errno;
-    }
-
-    if (ethertype != NETDEV_ETH_TYPE_NONE) {
-        /* Set non-blocking mode. */
-        error = set_nonblocking(netdev_fd);
+    if (!netdev_dev) {
+        error = create_device(options, &netdev_dev);
         if (error) {
-            goto error_already_set;
+            return error;
         }
+        update_device_args(netdev_dev, options->args);
 
-        /* Get ethernet device index. */
-        ifindex = do_get_ifindex(name);
-        if (ifindex < 0) {
-            return -ifindex;
-        }
+    } else if (options->may_open) {
+        if (!shash_is_empty(options->args) &&
+            !compare_device_args(netdev_dev, options->args)) {
 
-        /* Bind to specific ethernet device. */
-        memset(&sll, 0, sizeof sll);
-        sll.sll_family = AF_PACKET;
-        sll.sll_ifindex = ifindex;
-        if (bind(netdev_fd, (struct sockaddr *) &sll, sizeof sll) < 0) {
-            VLOG_ERR("bind to %s failed: %s", name, strerror(errno));
-            goto error;
+            VLOG_WARN("%s: attempted to open already created netdev with "
+                      "different arguments", options->name);
+            return EINVAL;
         }
+    } else {
+        VLOG_WARN("%s: attempted to create a netdev device with bound name",
+                  options->name);
+        return EEXIST;
+    }
 
-        /* Between the socket() and bind() calls above, the socket receives all
-         * packets of the requested type on all system interfaces.  We do not
-         * want to receive that data, but there is no way to avoid it.  So we
-         * must now drain out the receive queue. */
-        error = drain_rcvbuf(netdev_fd);
-        if (error) {
-            goto error_already_set;
+    error = netdev_dev->netdev_class->open(netdev_dev, options->ethertype, 
+                netdevp);
+
+    if (!error) {
+        netdev_dev->ref_cnt++;
+    } else {
+        if (!netdev_dev->ref_cnt) {
+            netdev_dev_uninit(netdev_dev, true);
         }
     }
 
-    /* Get MAC address. */
-    error = get_etheraddr(name, etheraddr, &hwaddr_family);
-    if (error) {
-        goto error_already_set;
-    }
+    return error;
+}
 
-    /* Get MTU. */
-    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-    if (ioctl(netdev_fd, SIOCGIFMTU, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCGIFMTU) on %s device failed: %s",
-                 name, strerror(errno));
-        goto error;
-    }
-    mtu = ifr.ifr_mtu;
+int
+netdev_open_default(const char *name, struct netdev **netdevp)
+{
+    struct netdev_options options;
+
+    memset(&options, 0, sizeof options);
+
+    options.name = name;
+    options.ethertype = NETDEV_ETH_TYPE_NONE;
+    options.may_create = true;
+    options.may_open = true;
+
+    return netdev_open(&options, netdevp);
+}
+
+/* Reconfigures the device 'netdev' with 'args'.  'args' may be empty
+ * or NULL if none are needed. */
+int
+netdev_reconfigure(struct netdev *netdev, const struct shash *args)
+{
+    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
+    struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
 
-    /* Get TX queue length. */
-    if (ioctl(netdev_fd, SIOCGIFTXQLEN, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCGIFTXQLEN) on %s device failed: %s",
-                 name, strerror(errno));
-        goto error;
+    if (!args) {
+        args = &empty_args;
     }
-    txqlen = ifr.ifr_qlen;
-
-    get_ipv6_address(name, &in6);
-
-    /* Allocate network device. */
-    netdev = xmalloc(sizeof *netdev);
-    netdev->name = xstrdup(name);
-    netdev->ifindex = ifindex;
-    netdev->txqlen = txqlen;
-    netdev->hwaddr_family = hwaddr_family;
-    netdev->netdev_fd = netdev_fd;
-    netdev->tap_fd = tap_fd < 0 ? netdev_fd : tap_fd;
-    memcpy(netdev->etheraddr, etheraddr, sizeof etheraddr);
-    netdev->mtu = mtu;
-    netdev->in6 = in6;
-
-    /* Save flags to restore at close or exit. */
-    error = get_flags(netdev->name, &netdev->save_flags);
-    if (error) {
-        goto error_already_set;
+
+    if (netdev_dev->netdev_class->reconfigure) {
+        if (!compare_device_args(netdev_dev, args)) {
+            update_device_args(netdev_dev, args);
+            return netdev_dev->netdev_class->reconfigure(netdev_dev, args);
+        }
+    } else if (!shash_is_empty(args)) {
+        VLOG_WARN("%s: arguments provided to device that does not have a "
+                  "reconfigure function", netdev_get_name(netdev));
     }
-    netdev->changed_flags = 0;
-    fatal_signal_block();
-    list_push_back(&netdev_list, &netdev->node);
-    fatal_signal_unblock();
 
-    /* Success! */
-    *netdev_ = netdev;
     return 0;
-
-error:
-    error = errno;
-error_already_set:
-    close(netdev_fd);
-    if (tap_fd >= 0) {
-        close(tap_fd);
-    }
-    return error;
 }
 
 /* Closes and destroys 'netdev'. */
@@ -514,36 +400,65 @@ void
 netdev_close(struct netdev *netdev)
 {
     if (netdev) {
-        /* Bring down interface and drop promiscuous mode, if we brought up
-         * the interface or enabled promiscuous mode. */
-        int error;
-        fatal_signal_block();
-        error = restore_flags(netdev);
-        list_remove(&netdev->node);
-        fatal_signal_unblock();
-        if (error) {
-            VLOG_WARN("failed to restore network device flags on %s: %s",
-                      netdev->name, strerror(error));
+        struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
+
+        assert(netdev_dev->ref_cnt);
+        netdev_dev->ref_cnt--;
+        netdev_uninit(netdev, true);
+
+        /* If the reference count for the netdev device is zero, destroy it. */
+        if (!netdev_dev->ref_cnt) {
+            netdev_dev_uninit(netdev_dev, true);
         }
+    }
+}
 
-        /* Free. */
-        free(netdev->name);
-        close(netdev->netdev_fd);
-        if (netdev->netdev_fd != netdev->tap_fd) {
-            close(netdev->tap_fd);
+/* Returns true if a network device named 'name' exists and may be opened,
+ * otherwise false. */
+bool
+netdev_exists(const char *name)
+{
+    struct netdev *netdev;
+    int error;
+
+    error = netdev_open_default(name, &netdev);
+    if (!error) {
+        netdev_close(netdev);
+        return true;
+    } else {
+        if (error != ENODEV) {
+            VLOG_WARN("failed to open network device %s: %s",
+                      name, strerror(error));
         }
-        free(netdev);
+        return false;
     }
 }
 
-/* Pads 'buffer' out with zero-bytes to the minimum valid length of an
- * Ethernet packet, if necessary.  */
-static void
-pad_to_minimum_length(struct ofpbuf *buffer)
+/*  Clears 'svec' and enumerates the names of all known network devices. */
+int
+netdev_enumerate(struct svec *svec)
 {
-    if (buffer->size < ETH_TOTAL_MIN) {
-        ofpbuf_put_zeros(buffer, ETH_TOTAL_MIN - buffer->size);
+    struct shash_node *node;
+    int error = 0;
+
+    netdev_initialize();
+    svec_clear(svec);
+
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        if (netdev_class->enumerate) {
+            int retval = netdev_class->enumerate(svec);
+            if (retval) {
+                VLOG_WARN("failed to enumerate %s network devices: %s",
+                          netdev_class->type, strerror(retval));
+                if (!error) {
+                    error = retval;
+                }
+            }
+        }
     }
+
+    return error;
 }
 
 /* Attempts to receive a packet from 'netdev' into 'buffer', which the caller
@@ -561,31 +476,22 @@ pad_to_minimum_length(struct ofpbuf *buffer)
 int
 netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
 {
-    ssize_t n_bytes;
+    int retval;
 
     assert(buffer->size == 0);
     assert(ofpbuf_tailroom(buffer) >= ETH_TOTAL_MIN);
-    do {
-        n_bytes = read(netdev->tap_fd,
-                       ofpbuf_tail(buffer), ofpbuf_tailroom(buffer));
-    } while (n_bytes < 0 && errno == EINTR);
-    if (n_bytes < 0) {
-        if (errno != EAGAIN) {
-            VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s",
-                         netdev->name, strerror(errno));
-        }
-        return errno;
-    } else {
+
+    retval = netdev_get_dev(netdev)->netdev_class->recv(netdev, buffer->data,
+             ofpbuf_tailroom(buffer));
+    if (retval >= 0) {
         COVERAGE_INC(netdev_received);
-        buffer->size += n_bytes;
-
-        /* When the kernel internally sends out an Ethernet frame on an
-         * interface, it gives us a copy *before* padding the frame to the
-         * minimum length.  Thus, when it sends out something like an ARP
-         * request, we see a too-short frame.  So pad it out to the minimum
-         * length. */
-        pad_to_minimum_length(buffer);
+        buffer->size += retval;
+        if (buffer->size < ETH_TOTAL_MIN) {
+            ofpbuf_put_zeros(buffer, ETH_TOTAL_MIN - buffer->size);
+        }
         return 0;
+    } else {
+        return -retval;
     }
 }
 
@@ -594,19 +500,14 @@ netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
 void
 netdev_recv_wait(struct netdev *netdev)
 {
-    poll_fd_wait(netdev->tap_fd, POLLIN);
+    netdev_get_dev(netdev)->netdev_class->recv_wait(netdev);
 }
 
 /* Discards all packets waiting to be received from 'netdev'. */
 int
 netdev_drain(struct netdev *netdev)
 {
-    if (netdev->tap_fd != netdev->netdev_fd) {
-        drain_fd(netdev->tap_fd, netdev->txqlen);
-        return 0;
-    } else {
-        return drain_rcvbuf(netdev->netdev_fd);
-    }
+    return netdev_get_dev(netdev)->netdev_class->drain(netdev);
 }
 
 /* Sends 'buffer' on 'netdev'.  Returns 0 if successful, otherwise a positive
@@ -621,32 +522,12 @@ netdev_drain(struct netdev *netdev)
 int
 netdev_send(struct netdev *netdev, const struct ofpbuf *buffer)
 {
-    ssize_t n_bytes;
-
-    do {
-        n_bytes = write(netdev->tap_fd, buffer->data, buffer->size);
-    } while (n_bytes < 0 && errno == EINTR);
-
-    if (n_bytes < 0) {
-        /* The Linux AF_PACKET implementation never blocks waiting for room
-         * for packets, instead returning ENOBUFS.  Translate this into EAGAIN
-         * for the caller. */
-        if (errno == ENOBUFS) {
-            return EAGAIN;
-        } else if (errno != EAGAIN) {
-            VLOG_WARN_RL(&rl, "error sending Ethernet packet on %s: %s",
-                         netdev->name, strerror(errno));
-        }
-        return errno;
-    } else if (n_bytes != buffer->size) {
-        VLOG_WARN_RL(&rl,
-                     "send partial Ethernet packet (%d bytes of %zu) on %s",
-                     (int) n_bytes, buffer->size, netdev->name);
-        return EMSGSIZE;
-    } else {
+    int error = netdev_get_dev(netdev)->netdev_class->send(netdev, 
+            buffer->data, buffer->size);
+    if (!error) {
         COVERAGE_INC(netdev_sent);
-        return 0;
     }
+    return error;
 }
 
 /* Registers with the poll loop to wake up from the next call to poll_block()
@@ -659,12 +540,7 @@ netdev_send(struct netdev *netdev, const struct ofpbuf *buffer)
 void
 netdev_send_wait(struct netdev *netdev)
 {
-    if (netdev->tap_fd == netdev->netdev_fd) {
-        poll_fd_wait(netdev->tap_fd, POLLOUT);
-    } else {
-        /* TAP device always accepts packets.*/
-        poll_immediate_wake();
-    }
+    return netdev_get_dev(netdev)->netdev_class->send_wait(netdev);
 }
 
 /* Attempts to set 'netdev''s MAC address to 'mac'.  Returns 0 if successful,
@@ -672,26 +548,16 @@ netdev_send_wait(struct netdev *netdev)
 int
 netdev_set_etheraddr(struct netdev *netdev, const uint8_t mac[ETH_ADDR_LEN])
 {
-    int error = set_etheraddr(netdev->name, netdev->hwaddr_family, mac);
-    if (!error) {
-        memcpy(netdev->etheraddr, mac, ETH_ADDR_LEN);
-    }
-    return error;
+    return netdev_get_dev(netdev)->netdev_class->set_etheraddr(netdev, mac);
 }
 
+/* Retrieves 'netdev''s MAC address.  If successful, returns 0 and copies the
+ * the MAC address into 'mac'.  On failure, returns a positive errno value and
+ * clears 'mac' to all-zeros. */
 int
-netdev_nodev_set_etheraddr(const char *name, const uint8_t mac[ETH_ADDR_LEN])
-{
-    init_netdev();
-    return set_etheraddr(name, ARPHRD_ETHER, mac);
-}
-
-/* Returns a pointer to 'netdev''s MAC address.  The caller must not modify or
- * free the returned buffer. */
-const uint8_t *
-netdev_get_etheraddr(const struct netdev *netdev)
+netdev_get_etheraddr(const struct netdev *netdev, uint8_t mac[ETH_ADDR_LEN])
 {
-    return netdev->etheraddr;
+    return netdev_get_dev(netdev)->netdev_class->get_etheraddr(netdev, mac);
 }
 
 /* Returns the name of the network device that 'netdev' represents,
@@ -699,16 +565,41 @@ netdev_get_etheraddr(const struct netdev *netdev)
 const char *
 netdev_get_name(const struct netdev *netdev)
 {
-    return netdev->name;
+    return netdev_get_dev(netdev)->name;
+}
+
+/* Retrieves the MTU of 'netdev'.  The MTU is the maximum size of transmitted
+ * (and received) packets, in bytes, not including the hardware header; thus,
+ * this is typically 1500 bytes for Ethernet devices.
+ *
+ * If successful, returns 0 and stores the MTU size in '*mtup'.  On failure,
+ * returns a positive errno value and stores ETH_PAYLOAD_MAX (1500) in
+ * '*mtup'. */
+int
+netdev_get_mtu(const struct netdev *netdev, int *mtup)
+{
+    int error = netdev_get_dev(netdev)->netdev_class->get_mtu(netdev, mtup);
+    if (error) {
+        VLOG_WARN_RL(&rl, "failed to retrieve MTU for network device %s: %s",
+                     netdev_get_name(netdev), strerror(error));
+        *mtup = ETH_PAYLOAD_MAX;
+    }
+    return error;
 }
 
-/* Returns the maximum size of transmitted (and received) packets on 'netdev',
- * in bytes, not including the hardware header; thus, this is typically 1500
- * bytes for Ethernet devices. */
+/* Returns the ifindex of 'netdev', if successful, as a positive number.  On
+ * failure, returns a negative errno value.
+ *
+ * The desired semantics of the ifindex value are a combination of those
+ * specified by POSIX for if_nametoindex() and by SNMP for ifIndex.  An ifindex
+ * value should be unique within a host and remain stable at least until
+ * reboot.  SNMP says an ifindex "ranges between 1 and the value of ifNumber"
+ * but many systems do not follow this rule anyhow.
+ */
 int
-netdev_get_mtu(const struct netdev *netdev) 
+netdev_get_ifindex(const struct netdev *netdev)
 {
-    return netdev->mtu;
+    return netdev_get_dev(netdev)->netdev_class->get_ifindex(netdev);
 }
 
 /* Stores the features supported by 'netdev' into each of '*current',
@@ -722,137 +613,98 @@ netdev_get_features(struct netdev *netdev,
                     uint32_t *supported, uint32_t *peer)
 {
     uint32_t dummy[4];
-    return do_get_features(netdev,
-                           current ? current : &dummy[0],
-                           advertised ? advertised : &dummy[1],
-                           supported ? supported : &dummy[2],
-                           peer ? peer : &dummy[3]);
-}
-
-int
-netdev_set_advertisements(struct netdev *netdev, uint32_t advertise)
-{
-    struct ethtool_cmd ecmd;
     int error;
 
-    memset(&ecmd, 0, sizeof ecmd);
-    error = do_ethtool(netdev, &ecmd, ETHTOOL_GSET, "ETHTOOL_GSET");
-    if (error) {
-        return error;
-    }
-
-    ecmd.advertising = 0;
-    if (advertise & OFPPF_10MB_HD) {
-        ecmd.advertising |= ADVERTISED_10baseT_Half;
-    }
-    if (advertise & OFPPF_10MB_FD) {
-        ecmd.advertising |= ADVERTISED_10baseT_Full;
+    if (!current) {
+        current = &dummy[0];
     }
-    if (advertise & OFPPF_100MB_HD) {
-        ecmd.advertising |= ADVERTISED_100baseT_Half;
+    if (!advertised) {
+        advertised = &dummy[1];
     }
-    if (advertise & OFPPF_100MB_FD) {
-        ecmd.advertising |= ADVERTISED_100baseT_Full;
+    if (!supported) {
+        supported = &dummy[2];
     }
-    if (advertise & OFPPF_1GB_HD) {
-        ecmd.advertising |= ADVERTISED_1000baseT_Half;
+    if (!peer) {
+        peer = &dummy[3];
     }
-    if (advertise & OFPPF_1GB_FD) {
-        ecmd.advertising |= ADVERTISED_1000baseT_Full;
-    }
-    if (advertise & OFPPF_10GB_FD) {
-        ecmd.advertising |= ADVERTISED_10000baseT_Full;
-    }
-    if (advertise & OFPPF_COPPER) {
-        ecmd.advertising |= ADVERTISED_TP;
-    }
-    if (advertise & OFPPF_FIBER) {
-        ecmd.advertising |= ADVERTISED_FIBRE;
-    }
-    if (advertise & OFPPF_AUTONEG) {
-        ecmd.advertising |= ADVERTISED_Autoneg;
-    }
-    if (advertise & OFPPF_PAUSE) {
-        ecmd.advertising |= ADVERTISED_Pause;
-    }
-    if (advertise & OFPPF_PAUSE_ASYM) {
-        ecmd.advertising |= ADVERTISED_Asym_Pause;
+
+    error = netdev_get_dev(netdev)->netdev_class->get_features(netdev, current,
+            advertised, supported, peer);
+    if (error) {
+        *current = *advertised = *supported = *peer = 0;
     }
-    return do_ethtool(netdev, &ecmd, ETHTOOL_SSET, "ETHTOOL_SSET");
+    return error;
 }
 
-/* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address
- * and '*mask' to the netmask (if they are non-null) and returns true.
- * Otherwise, returns false. */
-bool
-netdev_nodev_get_in4(const char *netdev_name, struct in_addr *in4,
-                     struct in_addr *mask)
+/* Returns the maximum speed of a network connection that has the "enum
+ * ofp_port_features" bits in 'features', in bits per second.  If no bits that
+ * indicate a speed are set in 'features', assumes 100Mbps. */
+uint64_t
+netdev_features_to_bps(uint32_t features)
 {
-    struct ifreq ifr;
-    struct in_addr ip = { INADDR_ANY };
-
-    init_netdev();
-
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    ifr.ifr_addr.sa_family = AF_INET;
-    COVERAGE_INC(netdev_get_in4);
-    if (ioctl(af_inet_sock, SIOCGIFADDR, &ifr) == 0) {
-        struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
-        ip = sin->sin_addr;
-    } else {
-        VLOG_DBG_RL(&rl, "%s: ioctl(SIOCGIFADDR) failed: %s",
-                    netdev_name, strerror(errno));
-    }
-    if (in4) {
-        *in4 = ip;
-    }
-
-    if (mask) {
-        if (ioctl(af_inet_sock, SIOCGIFNETMASK, &ifr) == 0) {
-            struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
-            *mask = sin->sin_addr;
-        } else {
-            VLOG_DBG_RL(&rl, "%s: ioctl(SIOCGIFNETMASK) failed: %s",
-                        netdev_name, strerror(errno));
-        }
-    }
-
-    return ip.s_addr != INADDR_ANY;
+    enum {
+        F_10000MB = OFPPF_10GB_FD,
+        F_1000MB = OFPPF_1GB_HD | OFPPF_1GB_FD,
+        F_100MB = OFPPF_100MB_HD | OFPPF_100MB_FD,
+        F_10MB = OFPPF_10MB_HD | OFPPF_10MB_FD
+    };
+
+    return (  features & F_10000MB  ? UINT64_C(10000000000)
+            : features & F_1000MB   ? UINT64_C(1000000000)
+            : features & F_100MB    ? UINT64_C(100000000)
+            : features & F_10MB     ? UINT64_C(10000000)
+                                    : UINT64_C(100000000));
 }
 
+/* Returns true if any of the "enum ofp_port_features" bits that indicate a
+ * full-duplex link are set in 'features', otherwise false. */
 bool
-netdev_get_in4(const struct netdev *netdev, struct in_addr *in4, struct
-               in_addr *mask)
+netdev_features_is_full_duplex(uint32_t features)
 {
-    return netdev_nodev_get_in4(netdev->name, in4, mask);
+    return (features & (OFPPF_10MB_FD | OFPPF_100MB_FD | OFPPF_1GB_FD
+                        | OFPPF_10GB_FD)) != 0;
 }
 
-static void
-make_in4_sockaddr(struct sockaddr *sa, struct in_addr addr)
+/* Set the features advertised by 'netdev' to 'advertise'.  Returns 0 if
+ * successful, otherwise a positive errno value. */
+int
+netdev_set_advertisements(struct netdev *netdev, uint32_t advertise)
 {
-    struct sockaddr_in sin;
-    memset(&sin, 0, sizeof sin);
-    sin.sin_family = AF_INET;
-    sin.sin_addr = addr;
-    sin.sin_port = 0;
-
-    memset(sa, 0, sizeof *sa);
-    memcpy(sa, &sin, sizeof sin);
+    return (netdev_get_dev(netdev)->netdev_class->set_advertisements
+            ? netdev_get_dev(netdev)->netdev_class->set_advertisements(
+                    netdev, advertise)
+            : EOPNOTSUPP);
 }
 
-static int
-do_set_addr(struct netdev *netdev, int sock,
-            int ioctl_nr, const char *ioctl_name, struct in_addr addr)
+/* If 'netdev' has an assigned IPv4 address, sets '*address' to that address
+ * and '*netmask' to its netmask and returns 0.  Otherwise, returns a positive
+ * errno value and sets '*address' to 0 (INADDR_ANY).
+ *
+ * The following error values have well-defined meanings:
+ *
+ *   - EADDRNOTAVAIL: 'netdev' has no assigned IPv4 address.
+ *
+ *   - EOPNOTSUPP: No IPv4 network stack attached to 'netdev'.
+ *
+ * 'address' or 'netmask' or both may be null, in which case the address or netmask
+ * is not reported. */
+int
+netdev_get_in4(const struct netdev *netdev,
+               struct in_addr *address_, struct in_addr *netmask_)
 {
-    struct ifreq ifr;
+    struct in_addr address;
+    struct in_addr netmask;
     int error;
 
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
-    make_in4_sockaddr(&ifr.ifr_addr, addr);
-    COVERAGE_INC(netdev_set_in4);
-    error = ioctl(sock, ioctl_nr, &ifr) < 0 ? errno : 0;
-    if (error) {
-        VLOG_WARN("ioctl(%s): %s", ioctl_name, strerror(error));
+    error = (netdev_get_dev(netdev)->netdev_class->get_in4
+             ? netdev_get_dev(netdev)->netdev_class->get_in4(netdev, 
+                    &address, &netmask)
+             : EOPNOTSUPP);
+    if (address_) {
+        address_->s_addr = error ? 0 : address.s_addr;
+    }
+    if (netmask_) {
+        netmask_->s_addr = error ? 0 : netmask.s_addr;
     }
     return error;
 }
@@ -863,69 +715,70 @@ do_set_addr(struct netdev *netdev, int sock,
 int
 netdev_set_in4(struct netdev *netdev, struct in_addr addr, struct in_addr mask)
 {
-    int error;
-
-    error = do_set_addr(netdev, af_inet_sock,
-                        SIOCSIFADDR, "SIOCSIFADDR", addr);
-    if (!error && addr.s_addr != INADDR_ANY) {
-        error = do_set_addr(netdev, af_inet_sock,
-                            SIOCSIFNETMASK, "SIOCSIFNETMASK", mask);
-    }
-    return error;
+    return (netdev_get_dev(netdev)->netdev_class->set_in4
+            ? netdev_get_dev(netdev)->netdev_class->set_in4(netdev, addr, mask)
+            : EOPNOTSUPP);
 }
 
-/* Adds 'router' as a default IP gateway. */
+/* Adds 'router' as a default IP gateway for the TCP/IP stack that corresponds
+ * to 'netdev'. */
 int
-netdev_add_router(struct in_addr router)
+netdev_add_router(struct netdev *netdev, struct in_addr router)
 {
-    struct in_addr any = { INADDR_ANY };
-    struct rtentry rt;
-    int error;
-
-    memset(&rt, 0, sizeof rt);
-    make_in4_sockaddr(&rt.rt_dst, any);
-    make_in4_sockaddr(&rt.rt_gateway, router);
-    make_in4_sockaddr(&rt.rt_genmask, any);
-    rt.rt_flags = RTF_UP | RTF_GATEWAY;
     COVERAGE_INC(netdev_add_router);
-    error = ioctl(af_inet_sock, SIOCADDRT, &rt) < 0 ? errno : 0;
-    if (error) {
-        VLOG_WARN("ioctl(SIOCADDRT): %s", strerror(error));
-    }
-    return error;
+    return (netdev_get_dev(netdev)->netdev_class->add_router
+            ? netdev_get_dev(netdev)->netdev_class->add_router(netdev, router)
+            : EOPNOTSUPP);
 }
 
-/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address (if
- * 'in6' is non-null) and returns true.  Otherwise, returns false. */
-bool
-netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6)
+/* Looks up the next hop for 'host' for the TCP/IP stack that corresponds to
+ * 'netdev'.  If a route cannot not be determined, sets '*next_hop' to 0,
+ * '*netdev_name' to null, and returns a positive errno value.  Otherwise, if a
+ * next hop is found, stores the next hop gateway's address (0 if 'host' is on
+ * a directly connected network) in '*next_hop' and a copy of the name of the
+ * device to reach 'host' in '*netdev_name', and returns 0.  The caller is
+ * responsible for freeing '*netdev_name' (by calling free()). */
+int
+netdev_get_next_hop(const struct netdev *netdev,
+                    const struct in_addr *host, struct in_addr *next_hop,
+                    char **netdev_name)
 {
-    if (in6) {
-        *in6 = netdev->in6;
+    int error = (netdev_get_dev(netdev)->netdev_class->get_next_hop
+                 ? netdev_get_dev(netdev)->netdev_class->get_next_hop(
+                        host, next_hop, netdev_name)
+                 : EOPNOTSUPP);
+    if (error) {
+        next_hop->s_addr = 0;
+        *netdev_name = NULL;
     }
-    return memcmp(&netdev->in6, &in6addr_any, sizeof netdev->in6) != 0;
+    return error;
 }
 
-/* Obtains the current flags for 'netdev' and stores them into '*flagsp'.
- * Returns 0 if successful, otherwise a positive errno value.  On failure,
- * stores 0 into '*flagsp'. */
+/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address and
+ * returns 0.  Otherwise, returns a positive errno value and sets '*in6' to
+ * all-zero-bits (in6addr_any).
+ *
+ * The following error values have well-defined meanings:
+ *
+ *   - EADDRNOTAVAIL: 'netdev' has no assigned IPv6 address.
+ *
+ *   - EOPNOTSUPP: No IPv6 network stack attached to 'netdev'.
+ *
+ * 'in6' may be null, in which case the address itself is not reported. */
 int
-netdev_get_flags(const struct netdev *netdev, enum netdev_flags *flagsp)
+netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6)
 {
-    return netdev_nodev_get_flags(netdev->name, flagsp);
-}
+    struct in6_addr dummy;
+    int error;
 
-static int
-nd_to_iff_flags(enum netdev_flags nd)
-{
-    int iff = 0;
-    if (nd & NETDEV_UP) {
-        iff |= IFF_UP;
+    error = (netdev_get_dev(netdev)->netdev_class->get_in6
+             ? netdev_get_dev(netdev)->netdev_class->get_in6(netdev, 
+                    in6 ? in6 : &dummy)
+             : EOPNOTSUPP);
+    if (error && in6) {
+        memset(in6, 0, sizeof *in6);
     }
-    if (nd & NETDEV_PROMISC) {
-        iff |= IFF_PROMISC;
-    }
-    return iff;
+    return error;
 }
 
 /* On 'netdev', turns off the flags in 'off' and then turns on the flags in
@@ -934,26 +787,45 @@ nd_to_iff_flags(enum netdev_flags nd)
  * successful, otherwise a positive errno value. */
 static int
 do_update_flags(struct netdev *netdev, enum netdev_flags off,
-                enum netdev_flags on, bool permanent)
+                enum netdev_flags on, enum netdev_flags *old_flagsp,
+                bool permanent)
 {
-    int old_flags, new_flags;
+    enum netdev_flags old_flags;
     int error;
 
-    error = get_flags(netdev->name, &old_flags);
+    error = netdev_get_dev(netdev)->netdev_class->update_flags(netdev, 
+                off & ~on, on, &old_flags);
     if (error) {
-        return error;
-    }
-
-    new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
-    if (!permanent) {
-        netdev->changed_flags |= new_flags ^ old_flags; 
+        VLOG_WARN_RL(&rl, "failed to %s flags for network device %s: %s",
+                     off || on ? "set" : "get", netdev_get_name(netdev),
+                     strerror(error));
+        old_flags = 0;
+    } else if ((off || on) && !permanent) {
+        enum netdev_flags new_flags = (old_flags & ~off) | on;
+        enum netdev_flags changed_flags = old_flags ^ new_flags;
+        if (changed_flags) {
+            if (!netdev->changed_flags) {
+                netdev->save_flags = old_flags;
+            }
+            netdev->changed_flags |= changed_flags;
+        }
     }
-    if (new_flags != old_flags) {
-        error = set_flags(netdev->name, new_flags);
+    if (old_flagsp) {
+        *old_flagsp = old_flags;
     }
     return error;
 }
 
+/* Obtains the current flags for 'netdev' and stores them into '*flagsp'.
+ * Returns 0 if successful, otherwise a positive errno value.  On failure,
+ * stores 0 into '*flagsp'. */
+int
+netdev_get_flags(const struct netdev *netdev_, enum netdev_flags *flagsp)
+{
+    struct netdev *netdev = (struct netdev *) netdev_;
+    return do_update_flags(netdev, 0, 0, flagsp, false);
+}
+
 /* Sets the flags for 'netdev' to 'flags'.
  * If 'permanent' is true, the changes will persist; otherwise, they
  * will be reverted when 'netdev' is closed or the program exits.
@@ -962,7 +834,7 @@ int
 netdev_set_flags(struct netdev *netdev, enum netdev_flags flags,
                  bool permanent)
 {
-    return do_update_flags(netdev, -1, flags, permanent);
+    return do_update_flags(netdev, -1, flags, NULL, permanent);
 }
 
 /* Turns on the specified 'flags' on 'netdev'.
@@ -973,7 +845,7 @@ int
 netdev_turn_flags_on(struct netdev *netdev, enum netdev_flags flags,
                      bool permanent)
 {
-    return do_update_flags(netdev, 0, flags, permanent);
+    return do_update_flags(netdev, 0, flags, NULL, permanent);
 }
 
 /* Turns off the specified 'flags' on 'netdev'.
@@ -984,719 +856,419 @@ int
 netdev_turn_flags_off(struct netdev *netdev, enum netdev_flags flags,
                       bool permanent)
 {
-    return do_update_flags(netdev, flags, 0, permanent);
+    return do_update_flags(netdev, flags, 0, NULL, permanent);
 }
 
 /* Looks up the ARP table entry for 'ip' on 'netdev'.  If one exists and can be
  * successfully retrieved, it stores the corresponding MAC address in 'mac' and
  * returns 0.  Otherwise, it returns a positive errno value; in particular,
- * ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'. */
+ * ENXIO indicates that there is no ARP table entry for 'ip' on 'netdev'. */
 int
-netdev_nodev_arp_lookup(const char *netdev_name, uint32_t ip, 
-                        uint8_t mac[ETH_ADDR_LEN]) 
+netdev_arp_lookup(const struct netdev *netdev,
+                  uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
 {
-    struct arpreq r;
-    struct sockaddr_in *pa;
-    int retval;
-
-    init_netdev();
-
-    memset(&r, 0, sizeof r);
-    pa = (struct sockaddr_in *) &r.arp_pa;
-    pa->sin_family = AF_INET;
-    pa->sin_addr.s_addr = ip;
-    pa->sin_port = 0;
-    r.arp_ha.sa_family = ARPHRD_ETHER;
-    r.arp_flags = 0;
-    strncpy(r.arp_dev, netdev_name, sizeof r.arp_dev);
-    COVERAGE_INC(netdev_arp_lookup);
-    retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
-    if (!retval) {
-        memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
-    } else if (retval != ENXIO) {
-        VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
-                     netdev_name, IP_ARGS(&ip), strerror(retval));
+    int error = (netdev_get_dev(netdev)->netdev_class->arp_lookup
+                 ? netdev_get_dev(netdev)->netdev_class->arp_lookup(netdev, 
+                        ip, mac)
+                 : EOPNOTSUPP);
+    if (error) {
+        memset(mac, 0, ETH_ADDR_LEN);
     }
-    return retval;
+    return error;
 }
 
+/* Sets 'carrier' to true if carrier is active (link light is on) on
+ * 'netdev'. */
 int
-netdev_arp_lookup(const struct netdev *netdev, uint32_t ip, 
-                  uint8_t mac[ETH_ADDR_LEN]) 
-{
-    return netdev_nodev_arp_lookup(netdev->name, ip, mac);
-}
-
-static int
-get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
+netdev_get_carrier(const struct netdev *netdev, bool *carrier)
 {
-    struct ofpbuf request;
-    struct ofpbuf *reply;
-    struct ifinfomsg *ifi;
-    const struct rtnl_link_stats *rtnl_stats;
-    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
-    int error;
-
-    ofpbuf_init(&request, 0);
-    nl_msg_put_nlmsghdr(&request, rtnl_sock, sizeof *ifi,
-                        RTM_GETLINK, NLM_F_REQUEST);
-    ifi = ofpbuf_put_zeros(&request, sizeof *ifi);
-    ifi->ifi_family = PF_UNSPEC;
-    ifi->ifi_index = ifindex;
-    error = nl_sock_transact(rtnl_sock, &request, &reply);
-    ofpbuf_uninit(&request);
+    int error = (netdev_get_dev(netdev)->netdev_class->get_carrier
+                 ? netdev_get_dev(netdev)->netdev_class->get_carrier(netdev, 
+                        carrier)
+                 : EOPNOTSUPP);
     if (error) {
-        return error;
-    }
-
-    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
-                         rtnlgrp_link_policy,
-                         attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
-        ofpbuf_delete(reply);
-        return EPROTO;
-    }
-
-    if (!attrs[IFLA_STATS]) {
-        VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
-        ofpbuf_delete(reply);
-        return EPROTO;
+        *carrier = false;
     }
-
-    rtnl_stats = nl_attr_get(attrs[IFLA_STATS]);
-    stats->rx_packets = rtnl_stats->rx_packets;
-    stats->tx_packets = rtnl_stats->tx_packets;
-    stats->rx_bytes = rtnl_stats->rx_bytes;
-    stats->tx_bytes = rtnl_stats->tx_bytes;
-    stats->rx_errors = rtnl_stats->rx_errors;
-    stats->tx_errors = rtnl_stats->tx_errors;
-    stats->rx_dropped = rtnl_stats->rx_dropped;
-    stats->tx_dropped = rtnl_stats->tx_dropped;
-    stats->multicast = rtnl_stats->multicast;
-    stats->collisions = rtnl_stats->collisions;
-    stats->rx_length_errors = rtnl_stats->rx_length_errors;
-    stats->rx_over_errors = rtnl_stats->rx_over_errors;
-    stats->rx_crc_errors = rtnl_stats->rx_crc_errors;
-    stats->rx_frame_errors = rtnl_stats->rx_frame_errors;
-    stats->rx_fifo_errors = rtnl_stats->rx_fifo_errors;
-    stats->rx_missed_errors = rtnl_stats->rx_missed_errors;
-    stats->tx_aborted_errors = rtnl_stats->tx_aborted_errors;
-    stats->tx_carrier_errors = rtnl_stats->tx_carrier_errors;
-    stats->tx_fifo_errors = rtnl_stats->tx_fifo_errors;
-    stats->tx_heartbeat_errors = rtnl_stats->tx_heartbeat_errors;
-    stats->tx_window_errors = rtnl_stats->tx_window_errors;
-
-    ofpbuf_delete(reply);
-
-    return 0;
+    return error;
 }
 
-static int
-get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
+/* Retrieves current device stats for 'netdev'. */
+int
+netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 {
-    static const char fn[] = "/proc/net/dev";
-    char line[1024];
-    FILE *stream;
-    int ln;
-
-    stream = fopen(fn, "r");
-    if (!stream) {
-        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
-        return errno;
-    }
+    int error;
 
-    ln = 0;
-    while (fgets(line, sizeof line, stream)) {
-        if (++ln >= 3) {
-            char devname[16];
-#define X64 "%"SCNu64
-            if (sscanf(line,
-                       " %15[^:]:"
-                       X64 X64 X64 X64 X64 X64 X64 "%*u"
-                       X64 X64 X64 X64 X64 X64 X64 "%*u",
-                       devname,
-                       &stats->rx_bytes,
-                       &stats->rx_packets,
-                       &stats->rx_errors,
-                       &stats->rx_dropped,
-                       &stats->rx_fifo_errors,
-                       &stats->rx_frame_errors,
-                       &stats->multicast,
-                       &stats->tx_bytes,
-                       &stats->tx_packets,
-                       &stats->tx_errors,
-                       &stats->tx_dropped,
-                       &stats->tx_fifo_errors,
-                       &stats->collisions,
-                       &stats->tx_carrier_errors) != 15) {
-                VLOG_WARN_RL(&rl, "%s:%d: parse error", fn, ln);
-            } else if (!strcmp(devname, netdev_name)) {
-                stats->rx_length_errors = UINT64_MAX;
-                stats->rx_over_errors = UINT64_MAX;
-                stats->rx_crc_errors = UINT64_MAX;
-                stats->rx_missed_errors = UINT64_MAX;
-                stats->tx_aborted_errors = UINT64_MAX;
-                stats->tx_heartbeat_errors = UINT64_MAX;
-                stats->tx_window_errors = UINT64_MAX;
-                fclose(stream);
-                return 0;
-            }
-        }
+    COVERAGE_INC(netdev_get_stats);
+    error = (netdev_get_dev(netdev)->netdev_class->get_stats
+             ? netdev_get_dev(netdev)->netdev_class->get_stats(netdev, stats)
+             : EOPNOTSUPP);
+    if (error) {
+        memset(stats, 0xff, sizeof *stats);
     }
-    VLOG_WARN_RL(&rl, "%s: no stats for %s", fn, netdev_name);
-    fclose(stream);
-    return ENODEV;
+    return error;
 }
 
+/* Attempts to set input rate limiting (policing) policy, such that up to
+ * 'kbits_rate' kbps of traffic is accepted, with a maximum accumulative burst
+ * size of 'kbits' kb. */
 int
-netdev_get_carrier(const struct netdev *netdev, bool *carrier)
+netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
+                    uint32_t kbits_burst)
 {
-    return netdev_nodev_get_carrier(netdev->name, carrier);
+    return (netdev_get_dev(netdev)->netdev_class->set_policing
+            ? netdev_get_dev(netdev)->netdev_class->set_policing(netdev, 
+                    kbits_rate, kbits_burst)
+            : EOPNOTSUPP);
 }
 
+/* If 'netdev' is a VLAN network device (e.g. one created with vconfig(8)),
+ * sets '*vlan_vid' to the VLAN VID associated with that device and returns 0.
+ * Otherwise returns a errno value (specifically ENOENT if 'netdev_name' is the
+ * name of a network device that is not a VLAN device) and sets '*vlan_vid' to
+ * -1. */
 int
-netdev_nodev_get_carrier(const char *netdev_name, bool *carrier)
+netdev_get_vlan_vid(const struct netdev *netdev, int *vlan_vid)
 {
-    char line[8];
-    int retval;
-    int error;
-    char *fn;
-    int fd;
-
-    *carrier = false;
-
-    fn = xasprintf("/sys/class/net/%s/carrier", netdev_name);
-    fd = open(fn, O_RDONLY);
-    if (fd < 0) {
-        error = errno;
-        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
-        goto exit;
-    }
-
-    retval = read(fd, line, sizeof line);
-    if (retval < 0) {
-        error = errno;
-        if (error == EINVAL) {
-            /* This is the normal return value when we try to check carrier if
-             * the network device is not up. */
-        } else {
-            VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
-        }
-        goto exit_close;
-    } else if (retval == 0) {
-        error = EPROTO;
-        VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
-        goto exit_close;
-    }
-
-    if (line[0] != '0' && line[0] != '1') {
-        error = EPROTO;
-        VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)", fn, line[0]);
-        goto exit_close;
+    int error = (netdev_get_dev(netdev)->netdev_class->get_vlan_vid
+                 ? netdev_get_dev(netdev)->netdev_class->get_vlan_vid(netdev, 
+                        vlan_vid)
+                 : ENOENT);
+    if (error) {
+        *vlan_vid = 0;
     }
-    *carrier = line[0] != '0';
-    error = 0;
-
-exit_close:
-    close(fd);
-exit:
-    free(fn);
     return error;
 }
 
-int
-netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
+/* Returns a network device that has 'in4' as its IP address, if one exists,
+ * otherwise a null pointer. */
+struct netdev *
+netdev_find_dev_by_in4(const struct in_addr *in4)
 {
-    int error;
-
-    COVERAGE_INC(netdev_get_stats);
-    if (use_netlink_stats) {
-        int ifindex;
+    struct netdev *netdev;
+    struct svec dev_list = SVEC_EMPTY_INITIALIZER;
+    size_t i;
 
-        error = get_ifindex(netdev, &ifindex);
-        if (!error) {
-            error = get_stats_via_netlink(ifindex, stats);
+    netdev_enumerate(&dev_list);
+    for (i = 0; i < dev_list.n; i++) {
+        const char *name = dev_list.names[i];
+        struct in_addr dev_in4;
+
+        if (!netdev_open_default(name, &netdev)
+            && !netdev_get_in4(netdev, &dev_in4, NULL)
+            && dev_in4.s_addr == in4->s_addr) {
+            goto exit;
         }
-    } else {
-        error = get_stats_via_proc(netdev->name, stats);
+        netdev_close(netdev);
     }
+    netdev = NULL;
 
-    if (error) {
-        memset(stats, 0xff, sizeof *stats);
-    }
-    return error;
+exit:
+    svec_destroy(&dev_list);
+    return netdev;
 }
-
-#define POLICE_ADD_CMD "/sbin/tc qdisc add dev %s handle ffff: ingress"
-#define POLICE_CONFIG_CMD "/sbin/tc filter add dev %s parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate %dkbit burst %dk mtu 65535 drop flowid :1"
-/* We redirect stderr to /dev/null because we often want to remove all
- * traffic control configuration on a port so its in a known state.  If
- * this done when there is no such configuration, tc complains, so we just
- * always ignore it.
- */
-#define POLICE_DEL_CMD "/sbin/tc qdisc del dev %s handle ffff: ingress 2>/dev/null"
-
-/* Attempts to set input rate limiting (policing) policy. */
-int
-netdev_nodev_set_policing(const char *netdev_name, uint32_t kbits_rate,
-                          uint32_t kbits_burst)
+\f
+/* Initializes 'netdev_dev' as a netdev device named 'name' of the
+ * specified 'netdev_class'.
+ *
+ * This function adds 'netdev_dev' to a netdev-owned shash, so it is
+ * very important that 'netdev_dev' only be freed after calling
+ * the refcount drops to zero.  */
+void
+netdev_dev_init(struct netdev_dev *netdev_dev, const char *name,
+                const struct netdev_class *netdev_class)
 {
-    char command[1024];
+    assert(!shash_find(&netdev_dev_shash, name));
 
-    init_netdev();
+    memset(netdev_dev, 0, sizeof *netdev_dev);
+    netdev_dev->netdev_class = netdev_class;
+    netdev_dev->name = xstrdup(name);
+    netdev_dev->node = shash_add(&netdev_dev_shash, name, netdev_dev);
+}
 
-    COVERAGE_INC(netdev_set_policing);
-    if (kbits_rate) {
-        if (!kbits_burst) {
-            /* Default to 10 kilobits if not specified. */
-            kbits_burst = 10;
-        }
+/* Undoes the results of initialization.
+ *
+ * Normally this function does not need to be called as netdev_close has
+ * the same effect when the refcount drops to zero.
+ * However, it may be called by providers due to an error on creation
+ * that occurs after initialization.  It this case netdev_close() would
+ * never be called. */
+void
+netdev_dev_uninit(struct netdev_dev *netdev_dev, bool destroy)
+{
+    char *name = netdev_dev->name;
 
-        /* xxx This should be more careful about only adding if it
-         * xxx actually exists, as opposed to always deleting it. */
-        snprintf(command, sizeof(command), POLICE_DEL_CMD, netdev_name);
-        if (system(command) == -1) {
-            VLOG_WARN_RL(&rl, "%s: problem removing policing", netdev_name);
-        }
+    assert(!netdev_dev->ref_cnt);
 
-        snprintf(command, sizeof(command), POLICE_ADD_CMD, netdev_name);
-        if (system(command) != 0) {
-            VLOG_WARN_RL(&rl, "%s: problem adding policing", netdev_name);
-            return -1;
-        }
+    shash_delete(&netdev_dev_shash, netdev_dev->node);
+    update_device_args(netdev_dev, NULL);
 
-        snprintf(command, sizeof(command), POLICE_CONFIG_CMD, netdev_name,
-                kbits_rate, kbits_burst);
-        if (system(command) != 0) {
-            VLOG_WARN_RL(&rl, "%s: problem configuring policing", 
-                    netdev_name);
-            return -1;
-        }
-    } else {
-        snprintf(command, sizeof(command), POLICE_DEL_CMD, netdev_name);
-        if (system(command) == -1) {
-            VLOG_WARN_RL(&rl, "%s: problem removing policing", netdev_name);
-        }
+    if (destroy) {
+        netdev_dev->netdev_class->destroy(netdev_dev);
     }
-
-    return 0;
+    free(name);
 }
 
-int
-netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
-                    uint32_t kbits_burst)
+/* Returns the class type of 'netdev_dev'.
+ *
+ * The caller must not free the returned value. */
+const char *
+netdev_dev_get_type(const struct netdev_dev *netdev_dev)
 {
-    return netdev_nodev_set_policing(netdev->name, kbits_rate, kbits_burst);
+    return netdev_dev->netdev_class->type;
 }
 
-/* Initializes 'svec' with a list of the names of all known network devices. */
-void
-netdev_enumerate(struct svec *svec)
+/* Returns the name of 'netdev_dev'.
+ *
+ * The caller must not free the returned value. */
+const char *
+netdev_dev_get_name(const struct netdev_dev *netdev_dev)
 {
-    struct if_nameindex *names;
-
-    svec_init(svec);
-    names = if_nameindex();
-    if (names) {
-        size_t i;
-
-        for (i = 0; names[i].if_name != NULL; i++) {
-            svec_add(svec, names[i].if_name);
-        }
-        if_freenameindex(names);
-    } else {
-        VLOG_WARN("could not obtain list of network device names: %s",
-                  strerror(errno));
-    }
+    return netdev_dev->name;
 }
 
-/* Attempts to locate a device based on its IPv4 address.  The caller
- * may provide a hint as to the device by setting 'netdev_name' to a
- * likely device name.  This string must be malloc'd, since if it is 
- * not correct then it will be freed.  If there is no hint, then
- * 'netdev_name' must be the NULL pointer.
+/* Returns the netdev_dev with 'name' or NULL if there is none.
  *
- * If the device is found, the return value will be true and 'netdev_name' 
- * contains the device's name as a string, which the caller is responsible 
- * for freeing.  If the device is not found, the return value is false. */
-bool
-netdev_find_dev_by_in4(const struct in_addr *in4, char **netdev_name)
+ * The caller must not free the returned value. */
+struct netdev_dev *
+netdev_dev_from_name(const char *name)
 {
-    int i;
-    struct in_addr dev_in4;
-    struct svec dev_list;
-
-    /* Check the hint first. */
-    if (*netdev_name && (netdev_nodev_get_in4(*netdev_name, &dev_in4, NULL))
-            && (dev_in4.s_addr == in4->s_addr)) {
-        return true;
-    }
-
-    free(*netdev_name);
-    *netdev_name = NULL;
-    netdev_enumerate(&dev_list);
-
-    for (i=0; i<dev_list.n; i++) {
-        if ((netdev_nodev_get_in4(dev_list.names[i], &dev_in4, NULL))
-                && (dev_in4.s_addr == in4->s_addr)) {
-            *netdev_name = xstrdup(dev_list.names[i]);
-            svec_destroy(&dev_list);
-            return true;
-        }
-    }
-
-    svec_destroy(&dev_list);
-    return false;
+    return shash_find_data(&netdev_dev_shash, name);
 }
 
-/* Looks up the next hop for 'ip'.  If the next hop can be found, the 
- * address is stored in 'next_hop'.  If a gateway is not required to
- * reach 'ip', zero is stored in 'next_hop'.  In either case, zero is
- * returned and a copy of the name of the device to reach 'ip' is stored
- * in 'netdev_name', which the caller is responsible for freeing.  If a 
- * route could not be determined, a positive errno is returned. */
-int
-netdev_get_next_hop(const struct in_addr *host, struct in_addr *next_hop, 
-                    char **netdev_name) 
-{
-    static const char fn[] = "/proc/net/route";
-    FILE *stream;
-    char line[256];
-    int ln;
-
-    *netdev_name = NULL;
-    stream = fopen(fn, "r");
-    if (stream == NULL) {
-        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
-        return errno;
-    }
-
-    ln = 0;
-    while (fgets(line, sizeof line, stream)) {
-        if (++ln >= 2) {
-            char iface[17];
-            uint32_t dest, gateway, mask;
-            int refcnt, metric, mtu;
-            unsigned int flags, use, window, irtt;
-
-            if (sscanf(line,
-                       "%16s %"SCNx32" %"SCNx32" %04X %d %u %d %"SCNx32
-                       " %d %u %u\n",
-                       iface, &dest, &gateway, &flags, &refcnt,
-                       &use, &metric, &mask, &mtu, &window, &irtt) != 11) {
-
-                VLOG_WARN_RL(&rl, "%s: could not parse line %d: %s", 
-                        fn, ln, line);
-                continue;
-            }
-            if (!(flags & RTF_UP)) {
-                /* Skip routes that aren't up. */
-                continue;
-            }
+/* Fills 'device_list' with devices that match 'netdev_class'.
+ *
+ * The caller is responsible for initializing and destroying 'device_list'
+ * but the contained netdev_devs must not be freed. */
+void
+netdev_dev_get_devices(const struct netdev_class *netdev_class,
+                       struct shash *device_list)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &netdev_dev_shash) {
+        struct netdev_dev *dev = node->data;
 
-            /* The output of 'dest', 'mask', and 'gateway' were given in
-             * network byte order, so we don't need need any endian 
-             * conversions here. */
-            if ((dest & mask) == (host->s_addr & mask)) {
-                if (!gateway) {
-                    /* The host is directly reachable. */
-                    next_hop->s_addr = 0;
-                } else {
-                    /* To reach the host, we must go through a gateway. */
-                    next_hop->s_addr = gateway;
-                }
-                *netdev_name = xstrdup(iface);
-                fclose(stream);
-                return 0;
-            }
+        if (dev->netdev_class == netdev_class) {
+            shash_add(device_list, node->name, node->data);
         }
     }
-
-    fclose(stream);
-    return ENXIO;
 }
 
-/* Obtains the current flags for the network device named 'netdev_name' and
- * stores them into '*flagsp'.  Returns 0 if successful, otherwise a positive
- * errno value.  On error, stores 0 into '*flagsp'.
+/* Initializes 'netdev' as a instance of the netdev_dev.
  *
- * If only device flags are needed, this is more efficient than calling
- * netdev_open(), netdev_get_flags(), netdev_close(). */
-int
-netdev_nodev_get_flags(const char *netdev_name, enum netdev_flags *flagsp)
+ * This function adds 'netdev' to a netdev-owned linked list, so it is very
+ * important that 'netdev' only be freed after calling netdev_close(). */
+void
+netdev_init(struct netdev *netdev, struct netdev_dev *netdev_dev)
 {
-    int error, flags;
-
-    init_netdev();
+    memset(netdev, 0, sizeof *netdev);
+    netdev->netdev_dev = netdev_dev;
+    list_push_back(&netdev_list, &netdev->node);
+}
 
-    *flagsp = 0;
-    error = get_flags(netdev_name, &flags);
+/* Undoes the results of initialization.
+ *
+ * Normally this function only needs to be called from netdev_close().
+ * However, it may be called by providers due to an error on opening
+ * that occurs after initialization.  It this case netdev_close() would
+ * never be called. */
+void
+netdev_uninit(struct netdev *netdev, bool close)
+{
+    /* Restore flags that we changed, if any. */
+    int error = restore_flags(netdev);
+    list_remove(&netdev->node);
     if (error) {
-        return error;
+        VLOG_WARN("failed to restore network device flags on %s: %s",
+                  netdev_get_name(netdev), strerror(error));
     }
 
-    if (flags & IFF_UP) {
-        *flagsp |= NETDEV_UP;
-    }
-    if (flags & IFF_PROMISC) {
-        *flagsp |= NETDEV_PROMISC;
+    if (close) {
+        netdev_get_dev(netdev)->netdev_class->close(netdev);
     }
-    return 0;
 }
 
-int
-netdev_nodev_get_etheraddr(const char *netdev_name, uint8_t mac[6])
-{
-    init_netdev();
 
-    return get_etheraddr(netdev_name, mac, NULL);
+/* Returns the class type of 'netdev'.  
+ *
+ * The caller must not free the returned value. */
+const char *
+netdev_get_type(const struct netdev *netdev)
+{
+    return netdev_get_dev(netdev)->netdev_class->type;
 }
 
-/* If 'netdev_name' is the name of a VLAN network device (e.g. one created with
- * vconfig(8)), sets '*vlan_vid' to the VLAN VID associated with that device
- * and returns 0.  Otherwise returns a errno value (specifically ENOENT if
- * 'netdev_name' is the name of a network device that is not a VLAN device) and
- * sets '*vlan_vid' to -1. */
-int
-netdev_get_vlan_vid(const char *netdev_name, int *vlan_vid)
+struct netdev_dev *
+netdev_get_dev(const struct netdev *netdev)
 {
-    struct ds line = DS_EMPTY_INITIALIZER;
-    FILE *stream = NULL;
-    int error;
-    char *fn;
-
-    COVERAGE_INC(netdev_get_vlan_vid);
-    fn = xasprintf("/proc/net/vlan/%s", netdev_name);
-    stream = fopen(fn, "r");
-    if (!stream) {
-        error = errno;
-        goto done;
-    }
-
-    if (ds_get_line(&line, stream)) {
-        if (ferror(stream)) {
-            error = errno;
-            VLOG_ERR_RL(&rl, "error reading \"%s\": %s", fn, strerror(errno));
-        } else {
-            error = EPROTO;
-            VLOG_ERR_RL(&rl, "unexpected end of file reading \"%s\"", fn);
-        }
-        goto done;
-    }
-
-    if (!sscanf(ds_cstr(&line), "%*s VID: %d", vlan_vid)) {
-        error = EPROTO;
-        VLOG_ERR_RL(&rl, "parse error reading \"%s\" line 1: \"%s\"",
-                    fn, ds_cstr(&line));
-        goto done;
-    }
-
-    error = 0;
+    return netdev->netdev_dev;
+}
 
-done:
-    free(fn);
-    if (stream) {
-        fclose(stream);
-    }
-    ds_destroy(&line);
-    if (error) {
-        *vlan_vid = -1;
-    }
-    return error;
+/* Initializes 'notifier' as a netdev notifier for 'netdev', for which
+ * notification will consist of calling 'cb', with auxiliary data 'aux'. */
+void
+netdev_notifier_init(struct netdev_notifier *notifier, struct netdev *netdev,
+                     void (*cb)(struct netdev_notifier *), void *aux)
+{
+    notifier->netdev = netdev;
+    notifier->cb = cb;
+    notifier->aux = aux;
 }
 \f
-static void restore_all_flags(void *aux);
+/* Tracks changes in the status of a set of network devices. */
+struct netdev_monitor {
+    struct shash polled_netdevs;
+    struct shash changed_netdevs;
+};
 
-/* Set up a signal hook to restore network device flags on program
- * termination.  */
-static void
-init_netdev(void)
+/* Creates and returns a new structure for monitor changes in the status of
+ * network devices. */
+struct netdev_monitor *
+netdev_monitor_create(void)
 {
-    static bool inited;
-    if (!inited) {
-        int ifindex;
-        int error;
-
-        inited = true;
-
-        fatal_signal_add_hook(restore_all_flags, NULL, true);
-
-        af_inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
-        if (af_inet_sock < 0) {
-            ovs_fatal(errno, "socket(AF_INET)");
-        }
-
-        error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, &rtnl_sock);
-        if (error) {
-            ovs_fatal(error, "socket(AF_NETLINK, NETLINK_ROUTE)");
-        }
-
-        /* Decide on the netdev_get_stats() implementation to use.  Netlink is
-         * preferable, so if that works, we'll use it. */
-        ifindex = do_get_ifindex("lo");
-        if (ifindex < 0) {
-            VLOG_WARN("failed to get ifindex for lo, "
-                      "obtaining netdev stats from proc");
-            use_netlink_stats = false;
-        } else {
-            struct netdev_stats stats;
-            error = get_stats_via_netlink(ifindex, &stats);
-            if (!error) {
-                VLOG_DBG("obtaining netdev stats via rtnetlink");
-                use_netlink_stats = true;
-            } else {
-                VLOG_INFO("RTM_GETLINK failed (%s), obtaining netdev stats "
-                          "via proc (you are probably running a pre-2.6.19 "
-                          "kernel)", strerror(error));
-                use_netlink_stats = false;
-            }
-        }
-    }
+    struct netdev_monitor *monitor = xmalloc(sizeof *monitor);
+    shash_init(&monitor->polled_netdevs);
+    shash_init(&monitor->changed_netdevs);
+    return monitor;
 }
 
-/* Restore the network device flags on 'netdev' to those that were active
- * before we changed them.  Returns 0 if successful, otherwise a positive
- * errno value.
- *
- * To avoid reentry, the caller must ensure that fatal signals are blocked. */
-static int
-restore_flags(struct netdev *netdev)
+/* Destroys 'monitor'. */
+void
+netdev_monitor_destroy(struct netdev_monitor *monitor)
 {
-    struct ifreq ifr;
-    int restore_flags;
-
-    /* Get current flags. */
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
-    COVERAGE_INC(netdev_get_flags);
-    if (ioctl(netdev->netdev_fd, SIOCGIFFLAGS, &ifr) < 0) {
-        return errno;
-    }
+    if (monitor) {
+        struct shash_node *node;
 
-    /* Restore flags that we might have changed, if necessary. */
-    restore_flags = netdev->changed_flags & (IFF_PROMISC | IFF_UP);
-    if ((ifr.ifr_flags ^ netdev->save_flags) & restore_flags) {
-        ifr.ifr_flags &= ~restore_flags;
-        ifr.ifr_flags |= netdev->save_flags & restore_flags;
-        COVERAGE_INC(netdev_set_flags);
-        if (ioctl(netdev->netdev_fd, SIOCSIFFLAGS, &ifr) < 0) {
-            return errno;
+        SHASH_FOR_EACH (node, &monitor->polled_netdevs) {
+            struct netdev_notifier *notifier = node->data;
+            netdev_get_dev(notifier->netdev)->netdev_class->poll_remove(
+                    notifier);
         }
-    }
 
-    return 0;
+        shash_destroy(&monitor->polled_netdevs);
+        shash_destroy(&monitor->changed_netdevs);
+        free(monitor);
+    }
 }
 
-/* Retores all the flags on all network devices that we modified.  Called from
- * a signal handler, so it does not attempt to report error conditions. */
 static void
-restore_all_flags(void *aux UNUSED)
+netdev_monitor_cb(struct netdev_notifier *notifier)
 {
-    struct netdev *netdev;
-    LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
-        restore_flags(netdev);
+    struct netdev_monitor *monitor = notifier->aux;
+    const char *name = netdev_get_name(notifier->netdev);
+    if (!shash_find(&monitor->changed_netdevs, name)) {
+        shash_add(&monitor->changed_netdevs, name, NULL);
     }
 }
 
-static int
-get_flags(const char *netdev_name, int *flags)
-{
-    struct ifreq ifr;
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    COVERAGE_INC(netdev_get_flags);
-    if (ioctl(af_inet_sock, SIOCGIFFLAGS, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCGIFFLAGS) on %s device failed: %s",
-                 netdev_name, strerror(errno));
-        return errno;
+/* Attempts to add 'netdev' as a netdev monitored by 'monitor'.  Returns 0 if
+ * successful, otherwise a positive errno value.
+ *
+ * Adding a given 'netdev' to a monitor multiple times is equivalent to adding
+ * it once. */
+int
+netdev_monitor_add(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    int error = 0;
+    if (!shash_find(&monitor->polled_netdevs, netdev_name)
+            && netdev_get_dev(netdev)->netdev_class->poll_add)
+    {
+        struct netdev_notifier *notifier;
+        error = netdev_get_dev(netdev)->netdev_class->poll_add(netdev,
+                    netdev_monitor_cb, monitor, &notifier);
+        if (!error) {
+            assert(notifier->netdev == netdev);
+            shash_add(&monitor->polled_netdevs, netdev_name, notifier);
+        }
     }
-    *flags = ifr.ifr_flags;
-    return 0;
+    return error;
 }
 
-static int
-set_flags(const char *netdev_name, int flags)
-{
-    struct ifreq ifr;
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    ifr.ifr_flags = flags;
-    COVERAGE_INC(netdev_set_flags);
-    if (ioctl(af_inet_sock, SIOCSIFFLAGS, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCSIFFLAGS) on %s device failed: %s",
-                 netdev_name, strerror(errno));
-        return errno;
+/* Removes 'netdev' from the set of netdevs monitored by 'monitor'.  (This has
+ * no effect if 'netdev' is not in the set of devices monitored by
+ * 'monitor'.) */
+void
+netdev_monitor_remove(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    struct shash_node *node;
+
+    node = shash_find(&monitor->polled_netdevs, netdev_name);
+    if (node) {
+        /* Cancel future notifications. */
+        struct netdev_notifier *notifier = node->data;
+        netdev_get_dev(netdev)->netdev_class->poll_remove(notifier);
+        shash_delete(&monitor->polled_netdevs, node);
+
+        /* Drop any pending notification. */
+        node = shash_find(&monitor->changed_netdevs, netdev_name);
+        if (node) {
+            shash_delete(&monitor->changed_netdevs, node);
+        }
     }
-    return 0;
 }
 
-static int
-do_get_ifindex(const char *netdev_name)
+/* Checks for changes to netdevs in the set monitored by 'monitor'.  If any of
+ * the attributes (Ethernet address, carrier status, speed or peer-advertised
+ * speed, flags, etc.) of a network device monitored by 'monitor' has changed,
+ * sets '*devnamep' to the name of a device that has changed and returns 0.
+ * The caller is responsible for freeing '*devnamep' (with free()).
+ *
+ * If no devices have changed, sets '*devnamep' to NULL and returns EAGAIN.
+ */
+int
+netdev_monitor_poll(struct netdev_monitor *monitor, char **devnamep)
 {
-    struct ifreq ifr;
-
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    COVERAGE_INC(netdev_get_ifindex);
-    if (ioctl(af_inet_sock, SIOCGIFINDEX, &ifr) < 0) {
-        VLOG_WARN_RL(&rl, "ioctl(SIOCGIFINDEX) on %s device failed: %s",
-                     netdev_name, strerror(errno));
-        return -errno;
+    struct shash_node *node = shash_first(&monitor->changed_netdevs);
+    if (!node) {
+        *devnamep = NULL;
+        return EAGAIN;
+    } else {
+        *devnamep = xstrdup(node->name);
+        shash_delete(&monitor->changed_netdevs, node);
+        return 0;
     }
-    return ifr.ifr_ifindex;
 }
 
-static int
-get_ifindex(const struct netdev *netdev, int *ifindexp)
+/* Registers with the poll loop to wake up from the next call to poll_block()
+ * when netdev_monitor_poll(monitor) would indicate that a device has
+ * changed. */
+void
+netdev_monitor_poll_wait(const struct netdev_monitor *monitor)
 {
-    *ifindexp = 0;
-    if (netdev->ifindex < 0) {
-        int ifindex = do_get_ifindex(netdev->name);
-        if (ifindex < 0) {
-            return -ifindex;
-        }
-        ((struct netdev *) netdev)->ifindex = ifindex;
+    if (!shash_is_empty(&monitor->changed_netdevs)) {
+        poll_immediate_wake();
+    } else {
+        /* XXX Nothing needed here for netdev_linux, but maybe other netdev
+         * classes need help. */
     }
-    *ifindexp = netdev->ifindex;
-    return 0;
 }
-
+\f
+/* Restore the network device flags on 'netdev' to those that were active
+ * before we changed them.  Returns 0 if successful, otherwise a positive
+ * errno value.
+ *
+ * To avoid reentry, the caller must ensure that fatal signals are blocked. */
 static int
-get_etheraddr(const char *netdev_name, uint8_t ea[ETH_ADDR_LEN],
-              int *hwaddr_familyp)
-{
-    struct ifreq ifr;
-
-    memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    COVERAGE_INC(netdev_get_hwaddr);
-    if (ioctl(af_inet_sock, SIOCGIFHWADDR, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCGIFHWADDR) on %s device failed: %s",
-                 netdev_name, strerror(errno));
-        return errno;
-    }
-    if (hwaddr_familyp) {
-        int hwaddr_family = ifr.ifr_hwaddr.sa_family;
-        *hwaddr_familyp = hwaddr_family;
-        if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER) {
-            VLOG_WARN("%s device has unknown hardware address family %d",
-                      netdev_name, hwaddr_family);
-        }
+restore_flags(struct netdev *netdev)
+{
+    if (netdev->changed_flags) {
+        enum netdev_flags restore = netdev->save_flags & netdev->changed_flags;
+        enum netdev_flags old_flags;
+        return netdev_get_dev(netdev)->netdev_class->update_flags(netdev,
+                                           netdev->changed_flags & ~restore,
+                                           restore, &old_flags);
     }
-    memcpy(ea, ifr.ifr_hwaddr.sa_data, ETH_ADDR_LEN);
     return 0;
 }
 
-static int
-set_etheraddr(const char *netdev_name, int hwaddr_family,
-              const uint8_t mac[ETH_ADDR_LEN])
-{
-    struct ifreq ifr;
-
-    memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, netdev_name, sizeof ifr.ifr_name);
-    ifr.ifr_hwaddr.sa_family = hwaddr_family;
-    memcpy(ifr.ifr_hwaddr.sa_data, mac, ETH_ADDR_LEN);
-    COVERAGE_INC(netdev_set_hwaddr);
-    if (ioctl(af_inet_sock, SIOCSIFHWADDR, &ifr) < 0) {
-        VLOG_ERR("ioctl(SIOCSIFHWADDR) on %s device failed: %s",
-                 netdev_name, strerror(errno));
-        return errno;
+/* Close all netdevs on shutdown so they can do any needed cleanup such as
+ * destroying devices, restoring flags, etc. */
+static void
+close_all_netdevs(void *aux OVS_UNUSED)
+{
+    struct netdev *netdev, *next;
+    LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) {
+        netdev_close(netdev);
     }
-    return 0;
 }
index 84ae16e..27eb82e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stddef.h>
 #include <stdint.h>
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 /* Generic interface to network devices.
  *
  * Currently, there is a single implementation of this interface that supports
 struct ofpbuf;
 struct in_addr;
 struct in6_addr;
+struct shash;
 struct svec;
 
 enum netdev_flags {
     NETDEV_UP = 0x0001,         /* Device enabled? */
-    NETDEV_PROMISC = 0x0002     /* Promiscuous mode? */
+    NETDEV_PROMISC = 0x0002,    /* Promiscuous mode? */
+    NETDEV_LOOPBACK = 0x0004    /* This is a loopback device. */
 };
 
 enum netdev_pseudo_ethertype {
@@ -43,6 +49,9 @@ enum netdev_pseudo_ethertype {
     NETDEV_ETH_TYPE_802_2        /* Receive all IEEE 802.2 frames. */
 };
 
+/* Network device statistics.
+ *
+ * Values of unsupported statistics are set to all-1-bits (UINT64_MAX). */
 struct netdev_stats {
     uint64_t rx_packets;        /* Total packets received. */
     uint64_t tx_packets;        /* Total packets transmitted. */
@@ -71,55 +80,87 @@ struct netdev_stats {
     uint64_t tx_window_errors;
 };
 
+struct netdev_options {
+    const char *name;
+    const char *type;
+    const struct shash *args;
+    int ethertype;
+    bool may_create;
+    bool may_open;
+};
+
 struct netdev;
+struct netdev_class;
+
+void netdev_run(void);
+void netdev_wait(void);
+
+int netdev_register_provider(const struct netdev_class *);
+int netdev_unregister_provider(const char *type);
+void netdev_enumerate_types(struct svec *types);
 
-int netdev_open(const char *name, int ethertype, struct netdev **);
-int netdev_open_tap(const char *name, struct netdev **);
+int netdev_open(struct netdev_options *, struct netdev **);
+int netdev_open_default(const char *name, struct netdev **);
+int netdev_reconfigure(struct netdev *, const struct shash *args);
 void netdev_close(struct netdev *);
 
+bool netdev_exists(const char *name);
+
+int netdev_enumerate(struct svec *);
+
+const char *netdev_get_name(const struct netdev *);
+const char *netdev_get_type(const struct netdev *);
+int netdev_get_mtu(const struct netdev *, int *mtup);
+int netdev_get_ifindex(const struct netdev *);
+
 int netdev_recv(struct netdev *, struct ofpbuf *);
 void netdev_recv_wait(struct netdev *);
 int netdev_drain(struct netdev *);
+
 int netdev_send(struct netdev *, const struct ofpbuf *);
 void netdev_send_wait(struct netdev *);
+
 int netdev_set_etheraddr(struct netdev *, const uint8_t mac[6]);
-const uint8_t *netdev_get_etheraddr(const struct netdev *);
-const char *netdev_get_name(const struct netdev *);
-int netdev_get_mtu(const struct netdev *);
+int netdev_get_etheraddr(const struct netdev *, uint8_t mac[6]);
+
+int netdev_get_carrier(const struct netdev *, bool *carrier);
 int netdev_get_features(struct netdev *,
                         uint32_t *current, uint32_t *advertised,
                         uint32_t *supported, uint32_t *peer);
+uint64_t netdev_features_to_bps(uint32_t features);
+bool netdev_features_is_full_duplex(uint32_t features);
 int netdev_set_advertisements(struct netdev *, uint32_t advertise);
-bool netdev_get_in4(const struct netdev *, struct in_addr *addr,
-                    struct in_addr *mask);
-int netdev_set_in4(struct netdev *, struct in_addr in4, struct in_addr mask);
-int netdev_add_router(struct in_addr router);
-bool netdev_get_in6(const struct netdev *, struct in6_addr *);
+
+int netdev_get_in4(const struct netdev *, struct in_addr *address,
+                   struct in_addr *netmask);
+int netdev_set_in4(struct netdev *, struct in_addr addr, struct in_addr mask);
+int netdev_get_in6(const struct netdev *, struct in6_addr *);
+int netdev_add_router(struct netdev *, struct in_addr router);
+int netdev_get_next_hop(const struct netdev *, const struct in_addr *host,
+                        struct in_addr *next_hop, char **);
+int netdev_arp_lookup(const struct netdev *, uint32_t ip, uint8_t mac[6]);
+
 int netdev_get_flags(const struct netdev *, enum netdev_flags *);
 int netdev_set_flags(struct netdev *, enum netdev_flags, bool permanent);
 int netdev_turn_flags_on(struct netdev *, enum netdev_flags, bool permanent);
 int netdev_turn_flags_off(struct netdev *, enum netdev_flags, bool permanent);
-int netdev_arp_lookup(const struct netdev *, uint32_t ip, uint8_t mac[6]);
-int netdev_get_carrier(const struct netdev *, bool *carrier);
+
 int netdev_get_stats(const struct netdev *, struct netdev_stats *);
 int netdev_set_policing(struct netdev *, uint32_t kbits_rate, 
                         uint32_t kbits_burst);
 
-void netdev_enumerate(struct svec *);
-bool netdev_find_dev_by_in4(const struct in_addr *in4, char **netdev_name);
-int netdev_get_next_hop(const struct in_addr *host, struct in_addr *next_hop,
-                        char **netdev_name);
-int netdev_nodev_get_flags(const char *netdev_name, enum netdev_flags *);
-bool netdev_nodev_get_in4(const char *netdev_name, struct in_addr *in4,
-                          struct in_addr *mask);
-int netdev_nodev_set_etheraddr(const char *name, const uint8_t mac[6]);
-int netdev_nodev_get_etheraddr(const char *netdev_name, uint8_t mac[6]);
-int netdev_nodev_set_policing(const char *netdev_name, uint32_t kbits_rate, 
-                              uint32_t kbits_burst);
-int netdev_nodev_arp_lookup(const char *netdev_name, uint32_t ip, 
-                            uint8_t mac[6]);
-int netdev_nodev_get_carrier(const char *netdev_name, bool *carrier);
-
-int netdev_get_vlan_vid(const char *netdev_name, int *vlan_vid);
+int netdev_get_vlan_vid(const struct netdev *, int *vlan_vid);
+struct netdev *netdev_find_dev_by_in4(const struct in_addr *);
+
+struct netdev_monitor *netdev_monitor_create(void);
+void netdev_monitor_destroy(struct netdev_monitor *);
+int netdev_monitor_add(struct netdev_monitor *, struct netdev *);
+void netdev_monitor_remove(struct netdev_monitor *, struct netdev *);
+int netdev_monitor_poll(struct netdev_monitor *, char **devnamep);
+void netdev_monitor_poll_wait(const struct netdev_monitor *);
+
+#ifdef  __cplusplus
+}
+#endif
 
 #endif /* netdev.h */
index a800162..87ac92b 100644 (file)
@@ -80,6 +80,9 @@ format_odp_action(struct ds *ds, const union odp_action *a)
         ds_put_format(ds, "set_nw_dst("IP_FMT")",
                       IP_ARGS(&a->nw_addr.nw_addr));
         break;
+    case ODPAT_SET_NW_TOS:
+        ds_put_format(ds, "set_nw_tos(%"PRIu8")", a->nw_tos.nw_tos);
+        break;
     case ODPAT_SET_TP_SRC:
         ds_put_format(ds, "set_tp_src(%"PRIu16")", ntohs(a->tp_port.tp_port));
         break;
index 63b59dd..f642531 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <config.h>
 #include "ofp-print.h"
-#include "xtoxll.h"
 
 #include <errno.h>
 #include <inttypes.h>
+#include <sys/types.h>
 #include <netinet/in.h>
 #include <sys/wait.h>
 #include <stdarg.h>
@@ -35,6 +35,7 @@
 #include "packets.h"
 #include "pcap.h"
 #include "util.h"
+#include "xtoxll.h"
 
 static void ofp_print_port_name(struct ds *string, uint16_t port);
 
@@ -47,7 +48,7 @@ static void ofp_print_port_name(struct ds *string, uint16_t port);
  *
  * This starts and kills a tcpdump subprocess so it's quite expensive. */
 char *
-ofp_packet_to_string(const void *data, size_t len, size_t total_len UNUSED)
+ofp_packet_to_string(const void *data, size_t len, size_t total_len OVS_UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     struct ofpbuf buf;
@@ -243,6 +244,10 @@ ofp_print_action(struct ds *string, const struct ofp_action_header *ah,
             sizeof(struct ofp_action_nw_addr),
             sizeof(struct ofp_action_nw_addr),
         },
+        [OFPAT_SET_NW_TOS] = {
+            sizeof(struct ofp_action_nw_tos),
+            sizeof(struct ofp_action_nw_tos),
+        },
         [OFPAT_SET_TP_SRC] = {
             sizeof(struct ofp_action_tp_port),
             sizeof(struct ofp_action_tp_port),
@@ -343,6 +348,12 @@ ofp_print_action(struct ds *string, const struct ofp_action_header *ah,
         break;
     }
 
+    case OFPAT_SET_NW_TOS: {
+        struct ofp_action_nw_tos *nt = (struct ofp_action_nw_tos *)ah;
+        ds_put_format(string, "mod_nw_tos:%d", nt->nw_tos);
+        break;
+    }
+
     case OFPAT_SET_TP_SRC: {
         struct ofp_action_tp_port *ta = (struct ofp_action_tp_port *)ah;
         ds_put_format(string, "mod_tp_src:%d", ntohs(ta->tp_port));
@@ -535,14 +546,14 @@ ofp_print_phy_port(struct ds *string, const struct ofp_phy_port *port)
  * 'string' at the given 'verbosity' level. */
 static void
 ofp_print_switch_features(struct ds *string, const void *oh, size_t len,
-                          int verbosity UNUSED)
+                          int verbosity OVS_UNUSED)
 {
     const struct ofp_switch_features *osf = oh;
     struct ofp_phy_port *port_list;
     int n_ports;
     int i;
 
-    ds_put_format(string, " ver:0x%x, dpid:%"PRIx64"\n", 
+    ds_put_format(string, " ver:0x%x, dpid:%016"PRIx64"\n", 
             osf->header.version, ntohll(osf->datapath_id));
     ds_put_format(string, "n_tables:%d, n_buffers:%d\n", osf->n_tables,
             ntohl(osf->n_buffers));
@@ -565,17 +576,13 @@ ofp_print_switch_features(struct ds *string, const void *oh, size_t len,
 /* Pretty-print the struct ofp_switch_config of 'len' bytes at 'oh' to 'string'
  * at the given 'verbosity' level. */
 static void
-ofp_print_switch_config(struct ds *string, const void *oh, size_t len UNUSED,
-                        int verbosity UNUSED)
+ofp_print_switch_config(struct ds *string, const void *oh,
+                        size_t len OVS_UNUSED, int verbosity OVS_UNUSED)
 {
     const struct ofp_switch_config *osc = oh;
     uint16_t flags;
 
     flags = ntohs(osc->flags);
-    if (flags & OFPC_SEND_FLOW_EXP) {
-        flags &= ~OFPC_SEND_FLOW_EXP;
-        ds_put_format(string, " (sending flow expirations)");
-    }
     if (flags) {
         ds_put_format(string, " ***unknown flags 0x%04"PRIx16"***", flags);
     }
@@ -669,6 +676,8 @@ ofp_match_to_string(const struct ofp_match *om, int verbosity)
                "%d", ntohs(om->in_port));
     print_wild(&f, "dl_vlan=", w & OFPFW_DL_VLAN, verbosity,
                "0x%04x", ntohs(om->dl_vlan));
+    print_wild(&f, "dl_vlan_pcp=", w & OFPFW_DL_VLAN_PCP, verbosity,
+               "%d", om->dl_vlan_pcp);
     print_wild(&f, "dl_src=", w & OFPFW_DL_SRC, verbosity,
                ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_src));
     print_wild(&f, "dl_dst=", w & OFPFW_DL_DST, verbosity,
@@ -682,8 +691,15 @@ ofp_match_to_string(const struct ofp_match *om, int verbosity)
     print_ip_netmask(&f, "nw_dst=", om->nw_dst,
                      (w & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT, verbosity);
     if (!skip_proto) {
-        print_wild(&f, "nw_proto=", w & OFPFW_NW_PROTO, verbosity,
-                   "%u", om->nw_proto);
+        if (om->dl_type == htons(ETH_TYPE_ARP)) {
+            print_wild(&f, "opcode=", w & OFPFW_NW_PROTO, verbosity,
+                       "%u", om->nw_proto);
+        } else {
+            print_wild(&f, "nw_proto=", w & OFPFW_NW_PROTO, verbosity,
+                       "%u", om->nw_proto);
+            print_wild(&f, "nw_tos=", w & OFPFW_NW_TOS, verbosity,
+                       "%u", om->nw_tos);
+        }
     }
     if (om->nw_proto == IP_TYPE_ICMP) {
         print_wild(&f, "icmp_type=", w & OFPFW_ICMP_TYPE, verbosity,
@@ -727,46 +743,53 @@ ofp_print_flow_mod(struct ds *string, const void *oh, size_t len,
     default:
         ds_put_format(string, " cmd:%d ", ntohs(ofm->command));
     }
-    ds_put_format(string, "idle:%d hard:%d pri:%d buf:%#x", 
+    ds_put_format(string, "cookie:%"PRIx64" idle:%d hard:%d pri:%d "
+            "buf:%#x flags:%"PRIx16" ", ntohll(ofm->cookie), 
             ntohs(ofm->idle_timeout), ntohs(ofm->hard_timeout),
             ofm->match.wildcards ? ntohs(ofm->priority) : (uint16_t)-1,
-            ntohl(ofm->buffer_id));
+            ntohl(ofm->buffer_id), ntohs(ofm->flags));
     ofp_print_actions(string, ofm->actions,
                       len - offsetof(struct ofp_flow_mod, actions));
     ds_put_char(string, '\n');
 }
 
-/* Pretty-print the OFPT_FLOW_EXPIRED packet of 'len' bytes at 'oh' to 'string'
+/* Pretty-print the OFPT_FLOW_REMOVED packet of 'len' bytes at 'oh' to 'string'
  * at the given 'verbosity' level. */
 static void
-ofp_print_flow_expired(struct ds *string, const void *oh, size_t len UNUSED
-                       int verbosity)
+ofp_print_flow_removed(struct ds *string, const void *oh
+                       size_t len OVS_UNUSED, int verbosity)
 {
-    const struct ofp_flow_expired *ofe = oh;
+    const struct ofp_flow_removed *ofr = oh;
 
-    ofp_print_match(string, &ofe->match, verbosity);
+    ofp_print_match(string, &ofr->match, verbosity);
     ds_put_cstr(string, " reason=");
-    switch (ofe->reason) {
-    case OFPER_IDLE_TIMEOUT:
+    switch (ofr->reason) {
+    case OFPRR_IDLE_TIMEOUT:
         ds_put_cstr(string, "idle");
         break;
-    case OFPER_HARD_TIMEOUT:
+    case OFPRR_HARD_TIMEOUT:
         ds_put_cstr(string, "hard");
         break;
+    case OFPRR_DELETE:
+        ds_put_cstr(string, "delete");
+        break;
     default:
-        ds_put_format(string, "**%"PRIu8"**", ofe->reason);
+        ds_put_format(string, "**%"PRIu8"**", ofr->reason);
         break;
     }
     ds_put_format(string, 
-         " pri%"PRIu16" secs%"PRIu32" pkts%"PRIu64" bytes%"PRIu64"\n", 
-         ofe->match.wildcards ? ntohs(ofe->priority) : (uint16_t)-1,
-         ntohl(ofe->duration), ntohll(ofe->packet_count), 
-         ntohll(ofe->byte_count));
+         " cookie%"PRIx64" pri%"PRIu16" secs%"PRIu32" nsecs%"PRIu32
+         " idle%"PRIu16" pkts%"PRIu64" bytes%"PRIu64"\n", 
+         ntohll(ofr->cookie),
+         ofr->match.wildcards ? ntohs(ofr->priority) : (uint16_t)-1,
+         ntohl(ofr->duration_sec), ntohl(ofr->duration_nsec),
+         ntohs(ofr->idle_timeout), ntohll(ofr->packet_count), 
+         ntohll(ofr->byte_count));
 }
 
 static void
-ofp_print_port_mod(struct ds *string, const void *oh, size_t len UNUSED,
-                   int verbosity UNUSED)
+ofp_print_port_mod(struct ds *string, const void *oh, size_t len OVS_UNUSED,
+                   int verbosity OVS_UNUSED)
 {
     const struct ofp_port_mod *opm = oh;
 
@@ -792,12 +815,18 @@ static const struct error_type error_types[] = {
 #define ERROR_CODE(TYPE, CODE) {TYPE, CODE, #CODE}
     ERROR_TYPE(OFPET_HELLO_FAILED),
     ERROR_CODE(OFPET_HELLO_FAILED, OFPHFC_INCOMPATIBLE),
+    ERROR_CODE(OFPET_HELLO_FAILED, OFPHFC_EPERM),
 
     ERROR_TYPE(OFPET_BAD_REQUEST),
     ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_VERSION),
     ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE),
     ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_STAT),
-    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_VERSION),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_EPERM),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BUFFER_EMPTY),
+    ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BUFFER_UNKNOWN),
 
     ERROR_TYPE(OFPET_BAD_ACTION),
     ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_BAD_TYPE),
@@ -805,9 +834,20 @@ static const struct error_type error_types[] = {
     ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_BAD_VENDOR),
     ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_BAD_VENDOR_TYPE),
     ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_BAD_OUT_PORT),
+    ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT),
+    ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_EPERM),
+    ERROR_CODE(OFPET_BAD_ACTION, OFPBAC_TOO_MANY),
 
     ERROR_TYPE(OFPET_FLOW_MOD_FAILED),
-    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_ALL_TABLES_FULL)
+    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_ALL_TABLES_FULL),
+    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_OVERLAP),
+    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_EPERM),
+    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_BAD_EMERG_TIMEOUT),
+    ERROR_CODE(OFPET_FLOW_MOD_FAILED, OFPFMFC_BAD_COMMAND),
+
+    ERROR_TYPE(OFPET_PORT_MOD_FAILED),
+    ERROR_CODE(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_PORT),
+    ERROR_CODE(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_HW_ADDR)
 };
 #define N_ERROR_TYPES ARRAY_SIZE(error_types)
 
@@ -841,7 +881,7 @@ lookup_error_code(int type, int code)
  * at the given 'verbosity' level. */
 static void
 ofp_print_error_msg(struct ds *string, const void *oh, size_t len, 
-                       int verbosity UNUSED)
+                       int verbosity OVS_UNUSED)
 {
     const struct ofp_error_msg *oem = oh;
     int type = ntohs(oem->type);
@@ -872,8 +912,8 @@ ofp_print_error_msg(struct ds *string, const void *oh, size_t len,
 /* Pretty-print the OFPT_PORT_STATUS packet of 'len' bytes at 'oh' to 'string'
  * at the given 'verbosity' level. */
 static void
-ofp_print_port_status(struct ds *string, const void *oh, size_t len UNUSED,
-                      int verbosity UNUSED)
+ofp_print_port_status(struct ds *string, const void *oh, size_t len OVS_UNUSED,
+                      int verbosity OVS_UNUSED)
 {
     const struct ofp_port_status *ops = oh;
 
@@ -889,20 +929,26 @@ ofp_print_port_status(struct ds *string, const void *oh, size_t len UNUSED,
 }
 
 static void
-ofp_desc_stats_reply(struct ds *string, const void *body, size_t len UNUSED,
-                     int verbosity UNUSED)
+ofp_desc_stats_reply(struct ds *string, const void *body,
+                     size_t len OVS_UNUSED, int verbosity OVS_UNUSED)
 {
     const struct ofp_desc_stats *ods = body;
 
-    ds_put_format(string, "Manufacturer: %s\n", ods->mfr_desc);
-    ds_put_format(string, "Hardware: %s\n", ods->hw_desc);
-    ds_put_format(string, "Software: %s\n", ods->sw_desc);
-    ds_put_format(string, "Serial Num: %s\n", ods->serial_num);
+    ds_put_format(string, "Manufacturer: %.*s\n", 
+            (int) sizeof ods->mfr_desc, ods->mfr_desc);
+    ds_put_format(string, "Hardware: %.*s\n",
+            (int) sizeof ods->hw_desc, ods->hw_desc);
+    ds_put_format(string, "Software: %.*s\n",
+            (int) sizeof ods->sw_desc, ods->sw_desc);
+    ds_put_format(string, "Serial Num: %.*s\n",
+            (int) sizeof ods->serial_num, ods->serial_num);
+    ds_put_format(string, "DP Description: %.*s\n",
+            (int) sizeof ods->dp_desc, ods->dp_desc);
 }
 
 static void
-ofp_flow_stats_request(struct ds *string, const void *oh, size_t len UNUSED,
-                      int verbosity) 
+ofp_flow_stats_request(struct ds *string, const void *oh,
+                       size_t len OVS_UNUSED, int verbosity)
 {
     const struct ofp_flow_stats_request *fsr = oh;
 
@@ -954,7 +1000,11 @@ ofp_flow_stats_reply(struct ds *string, const void *body_, size_t len,
             break;
         }
 
-        ds_put_format(string, "  duration=%"PRIu32"s, ", ntohl(fs->duration));
+        ds_put_format(string, "  cookie=%"PRIu64", ", ntohll(fs->cookie));
+        ds_put_format(string, "duration_sec=%"PRIu32"s, ", 
+                    ntohl(fs->duration_sec));
+        ds_put_format(string, "duration_nsec=%"PRIu32"ns, ", 
+                    ntohl(fs->duration_nsec));
         ds_put_format(string, "table_id=%"PRIu8", ", fs->table_id);
         ds_put_format(string, "priority=%"PRIu16", ", 
                     fs->match.wildcards ? ntohs(fs->priority) : (uint16_t)-1);
@@ -979,7 +1029,7 @@ ofp_flow_stats_reply(struct ds *string, const void *body_, size_t len,
 
 static void
 ofp_aggregate_stats_request(struct ds *string, const void *oh,
-                            size_t len UNUSED, int verbosity)
+                            size_t len OVS_UNUSED, int verbosity)
 {
     const struct ofp_aggregate_stats_request *asr = oh;
 
@@ -994,7 +1044,7 @@ ofp_aggregate_stats_request(struct ds *string, const void *oh,
 
 static void
 ofp_aggregate_stats_reply(struct ds *string, const void *body_,
-                          size_t len UNUSED, int verbosity UNUSED)
+                          size_t len OVS_UNUSED, int verbosity OVS_UNUSED)
 {
     const struct ofp_aggregate_stats_reply *asr = body_;
 
@@ -1019,6 +1069,14 @@ static void print_port_stat(struct ds *string, const char *leader,
     }
 }
 
+static void
+ofp_port_stats_request(struct ds *string, const void *body_,
+                       size_t len OVS_UNUSED, int verbosity OVS_UNUSED)
+{
+    const struct ofp_port_stats_request *psr = body_;
+    ds_put_format(string, "port_no=%"PRIu16, ntohs(psr->port_no));
+}
+
 static void
 ofp_port_stats_reply(struct ds *string, const void *body, size_t len,
                      int verbosity)
@@ -1081,7 +1139,7 @@ ofp_table_stats_reply(struct ds *string, const void *body, size_t len,
 
 static void
 vendor_stat(struct ds *string, const void *body, size_t len,
-            int verbosity UNUSED)
+            int verbosity OVS_UNUSED)
 {
     ds_put_format(string, " vendor=%08"PRIx32, ntohl(*(uint32_t *) body));
     ds_put_format(string, " %zu bytes additional data",
@@ -1143,7 +1201,9 @@ print_stats(struct ds *string, int type, const void *body, size_t body_len,
         {
             OFPST_PORT,
             "port",
-            { 0, 0, NULL, },
+            { sizeof(struct ofp_port_stats_request), 
+              sizeof(struct ofp_port_stats_request), 
+              ofp_port_stats_request },
             { 0, SIZE_MAX, ofp_port_stats_reply },
         },
         {
@@ -1298,10 +1358,10 @@ static const struct openflow_packet packets[] = {
         ofp_print_flow_mod,
     },
     {
-        OFPT_FLOW_EXPIRED,
-        "flow_expired",
-        sizeof (struct ofp_flow_expired),
-        ofp_print_flow_expired,
+        OFPT_FLOW_REMOVED,
+        "flow_removed",
+        sizeof (struct ofp_flow_removed),
+        ofp_print_flow_removed,
     },
     {
         OFPT_PORT_MOD,
@@ -1351,6 +1411,18 @@ static const struct openflow_packet packets[] = {
         sizeof (struct ofp_vendor_header),
         NULL,
     },
+    {
+        OFPT_BARRIER_REQUEST,
+        "barrier_request",
+        sizeof (struct ofp_header),
+        NULL,
+    },
+    {
+        OFPT_BARRIER_REPLY,
+        "barrier_reply",
+        sizeof (struct ofp_header),
+        NULL,
+    }
 };
 
 /* Composes and returns a string representing the OpenFlow packet of 'len'
index d1407e7..9cb2ceb 100644 (file)
@@ -19,6 +19,7 @@
 #include <assert.h>
 #include <stdlib.h>
 #include <string.h>
+#include "dynamic-string.h"
 #include "util.h"
 
 /* Initializes 'b' as an empty ofpbuf that contains the 'allocated' bytes of
@@ -36,7 +37,7 @@ ofpbuf_use(struct ofpbuf *b, void *base, size_t allocated)
     b->size = 0;
     b->l2 = b->l3 = b->l4 = b->l7 = NULL;
     b->next = NULL;
-    b->private = NULL;
+    b->private_p = NULL;
 }
 
 /* Initializes 'b' as an empty ofpbuf with an initial capacity of 'size'
@@ -103,7 +104,7 @@ ofpbuf_delete(struct ofpbuf *b)
  * commonly, the data in a ofpbuf is at its beginning, and thus the ofpbuf's
  * headroom is 0.) */
 size_t
-ofpbuf_headroom(struct ofpbuf *b) 
+ofpbuf_headroom(const struct ofpbuf *b)
 {
     return (char*)b->data - (char*)b->base;
 }
@@ -111,7 +112,7 @@ ofpbuf_headroom(struct ofpbuf *b)
 /* Returns the number of bytes that may be appended to the tail end of ofpbuf
  * 'b' before the ofpbuf must be reallocated. */
 size_t
-ofpbuf_tailroom(struct ofpbuf *b) 
+ofpbuf_tailroom(const struct ofpbuf *b)
 {
     return (char*)ofpbuf_end(b) - (char*)ofpbuf_tail(b);
 }
@@ -286,3 +287,18 @@ ofpbuf_try_pull(struct ofpbuf *b, size_t size)
 {
     return b->size >= size ? ofpbuf_pull(b, size) : NULL;
 }
+
+/* Returns a string that describes some of 'b''s metadata plus a hex dump of up
+ * to 'maxbytes' from the start of the buffer. */
+char *
+ofpbuf_to_string(const struct ofpbuf *b, size_t maxbytes)
+{
+    struct ds s;
+
+    ds_init(&s);
+    ds_put_format(&s, "size=%zu, allocated=%zu, head=%zu, tail=%zu\n",
+                  b->size, b->allocated,
+                  ofpbuf_headroom(b), ofpbuf_tailroom(b));
+    ds_put_hex_dump(&s, b->data, MIN(b->size, maxbytes), 0, false);
+    return ds_cstr(&s);
+}
index 06083ae..9072cc4 100644 (file)
 
 #include <stddef.h>
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 /* Buffer for holding arbitrary data.  An ofpbuf is automatically reallocated
  * as necessary if it grows too large for the available memory. */
 struct ofpbuf {
@@ -34,7 +38,7 @@ struct ofpbuf {
     void *l7;                   /* Application data. */
 
     struct ofpbuf *next;        /* Next in a list of ofpbufs. */
-    void *private;              /* Private pointer for use by owner. */
+    void *private_p;            /* Private pointer for use by owner. */
 };
 
 void ofpbuf_use(struct ofpbuf *, void *, size_t);
@@ -60,8 +64,8 @@ void ofpbuf_reserve(struct ofpbuf *, size_t);
 void *ofpbuf_push_uninit(struct ofpbuf *b, size_t);
 void *ofpbuf_push(struct ofpbuf *b, const void *, size_t);
 
-size_t ofpbuf_headroom(struct ofpbuf *);
-size_t ofpbuf_tailroom(struct ofpbuf *);
+size_t ofpbuf_headroom(const struct ofpbuf *);
+size_t ofpbuf_tailroom(const struct ofpbuf *);
 void ofpbuf_prealloc_headroom(struct ofpbuf *, size_t);
 void ofpbuf_prealloc_tailroom(struct ofpbuf *, size_t);
 void ofpbuf_trim(struct ofpbuf *);
@@ -70,4 +74,10 @@ void ofpbuf_clear(struct ofpbuf *);
 void *ofpbuf_pull(struct ofpbuf *, size_t);
 void *ofpbuf_try_pull(struct ofpbuf *, size_t);
 
+char *ofpbuf_to_string(const struct ofpbuf *, size_t maxbytes);
+
+#ifdef  __cplusplus
+}
+#endif
+
 #endif /* ofpbuf.h */
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
new file mode 100644 (file)
index 0000000..42fdf1e
--- /dev/null
@@ -0,0 +1,1617 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-data.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <float.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "shash.h"
+#include "sort.h"
+#include "unicode.h"
+
+static struct json *
+wrap_json(const char *name, struct json *wrapped)
+{
+    return json_array_create_2(json_string_create(name), wrapped);
+}
+
+void
+ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        atom->integer = 0;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        atom->real = 0.0;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        atom->boolean = false;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        atom->string = xmemdup("", 1);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        uuid_zero(&atom->uuid);
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+bool
+ovsdb_atom_is_default(const union ovsdb_atom *atom,
+                      enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return atom->integer == 0;
+
+    case OVSDB_TYPE_REAL:
+        return atom->real == 0.0;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return atom->boolean == false;
+
+    case OVSDB_TYPE_STRING:
+        return atom->string[0] == '\0';
+
+    case OVSDB_TYPE_UUID:
+        return uuid_is_zero(&atom->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+                 enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        new->integer = old->integer;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        new->real = old->real;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        new->boolean = old->boolean;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        new->string = xstrdup(old->string);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        new->uuid = old->uuid;
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b)
+{
+    union ovsdb_atom tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+uint32_t
+ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                uint32_t basis)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return hash_int(atom->integer, basis);
+
+    case OVSDB_TYPE_REAL:
+        return hash_double(atom->real, basis);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return hash_boolean(atom->boolean, basis);
+
+    case OVSDB_TYPE_STRING:
+        return hash_string(atom->string, basis);
+
+    case OVSDB_TYPE_UUID:
+        return hash_int(uuid_hash(&atom->uuid), basis);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+int
+ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+                        const union ovsdb_atom *b,
+                        enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return a->integer < b->integer ? -1 : a->integer > b->integer;
+
+    case OVSDB_TYPE_REAL:
+        return a->real < b->real ? -1 : a->real > b->real;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return a->boolean - b->boolean;
+
+    case OVSDB_TYPE_STRING:
+        return strcmp(a->string, b->string);
+
+    case OVSDB_TYPE_UUID:
+        return uuid_compare_3way(&a->uuid, &b->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct ovsdb_error *
+unwrap_json(const struct json *json, const char *name,
+            enum json_type value_type, const struct json **value)
+{
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 2
+        || json->u.array.elems[0]->type != JSON_STRING
+        || (name && strcmp(json->u.array.elems[0]->u.string, name))
+        || json->u.array.elems[1]->type != value_type)
+    {
+        return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name,
+                                  json_type_to_string(value_type));
+    }
+    *value = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+parse_json_pair(const struct json *json,
+                const struct json **elem0, const struct json **elem1)
+{
+    if (json->type != JSON_ARRAY || json->u.array.n != 2) {
+        return ovsdb_syntax_error(json, NULL, "expected 2-element array");
+    }
+    *elem0 = json->u.array.elems[0];
+    *elem1 = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error0;
+    const struct json *value;
+
+    error0 = unwrap_json(json, "uuid", JSON_STRING, &value);
+    if (!error0) {
+        const char *uuid_string = json_string(value);
+        if (!uuid_from_string(uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+    } else if (symtab) {
+        struct ovsdb_error *error1;
+
+        error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value);
+        if (!error1) {
+            const char *name = json_string(value);
+
+            ovsdb_error_destroy(error0);
+            *uuid = ovsdb_symbol_table_insert(symtab, name)->uuid;
+            return NULL;
+        }
+        ovsdb_error_destroy(error1);
+    }
+
+    return error0;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_atom_from_json__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                       const struct json *json,
+                       struct ovsdb_symbol_table *symtab)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (json->type == JSON_INTEGER) {
+            atom->integer = json->u.integer;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (json->type == JSON_INTEGER) {
+            atom->real = json->u.integer;
+            return NULL;
+        } else if (json->type == JSON_REAL) {
+            atom->real = json->u.real;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (json->type == JSON_TRUE) {
+            atom->boolean = true;
+            return NULL;
+        } else if (json->type == JSON_FALSE) {
+            atom->boolean = false;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (json->type == JSON_STRING) {
+            atom->string = xstrdup(json->u.string);
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return ovsdb_syntax_error(json, NULL, "expected %s",
+                              ovsdb_atomic_type_to_string(type));
+}
+
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom,
+                     const struct ovsdb_base_type *base,
+                     const struct json *json,
+                     struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    error = ovsdb_atom_from_json__(atom, base->type, json, symtab);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_atom_check_constraints(atom, base);
+    if (error) {
+        ovsdb_atom_destroy(atom, base->type);
+    }
+    return error;
+}
+
+struct json *
+ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return json_integer_create(atom->integer);
+
+    case OVSDB_TYPE_REAL:
+        return json_real_create(atom->real);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return json_boolean_create(atom->boolean);
+
+    case OVSDB_TYPE_STRING:
+        return json_string_create(atom->string);
+
+    case OVSDB_TYPE_UUID:
+        return wrap_json("uuid", json_string_create_nocopy(
+                             xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid))));
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static char *
+ovsdb_atom_from_string__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                         const char *s)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER: {
+        long long int integer;
+        if (!str_to_llong(s, 10, &integer)) {
+            return xasprintf("\"%s\" is not a valid integer", s);
+        }
+        atom->integer = integer;
+    }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (!str_to_double(s, &atom->real)) {
+            return xasprintf("\"%s\" is not a valid real number", s);
+        }
+        /* Our JSON input routines map negative zero to zero, so do that here
+         * too for consistency. */
+        if (atom->real == 0.0) {
+            atom->real = 0.0;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (!strcmp(s, "true") || !strcmp(s, "yes") || !strcmp(s, "on")
+            || !strcmp(s, "1")) {
+            atom->boolean = true;
+        } else if (!strcmp(s, "false") || !strcmp(s, "no") || !strcmp(s, "off")
+                   || !strcmp(s, "0")) {
+            atom->boolean = false;
+        } else {
+            return xasprintf("\"%s\" is not a valid boolean "
+                             "(use \"true\" or \"false\")", s);
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (*s == '\0') {
+            return xstrdup("An empty string is not valid as input; "
+                           "use \"\" to represent the empty string");
+        } else if (*s == '"') {
+            size_t s_len = strlen(s);
+
+            if (s_len < 2 || s[s_len - 1] != '"') {
+                return xasprintf("%s: missing quote at end of "
+                                 "quoted string", s);
+            } else if (!json_string_unescape(s + 1, s_len - 2,
+                                             &atom->string)) {
+                char *error = xasprintf("%s: %s", s, atom->string);
+                free(atom->string);
+                return error;
+            }
+        } else {
+            atom->string = xstrdup(s);
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        if (!uuid_from_string(&atom->uuid, s)) {
+            return xasprintf("\"%s\" is not a valid UUID", s);
+        }
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return NULL;
+}
+
+/* Initializes 'atom' to a value of type 'base' parsed from 's', which takes
+ * one of the following forms:
+ *
+ *      - OVSDB_TYPE_INTEGER: A decimal integer optionally preceded by a sign.
+ *
+ *      - OVSDB_TYPE_REAL: A floating-point number in the format accepted by
+ *        strtod().
+ *
+ *      - OVSDB_TYPE_BOOLEAN: "true", "yes", "on", "1" for true, or "false",
+ *        "no", "off", or "0" for false.
+ *
+ *      - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
+ *        an arbitrary string.
+ *
+ *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.
+ *
+ * Returns a null pointer if successful, otherwise an error message describing
+ * the problem.  The caller is responsible for freeing the error.
+ */
+char *
+ovsdb_atom_from_string(union ovsdb_atom *atom,
+                       const struct ovsdb_base_type *base, const char *s)
+{
+    struct ovsdb_error *error;
+    char *msg;
+
+    msg = ovsdb_atom_from_string__(atom, base->type, s);
+    if (msg) {
+        return msg;
+    }
+
+    error = ovsdb_atom_check_constraints(atom, base);
+    if (error) {
+        msg = ovsdb_error_to_string(error);
+        ovsdb_error_destroy(error);
+    }
+    return msg;
+}
+
+static bool
+string_needs_quotes(const char *s)
+{
+    const char *p = s;
+    unsigned char c;
+
+    c = *p++;
+    if (!isalpha(c) && c != '_') {
+        return true;
+    }
+
+    while ((c = *p++) != '\0') {
+        if (!isalpha(c) && c != '_' && c != '-' && c != '.') {
+            return true;
+        }
+    }
+
+    if (!strcmp(s, "true") || !strcmp(s, "false")) {
+        return true;
+    }
+
+    return false;
+}
+
+/* Appends 'atom' (which has the given 'type') to 'out', in a format acceptable
+ * to ovsdb_atom_from_string().  */
+void
+ovsdb_atom_to_string(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                     struct ds *out)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        ds_put_format(out, "%"PRId64, atom->integer);
+        break;
+
+    case OVSDB_TYPE_REAL:
+        ds_put_format(out, "%.*g", DBL_DIG, atom->real);
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        ds_put_cstr(out, atom->boolean ? "true" : "false");
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (string_needs_quotes(atom->string)) {
+            struct json json;
+
+            json.type = JSON_STRING;
+            json.u.string = atom->string;
+            json_to_ds(&json, 0, out);
+        } else {
+            ds_put_cstr(out, atom->string);
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        ds_put_format(out, UUID_FMT, UUID_ARGS(&atom->uuid));
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct ovsdb_error *
+check_string_constraints(const char *s,
+                         const struct ovsdb_string_constraints *c)
+{
+    size_t n_chars;
+    char *msg;
+
+    msg = utf8_validate(s, &n_chars);
+    if (msg) {
+        struct ovsdb_error *error;
+
+        error = ovsdb_error("constraint violation",
+                            "\"%s\" is not a valid UTF-8 string: %s",
+                            s, msg);
+        free(msg);
+        return error;
+    }
+
+    if (n_chars < c->minLen) {
+        return ovsdb_error(
+            "constraint violation",
+            "\"%s\" length %zu is less than minimum allowed "
+            "length %u", s, n_chars, c->minLen);
+    } else if (n_chars > c->maxLen) {
+        return ovsdb_error(
+            "constraint violation",
+            "\"%s\" length %zu is greater than maximum allowed "
+            "length %u", s, n_chars, c->maxLen);
+    }
+
+    return NULL;
+}
+
+/* Checks whether 'atom' meets the constraints (if any) defined in 'base'.
+ * (base->type must specify 'atom''s type.)  Returns a null pointer if the
+ * constraints are met, otherwise an error that explains the violation.
+ *
+ * Checking UUID constraints is deferred to transaction commit time, so this
+ * function does nothing for UUID constraints. */
+struct ovsdb_error *
+ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
+                             const struct ovsdb_base_type *base)
+{
+    if (base->enum_
+        && ovsdb_datum_find_key(base->enum_, atom, base->type) == UINT_MAX) {
+        struct ovsdb_error *error;
+        struct ds actual = DS_EMPTY_INITIALIZER;
+        struct ds valid = DS_EMPTY_INITIALIZER;
+
+        ovsdb_atom_to_string(atom, base->type, &actual);
+        ovsdb_datum_to_string(base->enum_,
+                              ovsdb_base_type_get_enum_type(base->type),
+                              &valid);
+        error = ovsdb_error("constraint violation",
+                            "%s is not one of the allowed values (%s)",
+                            ds_cstr(&actual), ds_cstr(&valid));
+        ds_destroy(&actual);
+        ds_destroy(&valid);
+
+        return error;
+    }
+
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (atom->integer >= base->u.integer.min
+            && atom->integer <= base->u.integer.max) {
+            return NULL;
+        } else if (base->u.integer.min != INT64_MIN) {
+            if (base->u.integer.max != INT64_MAX) {
+                return ovsdb_error("constraint violation",
+                                   "%"PRId64" is not in the valid range "
+                                   "%"PRId64" to %"PRId64" (inclusive)",
+                                   atom->integer,
+                                   base->u.integer.min, base->u.integer.max);
+            } else {
+                return ovsdb_error("constraint violation",
+                                   "%"PRId64" is less than minimum allowed "
+                                   "value %"PRId64,
+                                   atom->integer, base->u.integer.min);
+            }
+        } else {
+            return ovsdb_error("constraint violation",
+                               "%"PRId64" is greater than maximum allowed "
+                               "value %"PRId64,
+                               atom->integer, base->u.integer.max);
+        }
+        NOT_REACHED();
+
+    case OVSDB_TYPE_REAL:
+        if (atom->real >= base->u.real.min && atom->real <= base->u.real.max) {
+            return NULL;
+        } else if (base->u.real.min != -DBL_MAX) {
+            if (base->u.real.max != DBL_MAX) {
+                return ovsdb_error("constraint violation",
+                                   "%.*g is not in the valid range "
+                                   "%.*g to %.*g (inclusive)",
+                                   DBL_DIG, atom->real,
+                                   DBL_DIG, base->u.real.min,
+                                   DBL_DIG, base->u.real.max);
+            } else {
+                return ovsdb_error("constraint violation",
+                                   "%.*g is less than minimum allowed "
+                                   "value %.*g",
+                                   DBL_DIG, atom->real,
+                                   DBL_DIG, base->u.real.min);
+            }
+        } else {
+            return ovsdb_error("constraint violation",
+                               "%.*g is greater than maximum allowed "
+                               "value %.*g",
+                               DBL_DIG, atom->real,
+                               DBL_DIG, base->u.real.max);
+        }
+        NOT_REACHED();
+
+    case OVSDB_TYPE_BOOLEAN:
+        return NULL;
+
+    case OVSDB_TYPE_STRING:
+        return check_string_constraints(atom->string, &base->u.string);
+
+    case OVSDB_TYPE_UUID:
+        return NULL;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+static union ovsdb_atom *
+alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *atoms;
+        unsigned int i;
+
+        atoms = xmalloc(n * sizeof *atoms);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_init_default(&atoms[i], type);
+        }
+        return atoms;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_init_empty(struct ovsdb_datum *datum)
+{
+    datum->n = 0;
+    datum->keys = NULL;
+    datum->values = NULL;
+}
+
+void
+ovsdb_datum_init_default(struct ovsdb_datum *datum,
+                         const struct ovsdb_type *type)
+{
+    datum->n = type->n_min;
+    datum->keys = alloc_default_atoms(type->key.type, datum->n);
+    datum->values = alloc_default_atoms(type->value.type, datum->n);
+}
+
+bool
+ovsdb_datum_is_default(const struct ovsdb_datum *datum,
+                       const struct ovsdb_type *type)
+{
+    size_t i;
+
+    if (datum->n != type->n_min) {
+        return false;
+    }
+    for (i = 0; i < datum->n; i++) {
+        if (!ovsdb_atom_is_default(&datum->keys[i], type->key.type)) {
+            return false;
+        }
+        if (type->value.type != OVSDB_TYPE_VOID
+            && !ovsdb_atom_is_default(&datum->values[i], type->value.type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static union ovsdb_atom *
+clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *new;
+        unsigned int i;
+
+        new = xmalloc(n * sizeof *new);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_clone(&new[i], &old[i], type);
+        }
+        return new;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
+                  const struct ovsdb_type *type)
+{
+    unsigned int n = old->n;
+    new->n = n;
+    new->keys = clone_atoms(old->keys, type->key.type, n);
+    new->values = clone_atoms(old->values, type->value.type, n);
+}
+
+static void
+free_data(enum ovsdb_atomic_type type,
+          union ovsdb_atom *atoms, size_t n_atoms)
+{
+    if (ovsdb_atom_needs_destruction(type)) {
+        unsigned int i;
+        for (i = 0; i < n_atoms; i++) {
+            ovsdb_atom_destroy(&atoms[i], type);
+        }
+    }
+    free(atoms);
+}
+
+void
+ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    free_data(type->key.type, datum->keys, datum->n);
+    free_data(type->value.type, datum->values, datum->n);
+}
+
+void
+ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
+{
+    struct ovsdb_datum tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+struct ovsdb_datum_sort_cbdata {
+    enum ovsdb_atomic_type key_type;
+    struct ovsdb_datum *datum;
+};
+
+static int
+ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
+                                   &cbdata->datum->keys[b],
+                                   cbdata->key_type);
+}
+
+static void
+ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
+    if (cbdata->datum->values) {
+        ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_datum_sort(struct ovsdb_datum *datum, enum ovsdb_atomic_type key_type)
+{
+    if (datum->n < 2) {
+        return NULL;
+    } else {
+        struct ovsdb_datum_sort_cbdata cbdata;
+        size_t i;
+
+        cbdata.key_type = key_type;
+        cbdata.datum = datum;
+        sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
+             &cbdata);
+
+        for (i = 0; i < datum->n - 1; i++) {
+            if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
+                                  key_type)) {
+                if (datum->values) {
+                    return ovsdb_error(NULL, "map contains duplicate key");
+                } else {
+                    return ovsdb_error(NULL, "set contains duplicate");
+                }
+            }
+        }
+
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_sort_assert(struct ovsdb_datum *datum,
+                        enum ovsdb_atomic_type key_type)
+{
+    struct ovsdb_error *error = ovsdb_datum_sort(datum, key_type);
+    if (error) {
+        NOT_REACHED();
+    }
+}
+
+/* Checks that each of the atoms in 'datum' conforms to the constraints
+ * specified by its 'type'.  Returns an error if a constraint is violated,
+ * otherwise a null pointer.
+ *
+ * This function is not commonly useful because the most ordinary way to obtain
+ * a datum is ultimately via ovsdb_atom_from_string() or
+ * ovsdb_atom_from_json(), which check constraints themselves. */
+struct ovsdb_error *
+ovsdb_datum_check_constraints(const struct ovsdb_datum *datum,
+                              const struct ovsdb_type *type)
+{
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    for (i = 0; i < datum->n; i++) {
+        error = ovsdb_atom_check_constraints(&datum->keys[i], &type->key);
+        if (error) {
+            return error;
+        }
+    }
+
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        for (i = 0; i < datum->n; i++) {
+            error = ovsdb_atom_check_constraints(&datum->values[i],
+                                                 &type->value);
+            if (error) {
+                return error;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_datum_from_json(struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type,
+                      const struct json *json,
+                      struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    if (ovsdb_type_is_map(type)
+        || (json->type == JSON_ARRAY
+            && json->u.array.n > 0
+            && json->u.array.elems[0]->type == JSON_STRING
+            && !strcmp(json->u.array.elems[0]->u.string, "set"))) {
+        bool is_map = ovsdb_type_is_map(type);
+        const char *class = is_map ? "map" : "set";
+        const struct json *inner;
+        unsigned int i;
+        size_t n;
+
+        error = unwrap_json(json, class, JSON_ARRAY, &inner);
+        if (error) {
+            return error;
+        }
+
+        n = inner->u.array.n;
+        if (n < type->n_min || n > type->n_max) {
+            return ovsdb_syntax_error(json, NULL, "%s must have %u to "
+                                      "%u members but %zu are present",
+                                      class, type->n_min, type->n_max, n);
+        }
+
+        datum->n = 0;
+        datum->keys = xmalloc(n * sizeof *datum->keys);
+        datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL;
+        for (i = 0; i < n; i++) {
+            const struct json *element = inner->u.array.elems[i];
+            const struct json *key = NULL;
+            const struct json *value = NULL;
+
+            if (!is_map) {
+                key = element;
+            } else {
+                error = parse_json_pair(element, &key, &value);
+                if (error) {
+                    goto error;
+                }
+            }
+
+            error = ovsdb_atom_from_json(&datum->keys[i], &type->key,
+                                         key, symtab);
+            if (error) {
+                goto error;
+            }
+
+            if (is_map) {
+                error = ovsdb_atom_from_json(&datum->values[i],
+                                             &type->value, value, symtab);
+                if (error) {
+                    ovsdb_atom_destroy(&datum->keys[i], type->key.type);
+                    goto error;
+                }
+            }
+
+            datum->n++;
+        }
+
+        error = ovsdb_datum_sort(datum, type->key.type);
+        if (error) {
+            goto error;
+        }
+
+        return NULL;
+
+    error:
+        ovsdb_datum_destroy(datum, type);
+        return error;
+    } else {
+        datum->n = 1;
+        datum->keys = xmalloc(sizeof *datum->keys);
+        datum->values = NULL;
+
+        error = ovsdb_atom_from_json(&datum->keys[0], &type->key,
+                                     json, symtab);
+        if (error) {
+            free(datum->keys);
+        }
+        return error;
+    }
+}
+
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+                    const struct ovsdb_type *type)
+{
+    /* These tests somewhat tolerate a 'datum' that does not exactly match
+     * 'type', in particular a datum with 'n' not in the allowed range. */
+    if (datum->n == 1 && !ovsdb_type_is_map(type)) {
+        return ovsdb_atom_to_json(&datum->keys[0], type->key.type);
+    } else if (type->value.type == OVSDB_TYPE_VOID) {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key.type);
+        }
+
+        return wrap_json("set", json_array_create(elems, datum->n));
+    } else {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = json_array_create_2(
+                ovsdb_atom_to_json(&datum->keys[i], type->key.type),
+                ovsdb_atom_to_json(&datum->values[i], type->value.type));
+        }
+
+        return wrap_json("map", json_array_create(elems, datum->n));
+    }
+}
+
+static const char *
+skip_spaces(const char *p)
+{
+    while (isspace((unsigned char) *p)) {
+        p++;
+    }
+    return p;
+}
+
+static char *
+parse_atom_token(const char **s, const struct ovsdb_base_type *base,
+                 union ovsdb_atom *atom)
+{
+    char *token, *error;
+
+    error = ovsdb_token_parse(s, &token);
+    if (!error) {
+        error = ovsdb_atom_from_string(atom, base, token);
+        free(token);
+    }
+    return error;
+}
+
+static char *
+parse_key_value(const char **s, const struct ovsdb_type *type,
+                union ovsdb_atom *key, union ovsdb_atom *value)
+{
+    const char *start = *s;
+    char *error;
+
+    error = parse_atom_token(s, &type->key, key);
+    if (!error && type->value.type != OVSDB_TYPE_VOID) {
+        *s = skip_spaces(*s);
+        if (**s == '=') {
+            (*s)++;
+            *s = skip_spaces(*s);
+            error = parse_atom_token(s, &type->value, value);
+        } else {
+            error = xasprintf("%s: syntax error at \"%c\" expecting \"=\"",
+                              start, **s);
+        }
+        if (error) {
+            ovsdb_atom_destroy(key, type->key.type);
+        }
+    }
+    return error;
+}
+
+static void
+free_key_value(const struct ovsdb_type *type,
+               union ovsdb_atom *key, union ovsdb_atom *value)
+{
+    ovsdb_atom_destroy(key, type->key.type);
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(value, type->value.type);
+    }
+}
+
+/* Initializes 'datum' as a datum of the given 'type', parsing its contents
+ * from 's'.  The format of 's' is a series of space or comma separated atoms
+ * or, for a map, '='-delimited pairs of atoms.  Each atom must in a format
+ * acceptable to ovsdb_atom_from_string().  Optionally, a set may be enclosed
+ * in "[]" or a map in "{}"; for an empty set or map these punctuators are
+ * required. */
+char *
+ovsdb_datum_from_string(struct ovsdb_datum *datum,
+                        const struct ovsdb_type *type, const char *s)
+{
+    bool is_map = ovsdb_type_is_map(type);
+    struct ovsdb_error *dberror;
+    const char *p;
+    int end_delim;
+    char *error;
+
+    ovsdb_datum_init_empty(datum);
+
+    /* Swallow a leading delimiter if there is one. */
+    p = skip_spaces(s);
+    if (*p == (is_map ? '{' : '[')) {
+        end_delim = is_map ? '}' : ']';
+        p = skip_spaces(p + 1);
+    } else if (!*p) {
+        if (is_map) {
+            return xstrdup("use \"{}\" to specify the empty map");
+        } else {
+            return xstrdup("use \"[]\" to specify the empty set");
+        }
+    } else {
+        end_delim = 0;
+    }
+
+    while (*p && *p != end_delim) {
+        union ovsdb_atom key, value;
+
+        if (ovsdb_token_is_delim(*p)) {
+            error = xasprintf("%s: unexpected \"%c\" parsing %s",
+                              s, *p, ovsdb_type_to_english(type));
+            goto error;
+        }
+
+        /* Add to datum. */
+        error = parse_key_value(&p, type, &key, &value);
+        if (error) {
+            goto error;
+        }
+        ovsdb_datum_add_unsafe(datum, &key, &value, type);
+        free_key_value(type, &key, &value);
+
+        /* Skip optional white space and comma. */
+        p = skip_spaces(p);
+        if (*p == ',') {
+            p = skip_spaces(p + 1);
+        }
+    }
+
+    if (*p != end_delim) {
+        error = xasprintf("%s: missing \"%c\" at end of data", s, end_delim);
+        goto error;
+    }
+    if (end_delim) {
+        p = skip_spaces(p + 1);
+        if (*p) {
+            error = xasprintf("%s: trailing garbage after \"%c\"",
+                              s, end_delim);
+            goto error;
+        }
+    }
+
+    if (datum->n < type->n_min) {
+        error = xasprintf("%s: %u %s specified but the minimum number is %u",
+                          s, datum->n, is_map ? "pair(s)" : "value(s)",
+                          type->n_min);
+        goto error;
+    } else if (datum->n > type->n_max) {
+        error = xasprintf("%s: %u %s specified but the maximum number is %u",
+                          s, datum->n, is_map ? "pair(s)" : "value(s)",
+            type->n_max);
+        goto error;
+    }
+
+    dberror = ovsdb_datum_sort(datum, type->key.type);
+    if (dberror) {
+        ovsdb_error_destroy(dberror);
+        if (ovsdb_type_is_map(type)) {
+            error = xasprintf("%s: map contains duplicate key", s);
+        } else {
+            error = xasprintf("%s: set contains duplicate value", s);
+        }
+        goto error;
+    }
+
+    return NULL;
+
+error:
+    ovsdb_datum_destroy(datum, type);
+    ovsdb_datum_init_empty(datum);
+    return error;
+}
+
+/* Appends to 'out' the 'datum' (with the given 'type') in a format acceptable
+ * to ovsdb_datum_from_string(). */
+void
+ovsdb_datum_to_string(const struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type, struct ds *out)
+{
+    bool is_map = ovsdb_type_is_map(type);
+    size_t i;
+
+    if (type->n_max > 1 || !datum->n) {
+        ds_put_char(out, is_map ? '{' : '[');
+    }
+    for (i = 0; i < datum->n; i++) {
+        if (i > 0) {
+            ds_put_cstr(out, ", ");
+        }
+
+        ovsdb_atom_to_string(&datum->keys[i], type->key.type, out);
+        if (is_map) {
+            ds_put_char(out, '=');
+            ovsdb_atom_to_string(&datum->values[i], type->value.type, out);
+        }
+    }
+    if (type->n_max > 1 || !datum->n) {
+        ds_put_char(out, is_map ? '}' : ']');
+    }
+}
+
+static uint32_t
+hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
+           unsigned int n, uint32_t basis)
+{
+    if (type != OVSDB_TYPE_VOID) {
+        unsigned int i;
+
+        for (i = 0; i < n; i++) {
+            basis = ovsdb_atom_hash(&atoms[i], type, basis);
+        }
+    }
+    return basis;
+}
+
+uint32_t
+ovsdb_datum_hash(const struct ovsdb_datum *datum,
+                 const struct ovsdb_type *type, uint32_t basis)
+{
+    basis = hash_atoms(type->key.type, datum->keys, datum->n, basis);
+    basis ^= (type->key.type << 24) | (type->value.type << 16) | datum->n;
+    basis = hash_atoms(type->value.type, datum->values, datum->n, basis);
+    return basis;
+}
+
+static int
+atom_arrays_compare_3way(const union ovsdb_atom *a,
+                         const union ovsdb_atom *b,
+                         enum ovsdb_atomic_type type,
+                         size_t n)
+{
+    unsigned int i;
+
+    for (i = 0; i < n; i++) {
+        int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_datum_equals(const struct ovsdb_datum *a,
+                   const struct ovsdb_datum *b,
+                   const struct ovsdb_type *type)
+{
+    return !ovsdb_datum_compare_3way(a, b, type);
+}
+
+int
+ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    int cmp;
+
+    if (a->n != b->n) {
+        return a->n < b->n ? -1 : 1;
+    }
+
+    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key.type, a->n);
+    if (cmp) {
+        return cmp;
+    }
+
+    return (type->value.type == OVSDB_TYPE_VOID ? 0
+            : atom_arrays_compare_3way(a->values, b->values, type->value.type,
+                                       a->n));
+}
+
+/* If 'key' is one of the keys in 'datum', returns its index within 'datum',
+ * otherwise UINT_MAX.  'key.type' must be the type of the atoms stored in the
+ * 'keys' array in 'datum'.
+ */
+unsigned int
+ovsdb_datum_find_key(const struct ovsdb_datum *datum,
+                     const union ovsdb_atom *key,
+                     enum ovsdb_atomic_type key_type)
+{
+    unsigned int low = 0;
+    unsigned int high = datum->n;
+    while (low < high) {
+        unsigned int idx = (low + high) / 2;
+        int cmp = ovsdb_atom_compare_3way(key, &datum->keys[idx], key_type);
+        if (cmp < 0) {
+            high = idx;
+        } else if (cmp > 0) {
+            low = idx + 1;
+        } else {
+            return idx;
+        }
+    }
+    return UINT_MAX;
+}
+
+/* If 'key' and 'value' is one of the key-value pairs in 'datum', returns its
+ * index within 'datum', otherwise UINT_MAX.  'key.type' must be the type of
+ * the atoms stored in the 'keys' array in 'datum'.  'value_type' may be the
+ * type of the 'values' atoms or OVSDB_TYPE_VOID to compare only keys.
+ */
+unsigned int
+ovsdb_datum_find_key_value(const struct ovsdb_datum *datum,
+                           const union ovsdb_atom *key,
+                           enum ovsdb_atomic_type key_type,
+                           const union ovsdb_atom *value,
+                           enum ovsdb_atomic_type value_type)
+{
+    unsigned int idx = ovsdb_datum_find_key(datum, key, key_type);
+    if (idx != UINT_MAX
+        && value_type != OVSDB_TYPE_VOID
+        && !ovsdb_atom_equals(&datum->values[idx], value, value_type)) {
+        idx = UINT_MAX;
+    }
+    return idx;
+}
+
+/* If atom 'i' in 'a' is also in 'b', returns its index in 'b', otherwise
+ * UINT_MAX.  'type' must be the type of 'a' and 'b', except that
+ * type->value.type may be set to OVSDB_TYPE_VOID to compare keys but not
+ * values. */
+static unsigned int
+ovsdb_datum_find(const struct ovsdb_datum *a, int i,
+                 const struct ovsdb_datum *b,
+                 const struct ovsdb_type *type)
+{
+    return ovsdb_datum_find_key_value(b,
+                                      &a->keys[i], type->key.type,
+                                      a->values ? &a->values[i] : NULL,
+                                      type->value.type);
+}
+
+/* Returns true if every element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_includes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_find(a, i, b, type) == UINT_MAX) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* Returns true if no element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_find(a, i, b, type) != UINT_MAX) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void
+ovsdb_datum_reallocate(struct ovsdb_datum *a, const struct ovsdb_type *type,
+                       unsigned int capacity)
+{
+    a->keys = xrealloc(a->keys, capacity * sizeof *a->keys);
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        a->values = xrealloc(a->values, capacity * sizeof *a->values);
+    }
+}
+
+/* Removes the element with index 'idx' from 'datum', which has type 'type'.
+ * If 'idx' is not the last element in 'datum', then the removed element is
+ * replaced by the (former) last element.
+ *
+ * This function does not maintain ovsdb_datum invariants.  Use
+ * ovsdb_datum_sort() to check and restore these invariants. */
+void
+ovsdb_datum_remove_unsafe(struct ovsdb_datum *datum, size_t idx,
+                          const struct ovsdb_type *type)
+{
+    ovsdb_atom_destroy(&datum->keys[idx], type->key.type);
+    datum->keys[idx] = datum->keys[datum->n - 1];
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(&datum->values[idx], type->value.type);
+        datum->values[idx] = datum->values[datum->n - 1];
+    }
+    datum->n--;
+}
+
+/* Adds the element with the given 'key' and 'value' to 'datum', which must
+ * have the specified 'type'.
+ *
+ * This function always allocates memory, so it is not an efficient way to add
+ * a number of elements to a datum.
+ *
+ * This function does not maintain ovsdb_datum invariants.  Use
+ * ovsdb_datum_sort() to check and restore these invariants.  (But a datum with
+ * 0 or 1 elements cannot violate the invariants anyhow.) */
+void
+ovsdb_datum_add_unsafe(struct ovsdb_datum *datum,
+                       const union ovsdb_atom *key,
+                       const union ovsdb_atom *value,
+                       const struct ovsdb_type *type)
+{
+    size_t idx = datum->n++;
+    datum->keys = xrealloc(datum->keys, datum->n * sizeof *datum->keys);
+    ovsdb_atom_clone(&datum->keys[idx], key, type->key.type);
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        datum->values = xrealloc(datum->values,
+                                 datum->n * sizeof *datum->values);
+        ovsdb_atom_clone(&datum->values[idx], value, type->value.type);
+    }
+}
+
+void
+ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b,
+                  const struct ovsdb_type *type, bool replace)
+{
+    unsigned int n;
+    size_t bi;
+
+    n = a->n;
+    for (bi = 0; bi < b->n; bi++) {
+        unsigned int ai;
+
+        ai = ovsdb_datum_find_key(a, &b->keys[bi], type->key.type);
+        if (ai == UINT_MAX) {
+            if (n == a->n) {
+                ovsdb_datum_reallocate(a, type, a->n + (b->n - bi));
+            }
+            ovsdb_atom_clone(&a->keys[n], &b->keys[bi], type->key.type);
+            if (type->value.type != OVSDB_TYPE_VOID) {
+                ovsdb_atom_clone(&a->values[n], &b->values[bi],
+                                 type->value.type);
+            }
+            n++;
+        } else if (replace && type->value.type != OVSDB_TYPE_VOID) {
+            ovsdb_atom_destroy(&a->values[ai], type->value.type);
+            ovsdb_atom_clone(&a->values[ai], &b->values[bi],
+                             type->value.type);
+        }
+    }
+    if (n != a->n) {
+        struct ovsdb_error *error;
+        a->n = n;
+        error = ovsdb_datum_sort(a, type->key.type);
+        assert(!error);
+    }
+}
+
+void
+ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
+                     const struct ovsdb_datum *b,
+                     const struct ovsdb_type *b_type)
+{
+    bool changed = false;
+    size_t i;
+
+    assert(a_type->key.type == b_type->key.type);
+    assert(a_type->value.type == b_type->value.type
+           || b_type->value.type == OVSDB_TYPE_VOID);
+
+    /* XXX The big-O of this could easily be improved. */
+    for (i = 0; i < a->n; ) {
+        unsigned int idx = ovsdb_datum_find(a, i, b, b_type);
+        if (idx != UINT_MAX) {
+            changed = true;
+            ovsdb_datum_remove_unsafe(a, i, a_type);
+        } else {
+            i++;
+        }
+    }
+    if (changed) {
+        ovsdb_datum_sort_assert(a, a_type->key.type);
+    }
+}
+\f
+struct ovsdb_symbol_table {
+    struct shash sh;
+};
+
+struct ovsdb_symbol_table *
+ovsdb_symbol_table_create(void)
+{
+    struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab);
+    shash_init(&symtab->sh);
+    return symtab;
+}
+
+void
+ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab)
+{
+    if (symtab) {
+        struct shash_node *node, *next;
+
+        SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) {
+            struct ovsdb_symbol *symbol = node->data;
+            free(symbol);
+            shash_delete(&symtab->sh, node);
+        }
+        shash_destroy(&symtab->sh);
+        free(symtab);
+    }
+}
+
+struct ovsdb_symbol *
+ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab,
+                       const char *name)
+{
+    return shash_find_data(&symtab->sh, name);
+}
+
+struct ovsdb_symbol *
+ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name,
+                       const struct uuid *uuid, bool used)
+{
+    struct ovsdb_symbol *symbol;
+
+    assert(!ovsdb_symbol_table_get(symtab, name));
+    symbol = xmalloc(sizeof *symbol);
+    symbol->uuid = *uuid;
+    symbol->used = used;
+    shash_add(&symtab->sh, name, symbol);
+    return symbol;
+}
+
+struct ovsdb_symbol *
+ovsdb_symbol_table_insert(struct ovsdb_symbol_table *symtab,
+                          const char *name)
+{
+    struct ovsdb_symbol *symbol;
+
+    symbol = ovsdb_symbol_table_get(symtab, name);
+    if (!symbol) {
+        struct uuid uuid;
+
+        uuid_generate(&uuid);
+        symbol = ovsdb_symbol_table_put(symtab, name, &uuid, false);
+    }
+    return symbol;
+}
+\f
+/* Extracts a token from the beginning of 's' and returns a pointer just after
+ * the token.  Stores the token itself into '*outp', which the caller is
+ * responsible for freeing (with free()).
+ *
+ * If 's[0]' is a delimiter, the returned token is the empty string.
+ *
+ * A token extends from 's' to the first delimiter, as defined by
+ * ovsdb_token_is_delim(), or until the end of the string.  A delimiter can be
+ * escaped with a backslash, in which case the backslash does not appear in the
+ * output.  Double quotes also cause delimiters to be ignored, but the double
+ * quotes are retained in the output.  (Backslashes inside double quotes are
+ * not removed, either.)
+ */
+char *
+ovsdb_token_parse(const char **s, char **outp)
+{
+    const char *p;
+    struct ds out;
+    bool in_quotes;
+    char *error;
+
+    ds_init(&out);
+    in_quotes = false;
+    for (p = *s; *p != '\0'; ) {
+        int c = *p++;
+        if (c == '\\') {
+            if (in_quotes) {
+                ds_put_char(&out, '\\');
+            }
+            if (!*p) {
+                error = xasprintf("%s: backslash at end of argument", *s);
+                goto error;
+            }
+            ds_put_char(&out, *p++);
+        } else if (!in_quotes && ovsdb_token_is_delim(c)) {
+            p--;
+            break;
+        } else {
+            ds_put_char(&out, c);
+            if (c == '"') {
+                in_quotes = !in_quotes;
+            }
+        }
+    }
+    if (in_quotes) {
+        error = xasprintf("%s: quoted string extends past end of argument",
+                          *s);
+        goto error;
+    }
+    *outp = ds_cstr(&out);
+    *s = p;
+    return NULL;
+
+error:
+    ds_destroy(&out);
+    *outp = NULL;
+    return error;
+}
+
+/* Returns true if 'c' delimits tokens, or if 'c' is 0, and false otherwise. */
+bool
+ovsdb_token_is_delim(unsigned char c)
+{
+    return strchr(":=, []{}", c) != NULL;
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
new file mode 100644 (file)
index 0000000..a5c49f9
--- /dev/null
@@ -0,0 +1,227 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_DATA_H
+#define OVSDB_DATA_H 1
+
+#include <stdlib.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ds;
+struct ovsdb_symbol_table;
+
+/* One value of an atomic type (given by enum ovs_atomic_type). */
+union ovsdb_atom {
+    int64_t integer;
+    double real;
+    bool boolean;
+    char *string;
+    struct uuid uuid;
+};
+
+void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type);
+bool ovsdb_atom_is_default(const union ovsdb_atom *, enum ovsdb_atomic_type);
+void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *,
+                      enum ovsdb_atomic_type);
+void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *);
+
+static inline bool
+ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+{
+    return type == OVSDB_TYPE_STRING;
+}
+
+static inline void
+ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    if (type == OVSDB_TYPE_STRING) {
+        free(atom->string);
+    }
+}
+
+uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                         uint32_t basis);
+
+int ovsdb_atom_compare_3way(const union ovsdb_atom *,
+                            const union ovsdb_atom *,
+                            enum ovsdb_atomic_type);
+
+static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
+                                     const union ovsdb_atom *b,
+                                     enum ovsdb_atomic_type type)
+{
+    return !ovsdb_atom_compare_3way(a, b, type);
+}
+
+struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
+                                         const struct ovsdb_base_type *,
+                                         const struct json *,
+                                         struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
+                                enum ovsdb_atomic_type);
+
+char *ovsdb_atom_from_string(union ovsdb_atom *,
+                             const struct ovsdb_base_type *, const char *)
+    WARN_UNUSED_RESULT;
+void ovsdb_atom_to_string(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                          struct ds *);
+
+struct ovsdb_error *ovsdb_atom_check_constraints(
+    const union ovsdb_atom *, const struct ovsdb_base_type *)
+    WARN_UNUSED_RESULT;
+\f
+/* An instance of an OVSDB type (given by struct ovsdb_type).
+ *
+ * - The 'keys' must be unique and in sorted order.  Most functions that modify
+ *   an ovsdb_datum maintain these invariants.  Functions that don't maintain
+ *   the invariants have names that end in "_unsafe".  Use ovsdb_datum_sort()
+ *   to check and restore these invariants.
+ *
+ * - 'n' is constrained by the ovsdb_type's 'n_min' and 'n_max'.
+ *
+ *   If 'n' is nonzero, then 'keys' points to an array of 'n' atoms of the type
+ *   specified by the ovsdb_type's 'key_type'.  (Otherwise, 'keys' should be
+ *   null.)
+ *
+ *   If 'n' is nonzero and the ovsdb_type's 'value_type' is not
+ *   OVSDB_TYPE_VOID, then 'values' points to an array of 'n' atoms of the type
+ *   specified by the 'value_type'.  (Otherwise, 'values' should be null.)
+ *
+ *   Thus, for 'n' > 0, 'keys' will always be nonnull and 'values' will be
+ *   nonnull only for "map" types.
+ */
+struct ovsdb_datum {
+    unsigned int n;             /* Number of 'keys' and 'values'. */
+    union ovsdb_atom *keys;     /* Each of the ovsdb_type's 'key_type'. */
+    union ovsdb_atom *values;   /* Each of the ovsdb_type's 'value_type'. */
+};
+
+/* Basics. */
+void ovsdb_datum_init_empty(struct ovsdb_datum *);
+void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *);
+bool ovsdb_datum_is_default(const struct ovsdb_datum *,
+                            const struct ovsdb_type *);
+void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *,
+                       const struct ovsdb_type *);
+void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
+
+/* Checking and maintaining invariants. */
+struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
+                                     enum ovsdb_atomic_type key_type)
+    WARN_UNUSED_RESULT;
+
+void ovsdb_datum_sort_assert(struct ovsdb_datum *,
+                             enum ovsdb_atomic_type key_type);
+
+struct ovsdb_error *ovsdb_datum_check_constraints(
+    const struct ovsdb_datum *, const struct ovsdb_type *)
+    WARN_UNUSED_RESULT;
+
+/* Type conversion. */
+struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
+                                          const struct ovsdb_type *,
+                                          const struct json *,
+                                          struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
+                                 const struct ovsdb_type *);
+
+char *ovsdb_datum_from_string(struct ovsdb_datum *,
+                             const struct ovsdb_type *, const char *)
+    WARN_UNUSED_RESULT;
+void ovsdb_datum_to_string(const struct ovsdb_datum *,
+                           const struct ovsdb_type *, struct ds *);
+
+/* Comparison. */
+uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
+                          const struct ovsdb_type *, uint32_t basis);
+int ovsdb_datum_compare_3way(const struct ovsdb_datum *,
+                             const struct ovsdb_datum *,
+                             const struct ovsdb_type *);
+bool ovsdb_datum_equals(const struct ovsdb_datum *,
+                        const struct ovsdb_datum *,
+                        const struct ovsdb_type *);
+
+/* Search. */
+unsigned int ovsdb_datum_find_key(const struct ovsdb_datum *,
+                                  const union ovsdb_atom *key,
+                                  enum ovsdb_atomic_type key_type);
+unsigned int ovsdb_datum_find_key_value(const struct ovsdb_datum *,
+                                        const union ovsdb_atom *key,
+                                        enum ovsdb_atomic_type key_type,
+                                        const union ovsdb_atom *value,
+                                        enum ovsdb_atomic_type value_type);
+
+/* Set operations. */
+bool ovsdb_datum_includes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+void ovsdb_datum_union(struct ovsdb_datum *,
+                       const struct ovsdb_datum *,
+                       const struct ovsdb_type *,
+                       bool replace);
+void ovsdb_datum_subtract(struct ovsdb_datum *a,
+                          const struct ovsdb_type *a_type,
+                          const struct ovsdb_datum *b,
+                          const struct ovsdb_type *b_type);
+
+/* Raw operations that may not maintain the invariants. */
+void ovsdb_datum_remove_unsafe(struct ovsdb_datum *, size_t idx,
+                               const struct ovsdb_type *);
+void ovsdb_datum_add_unsafe(struct ovsdb_datum *,
+                            const union ovsdb_atom *key,
+                            const union ovsdb_atom *value,
+                            const struct ovsdb_type *);
+
+/* Type checking. */
+static inline bool
+ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
+                             const struct ovsdb_type *type)
+{
+    return datum->n >= type->n_min && datum->n <= type->n_max;
+}
+\f
+/* A table mapping from names to data items.  Currently the data items are
+ * always UUIDs; perhaps this will be expanded in the future. */
+
+struct ovsdb_symbol {
+    struct uuid uuid;           /* The UUID that the symbol represents. */
+    bool used;                  /* Already used as row UUID? */
+};
+
+struct ovsdb_symbol_table *ovsdb_symbol_table_create(void);
+void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *);
+struct ovsdb_symbol *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *,
+                                            const char *name);
+struct ovsdb_symbol *ovsdb_symbol_table_put(struct ovsdb_symbol_table *,
+                                            const char *name,
+                                            const struct uuid *, bool used);
+struct ovsdb_symbol *ovsdb_symbol_table_insert(struct ovsdb_symbol_table *,
+                                               const char *name);
+\f
+/* Tokenization
+ *
+ * Used by ovsdb_atom_from_string() and ovsdb_datum_from_string(). */
+
+char *ovsdb_token_parse(const char **, char **outp) WARN_UNUSED_RESULT;
+bool ovsdb_token_is_delim(unsigned char);
+
+#endif /* ovsdb-data.h */
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
new file mode 100644 (file)
index 0000000..c922149
--- /dev/null
@@ -0,0 +1,241 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-error.h"
+
+#include <inttypes.h>
+
+#include "backtrace.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "util.h"
+#include "vlog.h"
+
+#define THIS_MODULE VLM_ovsdb_error
+#include "vlog.h"
+
+struct ovsdb_error {
+    const char *tag;            /* String for "error" member. */
+    char *details;              /* String for "details" member. */
+    char *syntax;               /* String for "syntax" member. */
+    int errno_;                 /* Unix errno value, 0 if none. */
+};
+
+static struct ovsdb_error *
+ovsdb_error_valist(const char *tag, const char *details, va_list args)
+{
+    struct ovsdb_error *error = xmalloc(sizeof *error);
+    error->tag = tag ? tag : "ovsdb error";
+    error->details = details ? xvasprintf(details, args) : NULL;
+    error->syntax = NULL;
+    error->errno_ = 0;
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_error(const char *tag, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag, details, args);
+    va_end(args);
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_io_error(int errno_, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist("I/O error", details, args);
+    va_end(args);
+
+    error->errno_ = errno_;
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_syntax_error(const struct json *json, const char *tag,
+                   const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag ? tag : "syntax error", details, args);
+    va_end(args);
+
+    if (json) {
+        /* XXX this is much too much information in some cases */
+        error->syntax = json_to_string(json, 0);
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...)
+{
+    va_list args;
+    char *msg;
+
+    va_start(args, details);
+    msg = xvasprintf(details, args);
+    va_end(args);
+
+    if (error->details) {
+        char *new = xasprintf("%s: %s", msg, error->details);
+        free(error->details);
+        error->details = new;
+        free(msg);
+    } else {
+        error->details = msg;
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_internal_error(const char *file, int line, const char *details, ...)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct backtrace backtrace;
+    struct ovsdb_error *error;
+    va_list args;
+
+    ds_put_format(&ds, "%s:%d:", file, line);
+
+    if (details) {
+        ds_put_char(&ds, ' ');
+        va_start(args, details);
+        ds_put_format_valist(&ds, details, args);
+        va_end(args);
+    }
+
+    backtrace_capture(&backtrace);
+    if (backtrace.n_frames) {
+        int i;
+
+        ds_put_cstr(&ds, " (backtrace:");
+        for (i = 0; i < backtrace.n_frames; i++) {
+            ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]);
+        }
+        ds_put_char(&ds, ')');
+    }
+
+    ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
+
+    error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
+
+    ds_destroy(&ds);
+
+    return error;
+}
+
+void
+ovsdb_error_destroy(struct ovsdb_error *error)
+{
+    if (error) {
+        free(error->details);
+        free(error->syntax);
+        free(error);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_error_clone(const struct ovsdb_error *old)
+{
+    if (old) {
+        struct ovsdb_error *new = xmalloc(sizeof *new);
+        new->tag = old->tag;
+        new->details = old->details ? xstrdup(old->details) : NULL;
+        new->syntax = old->syntax ? xstrdup(old->syntax) : NULL;
+        new->errno_ = old->errno_;
+        return new;
+    } else {
+        return NULL;
+    }
+}
+
+static const char *
+ovsdb_errno_string(int error)
+{
+    return error == EOF ? "unexpected end of file" : strerror(error);
+}
+
+struct json *
+ovsdb_error_to_json(const struct ovsdb_error *error)
+{
+    struct json *json = json_object_create();
+    json_object_put_string(json, "error", error->tag);
+    if (error->details) {
+        json_object_put_string(json, "details", error->details);
+    }
+    if (error->syntax) {
+        json_object_put_string(json, "syntax", error->syntax);
+    }
+    if (error->errno_) {
+        json_object_put_string(json, "io-error",
+                               ovsdb_errno_string(error->errno_));
+    }
+    return json;
+}
+
+char *
+ovsdb_error_to_string(const struct ovsdb_error *error)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    if (error->syntax) {
+        ds_put_format(&ds, "syntax \"%s\": ", error->syntax);
+    }
+    ds_put_cstr(&ds, error->tag);
+    if (error->details) {
+        ds_put_format(&ds, ": %s", error->details);
+    }
+    if (error->errno_) {
+        ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_));
+    }
+    return ds_steal_cstr(&ds);
+}
+
+const char *
+ovsdb_error_get_tag(const struct ovsdb_error *error)
+{
+    return error->tag;
+}
+
+/* If 'error' is nonnull, logs it as an error and frees it.  To be used in
+ * situations where an error should never occur, but an 'ovsdb_error *' gets
+ * passed back anyhow. */
+void
+ovsdb_error_assert(struct ovsdb_error *error)
+{
+    if (error) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        char *s = ovsdb_error_to_string(error);
+        VLOG_ERR_RL(&rl, "unexpected ovsdb error: %s", s);
+        free(s);
+        ovsdb_error_destroy(error);
+    }
+}
+
diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h
new file mode 100644 (file)
index 0000000..2bff3ae
--- /dev/null
@@ -0,0 +1,55 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ERROR_H
+#define OVSDB_ERROR_H 1
+
+#include "compiler.h"
+
+struct json;
+
+struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag,
+                                       const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error,
+                                     const char *details, ...)
+    PRINTF_FORMAT(2, 3);
+
+struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+                                         const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+void ovsdb_error_destroy(struct ovsdb_error *);
+struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
+    WARN_UNUSED_RESULT;
+
+char *ovsdb_error_to_string(const struct ovsdb_error *);
+struct json *ovsdb_error_to_json(const struct ovsdb_error *);
+
+const char *ovsdb_error_get_tag(const struct ovsdb_error *);
+
+void ovsdb_error_assert(struct ovsdb_error *);
+
+#endif /* ovsdb-error.h */
diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
new file mode 100644 (file)
index 0000000..c86396c
--- /dev/null
@@ -0,0 +1,78 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_PROVIDER_H
+#define OVSDB_IDL_PROVIDER_H 1
+
+#include "hmap.h"
+#include "list.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "shash.h"
+#include "uuid.h"
+
+struct ovsdb_idl_row {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_table's 'rows'. */
+    struct uuid uuid;           /* Row "_uuid" field. */
+    struct list src_arcs;       /* Forward arcs (ovsdb_idl_arc.src_node). */
+    struct list dst_arcs;       /* Backward arcs (ovsdb_idl_arc.dst_node). */
+    struct ovsdb_idl_table *table; /* Containing table. */
+    struct ovsdb_datum *old;    /* Committed data (null if orphaned). */
+
+    /* Transactional data. */
+    struct ovsdb_datum *new;    /* Modified data (null to delete row). */
+    unsigned long int *prereqs; /* Bitmap of columns to verify in "old". */
+    unsigned long int *written; /* Bitmap of columns from "new" to write. */
+    struct hmap_node txn_node;  /* Node in ovsdb_idl_txn's list. */
+};
+
+struct ovsdb_idl_column {
+    char *name;
+    struct ovsdb_type type;
+    void (*parse)(struct ovsdb_idl_row *, const struct ovsdb_datum *);
+    void (*unparse)(struct ovsdb_idl_row *);
+};
+
+struct ovsdb_idl_table_class {
+    char *name;
+    const struct ovsdb_idl_column *columns;
+    size_t n_columns;
+    size_t allocation_size;
+};
+
+struct ovsdb_idl_table {
+    const struct ovsdb_idl_table_class *class;
+    struct shash columns;    /* Contains "const struct ovsdb_idl_column *"s. */
+    struct hmap rows;        /* Contains "struct ovsdb_idl_row"s. */
+    struct ovsdb_idl *idl;   /* Containing idl. */
+};
+
+struct ovsdb_idl_class {
+    const char *database;       /* <db-name> for this database. */
+    const struct ovsdb_idl_table_class *tables;
+    size_t n_tables;
+};
+
+struct ovsdb_idl_row *ovsdb_idl_get_row_arc(
+    struct ovsdb_idl_row *src,
+    struct ovsdb_idl_table_class *dst_table,
+    const struct uuid *dst_uuid);
+
+void ovsdb_idl_txn_verify(const struct ovsdb_idl_row *,
+                          const struct ovsdb_idl_column *);
+
+struct ovsdb_idl_txn *ovsdb_idl_txn_get(const struct ovsdb_idl_row *);
+
+#endif /* ovsdb-idl-provider.h */
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
new file mode 100644 (file)
index 0000000..42c53b8
--- /dev/null
@@ -0,0 +1,1699 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-idl.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "bitmap.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl-provider.h"
+#include "poll-loop.h"
+#include "shash.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_idl
+#include "vlog.h"
+
+/* An arc from one idl_row to another.  When row A contains a UUID that
+ * references row B, this is represented by an arc from A (the source) to B
+ * (the destination).
+ *
+ * Arcs from a row to itself are omitted, that is, src and dst are always
+ * different.
+ *
+ * Arcs are never duplicated, that is, even if there are multiple references
+ * from A to B, there is only a single arc from A to B.
+ *
+ * Arcs are directed: an arc from A to B is the converse of an an arc from B to
+ * A.  Both an arc and its converse may both be present, if each row refers
+ * to the other circularly.
+ *
+ * The source and destination row may be in the same table or in different
+ * tables.
+ */
+struct ovsdb_idl_arc {
+    struct list src_node;       /* In src->src_arcs list. */
+    struct list dst_node;       /* In dst->dst_arcs list. */
+    struct ovsdb_idl_row *src;  /* Source row. */
+    struct ovsdb_idl_row *dst;  /* Destination row. */
+};
+
+struct ovsdb_idl {
+    const struct ovsdb_idl_class *class;
+    struct jsonrpc_session *session;
+    struct shash table_by_name;
+    struct ovsdb_idl_table *tables;
+    struct json *monitor_request_id;
+    unsigned int last_monitor_request_seqno;
+    unsigned int change_seqno;
+
+    /* Transaction support. */
+    struct ovsdb_idl_txn *txn;
+    struct hmap outstanding_txns;
+};
+
+struct ovsdb_idl_txn {
+    struct hmap_node hmap_node;
+    struct json *request_id;
+    struct ovsdb_idl *idl;
+    struct hmap txn_rows;
+    enum ovsdb_idl_txn_status status;
+    char *error;
+    bool dry_run;
+    struct ds comment;
+
+    /* Increments. */
+    char *inc_table;
+    char *inc_column;
+    struct json *inc_where;
+    unsigned int inc_index;
+    int64_t inc_new_value;
+
+    /* Inserted rows. */
+    struct hmap inserted_rows;
+};
+
+struct ovsdb_idl_txn_insert {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_txn's inserted_rows. */
+    struct uuid dummy;          /* Dummy UUID used locally. */
+    int op_index;               /* Index into transaction's operation array. */
+    struct uuid real;           /* Real UUID used by database server. */
+};
+
+static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+static void ovsdb_idl_clear(struct ovsdb_idl *);
+static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *);
+static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *);
+static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *,
+                                                    const struct json *);
+static void ovsdb_idl_process_update(struct ovsdb_idl_table *,
+                                     const struct uuid *,
+                                     const struct json *old,
+                                     const struct json *new);
+static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *);
+static void ovsdb_idl_delete_row(struct ovsdb_idl_row *);
+static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *);
+
+static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create__(
+    const struct ovsdb_idl_table_class *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *,
+                                                  const struct uuid *);
+static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_row_parse(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_unparse(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_clear_old(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_clear_new(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_txn_abort_all(struct ovsdb_idl *);
+static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *,
+                                        const struct jsonrpc_msg *msg);
+
+struct ovsdb_idl *
+ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
+{
+    struct ovsdb_idl *idl;
+    size_t i;
+
+    idl = xzalloc(sizeof *idl);
+    idl->class = class;
+    idl->session = jsonrpc_session_open(remote);
+    shash_init(&idl->table_by_name);
+    idl->tables = xmalloc(class->n_tables * sizeof *idl->tables);
+    for (i = 0; i < class->n_tables; i++) {
+        const struct ovsdb_idl_table_class *tc = &class->tables[i];
+        struct ovsdb_idl_table *table = &idl->tables[i];
+        size_t j;
+
+        assert(!shash_find(&idl->table_by_name, tc->name));
+        shash_add(&idl->table_by_name, tc->name, table);
+        table->class = tc;
+        shash_init(&table->columns);
+        for (j = 0; j < tc->n_columns; j++) {
+            const struct ovsdb_idl_column *column = &tc->columns[j];
+
+            assert(!shash_find(&table->columns, column->name));
+            shash_add(&table->columns, column->name, column);
+        }
+        hmap_init(&table->rows);
+        table->idl = idl;
+    }
+    idl->last_monitor_request_seqno = UINT_MAX;
+    hmap_init(&idl->outstanding_txns);
+
+    return idl;
+}
+
+void
+ovsdb_idl_destroy(struct ovsdb_idl *idl)
+{
+    if (idl) {
+        size_t i;
+
+        assert(!idl->txn);
+        ovsdb_idl_clear(idl);
+        jsonrpc_session_close(idl->session);
+
+        for (i = 0; i < idl->class->n_tables; i++) {
+            struct ovsdb_idl_table *table = &idl->tables[i];
+            shash_destroy(&table->columns);
+            hmap_destroy(&table->rows);
+        }
+        shash_destroy(&idl->table_by_name);
+        free(idl->tables);
+        json_destroy(idl->monitor_request_id);
+        free(idl);
+    }
+}
+
+static void
+ovsdb_idl_clear(struct ovsdb_idl *idl)
+{
+    bool changed = false;
+    size_t i;
+
+    for (i = 0; i < idl->class->n_tables; i++) {
+        struct ovsdb_idl_table *table = &idl->tables[i];
+        struct ovsdb_idl_row *row, *next_row;
+
+        if (hmap_is_empty(&table->rows)) {
+            continue;
+        }
+
+        changed = true;
+        HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node,
+                            &table->rows) {
+            struct ovsdb_idl_arc *arc, *next_arc;
+
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_row_unparse(row);
+            }
+            LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node,
+                                &row->src_arcs) {
+                free(arc);
+            }
+            /* No need to do anything with dst_arcs: some node has those arcs
+             * as forward arcs and will destroy them itself. */
+
+            ovsdb_idl_row_destroy(row);
+        }
+    }
+
+    if (changed) {
+        idl->change_seqno++;
+    }
+}
+
+void
+ovsdb_idl_run(struct ovsdb_idl *idl)
+{
+    int i;
+
+    assert(!idl->txn);
+    jsonrpc_session_run(idl->session);
+    for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) {
+        struct jsonrpc_msg *msg, *reply;
+        unsigned int seqno;
+
+        seqno = jsonrpc_session_get_seqno(idl->session);
+        if (idl->last_monitor_request_seqno != seqno) {
+            idl->last_monitor_request_seqno = seqno;
+            ovsdb_idl_txn_abort_all(idl);
+            ovsdb_idl_send_monitor_request(idl);
+            break;
+        }
+
+        msg = jsonrpc_session_recv(idl->session);
+        if (!msg) {
+            break;
+        }
+
+        reply = NULL;
+        if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")
+                   && msg->params->type == JSON_ARRAY
+                   && msg->params->u.array.n == 2
+                   && msg->params->u.array.elems[0]->type == JSON_NULL) {
+            ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]);
+        } else if (msg->type == JSONRPC_REPLY
+                   && idl->monitor_request_id
+                   && json_equal(idl->monitor_request_id, msg->id)) {
+            json_destroy(idl->monitor_request_id);
+            idl->monitor_request_id = NULL;
+            ovsdb_idl_clear(idl);
+            ovsdb_idl_parse_update(idl, msg->result);
+        } else if (msg->type == JSONRPC_REPLY
+                   && msg->id && msg->id->type == JSON_STRING
+                   && !strcmp(msg->id->u.string, "echo")) {
+            /* It's a reply to our echo request.  Ignore it. */
+        } else if ((msg->type == JSONRPC_ERROR
+                    || msg->type == JSONRPC_REPLY)
+                   && ovsdb_idl_txn_process_reply(idl, msg)) {
+            /* ovsdb_idl_txn_process_reply() did everything needful. */
+        } else {
+            /* This can happen if ovsdb_idl_txn_destroy() is called to destroy
+             * a transaction before we receive the reply, so keep the log level
+             * low. */
+            VLOG_DBG("%s: received unexpected %s message",
+                     jsonrpc_session_get_name(idl->session),
+                     jsonrpc_msg_type_to_string(msg->type));
+        }
+        if (reply) {
+            jsonrpc_session_send(idl->session, reply);
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+void
+ovsdb_idl_wait(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_wait(idl->session);
+    jsonrpc_session_recv_wait(idl->session);
+}
+
+unsigned int
+ovsdb_idl_get_seqno(const struct ovsdb_idl *idl)
+{
+    return idl->change_seqno;
+}
+
+bool
+ovsdb_idl_has_ever_connected(const struct ovsdb_idl *idl)
+{
+    return ovsdb_idl_get_seqno(idl) != 0;
+}
+
+void
+ovsdb_idl_force_reconnect(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_force_reconnect(idl->session);
+}
+\f
+static void
+ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl)
+{
+    struct json *monitor_requests;
+    struct jsonrpc_msg *msg;
+    size_t i;
+
+    monitor_requests = json_object_create();
+    for (i = 0; i < idl->class->n_tables; i++) {
+        const struct ovsdb_idl_table *table = &idl->tables[i];
+        const struct ovsdb_idl_table_class *tc = table->class;
+        struct json *monitor_request, *columns;
+        size_t i;
+
+        monitor_request = json_object_create();
+        columns = json_array_create_empty();
+        for (i = 0; i < tc->n_columns; i++) {
+            const struct ovsdb_idl_column *column = &tc->columns[i];
+            json_array_add(columns, json_string_create(column->name));
+        }
+        json_object_put(monitor_request, "columns", columns);
+        json_object_put(monitor_requests, tc->name, monitor_request);
+    }
+
+    json_destroy(idl->monitor_request_id);
+    msg = jsonrpc_create_request(
+        "monitor",
+        json_array_create_3(json_string_create(idl->class->database),
+                            json_null_create(), monitor_requests),
+        &idl->monitor_request_id);
+    jsonrpc_session_send(idl->session, msg);
+}
+
+static void
+ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates)
+{
+    struct ovsdb_error *error;
+
+    idl->change_seqno++;
+
+    error = ovsdb_idl_parse_update__(idl, table_updates);
+    if (error) {
+        if (!VLOG_DROP_WARN(&syntax_rl)) {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "%s", s);
+            free(s);
+        }
+        ovsdb_error_destroy(error);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_idl_parse_update__(struct ovsdb_idl *idl,
+                         const struct json *table_updates)
+{
+    const struct shash_node *tables_node;
+
+    if (table_updates->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(table_updates, NULL,
+                                  "<table-updates> is not an object");
+    }
+    SHASH_FOR_EACH (tables_node, json_object(table_updates)) {
+        const struct json *table_update = tables_node->data;
+        const struct shash_node *table_node;
+        struct ovsdb_idl_table *table;
+
+        table = shash_find_data(&idl->table_by_name, tables_node->name);
+        if (!table) {
+            return ovsdb_syntax_error(
+                table_updates, NULL,
+                "<table-updates> includes unknown table \"%s\"",
+                tables_node->name);
+        }
+
+        if (table_update->type != JSON_OBJECT) {
+            return ovsdb_syntax_error(table_update, NULL,
+                                      "<table-update> for table \"%s\" is "
+                                      "not an object", table->class->name);
+        }
+        SHASH_FOR_EACH (table_node, json_object(table_update)) {
+            const struct json *row_update = table_node->data;
+            const struct json *old_json, *new_json;
+            struct uuid uuid;
+
+            if (!uuid_from_string(&uuid, table_node->name)) {
+                return ovsdb_syntax_error(table_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains bad UUID "
+                                          "\"%s\" as member name",
+                                          table->class->name,
+                                          table_node->name);
+            }
+            if (row_update->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains <row-update> for %s that "
+                                          "is not an object",
+                                          table->class->name,
+                                          table_node->name);
+            }
+
+            old_json = shash_find_data(json_object(row_update), "old");
+            new_json = shash_find_data(json_object(row_update), "new");
+            if (old_json && old_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(old_json, NULL,
+                                          "\"old\" <row> is not object");
+            } else if (new_json && new_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(new_json, NULL,
+                                          "\"new\" <row> is not object");
+            } else if ((old_json != NULL) + (new_json != NULL)
+                       != shash_count(json_object(row_update))) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> contains unexpected "
+                                          "member");
+            } else if (!old_json && !new_json) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> missing \"old\" "
+                                          "and \"new\" members");
+            }
+
+            ovsdb_idl_process_update(table, &uuid, old_json, new_json);
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node,
+                             uuid_hash(uuid), &table->rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_process_update(struct ovsdb_idl_table *table,
+                         const struct uuid *uuid, const struct json *old,
+                         const struct json *new)
+{
+    struct ovsdb_idl_row *row;
+
+    row = ovsdb_idl_get_row(table, uuid);
+    if (!new) {
+        /* Delete row. */
+        if (row && !ovsdb_idl_row_is_orphan(row)) {
+            /* XXX perhaps we should check the 'old' values? */
+            ovsdb_idl_delete_row(row);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" "
+                         "from table %s",
+                         UUID_ARGS(uuid), table->class->name);
+        }
+    } else if (!old) {
+        /* Insert row. */
+        if (!row) {
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        } else if (ovsdb_idl_row_is_orphan(row)) {
+            ovsdb_idl_insert_row(row, new);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to "
+                         "table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_modify_row(row, new);
+        }
+    } else {
+        /* Modify row. */
+        if (row) {
+            /* XXX perhaps we should check the 'old' values? */
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_modify_row(row, new);
+            } else {
+                VLOG_WARN_RL(&semantic_rl, "cannot modify missing but "
+                             "referenced row "UUID_FMT" in table %s",
+                             UUID_ARGS(uuid), table->class->name);
+                ovsdb_idl_insert_row(row, new);
+            }
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" "
+                         "in table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        }
+    }
+}
+
+static void
+ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    struct ovsdb_idl_table *table = row->table;
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, json_object(row_json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_idl_column *column;
+        struct ovsdb_datum datum;
+        struct ovsdb_error *error;
+
+        column = shash_find_data(&table->columns, column_name);
+        if (!column) {
+            VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT,
+                         column_name, UUID_ARGS(&row->uuid));
+            continue;
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
+        if (!error) {
+            ovsdb_datum_swap(&row->old[column - table->class->columns],
+                             &datum);
+            ovsdb_datum_destroy(&datum, &column->type);
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT
+                         " in table %s: %s", column_name,
+                         UUID_ARGS(&row->uuid), table->class->name, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        }
+    }
+}
+
+/* When a row A refers to row B through a column with a "refTable" constraint,
+ * but row B does not exist, row B is called an "orphan row".  Orphan rows
+ * should not persist, because the database enforces referential integrity, but
+ * they can appear transiently as changes from the database are received (the
+ * database doesn't try to topologically sort them and circular references mean
+ * it isn't always possible anyhow).
+ *
+ * This function returns true if 'row' is an orphan row, otherwise false.
+ */
+static bool
+ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row)
+{
+    return !row->old && !row->new;
+}
+
+/* Returns true if 'row' is conceptually part of the database as modified by
+ * the current transaction (if any), false otherwise.
+ *
+ * This function will return true if 'row' is not an orphan (see the comment on
+ * ovsdb_idl_row_is_orphan()) and:
+ *
+ *   - 'row' exists in the database and has not been deleted within the
+ *     current transaction (if any).
+ *
+ *   - 'row' was inserted within the current transaction and has not been
+ *     deleted.  (In the latter case you should not have passed 'row' in at
+ *     all, because ovsdb_idl_txn_delete() freed it.)
+ *
+ * This function will return false if 'row' is an orphan or if 'row' was
+ * deleted within the current transaction.
+ */
+static bool
+ovsdb_idl_row_exists(const struct ovsdb_idl_row *row)
+{
+    return row->new != NULL;
+}
+
+static void
+ovsdb_idl_row_parse(struct ovsdb_idl_row *row)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    for (i = 0; i < class->n_columns; i++) {
+        const struct ovsdb_idl_column *c = &class->columns[i];
+        (c->parse)(row, &row->old[i]);
+    }
+}
+
+static void
+ovsdb_idl_row_unparse(struct ovsdb_idl_row *row)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    for (i = 0; i < class->n_columns; i++) {
+        const struct ovsdb_idl_column *c = &class->columns[i];
+        (c->unparse)(row);
+    }
+}
+
+static void
+ovsdb_idl_row_clear_old(struct ovsdb_idl_row *row)
+{
+    assert(row->old == row->new);
+    if (!ovsdb_idl_row_is_orphan(row)) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+        size_t i;
+
+        for (i = 0; i < class->n_columns; i++) {
+            ovsdb_datum_destroy(&row->old[i], &class->columns[i].type);
+        }
+        free(row->old);
+        row->old = row->new = NULL;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_new(struct ovsdb_idl_row *row)
+{
+    if (row->old != row->new) {
+        if (row->new) {
+            const struct ovsdb_idl_table_class *class = row->table->class;
+            size_t i;
+
+            BITMAP_FOR_EACH_1 (i, class->n_columns, row->written) {
+                ovsdb_datum_destroy(&row->new[i], &class->columns[i].type);
+            }
+            free(row->new);
+            free(row->written);
+            row->written = NULL;
+        }
+        row->new = row->old;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* Delete all forward arcs.  If 'destroy_dsts', destroy any orphaned rows
+     * that this causes to be unreferenced. */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node,
+                        &row->src_arcs) {
+        list_remove(&arc->dst_node);
+        if (destroy_dsts
+            && ovsdb_idl_row_is_orphan(arc->dst)
+            && list_is_empty(&arc->dst->dst_arcs)) {
+            ovsdb_idl_row_destroy(arc->dst);
+        }
+        free(arc);
+    }
+    list_init(&row->src_arcs);
+}
+
+/* Force nodes that reference 'row' to reparse. */
+static void
+ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* This is trickier than it looks.  ovsdb_idl_row_clear_arcs() will destroy
+     * 'arc', so we need to use the "safe" variant of list traversal.  However,
+     * calling an ovsdb_idl_column's 'parse' function will add an arc
+     * equivalent to 'arc' to row->arcs.  That could be a problem for
+     * traversal, but it adds it at the beginning of the list to prevent us
+     * from stumbling upon it again.
+     *
+     * (If duplicate arcs were possible then we would need to make sure that
+     * 'next' didn't also point into 'arc''s destination, but we forbid
+     * duplicate arcs.) */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node,
+                        &row->dst_arcs) {
+        struct ovsdb_idl_row *ref = arc->src;
+
+        ovsdb_idl_row_unparse(ref);
+        ovsdb_idl_row_clear_arcs(ref, false);
+        ovsdb_idl_row_parse(ref);
+    }
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create__(const struct ovsdb_idl_table_class *class)
+{
+    struct ovsdb_idl_row *row = xzalloc(class->allocation_size);
+    list_init(&row->src_arcs);
+    list_init(&row->dst_arcs);
+    hmap_node_nullify(&row->txn_node);
+    return row;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row = ovsdb_idl_row_create__(table->class);
+    hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid));
+    row->uuid = *uuid;
+    row->table = table;
+    return row;
+}
+
+static void
+ovsdb_idl_row_destroy(struct ovsdb_idl_row *row)
+{
+    if (row) {
+        ovsdb_idl_row_clear_old(row);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        free(row);
+    }
+}
+
+static void
+ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    assert(!row->old && !row->new);
+    row->old = row->new = xmalloc(class->n_columns * sizeof *row->old);
+    for (i = 0; i < class->n_columns; i++) {
+        ovsdb_datum_init_default(&row->old[i], &class->columns[i].type);
+    }
+    ovsdb_idl_row_update(row, row_json);
+    ovsdb_idl_row_parse(row);
+
+    ovsdb_idl_row_reparse_backrefs(row);
+}
+
+static void
+ovsdb_idl_delete_row(struct ovsdb_idl_row *row)
+{
+    ovsdb_idl_row_unparse(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_clear_old(row);
+    if (list_is_empty(&row->dst_arcs)) {
+        ovsdb_idl_row_destroy(row);
+    } else {
+        ovsdb_idl_row_reparse_backrefs(row);
+    }
+}
+
+static void
+ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    ovsdb_idl_row_unparse(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_update(row, row_json);
+    ovsdb_idl_row_parse(row);
+}
+
+static bool
+may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst)
+{
+    const struct ovsdb_idl_arc *arc;
+
+    /* No self-arcs. */
+    if (src == dst) {
+        return false;
+    }
+
+    /* No duplicate arcs.
+     *
+     * We only need to test whether the first arc in dst->dst_arcs originates
+     * at 'src', since we add all of the arcs from a given source in a clump
+     * (in a single call to ovsdb_idl_row_parse()) and new arcs are always
+     * added at the front of the dst_arcs list. */
+    if (list_is_empty(&dst->dst_arcs)) {
+        return true;
+    }
+    arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node);
+    return arc->src != src;
+}
+
+static struct ovsdb_idl_table *
+ovsdb_idl_table_from_class(const struct ovsdb_idl *idl,
+                           const struct ovsdb_idl_table_class *table_class)
+{
+    return &idl->tables[table_class - idl->class->tables];
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
+                      struct ovsdb_idl_table_class *dst_table_class,
+                      const struct uuid *dst_uuid)
+{
+    struct ovsdb_idl *idl = src->table->idl;
+    struct ovsdb_idl_table *dst_table;
+    struct ovsdb_idl_arc *arc;
+    struct ovsdb_idl_row *dst;
+
+    dst_table = ovsdb_idl_table_from_class(idl, dst_table_class);
+    dst = ovsdb_idl_get_row(dst_table, dst_uuid);
+    if (idl->txn) {
+        /* We're being called from ovsdb_idl_txn_write().  We must not update
+         * any arcs, because the transaction will be backed out at commit or
+         * abort time and we don't want our graph screwed up.
+         *
+         * Just return the destination row, if there is one and it has not been
+         * deleted. */
+        if (dst && (hmap_node_is_null(&dst->txn_node) || dst->new)) {
+            return dst;
+        }
+        return NULL;
+    } else {
+        /* We're being called from some other context.  Update the graph. */
+        if (!dst) {
+            dst = ovsdb_idl_row_create(dst_table, dst_uuid);
+        }
+
+        /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */
+        if (may_add_arc(src, dst)) {
+            /* The arc *must* be added at the front of the dst_arcs list.  See
+             * ovsdb_idl_row_reparse_backrefs() for details. */
+            arc = xmalloc(sizeof *arc);
+            list_push_front(&src->src_arcs, &arc->src_node);
+            list_push_front(&dst->dst_arcs, &arc->dst_node);
+            arc->src = src;
+            arc->dst = dst;
+        }
+
+        return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL;
+    }
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_get_row_for_uuid(const struct ovsdb_idl *idl,
+                           const struct ovsdb_idl_table_class *tc,
+                           const struct uuid *uuid)
+{
+    return ovsdb_idl_get_row(ovsdb_idl_table_from_class(idl, tc), uuid);
+}
+
+static struct ovsdb_idl_row *
+next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node)
+{
+    for (; node; node = hmap_next(&table->rows, node)) {
+        struct ovsdb_idl_row *row;
+
+        row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node);
+        if (ovsdb_idl_row_exists(row)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_first_row(const struct ovsdb_idl *idl,
+                    const struct ovsdb_idl_table_class *table_class)
+{
+    struct ovsdb_idl_table *table
+        = ovsdb_idl_table_from_class(idl, table_class);
+    return next_real_row(table, hmap_first(&table->rows));
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_next_row(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_table *table = row->table;
+
+    return next_real_row(table, hmap_next(&table->rows, &row->hmap_node));
+}
+\f
+/* Transactions. */
+
+static void ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn,
+                                   enum ovsdb_idl_txn_status);
+
+const char *
+ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status)
+{
+    switch (status) {
+    case TXN_UNCHANGED:
+        return "unchanged";
+    case TXN_INCOMPLETE:
+        return "incomplete";
+    case TXN_ABORTED:
+        return "aborted";
+    case TXN_SUCCESS:
+        return "success";
+    case TXN_TRY_AGAIN:
+        return "try again";
+    case TXN_ERROR:
+        return "error";
+    }
+    return "<unknown>";
+}
+
+struct ovsdb_idl_txn *
+ovsdb_idl_txn_create(struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+
+    assert(!idl->txn);
+    idl->txn = txn = xmalloc(sizeof *txn);
+    txn->request_id = NULL;
+    txn->idl = idl;
+    hmap_init(&txn->txn_rows);
+    txn->status = TXN_INCOMPLETE;
+    txn->error = NULL;
+    txn->dry_run = false;
+    ds_init(&txn->comment);
+
+    txn->inc_table = NULL;
+    txn->inc_column = NULL;
+    txn->inc_where = NULL;
+
+    hmap_init(&txn->inserted_rows);
+
+    return txn;
+}
+
+/* Appends 's', which is treated as a printf()-type format string, to the
+ * comments that will be passed to the OVSDB server when 'txn' is committed.
+ * (The comment will be committed to the OVSDB log, which "ovsdb-tool
+ * show-log" can print in a relatively human-readable form.) */
+void
+ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *txn, const char *s, ...)
+{
+    va_list args;
+
+    if (txn->comment.length) {
+        ds_put_char(&txn->comment, '\n');
+    }
+
+    va_start(args, s);
+    ds_put_format_valist(&txn->comment, s, args);
+    va_end(args);
+}
+
+void
+ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *txn)
+{
+    txn->dry_run = true;
+}
+
+void
+ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn, const char *table,
+                        const char *column, const struct json *where)
+{
+    assert(!txn->inc_table);
+    txn->inc_table = xstrdup(table);
+    txn->inc_column = xstrdup(column);
+    txn->inc_where = where ? json_clone(where) : json_array_create_empty();
+}
+
+void
+ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_txn_insert *insert, *next;
+
+    json_destroy(txn->request_id);
+    if (txn->status == TXN_INCOMPLETE) {
+        hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node);
+    }
+    ovsdb_idl_txn_abort(txn);
+    ds_destroy(&txn->comment);
+    free(txn->error);
+    free(txn->inc_table);
+    free(txn->inc_column);
+    json_destroy(txn->inc_where);
+    HMAP_FOR_EACH_SAFE (insert, next, struct ovsdb_idl_txn_insert, hmap_node,
+                        &txn->inserted_rows) {
+        free(insert);
+    }
+    hmap_destroy(&txn->inserted_rows);
+    free(txn);
+}
+
+void
+ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *txn)
+{
+    if (txn->status != TXN_INCOMPLETE) {
+        poll_immediate_wake();
+    }
+}
+
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+    return
+        json_array_create_1(
+            json_array_create_3(
+                json_string_create("_uuid"),
+                json_string_create("=="),
+                json_array_create_2(
+                    json_string_create("uuid"),
+                    json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
+}
+
+static char *
+uuid_name_from_uuid(const struct uuid *uuid)
+{
+    char *name;
+    char *p;
+
+    name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid));
+    for (p = name; *p != '\0'; p++) {
+        if (*p == '-') {
+            *p = '_';
+        }
+    }
+
+    return name;
+}
+
+static const struct ovsdb_idl_row *
+ovsdb_idl_txn_get_row(const struct ovsdb_idl_txn *txn, const struct uuid *uuid)
+{
+    const struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, txn_node,
+                             uuid_hash(uuid), &txn->txn_rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+/* XXX there must be a cleaner way to do this */
+static struct json *
+substitute_uuids(struct json *json, const struct ovsdb_idl_txn *txn)
+{
+    if (json->type == JSON_ARRAY) {
+        struct uuid uuid;
+        size_t i;
+
+        if (json->u.array.n == 2
+            && json->u.array.elems[0]->type == JSON_STRING
+            && json->u.array.elems[1]->type == JSON_STRING
+            && !strcmp(json->u.array.elems[0]->u.string, "uuid")
+            && uuid_from_string(&uuid, json->u.array.elems[1]->u.string)) {
+            const struct ovsdb_idl_row *row;
+
+            row = ovsdb_idl_txn_get_row(txn, &uuid);
+            if (row && !row->old && row->new) {
+                json_destroy(json);
+
+                return json_array_create_2(
+                    json_string_create("named-uuid"),
+                    json_string_create_nocopy(uuid_name_from_uuid(&uuid)));
+            }
+        }
+
+        for (i = 0; i < json->u.array.n; i++) {
+            json->u.array.elems[i] = substitute_uuids(json->u.array.elems[i],
+                                                      txn);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            node->data = substitute_uuids(node->data, txn);
+        }
+    }
+    return json;
+}
+
+static void
+ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_row *row, *next;
+
+    /* This must happen early.  Otherwise, ovsdb_idl_row_parse() will call an
+     * ovsdb_idl_column's 'parse' function, which will call
+     * ovsdb_idl_get_row_arc(), which will seen that the IDL is in a
+     * transaction and fail to update the graph.  */
+    txn->idl->txn = NULL;
+
+    HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_idl_row, txn_node,
+                        &txn->txn_rows) {
+        if (row->old) {
+            if (row->written) {
+                ovsdb_idl_row_unparse(row);
+                ovsdb_idl_row_clear_arcs(row, false);
+                ovsdb_idl_row_parse(row);
+            }
+        } else {
+            ovsdb_idl_row_unparse(row);
+        }
+        ovsdb_idl_row_clear_new(row);
+
+        free(row->prereqs);
+        row->prereqs = NULL;
+
+        free(row->written);
+        row->written = NULL;
+
+        hmap_remove(&txn->txn_rows, &row->txn_node);
+        hmap_node_nullify(&row->txn_node);
+        if (!row->old) {
+            hmap_remove(&row->table->rows, &row->hmap_node);
+            free(row);
+        }
+    }
+    hmap_destroy(&txn->txn_rows);
+    hmap_init(&txn->txn_rows);
+}
+
+enum ovsdb_idl_txn_status
+ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_row *row;
+    struct json *operations;
+    bool any_updates;
+
+    if (txn != txn->idl->txn) {
+        return txn->status;
+    }
+
+    operations = json_array_create_1(
+        json_string_create(txn->idl->class->database));
+
+    /* Add prerequisites and declarations of new rows. */
+    HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) {
+        /* XXX check that deleted rows exist even if no prereqs? */
+        if (row->prereqs) {
+            const struct ovsdb_idl_table_class *class = row->table->class;
+            size_t n_columns = class->n_columns;
+            struct json *op, *columns, *row_json;
+            size_t idx;
+
+            op = json_object_create();
+            json_array_add(operations, op);
+            json_object_put_string(op, "op", "wait");
+            json_object_put_string(op, "table", class->name);
+            json_object_put(op, "timeout", json_integer_create(0));
+            json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            json_object_put_string(op, "until", "==");
+            columns = json_array_create_empty();
+            json_object_put(op, "columns", columns);
+            row_json = json_object_create();
+            json_object_put(op, "rows", json_array_create_1(row_json));
+
+            BITMAP_FOR_EACH_1 (idx, n_columns, row->prereqs) {
+                const struct ovsdb_idl_column *column = &class->columns[idx];
+                json_array_add(columns, json_string_create(column->name));
+                json_object_put(row_json, column->name,
+                                ovsdb_datum_to_json(&row->old[idx],
+                                                    &column->type));
+            }
+        }
+    }
+
+    /* Add updates. */
+    any_updates = false;
+    HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+
+        if (row->old == row->new) {
+            continue;
+        } else if (!row->new) {
+            struct json *op = json_object_create();
+            json_object_put_string(op, "op", "delete");
+            json_object_put_string(op, "table", class->name);
+            json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            json_array_add(operations, op);
+            any_updates = true;
+        } else {
+            struct json *row_json;
+            struct json *op;
+            size_t idx;
+
+            op = json_object_create();
+            json_object_put_string(op, "op", row->old ? "update" : "insert");
+            json_object_put_string(op, "table", class->name);
+            if (row->old) {
+                json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            } else {
+                struct ovsdb_idl_txn_insert *insert;
+
+                json_object_put(op, "uuid-name",
+                                json_string_create_nocopy(
+                                    uuid_name_from_uuid(&row->uuid)));
+
+                insert = xmalloc(sizeof *insert);
+                insert->dummy = row->uuid;
+                insert->op_index = operations->u.array.n - 1;
+                uuid_zero(&insert->real);
+                hmap_insert(&txn->inserted_rows, &insert->hmap_node,
+                            uuid_hash(&insert->dummy));
+            }
+            row_json = json_object_create();
+            json_object_put(op, "row", row_json);
+
+            BITMAP_FOR_EACH_1 (idx, class->n_columns, row->written) {
+                const struct ovsdb_idl_column *column = &class->columns[idx];
+
+                if (row->old
+                    ? !ovsdb_datum_equals(&row->old[idx], &row->new[idx],
+                                          &column->type)
+                    : !ovsdb_datum_is_default(&row->new[idx], &column->type)) {
+                    json_object_put(row_json, column->name,
+                                    substitute_uuids(
+                                        ovsdb_datum_to_json(&row->new[idx],
+                                                            &column->type),
+                                        txn));
+                }
+            }
+
+            if (!row->old || !shash_is_empty(json_object(row_json))) {
+                json_array_add(operations, op);
+                any_updates = true;
+            } else {
+                json_destroy(op);
+            }
+        }
+    }
+
+    /* Add increment. */
+    if (txn->inc_table && any_updates) {
+        struct json *op;
+
+        txn->inc_index = operations->u.array.n - 1;
+
+        op = json_object_create();
+        json_object_put_string(op, "op", "mutate");
+        json_object_put_string(op, "table", txn->inc_table);
+        json_object_put(op, "where",
+                        substitute_uuids(json_clone(txn->inc_where), txn));
+        json_object_put(op, "mutations",
+                        json_array_create_1(
+                            json_array_create_3(
+                                json_string_create(txn->inc_column),
+                                json_string_create("+="),
+                                json_integer_create(1))));
+        json_array_add(operations, op);
+
+        op = json_object_create();
+        json_object_put_string(op, "op", "select");
+        json_object_put_string(op, "table", txn->inc_table);
+        json_object_put(op, "where",
+                        substitute_uuids(json_clone(txn->inc_where), txn));
+        json_object_put(op, "columns",
+                        json_array_create_1(json_string_create(
+                                                txn->inc_column)));
+        json_array_add(operations, op);
+    }
+
+    if (txn->comment.length) {
+        struct json *op = json_object_create();
+        json_object_put_string(op, "op", "comment");
+        json_object_put_string(op, "comment", ds_cstr(&txn->comment));
+        json_array_add(operations, op);
+    }
+
+    if (txn->dry_run) {
+        struct json *op = json_object_create();
+        json_object_put_string(op, "op", "abort");
+        json_array_add(operations, op);
+    }
+
+    if (!any_updates) {
+        txn->status = TXN_UNCHANGED;
+        json_destroy(operations);
+    } else if (!jsonrpc_session_send(
+                   txn->idl->session,
+                   jsonrpc_create_request(
+                       "transact", operations, &txn->request_id))) {
+        hmap_insert(&txn->idl->outstanding_txns, &txn->hmap_node,
+                    json_hash(txn->request_id, 0));
+    } else {
+        txn->status = TXN_TRY_AGAIN;
+    }
+
+    ovsdb_idl_txn_disassemble(txn);
+    return txn->status;
+}
+
+/* Attempts to commit 'txn', blocking until the commit either succeeds or
+ * fails.  Returns the final commit status, which may be any TXN_* value other
+ * than TXN_INCOMPLETE. */
+enum ovsdb_idl_txn_status
+ovsdb_idl_txn_commit_block(struct ovsdb_idl_txn *txn)
+{
+    enum ovsdb_idl_txn_status status;
+
+    while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+        ovsdb_idl_run(txn->idl);
+        ovsdb_idl_wait(txn->idl);
+        ovsdb_idl_txn_wait(txn);
+        poll_block();
+    }
+    return status;
+}
+
+int64_t
+ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn)
+{
+    assert(txn->status == TXN_SUCCESS);
+    return txn->inc_new_value;
+}
+
+void
+ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn)
+{
+    ovsdb_idl_txn_disassemble(txn);
+    if (txn->status == TXN_INCOMPLETE) {
+        txn->status = TXN_ABORTED;
+    }
+}
+
+const char *
+ovsdb_idl_txn_get_error(const struct ovsdb_idl_txn *txn)
+{
+    if (txn->status != TXN_ERROR) {
+        return ovsdb_idl_txn_status_to_string(txn->status);
+    } else if (txn->error) {
+        return txn->error;
+    } else {
+        return "no error details available";
+    }
+}
+
+static void
+ovsdb_idl_txn_set_error_json(struct ovsdb_idl_txn *txn,
+                             const struct json *json)
+{
+    if (txn->error == NULL) {
+        txn->error = json_to_string(json, JSSF_SORT);
+    }
+}
+
+/* For transaction 'txn' that completed successfully, finds and returns the
+ * permanent UUID that the database assigned to a newly inserted row, given the
+ * 'uuid' that ovsdb_idl_txn_insert() assigned locally to that row.
+ *
+ * Returns NULL if 'uuid' is not a UUID assigned by ovsdb_idl_txn_insert() or
+ * if it was assigned by that function and then deleted by
+ * ovsdb_idl_txn_delete() within the same transaction.  (Rows that are inserted
+ * and then deleted within a single transaction are never sent to the database
+ * server, so it never assigns them a permanent UUID.) */
+const struct uuid *
+ovsdb_idl_txn_get_insert_uuid(const struct ovsdb_idl_txn *txn,
+                              const struct uuid *uuid)
+{
+    const struct ovsdb_idl_txn_insert *insert;
+
+    assert(txn->status == TXN_SUCCESS || txn->status == TXN_UNCHANGED);
+    HMAP_FOR_EACH_IN_BUCKET (insert, struct ovsdb_idl_txn_insert, hmap_node,
+                             uuid_hash(uuid), &txn->inserted_rows) {
+        if (uuid_equals(uuid, &insert->dummy)) {
+            return &insert->real;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn,
+                       enum ovsdb_idl_txn_status status)
+{
+    txn->status = status;
+    hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node);
+}
+
+void
+ovsdb_idl_txn_read(const struct ovsdb_idl_row *row,
+                   const struct ovsdb_idl_column *column,
+                   struct ovsdb_datum *datum)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    if (row->written && bitmap_is_set(row->written, column_idx)) {
+        ovsdb_datum_clone(datum, &row->new[column_idx], &column->type);
+    } else if (row->old) {
+        ovsdb_datum_clone(datum, &row->old[column_idx], &column->type);
+    } else {
+        ovsdb_datum_init_default(datum, &column->type);
+    }
+}
+
+void
+ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_,
+                    const struct ovsdb_idl_column *column,
+                    struct ovsdb_datum *datum)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    assert(column_idx < class->n_columns);
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    if (row->old == row->new) {
+        row->new = xmalloc(class->n_columns * sizeof *row->new);
+    }
+    if (!row->written) {
+        row->written = bitmap_allocate(class->n_columns);
+    }
+    if (bitmap_is_set(row->written, column_idx)) {
+        ovsdb_datum_destroy(&row->new[column_idx], &column->type);
+    } else {
+        bitmap_set1(row->written, column_idx);
+    }
+    row->new[column_idx] = *datum;
+    (column->unparse)(row);
+    (column->parse)(row, &row->new[column_idx]);
+}
+
+void
+ovsdb_idl_txn_verify(const struct ovsdb_idl_row *row_,
+                     const struct ovsdb_idl_column *column)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    if (!row->old
+        || (row->written && bitmap_is_set(row->written, column_idx))) {
+        return;
+    }
+
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    if (!row->prereqs) {
+        row->prereqs = bitmap_allocate(class->n_columns);
+    }
+    bitmap_set1(row->prereqs, column_idx);
+}
+
+void
+ovsdb_idl_txn_delete(const struct ovsdb_idl_row *row_)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+
+    assert(row->new != NULL);
+    if (!row->old) {
+        ovsdb_idl_row_unparse(row);
+        ovsdb_idl_row_clear_new(row);
+        assert(!row->prereqs);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        hmap_remove(&row->table->idl->txn->txn_rows, &row->txn_node);
+        free(row);
+        return;
+    }
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    ovsdb_idl_row_clear_new(row);
+    row->new = NULL;
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_txn_insert(struct ovsdb_idl_txn *txn,
+                     const struct ovsdb_idl_table_class *class)
+{
+    struct ovsdb_idl_row *row = ovsdb_idl_row_create__(class);
+    uuid_generate(&row->uuid);
+    row->table = ovsdb_idl_table_from_class(txn->idl, class);
+    row->new = xmalloc(class->n_columns * sizeof *row->new);
+    row->written = bitmap_allocate(class->n_columns);
+    hmap_insert(&row->table->rows, &row->hmap_node, uuid_hash(&row->uuid));
+    hmap_insert(&txn->txn_rows, &row->txn_node, uuid_hash(&row->uuid));
+    return row;
+}
+
+static void
+ovsdb_idl_txn_abort_all(struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+
+    HMAP_FOR_EACH (txn, struct ovsdb_idl_txn, hmap_node,
+                   &idl->outstanding_txns) {
+        ovsdb_idl_txn_complete(txn, TXN_TRY_AGAIN);
+    }
+}
+
+static struct ovsdb_idl_txn *
+ovsdb_idl_txn_find(struct ovsdb_idl *idl, const struct json *id)
+{
+    struct ovsdb_idl_txn *txn;
+
+    HMAP_FOR_EACH_WITH_HASH (txn, struct ovsdb_idl_txn, hmap_node,
+                             json_hash(id, 0), &idl->outstanding_txns) {
+        if (json_equal(id, txn->request_id)) {
+            return txn;
+        }
+    }
+    return NULL;
+}
+
+static bool
+check_json_type(const struct json *json, enum json_type type, const char *name)
+{
+    if (!json) {
+        VLOG_WARN_RL(&syntax_rl, "%s is missing", name);
+        return false;
+    } else if (json->type != type) {
+        VLOG_WARN_RL(&syntax_rl, "%s is %s instead of %s",
+                     name, json_type_to_string(json->type),
+                     json_type_to_string(type));
+        return false;
+    } else {
+        return true;
+    }
+}
+
+static bool
+ovsdb_idl_txn_process_inc_reply(struct ovsdb_idl_txn *txn,
+                                const struct json_array *results)
+{
+    struct json *count, *rows, *row, *column;
+    struct shash *mutate, *select;
+
+    if (txn->inc_index + 2 > results->n) {
+        VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations "
+                     "for increment (has %u, needs %u)",
+                     results->n, txn->inc_index + 2);
+        return false;
+    }
+
+    /* We know that this is a JSON object because the loop in
+     * ovsdb_idl_txn_process_reply() checked. */
+    mutate = json_object(results->elems[txn->inc_index]);
+    count = shash_find_data(mutate, "count");
+    if (!check_json_type(count, JSON_INTEGER, "\"mutate\" reply \"count\"")) {
+        return false;
+    }
+    if (count->u.integer != 1) {
+        VLOG_WARN_RL(&syntax_rl,
+                     "\"mutate\" reply \"count\" is %"PRId64" instead of 1",
+                     count->u.integer);
+        return false;
+    }
+
+    select = json_object(results->elems[txn->inc_index + 1]);
+    rows = shash_find_data(select, "rows");
+    if (!check_json_type(rows, JSON_ARRAY, "\"select\" reply \"rows\"")) {
+        return false;
+    }
+    if (rows->u.array.n != 1) {
+        VLOG_WARN_RL(&syntax_rl, "\"select\" reply \"rows\" has %u elements "
+                     "instead of 1",
+                     rows->u.array.n);
+        return false;
+    }
+    row = rows->u.array.elems[0];
+    if (!check_json_type(row, JSON_OBJECT, "\"select\" reply row")) {
+        return false;
+    }
+    column = shash_find_data(json_object(row), txn->inc_column);
+    if (!check_json_type(column, JSON_INTEGER,
+                         "\"select\" reply inc column")) {
+        return false;
+    }
+    txn->inc_new_value = column->u.integer;
+    return true;
+}
+
+static bool
+ovsdb_idl_txn_process_insert_reply(struct ovsdb_idl_txn_insert *insert,
+                                   const struct json_array *results)
+{
+    static const struct ovsdb_base_type uuid_type = OVSDB_BASE_UUID_INIT;
+    struct ovsdb_error *error;
+    struct json *json_uuid;
+    union ovsdb_atom uuid;
+    struct shash *reply;
+
+    if (insert->op_index >= results->n) {
+        VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations "
+                     "for insert (has %u, needs %u)",
+                     results->n, insert->op_index);
+        return false;
+    }
+
+    /* We know that this is a JSON object because the loop in
+     * ovsdb_idl_txn_process_reply() checked. */
+    reply = json_object(results->elems[insert->op_index]);
+    json_uuid = shash_find_data(reply, "uuid");
+    if (!check_json_type(json_uuid, JSON_ARRAY, "\"insert\" reply \"uuid\"")) {
+        return false;
+    }
+
+    error = ovsdb_atom_from_json(&uuid, &uuid_type, json_uuid, NULL);
+    if (error) {
+        char *s = ovsdb_error_to_string(error);
+        VLOG_WARN_RL(&syntax_rl, "\"insert\" reply \"uuid\" is not a JSON "
+                     "UUID: %s", s);
+        free(s);
+        return false;
+    }
+
+    insert->real = uuid.uuid;
+
+    return true;
+}
+
+static bool
+ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl,
+                            const struct jsonrpc_msg *msg)
+{
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+
+    txn = ovsdb_idl_txn_find(idl, msg->id);
+    if (!txn) {
+        return false;
+    }
+
+    if (msg->type == JSONRPC_ERROR) {
+        status = TXN_ERROR;
+    } else if (msg->result->type != JSON_ARRAY) {
+        VLOG_WARN_RL(&syntax_rl, "reply to \"transact\" is not JSON array");
+        status = TXN_ERROR;
+    } else {
+        struct json_array *ops = &msg->result->u.array;
+        int hard_errors = 0;
+        int soft_errors = 0;
+        size_t i;
+
+        for (i = 0; i < ops->n; i++) {
+            struct json *op = ops->elems[i];
+
+            if (op->type == JSON_NULL) {
+                /* This isn't an error in itself but indicates that some prior
+                 * operation failed, so make sure that we know about it. */
+                soft_errors++;
+            } else if (op->type == JSON_OBJECT) {
+                struct json *error;
+
+                error = shash_find_data(json_object(op), "error");
+                if (error) {
+                    if (error->type == JSON_STRING) {
+                        if (!strcmp(error->u.string, "timed out")) {
+                            soft_errors++;
+                        } else if (strcmp(error->u.string, "aborted")) {
+                            hard_errors++;
+                            ovsdb_idl_txn_set_error_json(txn, op);
+                        }
+                    } else {
+                        hard_errors++;
+                        ovsdb_idl_txn_set_error_json(txn, op);
+                        VLOG_WARN_RL(&syntax_rl,
+                                     "\"error\" in reply is not JSON string");
+                    }
+                }
+            } else {
+                hard_errors++;
+                ovsdb_idl_txn_set_error_json(txn, op);
+                VLOG_WARN_RL(&syntax_rl,
+                             "operation reply is not JSON null or object");
+            }
+        }
+
+        if (!soft_errors && !hard_errors) {
+            struct ovsdb_idl_txn_insert *insert;
+
+            if (txn->inc_table && !ovsdb_idl_txn_process_inc_reply(txn, ops)) {
+                hard_errors++;
+            }
+
+            HMAP_FOR_EACH (insert, struct ovsdb_idl_txn_insert, hmap_node,
+                           &txn->inserted_rows) {
+                if (!ovsdb_idl_txn_process_insert_reply(insert, ops)) {
+                    hard_errors++;
+                }
+            }
+        }
+
+        status = (hard_errors ? TXN_ERROR
+                  : soft_errors ? TXN_TRY_AGAIN
+                  : TXN_SUCCESS);
+    }
+
+    ovsdb_idl_txn_complete(txn, status);
+    return true;
+}
+
+struct ovsdb_idl_txn *
+ovsdb_idl_txn_get(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_txn *txn = row->table->idl->txn;
+    assert(txn != NULL);
+    return txn;
+}
+
+struct ovsdb_idl *
+ovsdb_idl_txn_get_idl (struct ovsdb_idl_txn *txn)
+{
+    return txn->idl;
+}
+
diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h
new file mode 100644 (file)
index 0000000..2aaaa71
--- /dev/null
@@ -0,0 +1,91 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_H
+#define OVSDB_IDL_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_datum;
+struct ovsdb_idl_class;
+struct ovsdb_idl_column;
+struct ovsdb_idl_table_class;
+struct uuid;
+
+struct ovsdb_idl *ovsdb_idl_create(const char *remote,
+                                   const struct ovsdb_idl_class *);
+void ovsdb_idl_destroy(struct ovsdb_idl *);
+
+void ovsdb_idl_run(struct ovsdb_idl *);
+void ovsdb_idl_wait(struct ovsdb_idl *);
+
+unsigned int ovsdb_idl_get_seqno(const struct ovsdb_idl *);
+bool ovsdb_idl_has_ever_connected(const struct ovsdb_idl *);
+void ovsdb_idl_force_reconnect(struct ovsdb_idl *);
+
+const struct ovsdb_idl_row *ovsdb_idl_get_row_for_uuid(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *,
+    const struct uuid *);
+const struct ovsdb_idl_row *ovsdb_idl_first_row(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *);
+const struct ovsdb_idl_row *ovsdb_idl_next_row(const struct ovsdb_idl_row *);
+
+enum ovsdb_idl_txn_status {
+    TXN_UNCHANGED,              /* Transaction didn't include any changes. */
+    TXN_INCOMPLETE,             /* Commit in progress, please wait. */
+    TXN_ABORTED,                /* ovsdb_idl_txn_abort() called. */
+    TXN_SUCCESS,                /* Commit successful. */
+    TXN_TRY_AGAIN,              /* Commit failed because a "verify" operation
+                                 * reported an inconsistency, due to a network
+                                 * problem, or other transient failure. */
+    TXN_ERROR                   /* Commit failed due to a hard error. */
+};
+
+const char *ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status);
+
+struct ovsdb_idl_txn *ovsdb_idl_txn_create(struct ovsdb_idl *);
+void ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *, const char *, ...)
+    PRINTF_FORMAT (2, 3);
+void ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_increment(struct ovsdb_idl_txn *, const char *table,
+                             const char *column, const struct json *where);
+void ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *);
+enum ovsdb_idl_txn_status ovsdb_idl_txn_commit(struct ovsdb_idl_txn *);
+enum ovsdb_idl_txn_status ovsdb_idl_txn_commit_block(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_abort(struct ovsdb_idl_txn *);
+
+const char *ovsdb_idl_txn_get_error(const struct ovsdb_idl_txn *);
+
+int64_t ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *);
+const struct uuid *ovsdb_idl_txn_get_insert_uuid(const struct ovsdb_idl_txn *,
+                                                 const struct uuid *);
+
+void ovsdb_idl_txn_read(const struct ovsdb_idl_row *,
+                        const struct ovsdb_idl_column *,
+                        struct ovsdb_datum *);
+void ovsdb_idl_txn_write(const struct ovsdb_idl_row *,
+                         const struct ovsdb_idl_column *,
+                         struct ovsdb_datum *);
+void ovsdb_idl_txn_delete(const struct ovsdb_idl_row *);
+const struct ovsdb_idl_row *ovsdb_idl_txn_insert(
+    struct ovsdb_idl_txn *, const struct ovsdb_idl_table_class *);
+
+struct ovsdb_idl *ovsdb_idl_txn_get_idl (struct ovsdb_idl_txn *);
+
+#endif /* ovsdb-idl.h */
diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c
new file mode 100644 (file)
index 0000000..2a4c3d9
--- /dev/null
@@ -0,0 +1,166 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-parser.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "ovsdb-error.h"
+
+void
+ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json,
+                  const char *name, ...)
+{
+    va_list args;
+
+    va_start(args, name);
+    parser->name = xvasprintf(name, args);
+    va_end(args);
+
+    svec_init(&parser->used);
+    parser->error = NULL;
+
+    parser->json = (json && json->type == JSON_OBJECT ? json : NULL);
+    if (!parser->json) {
+        ovsdb_parser_raise_error(parser, "Object expected.");
+    }
+}
+
+bool
+ovsdb_parser_is_id(const char *string)
+{
+    unsigned char c;
+
+    c = *string;
+    if (!isalpha(c) && c != '_') {
+        return false;
+    }
+
+    for (;;) {
+        c = *++string;
+        if (c == '\0') {
+            return true;
+        } else if (!isalpha(c) && !isdigit(c) && c != '_') {
+            return false;
+        }
+    }
+}
+
+const struct json *
+ovsdb_parser_member(struct ovsdb_parser *parser, const char *name,
+                    enum ovsdb_parser_types types)
+{
+    struct json *value;
+
+    if (!parser->json) {
+        return NULL;
+    }
+
+    value = shash_find_data(json_object(parser->json), name);
+    if (!value) {
+        if (!(types & OP_OPTIONAL)) {
+            ovsdb_parser_raise_error(parser,
+                                     "Required '%s' member is missing.", name);
+        }
+        return NULL;
+    }
+
+    if ((value->type >= 0 && value->type < JSON_N_TYPES
+         && types & (1u << value->type))
+        || (types & OP_ID && value->type == JSON_STRING
+            && ovsdb_parser_is_id(value->u.string)))
+    {
+        svec_add(&parser->used, name);
+        return value;
+    } else {
+        ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.",
+                                 name);
+        return NULL;
+    }
+}
+
+void
+ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...)
+{
+    if (!parser->error) {
+        struct ovsdb_error *error;
+        va_list args;
+        char *message;
+
+        va_start(args, format);
+        message = xvasprintf(format, args);
+        va_end(args);
+
+        error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s",
+                                   parser->name, message);
+        free(message);
+
+        parser->error = error;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_parser_get_error(const struct ovsdb_parser *parser)
+{
+    return parser->error ? ovsdb_error_clone(parser->error) : NULL;
+}
+
+bool
+ovsdb_parser_has_error(const struct ovsdb_parser *parser)
+{
+    return parser->error != NULL;
+}
+
+struct ovsdb_error *
+ovsdb_parser_finish(struct ovsdb_parser *parser)
+{
+    if (!parser->error) {
+        const struct shash *object = json_object(parser->json);
+        size_t n_unused;
+
+        /* XXX this method of detecting unused members can be made cheaper */
+        svec_sort_unique(&parser->used);
+        n_unused = shash_count(object) - parser->used.n;
+        if (n_unused) {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, object) {
+                if (!svec_contains(&parser->used, node->name)) {
+                    if (n_unused > 1) {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' and %zu other member%s "
+                            "are present but not allowed here.",
+                            node->name, n_unused - 1, n_unused > 2 ? "s" : "");
+                    } else {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' is present but not allowed here.",
+                            node->name);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    free(parser->name);
+    svec_destroy(&parser->used);
+
+    return parser->error;
+}
diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h
new file mode 100644 (file)
index 0000000..d6270bb
--- /dev/null
@@ -0,0 +1,79 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_PARSER_H
+#define OVSDB_PARSER_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "json.h"
+#include "svec.h"
+#include "util.h"
+
+struct ovsdb_parser {
+    char *name;                 /* Used only in error messages. */
+    struct svec used;           /* Already-parsed names from 'object'. */
+    const struct json *json;    /* JSON object being parsed. */
+    struct ovsdb_error *error;  /* Error signaled, if any. */
+};
+
+/* Check that the JSON types make the bitwise tricks below work OK. */
+BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10);
+BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10);
+BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10);
+BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10);
+BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10);
+BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10);
+BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10);
+BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10);
+BUILD_ASSERT_DECL(JSON_N_TYPES == 8);
+
+enum ovsdb_parser_types {
+    OP_NULL = 1 << JSON_NULL,             /* null */
+    OP_FALSE = 1 << JSON_FALSE,           /* false */
+    OP_TRUE = 1 << JSON_TRUE,             /* true */
+    OP_OBJECT = 1 << JSON_OBJECT,         /* {"a": b, "c": d, ...} */
+    OP_ARRAY = 1 << JSON_ARRAY,           /* [1, 2, 3, ...] */
+    OP_INTEGER = 1 << JSON_INTEGER,       /* 123. */
+    OP_NONINTEGER = 1 << JSON_REAL,       /* 123.456. */
+    OP_STRING = 1 << JSON_STRING,         /* "..." */
+    OP_ANY = (OP_NULL | OP_FALSE | OP_TRUE | OP_OBJECT | OP_ARRAY
+              | OP_INTEGER | OP_NONINTEGER | OP_STRING),
+
+    OP_BOOLEAN = OP_FALSE | OP_TRUE,
+    OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
+
+    OP_ID = 1 << JSON_N_TYPES,            /* "[_a-zA-Z][_a-zA-Z0-9]*" */
+    OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */
+};
+
+void ovsdb_parser_init(struct ovsdb_parser *, const struct json *,
+                       const char *name, ...)
+    PRINTF_FORMAT(3, 4);
+const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name,
+                                       enum ovsdb_parser_types);
+
+void ovsdb_parser_raise_error(struct ovsdb_parser *parser,
+                              const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+bool ovsdb_parser_has_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
+    WARN_UNUSED_RESULT;
+void ovsdb_parser_destroy(struct ovsdb_parser *);
+
+bool ovsdb_parser_is_id(const char *string);
+
+#endif /* ovsdb-parser.h */
diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c
new file mode 100644 (file)
index 0000000..b3452dd
--- /dev/null
@@ -0,0 +1,694 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-types.h"
+
+#include <float.h>
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+
+const struct ovsdb_type ovsdb_type_integer =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_INTEGER_INIT);
+const struct ovsdb_type ovsdb_type_real =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_REAL_INIT);
+const struct ovsdb_type ovsdb_type_boolean =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_BOOLEAN_INIT);
+const struct ovsdb_type ovsdb_type_string =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_STRING_INIT);
+const struct ovsdb_type ovsdb_type_uuid =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_UUID_INIT);
+\f
+/* ovsdb_atomic_type */
+const char *
+ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        return "void";
+
+    case OVSDB_TYPE_INTEGER:
+        return "integer";
+
+    case OVSDB_TYPE_REAL:
+        return "real";
+
+    case OVSDB_TYPE_BOOLEAN:
+        return "boolean";
+
+    case OVSDB_TYPE_STRING:
+        return "string";
+
+    case OVSDB_TYPE_UUID:
+        return "uuid";
+
+    case OVSDB_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+
+struct json *
+ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
+{
+    return json_string_create(ovsdb_atomic_type_to_string(type));
+}
+
+bool
+ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
+{
+    if (!strcmp(string, "integer")) {
+        *type = OVSDB_TYPE_INTEGER;
+    } else if (!strcmp(string, "real")) {
+        *type = OVSDB_TYPE_REAL;
+    } else if (!strcmp(string, "boolean")) {
+        *type = OVSDB_TYPE_BOOLEAN;
+    } else if (!strcmp(string, "string")) {
+        *type = OVSDB_TYPE_STRING;
+    } else if (!strcmp(string, "uuid")) {
+        *type = OVSDB_TYPE_UUID;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
+                            const struct json *json)
+{
+    if (json->type == JSON_STRING) {
+        if (ovsdb_atomic_type_from_string(json_string(json), type)) {
+            return NULL;
+        } else {
+            *type = OVSDB_TYPE_VOID;
+            return ovsdb_syntax_error(json, NULL,
+                                      "\"%s\" is not an atomic-type",
+                                      json_string(json));
+        }
+    } else {
+        *type = OVSDB_TYPE_VOID;
+        return ovsdb_syntax_error(json, NULL, "atomic-type expected");
+    }
+}
+\f
+/* ovsdb_base_type */
+
+void
+ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
+{
+    base->type = type;
+    base->enum_ = NULL;
+
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        break;
+
+    case OVSDB_TYPE_INTEGER:
+        base->u.integer.min = INT64_MIN;
+        base->u.integer.max = INT64_MAX;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        base->u.real.min = -DBL_MAX;
+        base->u.real.max = DBL_MAX;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        base->u.string.minLen = 0;
+        base->u.string.maxLen = UINT_MAX;
+        break;
+
+    case OVSDB_TYPE_UUID:
+        base->u.uuid.refTableName = NULL;
+        base->u.uuid.refTable = NULL;
+        break;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* Returns the type of the 'enum_' member for an ovsdb_base_type whose 'type'
+ * is 'atomic_type'. */
+const struct ovsdb_type *
+ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type atomic_type)
+{
+    static struct ovsdb_type *types[OVSDB_N_TYPES];
+
+    if (!types[atomic_type]) {
+        struct ovsdb_type *type;
+
+        types[atomic_type] = type = xmalloc(sizeof *type);
+        ovsdb_base_type_init(&type->key, atomic_type);
+        ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
+        type->n_min = 1;
+        type->n_max = UINT_MAX;
+    }
+    return types[atomic_type];
+}
+
+void
+ovsdb_base_type_clone(struct ovsdb_base_type *dst,
+                      const struct ovsdb_base_type *src)
+{
+    *dst = *src;
+
+    if (src->enum_) {
+        dst->enum_ = xmalloc(sizeof *dst->enum_);
+        ovsdb_datum_clone(dst->enum_, src->enum_,
+                          ovsdb_base_type_get_enum_type(dst->type));
+    }
+
+    switch (dst->type) {
+    case OVSDB_TYPE_VOID:
+    case OVSDB_TYPE_INTEGER:
+    case OVSDB_TYPE_REAL:
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        break;
+
+    case OVSDB_TYPE_UUID:
+        if (dst->u.uuid.refTableName) {
+            dst->u.uuid.refTableName = xstrdup(dst->u.uuid.refTableName);
+        }
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_base_type_destroy(struct ovsdb_base_type *base)
+{
+    if (base) {
+        if (base->enum_) {
+            ovsdb_datum_destroy(base->enum_,
+                                ovsdb_base_type_get_enum_type(base->type));
+            free(base->enum_);
+        }
+
+        switch (base->type) {
+        case OVSDB_TYPE_VOID:
+        case OVSDB_TYPE_INTEGER:
+        case OVSDB_TYPE_REAL:
+        case OVSDB_TYPE_BOOLEAN:
+            break;
+
+        case OVSDB_TYPE_STRING:
+            break;
+
+        case OVSDB_TYPE_UUID:
+            free(base->u.uuid.refTableName);
+            break;
+
+        case OVSDB_N_TYPES:
+            NOT_REACHED();
+
+        default:
+            NOT_REACHED();
+        }
+    }
+}
+
+bool
+ovsdb_base_type_is_valid(const struct ovsdb_base_type *base)
+{
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        return true;
+
+    case OVSDB_TYPE_INTEGER:
+        return base->u.integer.min <= base->u.integer.max;
+
+    case OVSDB_TYPE_REAL:
+        return base->u.real.min <= base->u.real.max;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return true;
+
+    case OVSDB_TYPE_STRING:
+        return base->u.string.minLen <= base->u.string.maxLen;
+
+    case OVSDB_TYPE_UUID:
+        return true;
+
+    case OVSDB_N_TYPES:
+    default:
+        return false;
+    }
+}
+
+bool
+ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
+{
+    if (base->enum_) {
+        return true;
+    }
+
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return (base->u.integer.min != INT64_MIN
+                || base->u.integer.max != INT64_MAX);
+
+    case OVSDB_TYPE_REAL:
+        return (base->u.real.min != -DBL_MAX
+                || base->u.real.max != DBL_MAX);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return false;
+
+    case OVSDB_TYPE_STRING:
+        return base->u.string.minLen != 0 || base->u.string.maxLen != UINT_MAX;
+
+    case OVSDB_TYPE_UUID:
+        return base->u.uuid.refTableName != NULL;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_base_type_clear_constraints(struct ovsdb_base_type *base)
+{
+    enum ovsdb_atomic_type type = base->type;
+    ovsdb_base_type_destroy(base);
+    ovsdb_base_type_init(base, type);
+}
+
+static struct ovsdb_error *
+parse_optional_uint(struct ovsdb_parser *parser, const char *member,
+                    unsigned int *uint)
+{
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_INTEGER | OP_OPTIONAL);
+    if (json) {
+        if (json->u.integer < 0 || json->u.integer > UINT_MAX) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "%s out of valid range 0 to %u",
+                                      member, UINT_MAX);
+        }
+        *uint = json->u.integer;
+    }
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_base_type_from_json(struct ovsdb_base_type *base,
+                          const struct json *json)
+{
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+    const struct json *type, *enum_;
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_atomic_type_from_json(&base->type, json);
+        if (error) {
+            return error;
+        }
+        ovsdb_base_type_init(base, base->type);
+        return NULL;
+    }
+
+    ovsdb_parser_init(&parser, json, "ovsdb type");
+    type = ovsdb_parser_member(&parser, "type", OP_STRING);
+    if (ovsdb_parser_has_error(&parser)) {
+        base->type = OVSDB_TYPE_VOID;
+        return ovsdb_parser_finish(&parser);
+    }
+
+    error = ovsdb_atomic_type_from_json(&base->type, type);
+    if (error) {
+        return error;
+    }
+
+    ovsdb_base_type_init(base, base->type);
+
+    enum_ = ovsdb_parser_member(&parser, "enum", OP_ANY | OP_OPTIONAL);
+    if (enum_) {
+        base->enum_ = xmalloc(sizeof *base->enum_);
+        error = ovsdb_datum_from_json(
+            base->enum_, ovsdb_base_type_get_enum_type(base->type),
+            enum_, NULL);
+        if (error) {
+            free(base->enum_);
+            base->enum_ = NULL;
+        }
+    } else if (base->type == OVSDB_TYPE_INTEGER) {
+        const struct json *min, *max;
+
+        min = ovsdb_parser_member(&parser, "minInteger",
+                                  OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "maxInteger",
+                                  OP_INTEGER | OP_OPTIONAL);
+        base->u.integer.min = min ? min->u.integer : INT64_MIN;
+        base->u.integer.max = max ? max->u.integer : INT64_MAX;
+        if (base->u.integer.min > base->u.integer.max) {
+            error = ovsdb_syntax_error(json, NULL,
+                                       "minInteger exceeds maxInteger");
+        }
+    } else if (base->type == OVSDB_TYPE_REAL) {
+        const struct json *min, *max;
+
+        min = ovsdb_parser_member(&parser, "minReal", OP_NUMBER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "maxReal", OP_NUMBER | OP_OPTIONAL);
+        base->u.real.min = min ? json_real(min) : -DBL_MAX;
+        base->u.real.max = max ? json_real(max) : DBL_MAX;
+        if (base->u.real.min > base->u.real.max) {
+            error = ovsdb_syntax_error(json, NULL, "minReal exceeds maxReal");
+        }
+    } else if (base->type == OVSDB_TYPE_STRING) {
+        if (!error) {
+            error = parse_optional_uint(&parser, "minLength",
+                                        &base->u.string.minLen);
+        }
+        if (!error) {
+            error = parse_optional_uint(&parser, "maxLength",
+                                        &base->u.string.maxLen);
+        }
+        if (!error && base->u.string.minLen > base->u.string.maxLen) {
+            error = ovsdb_syntax_error(json, NULL,
+                                       "minLength exceeds maxLength");
+        }
+    } else if (base->type == OVSDB_TYPE_UUID) {
+        const struct json *refTable;
+
+        refTable = ovsdb_parser_member(&parser, "refTable",
+                                       OP_ID | OP_OPTIONAL);
+        if (refTable) {
+            const struct json *refType;
+
+            base->u.uuid.refTableName = xstrdup(refTable->u.string);
+
+            /* We can't set base->u.uuid.refTable here because we don't have
+             * enough context (we might not even be running in ovsdb-server).
+             * ovsdb_create() will set refTable later. */
+
+            refType = ovsdb_parser_member(&parser, "refType",
+                                          OP_ID | OP_OPTIONAL);
+            if (refType) {
+                const char *refType_s = json_string(refType);
+                if (!strcmp(refType_s, "strong")) {
+                    base->u.uuid.refType = OVSDB_REF_STRONG;
+                } else if (!strcmp(refType_s, "weak")) {
+                    base->u.uuid.refType = OVSDB_REF_WEAK;
+                } else {
+                    error = ovsdb_syntax_error(json, NULL, "refType must be "
+                                               "\"strong\" or \"weak\" (not "
+                                               "\"%s\")", refType_s);
+                }
+            } else {
+                base->u.uuid.refType = OVSDB_REF_STRONG;
+            }
+        }
+    }
+
+    if (error) {
+        ovsdb_error_destroy(ovsdb_parser_finish(&parser));
+    } else {
+        error = ovsdb_parser_finish(&parser);
+    }
+    if (error) {
+        ovsdb_base_type_destroy(base);
+        base->type = OVSDB_TYPE_VOID;
+    }
+    return error;
+}
+
+struct json *
+ovsdb_base_type_to_json(const struct ovsdb_base_type *base)
+{
+    struct json *json;
+
+    if (!ovsdb_base_type_has_constraints(base)) {
+        return json_string_create(ovsdb_atomic_type_to_string(base->type));
+    }
+
+    json = json_object_create();
+    json_object_put_string(json, "type",
+                           ovsdb_atomic_type_to_string(base->type));
+
+    if (base->enum_) {
+        const struct ovsdb_type *type;
+
+        type = ovsdb_base_type_get_enum_type(base->type);
+        json_object_put(json, "enum", ovsdb_datum_to_json(base->enum_, type));
+    }
+
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (base->u.integer.min != INT64_MIN) {
+            json_object_put(json, "minInteger",
+                            json_integer_create(base->u.integer.min));
+        }
+        if (base->u.integer.max != INT64_MAX) {
+            json_object_put(json, "maxInteger",
+                            json_integer_create(base->u.integer.max));
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (base->u.real.min != -DBL_MAX) {
+            json_object_put(json, "minReal",
+                            json_real_create(base->u.real.min));
+        }
+        if (base->u.real.max != DBL_MAX) {
+            json_object_put(json, "maxReal",
+                            json_real_create(base->u.real.max));
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (base->u.string.minLen != 0) {
+            json_object_put(json, "minLength",
+                            json_integer_create(base->u.string.minLen));
+        }
+        if (base->u.string.maxLen != UINT_MAX) {
+            json_object_put(json, "maxLength",
+                            json_integer_create(base->u.string.maxLen));
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        if (base->u.uuid.refTableName) {
+            json_object_put_string(json, "refTable",
+                                   base->u.uuid.refTableName);
+            if (base->u.uuid.refType == OVSDB_REF_WEAK) {
+                json_object_put_string(json, "refType", "weak");
+            }
+        }
+        break;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+
+    return json;
+}
+\f
+/* ovsdb_type */
+
+void
+ovsdb_type_clone(struct ovsdb_type *dst, const struct ovsdb_type *src)
+{
+    ovsdb_base_type_clone(&dst->key, &src->key);
+    ovsdb_base_type_clone(&dst->value, &src->value);
+    dst->n_min = src->n_min;
+    dst->n_max = src->n_max;
+}
+
+void
+ovsdb_type_destroy(struct ovsdb_type *type)
+{
+    ovsdb_base_type_destroy(&type->key);
+    ovsdb_base_type_destroy(&type->value);
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+    return (type->key.type != OVSDB_TYPE_VOID
+            && ovsdb_base_type_is_valid(&type->key)
+            && ovsdb_base_type_is_valid(&type->value)
+            && type->n_min <= 1
+            && type->n_min <= type->n_max
+            && type->n_max >= 1);
+}
+
+static struct ovsdb_error *
+n_from_json(const struct json *json, unsigned int *n)
+{
+    if (!json) {
+        return NULL;
+    } else if (json->type == JSON_INTEGER
+               && json->u.integer >= 0 && json->u.integer < UINT_MAX) {
+        *n = json->u.integer;
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "bad min or max value");
+    }
+}
+
+char *
+ovsdb_type_to_english(const struct ovsdb_type *type)
+{
+    const char *key = ovsdb_atomic_type_to_string(type->key.type);
+    const char *value = ovsdb_atomic_type_to_string(type->value.type);
+    if (ovsdb_type_is_scalar(type)) {
+        return xstrdup(key);
+    } else {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map");
+        if (type->n_max == UINT_MAX) {
+            if (type->n_min) {
+                ds_put_format(&s, " of %u or more", type->n_min);
+            } else {
+                ds_put_cstr(&s, " of");
+            }
+        } else if (type->n_min) {
+            ds_put_format(&s, " of %u to %u", type->n_min, type->n_max);
+        } else {
+            ds_put_format(&s, " of up to %u", type->n_max);
+        }
+        if (ovsdb_type_is_set(type)) {
+            ds_put_format(&s, " %ss", key);
+        } else {
+            ds_put_format(&s, " (%s, %s) pairs", key, value);
+        }
+        return ds_cstr(&s);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
+{
+    ovsdb_base_type_init(&type->value, OVSDB_TYPE_VOID);
+    type->n_min = 1;
+    type->n_max = 1;
+
+    if (json->type == JSON_STRING) {
+        return ovsdb_base_type_from_json(&type->key, json);
+    } else if (json->type == JSON_OBJECT) {
+        const struct json *key, *value, *min, *max;
+        struct ovsdb_error *error;
+        struct ovsdb_parser parser;
+
+        ovsdb_parser_init(&parser, json, "ovsdb type");
+        key = ovsdb_parser_member(&parser, "key", OP_STRING | OP_OBJECT);
+        value = ovsdb_parser_member(&parser, "value",
+                                    OP_STRING | OP_OBJECT | OP_OPTIONAL);
+        min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "max",
+                                  OP_INTEGER | OP_STRING | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            return error;
+        }
+
+        error = ovsdb_base_type_from_json(&type->key, key);
+        if (error) {
+            return error;
+        }
+
+        if (value) {
+            error = ovsdb_base_type_from_json(&type->value, value);
+            if (error) {
+                return error;
+            }
+        }
+
+        error = n_from_json(min, &type->n_min);
+        if (error) {
+            return error;
+        }
+
+        if (max && max->type == JSON_STRING
+            && !strcmp(max->u.string, "unlimited")) {
+            type->n_max = UINT_MAX;
+        } else {
+            error = n_from_json(max, &type->n_max);
+            if (error) {
+                return error;
+            }
+        }
+
+        if (!ovsdb_type_is_valid(type)) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "ovsdb type fails constraint checks");
+        }
+
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "ovsdb type expected");
+    }
+}
+
+struct json *
+ovsdb_type_to_json(const struct ovsdb_type *type)
+{
+    if (ovsdb_type_is_scalar(type)
+        && !ovsdb_base_type_has_constraints(&type->key)) {
+        return ovsdb_base_type_to_json(&type->key);
+    } else {
+        struct json *json = json_object_create();
+        json_object_put(json, "key", ovsdb_base_type_to_json(&type->key));
+        if (type->value.type != OVSDB_TYPE_VOID) {
+            json_object_put(json, "value",
+                            ovsdb_base_type_to_json(&type->value));
+        }
+        if (type->n_min != 1) {
+            json_object_put(json, "min", json_integer_create(type->n_min));
+        }
+        if (type->n_max == UINT_MAX) {
+            json_object_put_string(json, "max", "unlimited");
+        } else if (type->n_max != 1) {
+            json_object_put(json, "max", json_integer_create(type->n_max));
+        }
+        return json;
+    }
+}
diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h
new file mode 100644 (file)
index 0000000..6903aa8
--- /dev/null
@@ -0,0 +1,221 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TYPES_H
+#define OVSDB_TYPES_H 1
+
+#include <float.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+#include "uuid.h"
+
+struct json;
+
+/* An atomic type: one that OVSDB regards as a single unit of data. */
+enum ovsdb_atomic_type {
+    OVSDB_TYPE_VOID,            /* No value. */
+    OVSDB_TYPE_INTEGER,         /* Signed 64-bit integer. */
+    OVSDB_TYPE_REAL,            /* IEEE 754 double-precision floating point. */
+    OVSDB_TYPE_BOOLEAN,         /* True or false. */
+    OVSDB_TYPE_STRING,          /* UTF-8 string. */
+    OVSDB_TYPE_UUID,            /* RFC 4122 UUID referencing a table row. */
+    OVSDB_N_TYPES
+};
+
+static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
+bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
+struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
+                                                const struct json *);
+const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
+struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
+\f
+/* An atomic type plus optional constraints. */
+
+enum ovsdb_ref_type {
+    OVSDB_REF_STRONG,           /* Target must exist. */
+    OVSDB_REF_WEAK              /* Delete reference if target disappears. */
+};
+
+struct ovsdb_base_type {
+    enum ovsdb_atomic_type type;
+
+    /* If nonnull, a datum with keys of type 'type' that expresses all the
+     * valid values for this base_type. */
+    struct ovsdb_datum *enum_;
+
+    union {
+        struct ovsdb_integer_constraints {
+            int64_t min;        /* minInteger or INT64_MIN. */
+            int64_t max;        /* maxInteger or INT64_MAX. */
+        } integer;
+
+        struct ovsdb_real_constraints {
+            double min;         /* minReal or -DBL_MAX. */
+            double max;         /* minReal or DBL_MAX. */
+        } real;
+
+        /* No constraints for Boolean types. */
+
+        struct ovsdb_string_constraints {
+            unsigned int minLen; /* minLength or 0. */
+            unsigned int maxLen; /* maxLength or UINT_MAX. */
+        } string;
+
+        struct ovsdb_uuid_constraints {
+            char *refTableName; /* Name of referenced table, or NULL. */
+            struct ovsdb_table *refTable; /* Referenced table, if available. */
+            enum ovsdb_ref_type refType;  /* Reference type. */
+        } uuid;
+    } u;
+};
+
+#define OVSDB_BASE_VOID_INIT    { .type = OVSDB_TYPE_VOID }
+#define OVSDB_BASE_INTEGER_INIT { .type = OVSDB_TYPE_INTEGER,           \
+                                  .u.integer = { INT64_MIN, INT64_MAX } }
+#define OVSDB_BASE_REAL_INIT    { .type = OVSDB_TYPE_REAL,          \
+                                  .u.real = { -DBL_MAX, DBL_MAX } }
+#define OVSDB_BASE_BOOLEAN_INIT { .type = OVSDB_TYPE_BOOLEAN }
+#define OVSDB_BASE_STRING_INIT  { .type = OVSDB_TYPE_STRING,    \
+                                  .u.string = { 0, UINT_MAX } }
+#define OVSDB_BASE_UUID_INIT    { .type = OVSDB_TYPE_UUID,      \
+                                  .u.uuid = { NULL, NULL, 0 } }
+
+void ovsdb_base_type_init(struct ovsdb_base_type *, enum ovsdb_atomic_type);
+void ovsdb_base_type_clone(struct ovsdb_base_type *,
+                           const struct ovsdb_base_type *);
+void ovsdb_base_type_destroy(struct ovsdb_base_type *);
+
+bool ovsdb_base_type_is_valid(const struct ovsdb_base_type *);
+bool ovsdb_base_type_has_constraints(const struct ovsdb_base_type *);
+void ovsdb_base_type_clear_constraints(struct ovsdb_base_type *);
+const struct ovsdb_type *ovsdb_base_type_get_enum_type(enum ovsdb_atomic_type);
+
+struct ovsdb_error *ovsdb_base_type_from_json(struct ovsdb_base_type *,
+                                              const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_base_type_to_json(const struct ovsdb_base_type *);
+
+static inline bool ovsdb_base_type_is_ref(const struct ovsdb_base_type *);
+static inline bool ovsdb_base_type_is_strong_ref(
+    const struct ovsdb_base_type *);
+static inline bool ovsdb_base_type_is_weak_ref(const struct ovsdb_base_type *);
+\f
+/* An OVSDB type.
+ *
+ * Several rules constrain the valid types.  See ovsdb_type_is_valid() (in
+ * ovsdb-types.c) for details.
+ *
+ * If 'value_type' is OVSDB_TYPE_VOID, 'n_min' is 1, and 'n_max' is 1, then the
+ * type is a single atomic 'key_type'.
+ *
+ * If 'value_type' is OVSDB_TYPE_VOID and 'n_min' or 'n_max' (or both) has a
+ * value other than 1, then the type is a set of 'key_type'.  If 'n_min' is 0
+ * and 'n_max' is 1, then the type can also be considered an optional
+ * 'key_type'.
+ *
+ * If 'value_type' is not OVSDB_TYPE_VOID, then the type is a map from
+ * 'key_type' to 'value_type'.  If 'n_min' is 0 and 'n_max' is 1, then the type
+ * can also be considered an optional pair of 'key_type' and 'value_type'.
+ */
+struct ovsdb_type {
+    struct ovsdb_base_type key;
+    struct ovsdb_base_type value;
+    unsigned int n_min;
+    unsigned int n_max;         /* UINT_MAX stands in for "unlimited". */
+};
+
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY) { KEY, OVSDB_BASE_VOID_INIT, 1, 1 }
+
+extern const struct ovsdb_type ovsdb_type_integer;
+extern const struct ovsdb_type ovsdb_type_real;
+extern const struct ovsdb_type ovsdb_type_boolean;
+extern const struct ovsdb_type ovsdb_type_string;
+extern const struct ovsdb_type ovsdb_type_uuid;
+
+void ovsdb_type_clone(struct ovsdb_type *, const struct ovsdb_type *);
+void ovsdb_type_destroy(struct ovsdb_type *);
+
+bool ovsdb_type_is_valid(const struct ovsdb_type *);
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *);
+
+char *ovsdb_type_to_english(const struct ovsdb_type *);
+
+struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *,
+                                         const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_type_to_json(const struct ovsdb_type *);
+\f
+/* Inline function implementations. */
+
+static inline bool
+ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
+{
+    return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
+}
+
+static inline bool
+ovsdb_base_type_is_ref(const struct ovsdb_base_type *base)
+{
+    return base->type == OVSDB_TYPE_UUID && base->u.uuid.refTable;
+}
+
+static inline bool
+ovsdb_base_type_is_strong_ref(const struct ovsdb_base_type *base)
+{
+    return (ovsdb_base_type_is_ref(base)
+            && base->u.uuid.refType == OVSDB_REF_STRONG);
+}
+
+static inline bool
+ovsdb_base_type_is_weak_ref(const struct ovsdb_base_type *base)
+{
+    return (ovsdb_base_type_is_ref(base)
+            && base->u.uuid.refType == OVSDB_REF_WEAK);
+}
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
+{
+    return (type->value.type == OVSDB_TYPE_VOID
+            && type->n_min == 1 && type->n_max == 1);
+}
+
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type)
+{
+    return type->n_min == 0;
+}
+
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
+{
+    return type->n_max > 1;
+}
+
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
+{
+    return (type->value.type == OVSDB_TYPE_VOID
+            && (type->n_min != 1 || type->n_max != 1));
+}
+
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
+{
+    return type->value.type != OVSDB_TYPE_VOID;
+}
+
+#endif /* ovsdb-types.h */
index 0547791..5c51feb 100644 (file)
 #include <config.h>
 #include "packets.h"
 #include <netinet/in.h>
+#include <stdlib.h>
 #include "ofpbuf.h"
 
+bool
+dpid_from_string(const char *s, uint64_t *dpidp)
+{
+    *dpidp = (strlen(s) == 16 && strspn(s, "0123456789abcdefABCDEF") == 16
+              ? strtoll(s, NULL, 16)
+              : 0);
+    return *dpidp != 0;
+}
+
+bool
+eth_addr_from_string(const char *s, uint8_t ea[ETH_ADDR_LEN])
+{
+    if (sscanf(s, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))
+        == ETH_ADDR_SCAN_COUNT) {
+        return true;
+    } else {
+        memset(ea, 0, ETH_ADDR_LEN);
+        return false;
+    }
+}
+
 /* Fills 'b' with an 802.2 SNAP packet with Ethernet source address 'eth_src',
  * the Nicira OUI as SNAP organization and 'snap_type' as SNAP type.  The text
  * string in 'tag' is enclosed as the packet payload.
index 4595c12..7ea462b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 struct ofpbuf;
 
+bool dpid_from_string(const char *s, uint64_t *dpidp);
+
 #define ETH_ADDR_LEN           6
 
-static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] UNUSED
+static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] OVS_UNUSED
     = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
 
 static inline bool eth_addr_is_broadcast(const uint8_t ea[6])
@@ -36,21 +38,16 @@ static inline bool eth_addr_is_broadcast(const uint8_t ea[6])
     return (ea[0] & ea[1] & ea[2] & ea[3] & ea[4] & ea[5]) == 0xff;
 }
 
-/* Returns true if 'ea' is an Ethernet address used for virtual interfaces
- * under XenServer.  Generally the actual Ethernet address is FE:FF:FF:FF:FF:FF
- * but it can be FE:FE:FE:FE:FE:FE in some cases. */
-static inline bool eth_addr_is_vif(const uint8_t ea[6])
-{
-    return ea[0] == 0xfe && (ea[1] & ea[2] & ea[3] & ea[4] & ea[5]) >= 0xfe;
-}
-
 static inline bool eth_addr_is_multicast(const uint8_t ea[6])
 {
     return ea[0] & 1;
 }
 static inline bool eth_addr_is_local(const uint8_t ea[6]) 
 {
-    return ea[0] & 2;
+    /* Local if it is either a locally administered address or a Nicira random
+     * address. */
+    return !!(ea[0] & 2)
+       || (ea[0] == 0x00 && ea[1] == 0x23 && ea[2] == 0x20 && !!(ea[3] & 0x80));
 }
 static inline bool eth_addr_is_zero(const uint8_t ea[6]) 
 {
@@ -89,6 +86,18 @@ static inline void eth_addr_random(uint8_t ea[ETH_ADDR_LEN])
     random_bytes(ea, ETH_ADDR_LEN);
     eth_addr_mark_random(ea);
 }
+static inline void eth_addr_nicira_random(uint8_t ea[ETH_ADDR_LEN])
+{
+    eth_addr_random(ea);
+
+    /* Set the OUI to the Nicira one. */
+    ea[0] = 0x00;
+    ea[1] = 0x23;
+    ea[2] = 0x20;
+
+    /* Set the top bit to indicate random Nicira address. */
+    ea[3] |= 0x80;
+}
 /* Returns true if 'ea' is a reserved multicast address, that a bridge must
  * never forward, false otherwise. */
 static inline bool eth_addr_is_reserved(const uint8_t ea[ETH_ADDR_LEN])
@@ -101,6 +110,8 @@ static inline bool eth_addr_is_reserved(const uint8_t ea[ETH_ADDR_LEN])
             && (ea[5] & 0xf0) == 0x00);
 }
 
+bool eth_addr_from_string(const char *, uint8_t ea[ETH_ADDR_LEN]);
+
 void compose_benign_packet(struct ofpbuf *, const char *tag,
                            uint16_t snap_type,
                            const uint8_t eth_src[ETH_ADDR_LEN]);
@@ -179,7 +190,10 @@ struct llc_snap_header {
 BUILD_ASSERT_DECL(LLC_SNAP_HEADER_LEN == sizeof(struct llc_snap_header));
 
 #define VLAN_VID_MASK 0x0fff
+#define VLAN_VID_SHIFT 0
+
 #define VLAN_PCP_MASK 0xe000
+#define VLAN_PCP_SHIFT 13
 
 #define VLAN_HEADER_LEN 4
 struct vlan_header {
@@ -213,6 +227,10 @@ BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
 #define IP_IHL(ip_ihl_ver) ((ip_ihl_ver) & 15)
 #define IP_IHL_VER(ihl, ver) (((ver) << 4) | (ihl))
 
+/* TOS fields. */
+#define IP_ECN_MASK 0x03
+#define IP_DSCP_MASK 0xfc
+
 #define IP_TYPE_ICMP 1
 #define IP_TYPE_TCP 6
 #define IP_TYPE_UDP 17
index 967bb5c..028dd0c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,14 +34,16 @@ struct pcap_hdr {
     uint32_t sigfigs;        /* accuracy of timestamps */
     uint32_t snaplen;        /* max length of captured packets */
     uint32_t network;        /* data link type */
-} PACKED;
+};
+BUILD_ASSERT_DECL(sizeof(struct pcap_hdr) == 24);
 
 struct pcaprec_hdr {
     uint32_t ts_sec;         /* timestamp seconds */
     uint32_t ts_usec;        /* timestamp microseconds */
     uint32_t incl_len;       /* number of octets of packet saved in file */
     uint32_t orig_len;       /* actual length of packet */
-} PACKED;
+};
+BUILD_ASSERT_DECL(sizeof(struct pcaprec_hdr) == 16);
 
 FILE *
 pcap_open(const char *file_name, const char *mode)
index 945b5c4..8f65f94 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 #include "backtrace.h"
 #include "coverage.h"
 #include "dynamic-string.h"
+#include "fatal-signal.h"
 #include "list.h"
 #include "timeval.h"
 
@@ -37,13 +38,10 @@ struct poll_waiter {
     struct list node;           /* Element in global waiters list. */
     int fd;                     /* File descriptor. */
     short int events;           /* Events to wait for (POLLIN, POLLOUT). */
-    poll_fd_func *function;     /* Callback function, if any, or null. */
-    void *aux;                  /* Argument to callback function. */
     struct backtrace *backtrace; /* Optionally, event that created waiter. */
 
     /* Set only when poll_block() is called. */
-    struct pollfd *pollfd;      /* Pointer to element of the pollfds array
-                                   (null if added from a callback). */
+    struct pollfd *pollfd;      /* Pointer to element of the pollfds array. */
 };
 
 /* All active poll waiters. */
@@ -59,12 +57,6 @@ static int timeout = -1;
 /* Backtrace of 'timeout''s registration, if debugging is enabled. */
 static struct backtrace timeout_backtrace;
 
-/* Callback currently running, to allow verifying that poll_cancel() is not
- * being called on a running callback. */
-#ifndef NDEBUG
-static struct poll_waiter *running_cb;
-#endif
-
 static struct poll_waiter *new_waiter(int fd, short int events);
 
 /* Registers 'fd' as waiting for the specified 'events' (which should be POLLIN
@@ -132,22 +124,21 @@ log_wakeup(const struct backtrace *backtrace, const char *format, ...)
 
 /* Blocks until one or more of the events registered with poll_fd_wait()
  * occurs, or until the minimum duration registered with poll_timer_wait()
- * elapses, or not at all if poll_immediate_wake() has been called.
- *
- * Also executes any autonomous subroutines registered with poll_fd_callback(),
- * if their file descriptors have become ready. */
+ * elapses, or not at all if poll_immediate_wake() has been called. */
 void
 poll_block(void)
 {
     static struct pollfd *pollfds;
     static size_t max_pollfds;
 
-    struct poll_waiter *pw;
-    struct list *node;
+    struct poll_waiter *pw, *next;
     int n_pollfds;
     int retval;
 
-    assert(!running_cb);
+    /* Register fatal signal events before actually doing any real work for
+     * poll_block. */
+    fatal_signal_wait();
+
     if (max_pollfds < n_waiters) {
         max_pollfds = n_waiters;
         pollfds = xrealloc(pollfds, max_pollfds * sizeof *pollfds);
@@ -173,76 +164,36 @@ poll_block(void)
         log_wakeup(&timeout_backtrace, "%d-ms timeout", timeout);
     }
 
-    for (node = waiters.next; node != &waiters; ) {
-        pw = CONTAINER_OF(node, struct poll_waiter, node);
-        if (!pw->pollfd || !pw->pollfd->revents) {
-            if (pw->function) {
-                node = node->next;
-                continue;
-            }
-        } else {
-            if (VLOG_IS_DBG_ENABLED()) {
-                log_wakeup(pw->backtrace, "%s%s%s%s%s on fd %d",
-                           pw->pollfd->revents & POLLIN ? "[POLLIN]" : "",
-                           pw->pollfd->revents & POLLOUT ? "[POLLOUT]" : "",
-                           pw->pollfd->revents & POLLERR ? "[POLLERR]" : "",
-                           pw->pollfd->revents & POLLHUP ? "[POLLHUP]" : "",
-                           pw->pollfd->revents & POLLNVAL ? "[POLLNVAL]" : "",
-                           pw->fd);
-            }
-
-            if (pw->function) {
-#ifndef NDEBUG
-                running_cb = pw;
-#endif
-                pw->function(pw->fd, pw->pollfd->revents, pw->aux);
-#ifndef NDEBUG
-                running_cb = NULL;
-#endif
-            }
+    LIST_FOR_EACH_SAFE (pw, next, struct poll_waiter, node, &waiters) {
+        if (pw->pollfd->revents && VLOG_IS_DBG_ENABLED()) {
+            log_wakeup(pw->backtrace, "%s%s%s%s%s on fd %d",
+                       pw->pollfd->revents & POLLIN ? "[POLLIN]" : "",
+                       pw->pollfd->revents & POLLOUT ? "[POLLOUT]" : "",
+                       pw->pollfd->revents & POLLERR ? "[POLLERR]" : "",
+                       pw->pollfd->revents & POLLHUP ? "[POLLHUP]" : "",
+                       pw->pollfd->revents & POLLNVAL ? "[POLLNVAL]" : "",
+                       pw->fd);
         }
-        node = node->next;
         poll_cancel(pw);
     }
 
     timeout = -1;
     timeout_backtrace.n_frames = 0;
-}
 
-/* Registers 'function' to be called with argument 'aux' by poll_block() when
- * 'fd' becomes ready for one of the events in 'events', which should be POLLIN
- * or POLLOUT or POLLIN | POLLOUT.
- *
- * The callback registration persists until the event actually occurs.  At that
- * point, it is automatically de-registered.  The callback function must
- * re-register the event by calling poll_fd_callback() again within the
- * callback, if it wants to be called back again later. */
-struct poll_waiter *
-poll_fd_callback(int fd, short int events, poll_fd_func *function, void *aux)
-{
-    struct poll_waiter *pw = new_waiter(fd, events);
-    pw->function = function;
-    pw->aux = aux;
-    return pw;
+    /* Handle any pending signals before doing anything else. */
+    fatal_signal_run();
 }
 
-/* Cancels the file descriptor event registered with poll_fd_wait() or
- * poll_fd_callback().  'pw' must be the struct poll_waiter returned by one of
- * those functions.
+/* Cancels the file descriptor event registered with poll_fd_wait() using 'pw',
+ * the struct poll_waiter returned by that function.
  *
  * An event registered with poll_fd_wait() may be canceled from its time of
  * registration until the next call to poll_block().  At that point, the event
- * is automatically canceled by the system and its poll_waiter is freed.
- *
- * An event registered with poll_fd_callback() may be canceled from its time of
- * registration until its callback is actually called.  At that point, the
- * event is automatically canceled by the system and its poll_waiter is
- * freed. */
+ * is automatically canceled by the system and its poll_waiter is freed. */
 void
 poll_cancel(struct poll_waiter *pw)
 {
     if (pw) {
-        assert(pw != running_cb);
         list_remove(&pw->node);
         free(pw->backtrace);
         free(pw);
@@ -254,7 +205,7 @@ poll_cancel(struct poll_waiter *pw)
 static struct poll_waiter *
 new_waiter(int fd, short int events)
 {
-    struct poll_waiter *waiter = xcalloc(1, sizeof *waiter);
+    struct poll_waiter *waiter = xzalloc(sizeof *waiter);
     assert(fd >= 0);
     waiter->fd = fd;
     waiter->events = events;
index 341d063..adb88d4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <poll.h>
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 struct poll_waiter;
 
 /* Schedule events to wake up the following poll_block(). */
@@ -44,12 +48,11 @@ void poll_immediate_wake(void);
 /* Wait until an event occurs. */
 void poll_block(void);
 
-/* Autonomous function callbacks. */
-typedef void poll_fd_func(int fd, short int revents, void *aux);
-struct poll_waiter *poll_fd_callback(int fd, short int events,
-                                     poll_fd_func *, void *aux);
-
 /* Cancel a file descriptor callback or event. */
 void poll_cancel(struct poll_waiter *);
 
+#ifdef  __cplusplus
+}
+#endif
+
 #endif /* poll-loop.h */
index 1fe3c12..af867ef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ static struct list all_processes = LIST_INITIALIZER(&all_processes);
 static bool sigchld_is_blocked(void);
 static void block_sigchld(sigset_t *);
 static void unblock_sigchld(const sigset_t *);
-static void sigchld_handler(int signr UNUSED);
+static void sigchld_handler(int signr OVS_UNUSED);
 static bool is_member(int x, const int *array, size_t);
 
 /* Initializes the process subsystem (if it is not already initialized).  Calls
@@ -103,7 +103,7 @@ process_escape_args(char **argv)
         if (argp != argv) {
             ds_put_char(&ds, ' ');
         }
-        if (arg[strcspn(arg, " \t\r\n\v\\")]) {
+        if (arg[strcspn(arg, " \t\r\n\v\\\'\"")]) {
             ds_put_char(&ds, '"');
             for (p = arg; *p; p++) {
                 if (*p == '\\' || *p == '\"') {
@@ -161,7 +161,7 @@ process_register(const char *name, pid_t pid)
 
     assert(sigchld_is_blocked());
 
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->pid = pid;
     slash = strrchr(name, '/');
     p->name = xstrdup(slash ? slash + 1 : name);
@@ -201,17 +201,14 @@ process_start(char **argv,
     }
 
     block_sigchld(&oldsigs);
-    fatal_signal_block();
     pid = fork();
     if (pid < 0) {
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         VLOG_WARN("fork failed: %s", strerror(errno));
         return errno;
     } else if (pid) {
         /* Running in parent process. */
         *pp = process_register(argv[0], pid);
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         return 0;
     } else {
@@ -220,7 +217,6 @@ process_start(char **argv,
         int fd;
 
         fatal_signal_fork();
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         for (fd = 0; fd < fd_max; fd++) {
             if (is_member(fd, null_fds, n_null_fds)) {
@@ -289,7 +285,7 @@ process_exited(struct process *p)
         return true;
     } else {
         char buf[_POSIX_PIPE_BUF];
-        read(fds[0], buf, sizeof buf);
+        ignore(read(fds[0], buf, sizeof buf));
         return false;
     }
 }
@@ -419,15 +415,13 @@ stream_open(struct stream *s)
 static void
 stream_read(struct stream *s)
 {
-    int error = 0;
-
     if (s->fds[0] < 0) {
         return;
     }
 
-    error = 0;
     for (;;) {
         char buffer[512];
+        int error;
         size_t n;
 
         error = read_fully(s->fds[0], buffer, sizeof buffer, &n);
@@ -521,12 +515,10 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
     }
 
     block_sigchld(&oldsigs);
-    fatal_signal_block();
     pid = fork();
     if (pid < 0) {
         int error = errno;
 
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         VLOG_WARN("fork failed: %s", strerror(error));
 
@@ -539,7 +531,6 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
         struct process *p;
 
         p = process_register(argv[0], pid);
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
 
         close(s_stdout.fds[1]);
@@ -575,7 +566,6 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
         int i;
 
         fatal_signal_fork();
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
 
         dup2(get_null_fd(), 0);
@@ -595,7 +585,7 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
 }
 \f
 static void
-sigchld_handler(int signr UNUSED)
+sigchld_handler(int signr OVS_UNUSED)
 {
     struct process *p;
 
@@ -617,7 +607,7 @@ sigchld_handler(int signr UNUSED)
             }
         }
     }
-    write(fds[1], "", 1);
+    ignore(write(fds[1], "", 1));
 }
 
 static bool
index 649db3b..d204a46 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -100,7 +100,7 @@ queue_pop_head(struct ovs_queue *q)
 
 /* Checks the internal integrity of 'q'.  For use in debugging. */
 static void
-check_queue(struct ovs_queue *q UNUSED)
+check_queue(struct ovs_queue *q OVS_UNUSED)
 {
 #if 0
     struct ofpbuf *iter;
index e94d20c..879f7a2 100644 (file)
@@ -17,6 +17,8 @@
 #ifndef QUEUE_H
 #define QUEUE_H 1
 
+#include <stdbool.h>
+
 /* Packet queue. */
 struct ovs_queue {
     int n;                      /* Number of queued packets. */
@@ -31,4 +33,9 @@ void queue_advance_head(struct ovs_queue *, struct ofpbuf *next);
 void queue_push_tail(struct ovs_queue *, struct ofpbuf *);
 struct ofpbuf *queue_pop_head(struct ovs_queue *);
 
+static inline bool queue_is_empty(const struct ovs_queue *q)
+{
+    return q->n == 0;
+}
+
 #endif /* queue.h */
index c0bc95d..ea45134 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -76,6 +76,7 @@ struct rconn {
     time_t last_connected;
     unsigned int packets_sent;
     unsigned int seqno;
+    int last_error;
 
     /* In S_ACTIVE and S_IDLE, probably_admitted reports whether we believe
      * that the peer has made a (positive) admission control decision on our
@@ -89,7 +90,7 @@ struct rconn {
     time_t last_admitted;
 
     /* These values are simply for statistics reporting, not used directly by
-     * anything internal to the rconn (or the secchan for that matter). */
+     * anything internal to the rconn (or ofproto for that matter). */
     unsigned int packets_received;
     unsigned int n_attempted_connections, n_successful_connections;
     time_t creation_time;
@@ -136,6 +137,7 @@ static void state_transition(struct rconn *, enum state);
 static void set_vconn_name(struct rconn *, const char *name);
 static int try_send(struct rconn *);
 static int reconnect(struct rconn *);
+static void report_error(struct rconn *, int error);
 static void disconnect(struct rconn *, int error);
 static void flush_queue(struct rconn *);
 static void question_connectivity(struct rconn *);
@@ -176,7 +178,7 @@ rconn_new_from_vconn(const char *name, struct vconn *vconn)
 struct rconn *
 rconn_create(int probe_interval, int max_backoff)
 {
-    struct rconn *rc = xcalloc(1, sizeof *rc);
+    struct rconn *rc = xzalloc(sizeof *rc);
 
     rc->state = S_VOID;
     rc->state_entered = time_now();
@@ -272,6 +274,7 @@ void
 rconn_reconnect(struct rconn *rc)
 {
     if (rc->state & (S_ACTIVE | S_IDLE)) {
+        VLOG_INFO("%s: disconnecting", rc->name);
         disconnect(rc, 0);
     }
 }
@@ -313,13 +316,13 @@ rconn_destroy(struct rconn *rc)
 }
 
 static unsigned int
-timeout_VOID(const struct rconn *rc UNUSED)
+timeout_VOID(const struct rconn *rc OVS_UNUSED)
 {
     return UINT_MAX;
 }
 
 static void
-run_VOID(struct rconn *rc UNUSED)
+run_VOID(struct rconn *rc OVS_UNUSED)
 {
     /* Nothing to do. */
 }
@@ -341,7 +344,7 @@ reconnect(struct rconn *rc)
     } else {
         VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval));
         rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */
-        disconnect(rc, 0);
+        disconnect(rc, retval);
     }
     return retval;
 }
@@ -381,7 +384,7 @@ run_CONNECTING(struct rconn *rc)
     } else if (timed_out(rc)) {
         VLOG_INFO("%s: connection timed out", rc->name);
         rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */
-        disconnect(rc, 0);
+        disconnect(rc, ETIMEDOUT);
     }
 }
 
@@ -446,7 +449,7 @@ run_IDLE(struct rconn *rc)
         VLOG_ERR("%s: no response to inactivity probe after %u "
                  "seconds, disconnecting",
                  rc->name, elapsed_in_this_state(rc));
-        disconnect(rc, 0);
+        disconnect(rc, ETIMEDOUT);
     } else {
         do_tx_work(rc);
     }
@@ -459,6 +462,15 @@ void
 rconn_run(struct rconn *rc)
 {
     int old_state;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run(rc->monitors[i]);
+    }
+
     do {
         old_state = rc->state;
         switch (rc->state) {
@@ -476,7 +488,17 @@ rconn_run(struct rconn *rc)
 void
 rconn_run_wait(struct rconn *rc)
 {
-    unsigned int timeo = timeout(rc);
+    unsigned int timeo;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run_wait(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run_wait(rc->monitors[i]);
+    }
+
+    timeo = timeout(rc);
     if (timeo != UINT_MAX) {
         unsigned int expires = sat_add(rc->state_entered, timeo);
         unsigned int remaining = sat_sub(expires, time_now());
@@ -511,6 +533,7 @@ rconn_recv(struct rconn *rc)
             }
             return buffer;
         } else if (error != EAGAIN) {
+            report_error(rc, error);
             disconnect(rc, error);
         }
     }
@@ -547,7 +570,7 @@ rconn_send(struct rconn *rc, struct ofpbuf *b,
     if (rconn_is_connected(rc)) {
         COVERAGE_INC(rconn_queued);
         copy_to_monitor(rc, b);
-        b->private = counter;
+        b->private_p = counter;
         if (counter) {
             rconn_packet_counter_inc(counter);
         }
@@ -789,6 +812,22 @@ rconn_get_connection_seqno(const struct rconn *rc)
 {
     return rc->seqno;
 }
+
+/* Returns a value that explains why 'rc' last disconnected:
+ *
+ *   - 0 means that the last disconnection was caused by a call to
+ *     rconn_disconnect(), or that 'rc' is new and has not yet completed its
+ *     initial connection or connection attempt.
+ *
+ *   - EOF means that the connection was closed in the normal way by the peer.
+ *
+ *   - A positive integer is an errno value that represents the error.
+ */
+int
+rconn_get_last_error(const struct rconn *rc)
+{
+    return rc->last_error;
+}
 \f
 struct rconn_packet_counter *
 rconn_packet_counter_create(void)
@@ -845,10 +884,11 @@ try_send(struct rconn *rc)
 {
     int retval = 0;
     struct ofpbuf *next = rc->txq.head->next;
-    struct rconn_packet_counter *counter = rc->txq.head->private;
+    struct rconn_packet_counter *counter = rc->txq.head->private_p;
     retval = vconn_send(rc->vconn, rc->txq.head);
     if (retval) {
         if (retval != EAGAIN) {
+            report_error(rc, retval);
             disconnect(rc, retval);
         }
         return retval;
@@ -862,26 +902,41 @@ try_send(struct rconn *rc)
     return 0;
 }
 
-/* Disconnects 'rc'.  'error' is used only for logging purposes.  If it is
- * nonzero, then it should be EOF to indicate the connection was closed by the
- * peer in a normal fashion or a positive errno value. */
+/* Reports that 'error' caused 'rc' to disconnect.  'error' may be a positive
+ * errno value, or it may be EOF to indicate that the connection was closed
+ * normally. */
+static void
+report_error(struct rconn *rc, int error)
+{
+    if (error == EOF) {
+        /* If 'rc' isn't reliable, then we don't really expect this connection
+         * to last forever anyway (probably it's a connection that we received
+         * via accept()), so use DBG level to avoid cluttering the logs. */
+        enum vlog_level level = rc->reliable ? VLL_INFO : VLL_DBG;
+        VLOG(level, "%s: connection closed by peer", rc->name);
+    } else {
+        VLOG_WARN("%s: connection dropped (%s)", rc->name, strerror(error));
+    }
+}
+
+/* Disconnects 'rc' and records 'error' as the error that caused 'rc''s last
+ * disconnection:
+ *
+ *   - 0 means that this disconnection is due to a request by 'rc''s client,
+ *     not due to any kind of network error.
+ *
+ *   - EOF means that the connection was closed in the normal way by the peer.
+ *
+ *   - A positive integer is an errno value that represents the error.
+ */
 static void
 disconnect(struct rconn *rc, int error)
 {
+    rc->last_error = error;
     if (rc->reliable) {
         time_t now = time_now();
 
         if (rc->state & (S_CONNECTING | S_ACTIVE | S_IDLE)) {
-            if (error > 0) {
-                VLOG_WARN("%s: connection dropped (%s)",
-                          rc->name, strerror(error));
-            } else if (error == EOF) {
-                if (rc->reliable) {
-                    VLOG_INFO("%s: connection closed by peer", rc->name);
-                }
-            } else {
-                VLOG_INFO("%s: connection dropped", rc->name);
-            }
             vconn_close(rc->vconn);
             rc->vconn = NULL;
             flush_queue(rc);
@@ -914,7 +969,7 @@ flush_queue(struct rconn *rc)
     }
     while (rc->txq.n > 0) {
         struct ofpbuf *b = queue_pop_head(&rc->txq);
-        struct rconn_packet_counter *counter = b->private;
+        struct rconn_packet_counter *counter = b->private_p;
         if (counter) {
             rconn_packet_counter_dec(counter);
         }
index ef4e16c..765e88c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -88,6 +88,7 @@ unsigned long int rconn_get_total_time_connected(const struct rconn *);
 int rconn_get_backoff(const struct rconn *);
 unsigned int rconn_get_state_elapsed(const struct rconn *);
 unsigned int rconn_get_connection_seqno(const struct rconn *);
+int rconn_get_last_error(const struct rconn *);
 
 /* Counts the number of packets queued into an rconn by a given source. */
 struct rconn_packet_counter {
diff --git a/lib/reconnect.c b/lib/reconnect.c
new file mode 100644 (file)
index 0000000..f159f01
--- /dev/null
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "reconnect.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "poll-loop.h"
+
+#define THIS_MODULE VLM_reconnect
+#include "vlog.h"
+
+#define STATES                                  \
+    STATE(VOID, 1 << 0)                         \
+    STATE(BACKOFF, 1 << 1)                      \
+    STATE(START_CONNECT, 1 << 2)                \
+    STATE(CONNECT_IN_PROGRESS, 1 << 3)          \
+    STATE(ACTIVE, 1 << 4)                       \
+    STATE(IDLE, 1 << 5)                         \
+    STATE(RECONNECT, 1 << 6)
+enum state {
+#define STATE(NAME, VALUE) S_##NAME = VALUE,
+    STATES
+#undef STATE
+};
+
+static bool
+is_connected_state(enum state state)
+{
+    return (state & (S_ACTIVE | S_IDLE)) != 0;
+}
+
+struct reconnect {
+    /* Configuration. */
+    char *name;
+    int min_backoff;
+    int max_backoff;
+    int probe_interval;
+
+    /* State. */
+    enum state state;
+    long long int state_entered;
+    int backoff;
+    long long int last_received;
+    long long int last_connected;
+    unsigned int max_tries;
+
+    /* These values are simply for statistics reporting, not otherwise used
+     * directly by anything internal. */
+    long long int creation_time;
+    unsigned int n_attempted_connections, n_successful_connections;
+    unsigned int total_connected_duration;
+    unsigned int seqno;
+};
+
+static void reconnect_transition__(struct reconnect *, long long int now,
+                                   enum state state);
+static long long int reconnect_deadline__(const struct reconnect *);
+static bool reconnect_may_retry(struct reconnect *);
+
+static const char *
+reconnect_state_name__(enum state state)
+{
+    switch (state) {
+#define STATE(NAME, VALUE) case S_##NAME: return #NAME;
+        STATES
+#undef STATE
+    }
+    return "***ERROR***";
+}
+
+/* Creates and returns a new reconnect FSM with default settings.  The FSM is
+ * initially disabled.  The caller will likely want to call reconnect_enable()
+ * and reconnect_set_name() on the returned object. */
+struct reconnect *
+reconnect_create(long long int now)
+{
+    struct reconnect *fsm = xzalloc(sizeof *fsm);
+
+    fsm->name = xstrdup("void");
+    fsm->min_backoff = 1000;
+    fsm->max_backoff = 8000;
+    fsm->probe_interval = 5000;
+
+    fsm->state = S_VOID;
+    fsm->state_entered = now;
+    fsm->backoff = 0;
+    fsm->last_received = now;
+    fsm->last_connected = now;
+    fsm->max_tries = UINT_MAX;
+    fsm->creation_time = now;
+
+    return fsm;
+}
+
+/* Frees 'fsm'. */
+void
+reconnect_destroy(struct reconnect *fsm)
+{
+    if (fsm) {
+        free(fsm->name);
+        free(fsm);
+    }
+}
+
+/* Returns 'fsm''s name. */
+const char *
+reconnect_get_name(const struct reconnect *fsm)
+{
+    return fsm->name;
+}
+
+/* Sets 'fsm''s name to 'name'.  If 'name' is null, then "void" is used
+ * instead.
+ *
+ * The name set for 'fsm' is used in log messages. */
+void
+reconnect_set_name(struct reconnect *fsm, const char *name)
+{
+    free(fsm->name);
+    fsm->name = xstrdup(name ? name : "void");
+}
+
+/* Return the minimum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 1000 ms. */
+int
+reconnect_get_min_backoff(const struct reconnect *fsm)
+{
+    return fsm->min_backoff;
+}
+
+/* Return the maximum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 8000 ms. */
+int
+reconnect_get_max_backoff(const struct reconnect *fsm)
+{
+    return fsm->max_backoff;
+}
+
+/* Returns the "probe interval" for 'fsm' in milliseconds.  If this is zero, it
+ * disables the connection keepalive feature.  If it is nonzero, then if the
+ * interval passes while 'fsm' is connected and without reconnect_received()
+ * being called for 'fsm', reconnect_run() returns RECONNECT_PROBE.  If the
+ * interval passes again without reconnect_received() being called,
+ * reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'. */
+int
+reconnect_get_probe_interval(const struct reconnect *fsm)
+{
+    return fsm->probe_interval;
+}
+
+/* Limits the maximum number of times that 'fsm' will ask the client to try to
+ * reconnect to 'max_tries'.  UINT_MAX (the default) means an unlimited number
+ * of tries.
+ *
+ * After the number of tries has expired, the 'fsm' will disable itself
+ * instead of backing off and retrying. */
+void
+reconnect_set_max_tries(struct reconnect *fsm, unsigned int max_tries)
+{
+    fsm->max_tries = max_tries;
+}
+
+/* Returns the current remaining number of connection attempts, UINT_MAX if
+ * the number is unlimited.  */
+unsigned int
+reconnect_get_max_tries(struct reconnect *fsm)
+{
+    return fsm->max_tries;
+}
+
+/* Configures the backoff parameters for 'fsm'.  'min_backoff' is the minimum
+ * number of milliseconds, and 'max_backoff' is the maximum, between connection
+ * attempts.
+ *
+ * 'min_backoff' must be at least 1000, and 'max_backoff' must be greater than
+ * or equal to 'min_backoff'. */
+void
+reconnect_set_backoff(struct reconnect *fsm, int min_backoff, int max_backoff)
+{
+    fsm->min_backoff = MAX(min_backoff, 1000);
+    fsm->max_backoff = max_backoff ? MAX(max_backoff, 1000) : 8000;
+    if (fsm->min_backoff > fsm->max_backoff) {
+        fsm->max_backoff = fsm->min_backoff;
+    }
+
+    if (fsm->state == S_BACKOFF && fsm->backoff > max_backoff) {
+        fsm->backoff = max_backoff;
+    }
+}
+
+/* Sets the "probe interval" for 'fsm' to 'probe_interval', in milliseconds.
+ * If this is zero, it disables the connection keepalive feature.  If it is
+ * nonzero, then if the interval passes while 'fsm' is connected and without
+ * reconnect_received() being called for 'fsm', reconnect_run() returns
+ * RECONNECT_PROBE.  If the interval passes again without reconnect_received()
+ * being called, reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'.
+ *
+ * If 'probe_interval' is nonzero, then it will be forced to a value of at
+ * least 1000 ms. */
+void
+reconnect_set_probe_interval(struct reconnect *fsm, int probe_interval)
+{
+    fsm->probe_interval = probe_interval ? MAX(1000, probe_interval) : 0;
+}
+
+/* Returns true if 'fsm' has been enabled with reconnect_enable().  Calling
+ * another function that indicates a change in connection state, such as
+ * reconnect_disconnected() or reconnect_force_reconnect(), will also enable
+ * a reconnect FSM. */
+bool
+reconnect_is_enabled(const struct reconnect *fsm)
+{
+    return fsm->state != S_VOID;
+}
+
+/* If 'fsm' is disabled (the default for newly created FSMs), enables it, so
+ * that the next call to reconnect_run() for 'fsm' will return
+ * RECONNECT_CONNECT.
+ *
+ * If 'fsm' is not disabled, this function has no effect. */
+void
+reconnect_enable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state == S_VOID && reconnect_may_retry(fsm)) {
+        reconnect_transition__(fsm, now, S_BACKOFF);
+        fsm->backoff = 0;
+    }
+}
+
+/* Disables 'fsm'.  Until 'fsm' is enabled again, reconnect_run() will always
+ * return 0. */
+void
+reconnect_disable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_VOID) {
+        reconnect_transition__(fsm, now, S_VOID);
+    }
+}
+
+/* If 'fsm' is enabled and currently connected (or attempting to connect),
+ * forces reconnect_run() for 'fsm' to return RECONNECT_DISCONNECT the next
+ * time it is called, which should cause the client to drop the connection (or
+ * attempt), back off, and then reconnect. */
+void
+reconnect_force_reconnect(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state & (S_START_CONNECT | S_CONNECT_IN_PROGRESS
+                      | S_ACTIVE | S_IDLE)) {
+        reconnect_transition__(fsm, now, S_RECONNECT);
+    }
+}
+
+/* Tell 'fsm' that the connection dropped or that a connection attempt failed.
+ * 'error' specifies the reason: a positive value represents an errno value,
+ * EOF indicates that the connection was closed by the peer (e.g. read()
+ * returned 0), and 0 indicates no specific error.
+ *
+ * The FSM will back off, then reconnect. */
+void
+reconnect_disconnected(struct reconnect *fsm, long long int now, int error)
+{
+    if (!(fsm->state & (S_BACKOFF | S_VOID))) {
+        /* Report what happened. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)) {
+            if (error > 0) {
+                VLOG_WARN("%s: connection dropped (%s)",
+                          fsm->name, strerror(error));
+            } else if (error == EOF) {
+                VLOG_INFO("%s: connection closed by peer", fsm->name);
+            } else {
+                VLOG_INFO("%s: connection dropped", fsm->name);
+            }
+        } else {
+            if (error > 0) {
+                VLOG_WARN("%s: connection attempt failed (%s)",
+                          fsm->name, strerror(error));
+            } else {
+                VLOG_INFO("%s: connection attempt timed out", fsm->name);
+            }
+        }
+
+        /* Back off. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)
+            && fsm->last_received - fsm->last_connected >= fsm->backoff) {
+            fsm->backoff = fsm->min_backoff;
+        } else {
+            if (fsm->backoff < fsm->min_backoff) {
+                fsm->backoff = fsm->min_backoff;
+            } else if (fsm->backoff >= fsm->max_backoff / 2) {
+                fsm->backoff = fsm->max_backoff;
+            } else {
+                fsm->backoff *= 2;
+            }
+            VLOG_INFO("%s: waiting %.3g seconds before reconnect\n",
+                      fsm->name, fsm->backoff / 1000.0);
+        }
+
+        reconnect_transition__(fsm, now,
+                               reconnect_may_retry(fsm) ? S_BACKOFF : S_VOID);
+    }
+}
+
+/* Tell 'fsm' that a connection attempt is in progress.
+ *
+ * The FSM will start a timer, after which the connection attempt will be
+ * aborted (by returning RECONNECT_DISCONNECT from reconect_run()).  */
+void
+reconnect_connecting(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_CONNECT_IN_PROGRESS) {
+        VLOG_INFO("%s: connecting...", fsm->name);
+        reconnect_transition__(fsm, now, S_CONNECT_IN_PROGRESS);
+    }
+}
+
+/* Tell 'fsm' that the connection was successful.
+ *
+ * The FSM will start the probe interval timer, which is reset by
+ * reconnect_received().  If the timer expires, a probe will be sent (by
+ * returning RECONNECT_PROBE from reconnect_run()).  If the timer expires
+ * again without being reset, the connection will be aborted (by returning
+ * RECONNECT_DISCONNECT from reconnect_run()). */
+void
+reconnect_connected(struct reconnect *fsm, long long int now)
+{
+    if (!is_connected_state(fsm->state)) {
+        reconnect_connecting(fsm, now);
+
+        VLOG_INFO("%s: connected", fsm->name);
+        reconnect_transition__(fsm, now, S_ACTIVE);
+        fsm->last_connected = now;
+    }
+}
+
+/* Tell 'fsm' that the connection attempt failed.
+ *
+ * The FSM will back off and attempt to reconnect. */
+void
+reconnect_connect_failed(struct reconnect *fsm, long long int now, int error)
+{
+    reconnect_connecting(fsm, now);
+    reconnect_disconnected(fsm, now, error);
+}
+
+/* Tell 'fsm' that some data was received.  This resets the probe interval
+ * timer, so that the connection is known not to be idle. */
+void
+reconnect_received(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_ACTIVE) {
+        reconnect_transition__(fsm, now, S_ACTIVE);
+    }
+    fsm->last_received = now;
+}
+
+static void
+reconnect_transition__(struct reconnect *fsm, long long int now,
+                       enum state state)
+{
+    if (fsm->state == S_CONNECT_IN_PROGRESS) {
+        fsm->n_attempted_connections++;
+        if (state == S_ACTIVE) {
+            fsm->n_successful_connections++;
+        }
+    }
+    if (is_connected_state(fsm->state) != is_connected_state(state)) {
+        if (is_connected_state(fsm->state)) {
+            fsm->total_connected_duration += now - fsm->last_connected;
+        }
+        fsm->seqno++;
+    }
+
+    VLOG_DBG("%s: entering %s", fsm->name, reconnect_state_name__(state));
+    fsm->state = state;
+    fsm->state_entered = now;
+}
+
+static long long int
+reconnect_deadline__(const struct reconnect *fsm)
+{
+    assert(fsm->state_entered != LLONG_MIN);
+    switch (fsm->state) {
+    case S_VOID:
+        return LLONG_MAX;
+
+    case S_BACKOFF:
+        return fsm->state_entered + fsm->backoff;
+
+    case S_START_CONNECT:
+    case S_CONNECT_IN_PROGRESS:
+        return fsm->state_entered + MAX(1000, fsm->backoff);
+
+    case S_ACTIVE:
+        if (fsm->probe_interval) {
+            long long int base = MAX(fsm->last_received, fsm->state_entered);
+            return base + fsm->probe_interval;
+        }
+        return LLONG_MAX;
+
+    case S_IDLE:
+        return fsm->state_entered + fsm->probe_interval;
+
+    case S_RECONNECT:
+        return fsm->state_entered;
+    }
+
+    NOT_REACHED();
+}
+
+/* Assesses whether any action should be taken on 'fsm'.  The return value is
+ * one of:
+ *
+ *     - 0: The client need not take any action.
+ *
+ *     - RECONNECT_CONNECT: The client should start a connection attempt and
+ *       indicate this by calling reconnect_connecting().  If the connection
+ *       attempt has definitely succeeded, it should call
+ *       reconnect_connected().  If the connection attempt has definitely
+ *       failed, it should call reconnect_connect_failed().
+ *
+ *       The FSM is smart enough to back off correctly after successful
+ *       connections that quickly abort, so it is OK to call
+ *       reconnect_connected() after a low-level successful connection
+ *       (e.g. connect()) even if the connection might soon abort due to a
+ *       failure at a high-level (e.g. SSL negotiation failure).
+ *
+ *     - RECONNECT_DISCONNECT: The client should abort the current connection
+ *       or connection attempt and call reconnect_disconnected() or
+ *       reconnect_connect_failed() to indicate it.
+ *
+ *     - RECONNECT_PROBE: The client should send some kind of request to the
+ *       peer that will elicit a response, to ensure that the connection is
+ *       indeed in working order.  (This will only be returned if the "probe
+ *       interval" is nonzero--see reconnect_set_probe_interval()).
+ */
+enum reconnect_action
+reconnect_run(struct reconnect *fsm, long long int now)
+{
+    if (now >= reconnect_deadline__(fsm)) {
+        switch (fsm->state) {
+        case S_VOID:
+            return 0;
+
+        case S_BACKOFF:
+            return RECONNECT_CONNECT;
+
+        case S_START_CONNECT:
+        case S_CONNECT_IN_PROGRESS:
+            return RECONNECT_DISCONNECT;
+
+        case S_ACTIVE:
+            VLOG_DBG("%s: idle %lld ms, sending inactivity probe", fsm->name,
+                     now - MAX(fsm->last_received, fsm->state_entered));
+            reconnect_transition__(fsm, now, S_IDLE);
+            return RECONNECT_PROBE;
+
+        case S_IDLE:
+            VLOG_ERR("%s: no response to inactivity probe after %.3g "
+                     "seconds, disconnecting",
+                     fsm->name, (now - fsm->state_entered) / 1000.0);
+            return RECONNECT_DISCONNECT;
+
+        case S_RECONNECT:
+            return RECONNECT_DISCONNECT;
+        }
+
+        NOT_REACHED();
+    } else {
+        return fsm->state == S_START_CONNECT ? RECONNECT_CONNECT : 0;
+    }
+}
+
+/* Causes the next call to poll_block() to wake up when reconnect_run() should
+ * be called on 'fsm'. */
+void
+reconnect_wait(struct reconnect *fsm, long long int now)
+{
+    int timeout = reconnect_timeout(fsm, now);
+    if (timeout >= 0) {
+        poll_timer_wait(timeout);
+    }
+}
+
+/* Returns the number of milliseconds after which reconnect_run() should be
+ * called on 'fsm' if nothing else notable happens in the meantime, or a
+ * negative number if this is currently unnecessary. */
+int
+reconnect_timeout(struct reconnect *fsm, long long int now)
+{
+    long long int deadline = reconnect_deadline__(fsm);
+    if (deadline != LLONG_MAX) {
+        long long int remaining = deadline - now;
+        return MAX(0, MIN(INT_MAX, remaining));
+    }
+    return -1;
+}
+
+/* Returns true if 'fsm' is currently believed to be connected, that is, if
+ * reconnect_connected() was called more recently than any call to
+ * reconnect_connect_failed() or reconnect_disconnected() or
+ * reconnect_disable(), and false otherwise.  */
+bool
+reconnect_is_connected(const struct reconnect *fsm)
+{
+    return is_connected_state(fsm->state);
+}
+
+/* Returns the number of milliseconds for which 'fsm' has been continuously
+ * connected to its peer.  (If 'fsm' is not currently connected, this is 0.) */
+unsigned int
+reconnect_get_connection_duration(const struct reconnect *fsm,
+                                  long long int now)
+{
+    return reconnect_is_connected(fsm) ? now - fsm->last_connected : 0;
+}
+
+/* Copies various statistics for 'fsm' into '*stats'. */
+void
+reconnect_get_stats(const struct reconnect *fsm, long long int now,
+                    struct reconnect_stats *stats)
+{
+    stats->creation_time = fsm->creation_time;
+    stats->last_received = fsm->last_received;
+    stats->last_connected = fsm->last_connected;
+    stats->backoff = fsm->backoff;
+    stats->seqno = fsm->seqno;
+    stats->is_connected = reconnect_is_connected(fsm);
+    stats->current_connection_duration
+        = reconnect_get_connection_duration(fsm, now);
+    stats->total_connected_duration = (stats->current_connection_duration
+                                       + fsm->total_connected_duration);
+    stats->n_attempted_connections = fsm->n_attempted_connections;
+    stats->n_successful_connections = fsm->n_successful_connections;
+    stats->state = reconnect_state_name__(fsm->state);
+    stats->state_elapsed = now - fsm->state_entered;
+}
+
+static bool
+reconnect_may_retry(struct reconnect *fsm)
+{
+    bool may_retry = fsm->max_tries > 0;
+    if (may_retry && fsm->max_tries != UINT_MAX) {
+        fsm->max_tries--;
+    }
+    return may_retry;
+}
diff --git a/lib/reconnect.h b/lib/reconnect.h
new file mode 100644 (file)
index 0000000..76c7f78
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RECONNECT_H
+#define RECONNECT_H 1
+
+/* This library implements a finite-state machine for connecting and
+ * reconnecting to a network resource with exponential backoff.  It also
+ * provides optional support for detecting a connection on which the peer is no
+ * longer responding.
+ *
+ * The library does not implement anything networking related, only an FSM for
+ * networking code to use.
+ *
+ * Many "reconnect" functions take a "now" argument.  This makes testing easier
+ * since there is no hidden state.  When not testing, just pass the return
+ * value of time_msec() from timeval.h.  (Perhaps this design should be
+ * revisited later.) */
+
+#include <stdbool.h>
+
+struct reconnect *reconnect_create(long long int now);
+void reconnect_destroy(struct reconnect *);
+
+const char *reconnect_get_name(const struct reconnect *);
+void reconnect_set_name(struct reconnect *, const char *name);
+
+int reconnect_get_min_backoff(const struct reconnect *);
+int reconnect_get_max_backoff(const struct reconnect *);
+int reconnect_get_probe_interval(const struct reconnect *);
+
+void reconnect_set_max_tries(struct reconnect *, unsigned int max_tries);
+unsigned int reconnect_get_max_tries(struct reconnect *);
+
+void reconnect_set_backoff(struct reconnect *,
+                           int min_backoff, int max_backoff);
+void reconnect_set_probe_interval(struct reconnect *, int probe_interval);
+
+bool reconnect_is_enabled(const struct reconnect *);
+void reconnect_enable(struct reconnect *, long long int now);
+void reconnect_disable(struct reconnect *, long long int now);
+
+void reconnect_force_reconnect(struct reconnect *, long long int now);
+
+bool reconnect_is_connected(const struct reconnect *);
+unsigned int reconnect_get_connection_duration(const struct reconnect *,
+                                               long long int now);
+
+void reconnect_disconnected(struct reconnect *, long long int now, int error);
+void reconnect_connecting(struct reconnect *, long long int now);
+void reconnect_connected(struct reconnect *, long long int now);
+void reconnect_connect_failed(struct reconnect *, long long int now,
+                              int error);
+void reconnect_received(struct reconnect *, long long int now);
+
+enum reconnect_action {
+    RECONNECT_CONNECT = 1,
+    RECONNECT_DISCONNECT,
+    RECONNECT_PROBE,
+};
+enum reconnect_action reconnect_run(struct reconnect *, long long int now);
+void reconnect_wait(struct reconnect *, long long int now);
+int reconnect_timeout(struct reconnect *, long long int now);
+
+struct reconnect_stats {
+    /* All times and durations in this structure are in milliseconds. */
+    long long int creation_time;  /* Time reconnect_create() called. */
+    long long int last_received; /* Last call to reconnect_received(). */
+    long long int last_connected; /* Last call to reconnect_connected(). */
+    int backoff;                  /* Current backoff duration.  */
+
+    unsigned int seqno;         /* # of connections + # of disconnections. */
+
+    bool is_connected;          /* Currently connected? */
+    unsigned int current_connection_duration; /* Time of current connection. */
+    unsigned int total_connected_duration;    /* Sum of all connections. */
+    unsigned int n_attempted_connections;
+    unsigned int n_successful_connections;
+
+    /* These should only be provided to a human user for debugging purposes.
+     * The client should not attempt to interpret them. */
+    const char *state;            /* FSM state. */
+    unsigned int state_elapsed;   /* Time since FSM state entered. */
+};
+
+void reconnect_get_stats(const struct reconnect *, long long int now,
+                         struct reconnect_stats *);
+
+#endif /* reconnect.h */
diff --git a/lib/rtnetlink.c b/lib/rtnetlink.c
new file mode 100644 (file)
index 0000000..1d302ea
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "rtnetlink.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <poll.h>
+
+#include "coverage.h"
+#include "netlink.h"
+#include "ofpbuf.h"
+
+#define THIS_MODULE VLM_rtnetlink
+#include "vlog.h"
+
+/* rtnetlink socket. */
+static struct nl_sock *notify_sock;
+
+/* All registered notifiers. */
+static struct list all_notifiers = LIST_INITIALIZER(&all_notifiers);
+
+static void rtnetlink_report_change(const struct nlmsghdr *,
+                                    const struct ifinfomsg *,
+                                    struct nlattr *attrs[]);
+static void rtnetlink_report_notify_error(void);
+
+/* Registers 'cb' to be called with auxiliary data 'aux' with network device
+ * change notifications.  The notifier is stored in 'notifier', which the
+ * caller must not modify or free.
+ *
+ * This is probably not the function that you want.  You should probably be
+ * using dpif_port_poll() or netdev_monitor_create(), which unlike this
+ * function are not Linux-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+rtnetlink_notifier_register(struct rtnetlink_notifier *notifier,
+                            rtnetlink_notify_func *cb, void *aux)
+{
+    if (!notify_sock) {
+        int error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0,
+                                   &notify_sock);
+        if (error) {
+            VLOG_WARN("could not create rtnetlink socket: %s",
+                      strerror(error));
+            return error;
+        }
+    } else {
+        /* Catch up on notification work so that the new notifier won't
+         * receive any stale notifications. */
+        rtnetlink_notifier_run();
+    }
+
+    list_push_back(&all_notifiers, &notifier->node);
+    notifier->cb = cb;
+    notifier->aux = aux;
+    return 0;
+}
+
+/* Cancels notification on 'notifier', which must have previously been
+ * registered with rtnetlink_notifier_register(). */
+void
+rtnetlink_notifier_unregister(struct rtnetlink_notifier *notifier)
+{
+    list_remove(&notifier->node);
+    if (list_is_empty(&all_notifiers)) {
+        nl_sock_destroy(notify_sock);
+        notify_sock = NULL;
+    }
+}
+
+/* Calls all of the registered notifiers, passing along any as-yet-unreported
+ * netdev change events. */
+void
+rtnetlink_notifier_run(void)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+    if (!notify_sock) {
+        return;
+    }
+
+    for (;;) {
+        /* Policy for RTNLGRP_LINK messages.
+         *
+         * There are *many* more fields in these messages, but currently we
+         * only care about these fields. */
+        static const struct nl_policy rtnetlink_policy[] = {
+            [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+            [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
+        };
+
+        struct nlattr *attrs[ARRAY_SIZE(rtnetlink_policy)];
+        struct ofpbuf *buf;
+        int error;
+
+        error = nl_sock_recv(notify_sock, &buf, false);
+        if (!error) {
+            if (nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                                rtnetlink_policy,
+                                attrs, ARRAY_SIZE(rtnetlink_policy))) {
+                struct ifinfomsg *ifinfo;
+
+                ifinfo = (void *) ((char *) buf->data + NLMSG_HDRLEN);
+                rtnetlink_report_change(buf->data, ifinfo, attrs);
+            } else {
+                VLOG_WARN_RL(&rl, "received bad rtnl message");
+                rtnetlink_report_notify_error();
+            }
+            ofpbuf_delete(buf);
+        } else if (error == EAGAIN) {
+            return;
+        } else {
+            if (error == ENOBUFS) {
+                VLOG_WARN_RL(&rl, "rtnetlink receive buffer overflowed");
+            } else {
+                VLOG_WARN_RL(&rl, "error reading rtnetlink socket: %s",
+                             strerror(error));
+            }
+            rtnetlink_report_notify_error();
+        }
+    }
+}
+
+/* Causes poll_block() to wake up when network device change notifications are
+ * ready. */
+void
+rtnetlink_notifier_wait(void)
+{
+    if (notify_sock) {
+        nl_sock_wait(notify_sock, POLLIN);
+    }
+}
+
+static void
+rtnetlink_report_change(const struct nlmsghdr *nlmsg,
+                           const struct ifinfomsg *ifinfo,
+                           struct nlattr *attrs[])
+{
+    struct rtnetlink_notifier *notifier;
+    struct rtnetlink_change change;
+
+    COVERAGE_INC(rtnetlink_changed);
+
+    change.nlmsg_type = nlmsg->nlmsg_type;
+    change.ifi_index = ifinfo->ifi_index;
+    change.ifname = nl_attr_get_string(attrs[IFLA_IFNAME]);
+    change.master_ifindex = (attrs[IFLA_MASTER]
+                             ? nl_attr_get_u32(attrs[IFLA_MASTER]) : 0);
+
+    LIST_FOR_EACH (notifier, struct rtnetlink_notifier, node,
+                   &all_notifiers) {
+        notifier->cb(&change, notifier->aux);
+    }
+}
+
+static void
+rtnetlink_report_notify_error(void)
+{
+    struct rtnetlink_notifier *notifier;
+
+    LIST_FOR_EACH (notifier, struct rtnetlink_notifier, node,
+                   &all_notifiers) {
+        notifier->cb(NULL, notifier->aux);
+    }
+}
diff --git a/lib/rtnetlink.h b/lib/rtnetlink.h
new file mode 100644 (file)
index 0000000..8f18805
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RTNETLINK_H
+#define RTNETLINK_H 1
+
+/* These functions are Linux specific, so they should be used directly only by
+ * Linux-specific code. */
+
+#include "list.h"
+
+/* A digested version of an rtnetlink message sent down by the kernel to
+ * indicate that a network device has been created or destroyed or changed.  */
+struct rtnetlink_change {
+    /* Copied from struct nlmsghdr. */
+    int nlmsg_type;             /* e.g. RTM_NEWLINK, RTM_DELLINK. */
+
+    /* Copied from struct ifinfomsg. */
+    int ifi_index;              /* Index of network device. */
+
+    /* Extracted from Netlink attributes. */
+    const char *ifname;         /* Name of network device. */
+    int master_ifindex;         /* Ifindex of datapath master (0 if none). */
+};
+
+/* Function called to report that a netdev has changed.  'change' describes the
+ * specific change.  It may be null if the buffer of change information
+ * overflowed, in which case the function must assume that every device may
+ * have changed.  'aux' is as specified in the call to
+ * rtnetlink_notifier_register().  */
+typedef void rtnetlink_notify_func(const struct rtnetlink_change *, void *aux);
+
+struct rtnetlink_notifier {
+    struct list node;
+    rtnetlink_notify_func *cb;
+    void *aux;
+};
+
+int rtnetlink_notifier_register(struct rtnetlink_notifier *,
+                                rtnetlink_notify_func *, void *aux);
+void rtnetlink_notifier_unregister(struct rtnetlink_notifier *);
+void rtnetlink_notifier_run(void);
+void rtnetlink_notifier_wait(void);
+
+#endif /* rtnetlink.h */
diff --git a/lib/sflow.h b/lib/sflow.h
new file mode 100644 (file)
index 0000000..397ae2d
--- /dev/null
@@ -0,0 +1,548 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#ifndef SFLOW_H
+#define SFLOW_H 1
+
+enum SFLAddress_type {
+    SFLADDRESSTYPE_IP_V4 = 1,
+    SFLADDRESSTYPE_IP_V6 = 2
+};
+
+typedef struct {
+    u_int32_t addr;
+} SFLIPv4;
+
+typedef struct {
+    u_char addr[16];
+} SFLIPv6;
+
+typedef union _SFLAddress_value {
+    SFLIPv4 ip_v4;
+    SFLIPv6 ip_v6;
+} SFLAddress_value;
+
+typedef struct _SFLAddress {
+    u_int32_t type;           /* enum SFLAddress_type */
+    SFLAddress_value address;
+} SFLAddress;
+
+/* Packet header data */
+
+#define SFL_DEFAULT_HEADER_SIZE 128
+#define SFL_DEFAULT_COLLECTOR_PORT 6343
+#define SFL_DEFAULT_SAMPLING_RATE 400
+#define SFL_DEFAULT_POLLING_INTERVAL 30
+
+/* The header protocol describes the format of the sampled header */
+enum SFLHeader_protocol {
+    SFLHEADER_ETHERNET_ISO8023     = 1,
+    SFLHEADER_ISO88024_TOKENBUS    = 2,
+    SFLHEADER_ISO88025_TOKENRING   = 3,
+    SFLHEADER_FDDI                 = 4,
+    SFLHEADER_FRAME_RELAY          = 5,
+    SFLHEADER_X25                  = 6,
+    SFLHEADER_PPP                  = 7,
+    SFLHEADER_SMDS                 = 8,
+    SFLHEADER_AAL5                 = 9,
+    SFLHEADER_AAL5_IP              = 10, /* e.g. Cisco AAL5 mux */
+    SFLHEADER_IPv4                 = 11,
+    SFLHEADER_IPv6                 = 12,
+    SFLHEADER_MPLS                 = 13
+};
+
+/* raw sampled header */
+
+typedef struct _SFLSampled_header {
+    u_int32_t header_protocol;            /* (enum SFLHeader_protocol) */
+    u_int32_t frame_length;               /* Original length of packet before sampling */
+    u_int32_t stripped;                   /* header/trailer bytes stripped by sender */
+    u_int32_t header_length;              /* length of sampled header bytes to follow */
+    u_int8_t *header_bytes;               /* Header bytes */
+} SFLSampled_header;
+
+/* decoded ethernet header */
+
+typedef struct _SFLSampled_ethernet {
+    u_int32_t eth_len;       /* The length of the MAC packet excluding 
+                               lower layer encapsulations */
+    u_int8_t src_mac[8];    /* 6 bytes + 2 pad */
+    u_int8_t dst_mac[8];
+    u_int32_t eth_type;
+} SFLSampled_ethernet;
+
+/* decoded IP version 4 header */
+
+typedef struct _SFLSampled_ipv4 {
+    u_int32_t length;      /* The length of the IP packet
+                             excluding lower layer encapsulations */
+    u_int32_t protocol;    /* IP Protocol type (for example, TCP = 6, UDP = 17) */
+    SFLIPv4   src_ip;      /* Source IP Address */
+    SFLIPv4   dst_ip;      /* Destination IP Address */
+    u_int32_t src_port;    /* TCP/UDP source port number or equivalent */
+    u_int32_t dst_port;    /* TCP/UDP destination port number or equivalent */
+    u_int32_t tcp_flags;   /* TCP flags */
+    u_int32_t tos;         /* IP type of service */
+} SFLSampled_ipv4;
+
+/* decoded IP version 6 data */
+
+typedef struct _SFLSampled_ipv6 {
+    u_int32_t length;       /* The length of the IP packet
+                              excluding lower layer encapsulations */
+    u_int32_t protocol;     /* IP Protocol type (for example, TCP = 6, UDP = 17) */
+    SFLIPv6   src_ip;       /* Source IP Address */
+    SFLIPv6   dst_ip;       /* Destination IP Address */
+    u_int32_t src_port;     /* TCP/UDP source port number or equivalent */
+    u_int32_t dst_port;     /* TCP/UDP destination port number or equivalent */
+    u_int32_t tcp_flags;    /* TCP flags */
+    u_int32_t priority;     /* IP priority */
+} SFLSampled_ipv6;
+
+/* Extended data types */
+
+/* Extended switch data */
+
+typedef struct _SFLExtended_switch {
+    u_int32_t src_vlan;       /* The 802.1Q VLAN id of incomming frame */
+    u_int32_t src_priority;   /* The 802.1p priority */
+    u_int32_t dst_vlan;       /* The 802.1Q VLAN id of outgoing frame */
+    u_int32_t dst_priority;   /* The 802.1p priority */
+} SFLExtended_switch;
+
+/* Extended router data */
+
+typedef struct _SFLExtended_router {
+    SFLAddress nexthop;               /* IP address of next hop router */
+    u_int32_t src_mask;               /* Source address prefix mask bits */
+    u_int32_t dst_mask;               /* Destination address prefix mask bits */
+} SFLExtended_router;
+
+/* Extended gateway data */
+enum SFLExtended_as_path_segment_type {
+    SFLEXTENDED_AS_SET = 1,      /* Unordered set of ASs */
+    SFLEXTENDED_AS_SEQUENCE = 2  /* Ordered sequence of ASs */
+};
+  
+typedef struct _SFLExtended_as_path_segment {
+    u_int32_t type;   /* enum SFLExtended_as_path_segment_type */
+    u_int32_t length; /* number of AS numbers in set/sequence */
+    union {
+       u_int32_t *set;
+       u_int32_t *seq;
+    } as;
+} SFLExtended_as_path_segment;
+
+typedef struct _SFLExtended_gateway {
+    SFLAddress nexthop;                       /* Address of the border router that should
+                                                be used for the destination network */
+    u_int32_t as;                             /* AS number for this gateway */
+    u_int32_t src_as;                         /* AS number of source (origin) */
+    u_int32_t src_peer_as;                    /* AS number of source peer */
+    u_int32_t dst_as_path_segments;           /* number of segments in path */
+    SFLExtended_as_path_segment *dst_as_path; /* list of seqs or sets */
+    u_int32_t communities_length;             /* number of communities */
+    u_int32_t *communities;                   /* set of communities */
+    u_int32_t localpref;                      /* LocalPref associated with this route */
+} SFLExtended_gateway;
+
+typedef struct _SFLString {
+    u_int32_t len;
+    char *str;
+} SFLString;
+
+/* Extended user data */
+
+typedef struct _SFLExtended_user {
+    u_int32_t src_charset;  /* MIBEnum value of character set used to encode a string - See RFC 2978
+                              Where possible UTF-8 encoding (MIBEnum=106) should be used. A value
+                              of zero indicates an unknown encoding. */
+    SFLString src_user;
+    u_int32_t dst_charset;
+    SFLString dst_user;
+} SFLExtended_user;
+
+/* Extended URL data */
+
+enum SFLExtended_url_direction {
+    SFLEXTENDED_URL_SRC = 1, /* URL is associated with source address */
+    SFLEXTENDED_URL_DST = 2  /* URL is associated with destination address */
+};
+
+typedef struct _SFLExtended_url {
+    u_int32_t direction;   /* enum SFLExtended_url_direction */
+    SFLString url;         /* URL associated with the packet flow.
+                             Must be URL encoded */
+    SFLString host;        /* The host field from the HTTP header */
+} SFLExtended_url;
+
+/* Extended MPLS data */
+
+typedef struct _SFLLabelStack {
+    u_int32_t depth;
+    u_int32_t *stack; /* first entry is top of stack - see RFC 3032 for encoding */
+} SFLLabelStack;
+
+typedef struct _SFLExtended_mpls {
+    SFLAddress nextHop;        /* Address of the next hop */ 
+    SFLLabelStack in_stack;
+    SFLLabelStack out_stack;
+} SFLExtended_mpls;
+
+/* Extended NAT data
+   Packet header records report addresses as seen at the sFlowDataSource.
+   The extended_nat structure reports on translated source and/or destination
+   addesses for this packet. If an address was not translated it should 
+   be equal to that reported for the header. */
+
+typedef struct _SFLExtended_nat {
+    SFLAddress src;    /* Source address */
+    SFLAddress dst;    /* Destination address */
+} SFLExtended_nat;
+
+/* additional Extended MPLS stucts */
+
+typedef struct _SFLExtended_mpls_tunnel {
+    SFLString tunnel_lsp_name;  /* Tunnel name */
+    u_int32_t tunnel_id;        /* Tunnel ID */
+    u_int32_t tunnel_cos;       /* Tunnel COS value */
+} SFLExtended_mpls_tunnel;
+
+typedef struct _SFLExtended_mpls_vc {
+    SFLString vc_instance_name; /* VC instance name */
+    u_int32_t vll_vc_id;        /* VLL/VC instance ID */
+    u_int32_t vc_label_cos;     /* VC Label COS value */
+} SFLExtended_mpls_vc;
+
+/* Extended MPLS FEC
+   - Definitions from MPLS-FTN-STD-MIB mplsFTNTable */
+
+typedef struct _SFLExtended_mpls_FTN {
+    SFLString mplsFTNDescr;
+    u_int32_t mplsFTNMask;
+} SFLExtended_mpls_FTN;
+
+/* Extended MPLS LVP FEC
+   - Definition from MPLS-LDP-STD-MIB mplsFecTable
+   Note: mplsFecAddrType, mplsFecAddr information available
+   from packet header */
+
+typedef struct _SFLExtended_mpls_LDP_FEC {
+    u_int32_t mplsFecAddrPrefixLength;
+} SFLExtended_mpls_LDP_FEC;
+
+/* Extended VLAN tunnel information 
+   Record outer VLAN encapsulations that have 
+   been stripped. extended_vlantunnel information 
+   should only be reported if all the following conditions are satisfied: 
+   1. The packet has nested vlan tags, AND 
+   2. The reporting device is VLAN aware, AND 
+   3. One or more VLAN tags have been stripped, either 
+   because they represent proprietary encapsulations, or 
+   because switch hardware automatically strips the outer VLAN 
+   encapsulation. 
+   Reporting extended_vlantunnel information is not a substitute for 
+   reporting extended_switch information. extended_switch data must 
+   always be reported to describe the ingress/egress VLAN information 
+   for the packet. The extended_vlantunnel information only applies to 
+   nested VLAN tags, and then only when one or more tags has been 
+   stripped. */ 
+
+typedef SFLLabelStack SFLVlanStack;
+typedef struct _SFLExtended_vlan_tunnel { 
+    SFLVlanStack stack;  /* List of stripped 802.1Q TPID/TCI layers. Each 
+                           TPID,TCI pair is represented as a single 32 bit 
+                           integer. Layers listed from outermost to 
+                           innermost. */ 
+} SFLExtended_vlan_tunnel;
+
+enum SFLFlow_type_tag {
+    /* enterprise = 0, format = ... */
+    SFLFLOW_HEADER    = 1,      /* Packet headers are sampled */
+    SFLFLOW_ETHERNET  = 2,      /* MAC layer information */
+    SFLFLOW_IPV4      = 3,      /* IP version 4 data */
+    SFLFLOW_IPV6      = 4,      /* IP version 6 data */
+    SFLFLOW_EX_SWITCH    = 1001,      /* Extended switch information */
+    SFLFLOW_EX_ROUTER    = 1002,      /* Extended router information */
+    SFLFLOW_EX_GATEWAY   = 1003,      /* Extended gateway router information */
+    SFLFLOW_EX_USER      = 1004,      /* Extended TACAS/RADIUS user information */
+    SFLFLOW_EX_URL       = 1005,      /* Extended URL information */
+    SFLFLOW_EX_MPLS      = 1006,      /* Extended MPLS information */
+    SFLFLOW_EX_NAT       = 1007,      /* Extended NAT information */
+    SFLFLOW_EX_MPLS_TUNNEL  = 1008,   /* additional MPLS information */
+    SFLFLOW_EX_MPLS_VC      = 1009,
+    SFLFLOW_EX_MPLS_FTN     = 1010,
+    SFLFLOW_EX_MPLS_LDP_FEC = 1011,
+    SFLFLOW_EX_VLAN_TUNNEL  = 1012,   /* VLAN stack */
+};
+
+typedef union _SFLFlow_type {
+    SFLSampled_header header;
+    SFLSampled_ethernet ethernet;
+    SFLSampled_ipv4 ipv4;
+    SFLSampled_ipv6 ipv6;
+    SFLExtended_switch sw;
+    SFLExtended_router router;
+    SFLExtended_gateway gateway;
+    SFLExtended_user user;
+    SFLExtended_url url;
+    SFLExtended_mpls mpls;
+    SFLExtended_nat nat;
+    SFLExtended_mpls_tunnel mpls_tunnel;
+    SFLExtended_mpls_vc mpls_vc;
+    SFLExtended_mpls_FTN mpls_ftn;
+    SFLExtended_mpls_LDP_FEC mpls_ldp_fec;
+    SFLExtended_vlan_tunnel vlan_tunnel;
+} SFLFlow_type;
+
+typedef struct _SFLFlow_sample_element {
+    struct _SFLFlow_sample_element *nxt;
+    u_int32_t tag;  /* SFLFlow_type_tag */
+    u_int32_t length;
+    SFLFlow_type flowType;
+} SFLFlow_sample_element;
+
+enum SFL_sample_tag {
+    SFLFLOW_SAMPLE = 1,              /* enterprise = 0 : format = 1 */
+    SFLCOUNTERS_SAMPLE = 2,          /* enterprise = 0 : format = 2 */
+    SFLFLOW_SAMPLE_EXPANDED = 3,     /* enterprise = 0 : format = 3 */
+    SFLCOUNTERS_SAMPLE_EXPANDED = 4  /* enterprise = 0 : format = 4 */
+};
+  
+/* Format of a single flow sample */
+
+typedef struct _SFLFlow_sample {
+    /* u_int32_t tag;    */         /* SFL_sample_tag -- enterprise = 0 : format = 1 */
+    /* u_int32_t length; */
+    u_int32_t sequence_number;      /* Incremented with each flow sample
+                                      generated */
+    u_int32_t source_id;            /* fsSourceId */
+    u_int32_t sampling_rate;        /* fsPacketSamplingRate */
+    u_int32_t sample_pool;          /* Total number of packets that could have been
+                                      sampled (i.e. packets skipped by sampling
+                                      process + total number of samples) */
+    u_int32_t drops;                /* Number of times a packet was dropped due to
+                                      lack of resources */
+    u_int32_t input;                /* SNMP ifIndex of input interface.
+                                      0 if interface is not known. */
+    u_int32_t output;               /* SNMP ifIndex of output interface,
+                                      0 if interface is not known.
+                                      Set most significant bit to indicate
+                                      multiple destination interfaces
+                                      (i.e. in case of broadcast or multicast)
+                                      and set lower order bits to indicate
+                                      number of destination interfaces.
+                                      Examples:
+                                      0x00000002  indicates ifIndex = 2
+                                      0x00000000  ifIndex unknown.
+                                      0x80000007  indicates a packet sent
+                                      to 7 interfaces.
+                                      0x80000000  indicates a packet sent to
+                                      an unknown number of
+                                      interfaces greater than 1.*/
+    u_int32_t num_elements;
+    SFLFlow_sample_element *elements;
+} SFLFlow_sample;
+
+/* same thing, but the expanded version (for full 32-bit ifIndex numbers) */
+
+typedef struct _SFLFlow_sample_expanded {
+    /* u_int32_t tag;    */         /* SFL_sample_tag -- enterprise = 0 : format = 1 */
+    /* u_int32_t length; */
+    u_int32_t sequence_number;      /* Incremented with each flow sample
+                                      generated */
+    u_int32_t ds_class;             /* EXPANDED */
+    u_int32_t ds_index;             /* EXPANDED */
+    u_int32_t sampling_rate;        /* fsPacketSamplingRate */
+    u_int32_t sample_pool;          /* Total number of packets that could have been
+                                      sampled (i.e. packets skipped by sampling
+                                      process + total number of samples) */
+    u_int32_t drops;                /* Number of times a packet was dropped due to
+                                      lack of resources */
+    u_int32_t inputFormat;          /* EXPANDED */
+    u_int32_t input;                /* SNMP ifIndex of input interface.
+                                      0 if interface is not known. */
+    u_int32_t outputFormat;         /* EXPANDED */
+    u_int32_t output;               /* SNMP ifIndex of output interface,
+                                      0 if interface is not known. */
+    u_int32_t num_elements;
+    SFLFlow_sample_element *elements;
+} SFLFlow_sample_expanded;
+
+/* Counter types */
+
+/* Generic interface counters - see RFC 1573, 2233 */
+
+typedef struct _SFLIf_counters {
+    u_int32_t ifIndex;
+    u_int32_t ifType;
+    u_int64_t ifSpeed;
+    u_int32_t ifDirection;        /* Derived from MAU MIB (RFC 2668)
+                                    0 = unknown, 1 = full-duplex,
+                                    2 = half-duplex, 3 = in, 4 = out */
+    u_int32_t ifStatus;           /* bit field with the following bits assigned:
+                                    bit 0 = ifAdminStatus (0 = down, 1 = up)
+                                    bit 1 = ifOperStatus (0 = down, 1 = up) */
+    u_int64_t ifInOctets;
+    u_int32_t ifInUcastPkts;
+    u_int32_t ifInMulticastPkts;
+    u_int32_t ifInBroadcastPkts;
+    u_int32_t ifInDiscards;
+    u_int32_t ifInErrors;
+    u_int32_t ifInUnknownProtos;
+    u_int64_t ifOutOctets;
+    u_int32_t ifOutUcastPkts;
+    u_int32_t ifOutMulticastPkts;
+    u_int32_t ifOutBroadcastPkts;
+    u_int32_t ifOutDiscards;
+    u_int32_t ifOutErrors;
+    u_int32_t ifPromiscuousMode;
+} SFLIf_counters;
+
+/* Ethernet interface counters - see RFC 2358 */
+typedef struct _SFLEthernet_counters {
+    u_int32_t dot3StatsAlignmentErrors;
+    u_int32_t dot3StatsFCSErrors;
+    u_int32_t dot3StatsSingleCollisionFrames;
+    u_int32_t dot3StatsMultipleCollisionFrames;
+    u_int32_t dot3StatsSQETestErrors;
+    u_int32_t dot3StatsDeferredTransmissions;
+    u_int32_t dot3StatsLateCollisions;
+    u_int32_t dot3StatsExcessiveCollisions;
+    u_int32_t dot3StatsInternalMacTransmitErrors;
+    u_int32_t dot3StatsCarrierSenseErrors;
+    u_int32_t dot3StatsFrameTooLongs;
+    u_int32_t dot3StatsInternalMacReceiveErrors;
+    u_int32_t dot3StatsSymbolErrors;
+} SFLEthernet_counters;
+
+/* Token ring counters - see RFC 1748 */
+
+typedef struct _SFLTokenring_counters {
+    u_int32_t dot5StatsLineErrors;
+    u_int32_t dot5StatsBurstErrors;
+    u_int32_t dot5StatsACErrors;
+    u_int32_t dot5StatsAbortTransErrors;
+    u_int32_t dot5StatsInternalErrors;
+    u_int32_t dot5StatsLostFrameErrors;
+    u_int32_t dot5StatsReceiveCongestions;
+    u_int32_t dot5StatsFrameCopiedErrors;
+    u_int32_t dot5StatsTokenErrors;
+    u_int32_t dot5StatsSoftErrors;
+    u_int32_t dot5StatsHardErrors;
+    u_int32_t dot5StatsSignalLoss;
+    u_int32_t dot5StatsTransmitBeacons;
+    u_int32_t dot5StatsRecoverys;
+    u_int32_t dot5StatsLobeWires;
+    u_int32_t dot5StatsRemoves;
+    u_int32_t dot5StatsSingles;
+    u_int32_t dot5StatsFreqErrors;
+} SFLTokenring_counters;
+
+/* 100 BaseVG interface counters - see RFC 2020 */
+
+typedef struct _SFLVg_counters {
+    u_int32_t dot12InHighPriorityFrames;
+    u_int64_t dot12InHighPriorityOctets;
+    u_int32_t dot12InNormPriorityFrames;
+    u_int64_t dot12InNormPriorityOctets;
+    u_int32_t dot12InIPMErrors;
+    u_int32_t dot12InOversizeFrameErrors;
+    u_int32_t dot12InDataErrors;
+    u_int32_t dot12InNullAddressedFrames;
+    u_int32_t dot12OutHighPriorityFrames;
+    u_int64_t dot12OutHighPriorityOctets;
+    u_int32_t dot12TransitionIntoTrainings;
+    u_int64_t dot12HCInHighPriorityOctets;
+    u_int64_t dot12HCInNormPriorityOctets;
+    u_int64_t dot12HCOutHighPriorityOctets;
+} SFLVg_counters;
+
+typedef struct _SFLVlan_counters {
+    u_int32_t vlan_id;
+    u_int64_t octets;
+    u_int32_t ucastPkts;
+    u_int32_t multicastPkts;
+    u_int32_t broadcastPkts;
+    u_int32_t discards;
+} SFLVlan_counters;
+
+/* Counters data */
+
+enum SFLCounters_type_tag {
+    /* enterprise = 0, format = ... */
+    SFLCOUNTERS_GENERIC      = 1,
+    SFLCOUNTERS_ETHERNET     = 2,
+    SFLCOUNTERS_TOKENRING    = 3,
+    SFLCOUNTERS_VG           = 4,
+    SFLCOUNTERS_VLAN         = 5
+};
+
+typedef union _SFLCounters_type {
+    SFLIf_counters generic;
+    SFLEthernet_counters ethernet;
+    SFLTokenring_counters tokenring;
+    SFLVg_counters vg;
+    SFLVlan_counters vlan;
+} SFLCounters_type;
+
+typedef struct _SFLCounters_sample_element {
+    struct _SFLCounters_sample_element *nxt; /* linked list */
+    u_int32_t tag; /* SFLCounters_type_tag */
+    u_int32_t length;
+    SFLCounters_type counterBlock;
+} SFLCounters_sample_element;
+
+typedef struct _SFLCounters_sample {
+    /* u_int32_t tag;    */       /* SFL_sample_tag -- enterprise = 0 : format = 2 */
+    /* u_int32_t length; */
+    u_int32_t sequence_number;    /* Incremented with each counters sample
+                                    generated by this source_id */
+    u_int32_t source_id;          /* fsSourceId */
+    u_int32_t num_elements;
+    SFLCounters_sample_element *elements;
+} SFLCounters_sample;
+
+/* same thing, but the expanded version, so ds_index can be a full 32 bits */
+typedef struct _SFLCounters_sample_expanded {
+    /* u_int32_t tag;    */       /* SFL_sample_tag -- enterprise = 0 : format = 2 */
+    /* u_int32_t length; */
+    u_int32_t sequence_number;    /* Incremented with each counters sample
+                                    generated by this source_id */
+    u_int32_t ds_class;           /* EXPANDED */
+    u_int32_t ds_index;           /* EXPANDED */
+    u_int32_t num_elements;
+    SFLCounters_sample_element *elements;
+} SFLCounters_sample_expanded;
+
+#define SFLADD_ELEMENT(_sm, _el) do { (_el)->nxt = (_sm)->elements; (_sm)->elements = (_el); } while(0)
+
+/* Format of a sample datagram */
+
+enum SFLDatagram_version {
+    SFLDATAGRAM_VERSION2 = 2,
+    SFLDATAGRAM_VERSION4 = 4,
+    SFLDATAGRAM_VERSION5 = 5
+};
+
+typedef struct _SFLSample_datagram_hdr {
+    u_int32_t datagram_version;      /* (enum SFLDatagram_version) = VERSION5 = 5 */
+    SFLAddress agent_address;        /* IP address of sampling agent */
+    u_int32_t sub_agent_id;          /* Used to distinguishing between datagram
+                                       streams from separate agent sub entities
+                                       within an device. */
+    u_int32_t sequence_number;       /* Incremented with each sample datagram
+                                       generated */
+    u_int32_t uptime;                /* Current time (in milliseconds since device
+                                       last booted). Should be set as close to
+                                       datagram transmission time as possible.*/
+    u_int32_t num_records;           /* Number of tag-len-val flow/counter records to follow */
+} SFLSample_datagram_hdr;
+
+#define SFL_MAX_DATAGRAM_SIZE 1500
+#define SFL_MIN_DATAGRAM_SIZE 200
+#define SFL_DEFAULT_DATAGRAM_SIZE 1400
+
+#define SFL_DATA_PAD 400
+
+#endif /* SFLOW_H */
diff --git a/lib/sflow_agent.c b/lib/sflow_agent.c
new file mode 100644 (file)
index 0000000..4b25c25
--- /dev/null
@@ -0,0 +1,492 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#include "sflow_api.h"
+
+static void * sflAlloc(SFLAgent *agent, size_t bytes);
+static void sflFree(SFLAgent *agent, void *obj);
+static void sfl_agent_jumpTableAdd(SFLAgent *agent, SFLSampler *sampler);
+static void sfl_agent_jumpTableRemove(SFLAgent *agent, SFLSampler *sampler);
+
+/*________________--------------------------__________________
+  ________________    sfl_agent_init        __________________
+  ----------------__________________________------------------
+*/
+
+void sfl_agent_init(SFLAgent *agent,
+                   SFLAddress *myIP, /* IP address of this agent in net byte order */
+                   u_int32_t subId,  /* agent_sub_id */
+                   time_t bootTime,  /* agent boot time */
+                   time_t now,       /* time now */
+                   void *magic,      /* ptr to pass back in logging and alloc fns */
+                   allocFn_t allocFn,
+                   freeFn_t freeFn,
+                   errorFn_t errorFn,
+                   sendFn_t sendFn)
+{
+    /* first clear everything */
+    memset(agent, 0, sizeof(*agent));
+    /* now copy in the parameters */
+    agent->myIP = *myIP; /* structure copy */
+    agent->subId = subId;
+    agent->bootTime = bootTime;
+    agent->now = now;
+    agent->magic = magic;
+    agent->allocFn = allocFn;
+    agent->freeFn = freeFn;
+    agent->errorFn = errorFn;
+    agent->sendFn = sendFn;
+
+#ifdef SFLOW_DO_SOCKET  
+    if(sendFn == NULL) {
+       /* open the socket - really need one for v4 and another for v6? */
+       if((agent->receiverSocket4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
+           sfl_agent_sysError(agent, "agent", "IPv4 socket open failed");
+       if((agent->receiverSocket6 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1)
+           sfl_agent_sysError(agent, "agent", "IPv6 socket open failed");
+    }
+#endif
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_release       __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_agent_release(SFLAgent *agent)
+{
+    /* release and free the samplers, pollers and receivers */
+    SFLSampler *sm = agent->samplers;
+    SFLPoller *pl = agent->pollers;
+    SFLReceiver *rcv = agent->receivers;
+
+    for(; sm != NULL; ) {
+       SFLSampler *nextSm = sm->nxt;
+       sflFree(agent, sm);
+       sm = nextSm;
+    }
+    agent->samplers = NULL;
+
+    for(; pl != NULL; ) {
+       SFLPoller *nextPl = pl->nxt;
+       sflFree(agent, pl);
+       pl = nextPl;
+    }
+    agent->pollers = NULL;
+
+    for(; rcv != NULL; ) {
+       SFLReceiver *nextRcv = rcv->nxt;
+       sflFree(agent, rcv);
+       rcv = nextRcv;
+    }
+    agent->receivers = NULL;
+
+#ifdef SFLOW_DO_SOCKET
+    /* close the sockets */
+    if(agent->receiverSocket4 > 0) close(agent->receiverSocket4);
+    if(agent->receiverSocket6 > 0) close(agent->receiverSocket6);
+#endif
+}
+
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_set_*         __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_agent_set_agentAddress(SFLAgent *agent, SFLAddress *addr)
+{
+    if(addr && memcmp(addr, &agent->myIP, sizeof(agent->myIP)) != 0) {
+       /* change of address */
+       agent->myIP = *addr; /* structure copy */
+       /* reset sequence numbers here? */
+    }
+}
+
+void sfl_agent_set_agentSubId(SFLAgent *agent, u_int32_t subId)
+{
+    if(subId != agent->subId) {
+       /* change of subId */
+       agent->subId = subId;
+       /* reset sequence numbers here? */
+    }
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_tick          __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_agent_tick(SFLAgent *agent, time_t now)
+{
+    SFLReceiver *rcv = agent->receivers;
+    SFLSampler *sm = agent->samplers;
+    SFLPoller *pl = agent->pollers;
+    agent->now = now;
+    /* receivers use ticks to flush send data */
+    for(; rcv != NULL; rcv = rcv->nxt) sfl_receiver_tick(rcv, now);
+    /* samplers use ticks to decide when they are sampling too fast */
+    for(; sm != NULL; sm = sm->nxt) sfl_sampler_tick(sm, now);
+    /* pollers use ticks to decide when to ask for counters */
+    for(; pl != NULL; pl = pl->nxt) sfl_poller_tick(pl, now);
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_addReceiver   __________________
+  -----------------___________________________------------------
+*/
+
+SFLReceiver *sfl_agent_addReceiver(SFLAgent *agent)
+{
+    SFLReceiver *rcv = (SFLReceiver *)sflAlloc(agent, sizeof(SFLReceiver));
+    sfl_receiver_init(rcv, agent);
+    /* add to end of list - to preserve the receiver index numbers for existing receivers */
+    {
+       SFLReceiver *r, *prev = NULL;
+       for(r = agent->receivers; r != NULL; prev = r, r = r->nxt);
+       if(prev) prev->nxt = rcv;
+       else agent->receivers = rcv;
+       rcv->nxt = NULL;
+    }
+    return rcv;
+}
+
+/*_________________---------------------------__________________
+  _________________     sfl_dsi_compare       __________________
+  -----------------___________________________------------------
+
+  Note that if there is a mixture of ds_classes for this agent, then
+  the simple numeric comparison may not be correct - the sort order (for
+  the purposes of the SNMP MIB) should really be determined by the OID
+  that these numeric ds_class numbers are a shorthand for.  For example,
+  ds_class == 0 means ifIndex, which is the oid "1.3.6.1.2.1.2.2.1"
+*/
+
+static inline int sfl_dsi_compare(SFLDataSource_instance *pdsi1, SFLDataSource_instance *pdsi2) {
+    /* could have used just memcmp(),  but not sure if that would
+       give the right answer on little-endian platforms. Safer to be explicit... */
+    int cmp = pdsi2->ds_class - pdsi1->ds_class;
+    if(cmp == 0) cmp = pdsi2->ds_index - pdsi1->ds_index;
+    if(cmp == 0) cmp = pdsi2->ds_instance - pdsi1->ds_instance;
+    return cmp;
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_addSampler    __________________
+  -----------------___________________________------------------
+*/
+
+SFLSampler *sfl_agent_addSampler(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* Keep the list sorted. */
+    SFLSampler *prev = NULL, *sm = agent->samplers;
+    for(; sm != NULL; prev = sm, sm = sm->nxt) {
+       int64_t cmp = sfl_dsi_compare(pdsi, &sm->dsi);
+       if(cmp == 0) return sm;  /* found - return existing one */
+       if(cmp < 0) break;       /* insert here */
+    }
+    /* either we found the insert point, or reached the end of the list...*/
+    
+    {
+       SFLSampler *newsm = (SFLSampler *)sflAlloc(agent, sizeof(SFLSampler));
+       sfl_sampler_init(newsm, agent, pdsi);
+       if(prev) prev->nxt = newsm;
+       else agent->samplers = newsm;
+       newsm->nxt = sm;
+       
+       /* see if we should go in the ifIndex jumpTable */
+       if(SFL_DS_CLASS(newsm->dsi) == 0) {
+           SFLSampler *test = sfl_agent_getSamplerByIfIndex(agent, SFL_DS_INDEX(newsm->dsi));
+           if(test && (SFL_DS_INSTANCE(newsm->dsi) < SFL_DS_INSTANCE(test->dsi))) {
+               /* replace with this new one because it has a lower ds_instance number */
+               sfl_agent_jumpTableRemove(agent, test);
+               test = NULL;
+           }
+           if(test == NULL) sfl_agent_jumpTableAdd(agent, newsm);
+       }
+       return newsm;
+    }
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_agent_addPoller     __________________
+  -----------------___________________________------------------
+*/
+
+SFLPoller *sfl_agent_addPoller(SFLAgent *agent,
+                              SFLDataSource_instance *pdsi,
+                              void *magic,         /* ptr to pass back in getCountersFn() */
+                              getCountersFn_t getCountersFn)
+{
+    /* keep the list sorted */
+    SFLPoller *prev = NULL, *pl = agent->pollers;
+    for(; pl != NULL; prev = pl, pl = pl->nxt) {
+       int64_t cmp = sfl_dsi_compare(pdsi, &pl->dsi);
+       if(cmp == 0) return pl;  /* found - return existing one */
+       if(cmp < 0) break;       /* insert here */
+    }
+    /* either we found the insert point, or reached the end of the list... */
+    {
+       SFLPoller *newpl = (SFLPoller *)sflAlloc(agent, sizeof(SFLPoller));
+       sfl_poller_init(newpl, agent, pdsi, magic, getCountersFn);
+       if(prev) prev->nxt = newpl;
+       else agent->pollers = newpl;
+       newpl->nxt = pl;
+       return newpl;
+    }
+}
+
+/*_________________---------------------------__________________
+  _________________  sfl_agent_removeSampler  __________________
+  -----------------___________________________------------------
+*/
+
+int sfl_agent_removeSampler(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* find it, unlink it and free it */
+    SFLSampler *prev = NULL, *sm = agent->samplers;
+    for(; sm != NULL; prev = sm, sm = sm->nxt) {
+       if(sfl_dsi_compare(pdsi, &sm->dsi) == 0) {
+           if(prev == NULL) agent->samplers = sm->nxt;
+           else prev->nxt = sm->nxt;
+           sfl_agent_jumpTableRemove(agent, sm);
+           sflFree(agent, sm);
+           return 1;
+       }
+    }
+    /* not found */
+    return 0;
+}
+
+/*_________________---------------------------__________________
+  _________________  sfl_agent_removePoller   __________________
+  -----------------___________________________------------------
+*/
+
+int sfl_agent_removePoller(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* find it, unlink it and free it */
+    SFLPoller *prev = NULL, *pl = agent->pollers;
+    for(; pl != NULL; prev = pl, pl = pl->nxt) {
+       if(sfl_dsi_compare(pdsi, &pl->dsi) == 0) {
+           if(prev == NULL) agent->pollers = pl->nxt;
+           else prev->nxt = pl->nxt;
+           sflFree(agent, pl);
+           return 1;
+       }
+    }
+    /* not found */
+    return 0;
+}
+
+/*_________________--------------------------------__________________
+  _________________  sfl_agent_jumpTableAdd        __________________
+  -----------------________________________________------------------
+*/
+
+static void sfl_agent_jumpTableAdd(SFLAgent *agent, SFLSampler *sampler)
+{
+    u_int32_t hashIndex = SFL_DS_INDEX(sampler->dsi) % SFL_HASHTABLE_SIZ;
+    sampler->hash_nxt = agent->jumpTable[hashIndex];
+    agent->jumpTable[hashIndex] = sampler;
+}
+
+/*_________________--------------------------------__________________
+  _________________  sfl_agent_jumpTableRemove     __________________
+  -----------------________________________________------------------
+*/
+
+static void sfl_agent_jumpTableRemove(SFLAgent *agent, SFLSampler *sampler)
+{
+    u_int32_t hashIndex = SFL_DS_INDEX(sampler->dsi) % SFL_HASHTABLE_SIZ;
+    SFLSampler *search = agent->jumpTable[hashIndex], *prev = NULL;
+    for( ; search != NULL; prev = search, search = search->hash_nxt) if(search == sampler) break;
+    if(search) {
+       // found - unlink
+       if(prev) prev->hash_nxt = search->hash_nxt;
+       else agent->jumpTable[hashIndex] = search->hash_nxt;
+       search->hash_nxt = NULL;
+    }
+}
+
+/*_________________--------------------------------__________________
+  _________________  sfl_agent_getSamplerByIfIndex __________________
+  -----------------________________________________------------------
+  fast lookup (pointers cached in hash table).  If there are multiple
+  sampler instances for a given ifIndex, then this fn will return
+  the one with the lowest instance number.  Since the samplers
+  list is sorted, this means the other instances will be accesible
+  by following the sampler->nxt pointer (until the ds_class
+  or ds_index changes).  This is helpful if you need to offer
+  the same flowSample to multiple samplers.
+*/
+
+SFLSampler *sfl_agent_getSamplerByIfIndex(SFLAgent *agent, u_int32_t ifIndex)
+{
+    SFLSampler *search = agent->jumpTable[ifIndex % SFL_HASHTABLE_SIZ];
+    for( ; search != NULL; search = search->hash_nxt) if(SFL_DS_INDEX(search->dsi) == ifIndex) break;
+    return search;
+}
+
+/*_________________---------------------------__________________
+  _________________  sfl_agent_getSampler     __________________
+  -----------------___________________________------------------
+*/
+
+SFLSampler *sfl_agent_getSampler(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* find it and return it */
+    SFLSampler *sm = agent->samplers;
+    for(; sm != NULL; sm = sm->nxt)
+       if(sfl_dsi_compare(pdsi, &sm->dsi) == 0) return sm;
+    /* not found */
+    return NULL;
+}
+
+/*_________________---------------------------__________________
+  _________________  sfl_agent_getPoller      __________________
+  -----------------___________________________------------------
+*/
+
+SFLPoller *sfl_agent_getPoller(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* find it and return it */
+    SFLPoller *pl = agent->pollers;
+    for(; pl != NULL; pl = pl->nxt)
+       if(sfl_dsi_compare(pdsi, &pl->dsi) == 0) return pl;
+    /* not found */
+    return NULL;
+}
+
+/*_________________---------------------------__________________
+  _________________  sfl_agent_getReceiver    __________________
+  -----------------___________________________------------------
+*/
+
+SFLReceiver *sfl_agent_getReceiver(SFLAgent *agent, u_int32_t receiverIndex)
+{
+    u_int32_t rcvIdx = 0;
+    SFLReceiver *rcv = agent->receivers;
+    for(;  rcv != NULL; rcv = rcv->nxt)
+       if(receiverIndex == ++rcvIdx) return rcv;
+
+    /* not found - ran off the end of the table */
+    return NULL;
+}
+
+/*_________________---------------------------__________________
+  _________________ sfl_agent_getNextSampler  __________________
+  -----------------___________________________------------------
+*/
+
+SFLSampler *sfl_agent_getNextSampler(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* return the one lexograpically just after it - assume they are sorted
+       correctly according to the lexographical ordering of the object ids */
+    SFLSampler *sm = sfl_agent_getSampler(agent, pdsi);
+    return sm ? sm->nxt : NULL;
+}
+
+/*_________________---------------------------__________________
+  _________________ sfl_agent_getNextPoller   __________________
+  -----------------___________________________------------------
+*/
+
+SFLPoller *sfl_agent_getNextPoller(SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* return the one lexograpically just after it - assume they are sorted
+       correctly according to the lexographical ordering of the object ids */
+    SFLPoller *pl = sfl_agent_getPoller(agent, pdsi);
+    return pl ? pl->nxt : NULL;
+}
+
+/*_________________---------------------------__________________
+  _________________ sfl_agent_getNextReceiver __________________
+  -----------------___________________________------------------
+*/
+
+SFLReceiver *sfl_agent_getNextReceiver(SFLAgent *agent, u_int32_t receiverIndex)
+{
+    return sfl_agent_getReceiver(agent, receiverIndex + 1);
+}
+
+
+/*_________________---------------------------__________________
+  _________________ sfl_agent_resetReceiver   __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_agent_resetReceiver(SFLAgent *agent, SFLReceiver *receiver)
+{
+    /* tell samplers and pollers to stop sending to this receiver */
+    /* first get his receiverIndex */
+    u_int32_t rcvIdx = 0;
+    SFLReceiver *rcv = agent->receivers;
+    for(; rcv != NULL; rcv = rcv->nxt) {
+       rcvIdx++; /* thanks to Diego Valverde for pointing out this bugfix */
+       if(rcv == receiver) {
+           /* now tell anyone that is using it to stop */
+           SFLSampler *sm = agent->samplers;
+           SFLPoller *pl = agent->pollers;
+
+           for(; sm != NULL; sm = sm->nxt)
+               if(sfl_sampler_get_sFlowFsReceiver(sm) == rcvIdx) sfl_sampler_set_sFlowFsReceiver(sm, 0);
+      
+           for(; pl != NULL; pl = pl->nxt)
+               if(sfl_poller_get_sFlowCpReceiver(pl) == rcvIdx) sfl_poller_set_sFlowCpReceiver(pl, 0);
+
+           break;
+       }
+    }
+}
+  
+/*_________________---------------------------__________________
+  _________________     sfl_agent_error       __________________
+  -----------------___________________________------------------
+*/
+#define MAX_ERRMSG_LEN 1000
+
+void sfl_agent_error(SFLAgent *agent, char *modName, char *msg)
+{
+    char errm[MAX_ERRMSG_LEN];
+    sprintf(errm, "sfl_agent_error: %s: %s\n", modName, msg);
+    if(agent->errorFn) (*agent->errorFn)(agent->magic, agent, errm);
+    else {
+       fprintf(stderr, "%s\n", errm);
+       fflush(stderr);
+    }
+}
+
+/*_________________---------------------------__________________
+  _________________     sfl_agent_sysError    __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_agent_sysError(SFLAgent *agent, char *modName, char *msg)
+{
+    char errm[MAX_ERRMSG_LEN];
+    sprintf(errm, "sfl_agent_sysError: %s: %s (errno = %d - %s)\n", modName, msg, errno, strerror(errno));
+    if(agent->errorFn) (*agent->errorFn)(agent->magic, agent, errm);
+    else {
+       fprintf(stderr, "%s\n", errm);
+       fflush(stderr);
+    }
+}
+
+
+/*_________________---------------------------__________________
+  _________________       alloc and free      __________________
+  -----------------___________________________------------------
+*/
+
+static void * sflAlloc(SFLAgent *agent, size_t bytes)
+{
+    if(agent->allocFn) return (*agent->allocFn)(agent->magic, agent, bytes);
+    else return SFL_ALLOC(bytes);
+}
+
+static void sflFree(SFLAgent *agent, void *obj)
+{
+    if(agent->freeFn) (*agent->freeFn)(agent->magic, agent, obj);
+    else SFL_FREE(obj);
+}
diff --git a/lib/sflow_api.h b/lib/sflow_api.h
new file mode 100644 (file)
index 0000000..be8d997
--- /dev/null
@@ -0,0 +1,340 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#ifndef SFLOW_API_H
+#define SFLOW_API_H 1
+
+/* define SFLOW_DO_SOCKET to 1 if you want the agent
+   to send the packets itself, otherwise set the sendFn
+   callback in sfl_agent_init.*/
+/* #define SFLOW_DO_SOCKET */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <arpa/inet.h> /* for htonl */
+
+#ifdef SFLOW_DO_SOCKET
+#include <sys/socket.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#endif
+
+#include "sflow.h"
+  
+/* define SFLOW_SOFTWARE_SAMPLING to 1 if you need to use the
+   sfl_sampler_takeSample routine and give it every packet */
+/* #define SFLOW_SOFTWARE_SAMPLING */
+
+/*
+  uncomment this preprocessor flag  (or compile with -DSFL_USE_32BIT_INDEX)
+  if your ds_index numbers can ever be >= 2^30-1 (i.e. >= 0x3FFFFFFF)
+*/
+/* #define SFL_USE_32BIT_INDEX */
+
+
+/* Used to combine ds_class, ds_index and instance into
+   a single 64-bit number like this:
+   __________________________________
+   | cls|  index     |   instance     |
+   ----------------------------------
+   but now is opened up to a 12-byte struct to ensure
+   that ds_index has a full 32-bit field, and to make
+   accessing the components simpler. The macros have
+   the same behavior as before, so this change should
+   be transparent.  The only difference is that these
+   objects are now passed around by reference instead
+   of by value, and the comparison is done using a fn.
+*/
+
+typedef struct _SFLDataSource_instance {
+    u_int32_t ds_class;
+    u_int32_t ds_index;
+    u_int32_t ds_instance;
+} SFLDataSource_instance;
+
+#ifdef SFL_USE_32BIT_INDEX
+#define SFL_FLOW_SAMPLE_TYPE SFLFlow_sample_expanded
+#define SFL_COUNTERS_SAMPLE_TYPE SFLCounters_sample_expanded
+#else
+#define SFL_FLOW_SAMPLE_TYPE SFLFlow_sample
+#define SFL_COUNTERS_SAMPLE_TYPE SFLCounters_sample
+/* if index numbers are not going to use all 32 bits, then we can use
+   the more compact encoding, with the dataSource class and index merged */
+#define SFL_DS_DATASOURCE(dsi) (((dsi).ds_class << 24) + (dsi).ds_index)
+#endif
+
+#define SFL_DS_INSTANCE(dsi) (dsi).ds_instance
+#define SFL_DS_CLASS(dsi) (dsi).ds_class
+#define SFL_DS_INDEX(dsi) (dsi).ds_index
+#define SFL_DS_SET(dsi,clss,indx,inst)         \
+    do {                                       \
+       (dsi).ds_class = (clss);                \
+       (dsi).ds_index = (indx);                \
+       (dsi).ds_instance = (inst);             \
+    } while(0)
+
+typedef struct _SFLSampleCollector {
+    u_int32_t data[(SFL_MAX_DATAGRAM_SIZE + SFL_DATA_PAD) / sizeof(u_int32_t)];
+    u_int32_t *datap; /* packet fill pointer */
+    u_int32_t pktlen; /* accumulated size */
+    u_int32_t packetSeqNo;
+    u_int32_t numSamples;
+} SFLSampleCollector;
+
+struct _SFLAgent;  /* forward decl */
+
+typedef struct _SFLReceiver {
+    struct _SFLReceiver *nxt;
+    /* MIB fields */
+    char *sFlowRcvrOwner;
+    time_t sFlowRcvrTimeout;
+    u_int32_t sFlowRcvrMaximumDatagramSize;
+    SFLAddress sFlowRcvrAddress;
+    u_int32_t sFlowRcvrPort;
+    u_int32_t sFlowRcvrDatagramVersion;
+    /* public fields */
+    struct _SFLAgent *agent;    /* pointer to my agent */
+    /* private fields */
+    SFLSampleCollector sampleCollector;
+#ifdef SFLOW_DO_SOCKET
+    struct sockaddr_in receiver4;
+    struct sockaddr_in6 receiver6;
+#endif
+} SFLReceiver;
+
+typedef struct _SFLSampler {
+    /* for linked list */
+    struct _SFLSampler *nxt;
+    /* for hash lookup table */
+    struct _SFLSampler *hash_nxt;
+    /* MIB fields */
+    SFLDataSource_instance dsi;
+    u_int32_t sFlowFsReceiver;
+    u_int32_t sFlowFsPacketSamplingRate;
+    u_int32_t sFlowFsMaximumHeaderSize;
+    /* public fields */
+    struct _SFLAgent *agent; /* pointer to my agent */
+    /* private fields */
+    SFLReceiver *myReceiver;
+    u_int32_t skip;
+    u_int32_t samplePool;
+    u_int32_t flowSampleSeqNo;
+    /* rate checking */
+    u_int32_t samplesThisTick;
+    u_int32_t samplesLastTick;
+    u_int32_t backoffThreshold;
+} SFLSampler;
+
+/* declare */
+struct _SFLPoller;
+
+typedef void (*getCountersFn_t)(void *magic,                   /* callback to get counters */
+                               struct _SFLPoller *sampler,    /* called with self */
+                               SFL_COUNTERS_SAMPLE_TYPE *cs); /* struct to fill in */
+
+typedef struct _SFLPoller {
+    /* for linked list */
+    struct _SFLPoller *nxt;
+    /* MIB fields */
+    SFLDataSource_instance dsi;
+    u_int32_t sFlowCpReceiver;
+    time_t sFlowCpInterval;
+    /* public fields */
+    struct _SFLAgent *agent; /* pointer to my agent */
+    void *magic;             /* ptr to pass back in getCountersFn() */
+    getCountersFn_t getCountersFn;
+    u_int32_t bridgePort; /* port number local to bridge */
+    /* private fields */
+    SFLReceiver *myReceiver;
+    time_t countersCountdown;
+    u_int32_t countersSampleSeqNo;
+} SFLPoller;
+
+typedef void *(*allocFn_t)(void *magic,               /* callback to allocate space on heap */
+                          struct _SFLAgent *agent,   /* called with self */
+                          size_t bytes);             /* bytes requested */
+
+typedef int (*freeFn_t)(void *magic,                  /* callback to free space on heap */
+                       struct _SFLAgent *agent,      /* called with self */
+                       void *obj);                   /* obj to free */
+
+typedef void (*errorFn_t)(void *magic,                /* callback to log error message */
+                         struct _SFLAgent *agent,    /* called with self */
+                         char *msg);                 /* error message */
+
+typedef void (*sendFn_t)(void *magic,                 /* optional override fn to send packet */
+                        struct _SFLAgent *agent,
+                        SFLReceiver *receiver,
+                        u_char *pkt,
+                        u_int32_t pktLen);
+
+
+/* prime numbers are good for hash tables */
+#define SFL_HASHTABLE_SIZ 199
+
+typedef struct _SFLAgent {
+    SFLSampler *jumpTable[SFL_HASHTABLE_SIZ]; /* fast lookup table for samplers (by ifIndex) */
+    SFLSampler *samplers;   /* the list of samplers */
+    SFLPoller  *pollers;    /* the list of samplers */
+    SFLReceiver *receivers; /* the array of receivers */
+    time_t bootTime;        /* time when we booted or started */
+    time_t now;             /* time now */
+    SFLAddress myIP;        /* IP address of this node */
+    u_int32_t subId;        /* sub_agent_id */
+    void *magic;            /* ptr to pass back in logging and alloc fns */
+    allocFn_t allocFn;
+    freeFn_t freeFn;
+    errorFn_t errorFn;
+    sendFn_t sendFn;
+#ifdef SFLOW_DO_SOCKET
+    int receiverSocket4;
+    int receiverSocket6;
+#endif
+} SFLAgent;
+
+/* call this at the start with a newly created agent */
+void sfl_agent_init(SFLAgent *agent,
+                   SFLAddress *myIP, /* IP address of this agent */
+                   u_int32_t subId,  /* agent_sub_id */
+                   time_t bootTime,  /* agent boot time */
+                   time_t now,       /* time now */
+                   void *magic,      /* ptr to pass back in logging and alloc fns */
+                   allocFn_t allocFn,
+                   freeFn_t freeFn,
+                   errorFn_t errorFn,
+                   sendFn_t sendFn);
+
+/* call this to create samplers */
+SFLSampler *sfl_agent_addSampler(SFLAgent *agent, SFLDataSource_instance *pdsi);
+
+/* call this to create pollers */
+SFLPoller *sfl_agent_addPoller(SFLAgent *agent,
+                              SFLDataSource_instance *pdsi,
+                              void *magic, /* ptr to pass back in getCountersFn() */
+                              getCountersFn_t getCountersFn);
+
+/* call this to create receivers */
+SFLReceiver *sfl_agent_addReceiver(SFLAgent *agent);
+
+/* call this to remove samplers */
+int sfl_agent_removeSampler(SFLAgent *agent, SFLDataSource_instance *pdsi);
+
+/* call this to remove pollers */
+int sfl_agent_removePoller(SFLAgent *agent, SFLDataSource_instance *pdsi);
+
+/* note: receivers should not be removed. Typically the receivers
+   list will be created at init time and never changed */
+
+/* call these fns to retrieve sampler, poller or receiver (e.g. for SNMP GET or GETNEXT operation) */
+SFLSampler  *sfl_agent_getSampler(SFLAgent *agent, SFLDataSource_instance *pdsi);
+SFLSampler  *sfl_agent_getNextSampler(SFLAgent *agent, SFLDataSource_instance *pdsi);
+SFLPoller   *sfl_agent_getPoller(SFLAgent *agent, SFLDataSource_instance *pdsi);
+SFLPoller   *sfl_agent_getNextPoller(SFLAgent *agent, SFLDataSource_instance *pdsi);
+SFLReceiver *sfl_agent_getReceiver(SFLAgent *agent, u_int32_t receiverIndex);
+SFLReceiver *sfl_agent_getNextReceiver(SFLAgent *agent, u_int32_t receiverIndex);
+
+/* jump table access - for performance */
+SFLSampler *sfl_agent_getSamplerByIfIndex(SFLAgent *agent, u_int32_t ifIndex);
+
+/* call these functions to GET and SET MIB values */
+
+/* receiver */
+char *      sfl_receiver_get_sFlowRcvrOwner(SFLReceiver *receiver);
+void        sfl_receiver_set_sFlowRcvrOwner(SFLReceiver *receiver, char *sFlowRcvrOwner);
+time_t      sfl_receiver_get_sFlowRcvrTimeout(SFLReceiver *receiver);
+void        sfl_receiver_set_sFlowRcvrTimeout(SFLReceiver *receiver, time_t sFlowRcvrTimeout);
+u_int32_t   sfl_receiver_get_sFlowRcvrMaximumDatagramSize(SFLReceiver *receiver);
+void        sfl_receiver_set_sFlowRcvrMaximumDatagramSize(SFLReceiver *receiver, u_int32_t sFlowRcvrMaximumDatagramSize);
+SFLAddress *sfl_receiver_get_sFlowRcvrAddress(SFLReceiver *receiver);
+void        sfl_receiver_set_sFlowRcvrAddress(SFLReceiver *receiver, SFLAddress *sFlowRcvrAddress);
+u_int32_t   sfl_receiver_get_sFlowRcvrPort(SFLReceiver *receiver);
+void        sfl_receiver_set_sFlowRcvrPort(SFLReceiver *receiver, u_int32_t sFlowRcvrPort);
+/* sampler */
+u_int32_t sfl_sampler_get_sFlowFsReceiver(SFLSampler *sampler);
+void      sfl_sampler_set_sFlowFsReceiver(SFLSampler *sampler, u_int32_t sFlowFsReceiver);
+u_int32_t sfl_sampler_get_sFlowFsPacketSamplingRate(SFLSampler *sampler);
+void      sfl_sampler_set_sFlowFsPacketSamplingRate(SFLSampler *sampler, u_int32_t sFlowFsPacketSamplingRate);
+u_int32_t sfl_sampler_get_sFlowFsMaximumHeaderSize(SFLSampler *sampler);
+void      sfl_sampler_set_sFlowFsMaximumHeaderSize(SFLSampler *sampler, u_int32_t sFlowFsMaximumHeaderSize);
+u_int32_t sfl_sampler_get_samplesLastTick(SFLSampler *sampler);
+/* poller */
+u_int32_t sfl_poller_get_sFlowCpReceiver(SFLPoller *poller);
+void      sfl_poller_set_sFlowCpReceiver(SFLPoller *poller, u_int32_t sFlowCpReceiver);
+u_int32_t sfl_poller_get_sFlowCpInterval(SFLPoller *poller);
+void      sfl_poller_set_sFlowCpInterval(SFLPoller *poller, u_int32_t sFlowCpInterval);
+
+/* fns to set the sflow agent address or sub-id */
+void sfl_agent_set_agentAddress(SFLAgent *agent, SFLAddress *addr);
+void sfl_agent_set_agentSubId(SFLAgent *agent, u_int32_t subId);
+
+/* The poller may need a separate number to reference the local bridge port
+   to get counters if it is not the same as the global ifIndex */
+void sfl_poller_set_bridgePort(SFLPoller *poller, u_int32_t port_no);
+u_int32_t sfl_poller_get_bridgePort(SFLPoller *poller);
+
+/* call this to indicate a discontinuity with a counter like samplePool so that the
+   sflow collector will ignore the next delta */
+void sfl_sampler_resetFlowSeqNo(SFLSampler *sampler);
+
+/* call this to indicate a discontinuity with one or more of the counters so that the
+   sflow collector will ignore the next delta */
+void sfl_poller_resetCountersSeqNo(SFLPoller *poller);
+
+#ifdef SFLOW_SOFTWARE_SAMLING
+/* software sampling: call this with every packet - returns non-zero if the packet
+   should be sampled (in which case you then call sfl_sampler_writeFlowSample()) */
+int sfl_sampler_takeSample(SFLSampler *sampler);
+#endif
+
+/* call this to set a maximum samples-per-second threshold. If the sampler reaches this
+   threshold it will automatically back off the sampling rate. A value of 0 disables the
+   mechanism */
+void sfl_sampler_set_backoffThreshold(SFLSampler *sampler, u_int32_t samplesPerSecond);
+u_int32_t sfl_sampler_get_backoffThreshold(SFLSampler *sampler);
+
+/* call this once per second (N.B. not on interrupt stack i.e. not hard real-time) */
+void sfl_agent_tick(SFLAgent *agent, time_t now);
+
+/* call this with each flow sample */
+void sfl_sampler_writeFlowSample(SFLSampler *sampler, SFL_FLOW_SAMPLE_TYPE *fs);
+
+/* call this to push counters samples (usually done in the getCountersFn callback) */
+void sfl_poller_writeCountersSample(SFLPoller *poller, SFL_COUNTERS_SAMPLE_TYPE *cs);
+
+/* call this to deallocate resources */
+void sfl_agent_release(SFLAgent *agent);
+
+
+/* internal fns */
+
+void sfl_receiver_init(SFLReceiver *receiver, SFLAgent *agent);
+void sfl_sampler_init(SFLSampler *sampler, SFLAgent *agent, SFLDataSource_instance *pdsi);
+void sfl_poller_init(SFLPoller *poller, SFLAgent *agent, SFLDataSource_instance *pdsi, void *magic, getCountersFn_t getCountersFn);
+
+
+void sfl_receiver_tick(SFLReceiver *receiver, time_t now);
+void sfl_poller_tick(SFLPoller *poller, time_t now);
+void sfl_sampler_tick(SFLSampler *sampler, time_t now);
+
+int sfl_receiver_writeFlowSample(SFLReceiver *receiver, SFL_FLOW_SAMPLE_TYPE *fs);
+int sfl_receiver_writeCountersSample(SFLReceiver *receiver, SFL_COUNTERS_SAMPLE_TYPE *cs);
+
+void sfl_agent_resetReceiver(SFLAgent *agent, SFLReceiver *receiver);
+
+void sfl_agent_error(SFLAgent *agent, char *modName, char *msg);
+void sfl_agent_sysError(SFLAgent *agent, char *modName, char *msg);
+
+u_int32_t sfl_receiver_samplePacketsSent(SFLReceiver *receiver);
+
+#define SFL_ALLOC malloc
+#define SFL_FREE free
+
+#endif /* SFLOW_API_H */
+
+
diff --git a/lib/sflow_poller.c b/lib/sflow_poller.c
new file mode 100644 (file)
index 0000000..e7dc2b1
--- /dev/null
@@ -0,0 +1,161 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#include "sflow_api.h"
+
+/*_________________--------------------------__________________
+  _________________    sfl_poller_init       __________________
+  -----------------__________________________------------------
+*/
+
+void sfl_poller_init(SFLPoller *poller,
+                    SFLAgent *agent,
+                    SFLDataSource_instance *pdsi,
+                    void *magic,         /* ptr to pass back in getCountersFn() */
+                    getCountersFn_t getCountersFn)
+{
+    /* copy the dsi in case it points to poller->dsi, which we are about to clear */
+    SFLDataSource_instance dsi = *pdsi;
+
+    /* preserve the *nxt pointer too, in case we are resetting this poller and it is
+       already part of the agent's linked list (thanks to Matt Woodly for pointing this out) */
+    SFLPoller *nxtPtr = poller->nxt;
+
+    /* clear everything */
+    memset(poller, 0, sizeof(*poller));
+  
+    /* restore the linked list ptr */
+    poller->nxt = nxtPtr;
+  
+    /* now copy in the parameters */
+    poller->agent = agent;
+    poller->dsi = dsi; /* structure copy */
+    poller->magic = magic;
+    poller->getCountersFn = getCountersFn;
+}
+
+/*_________________--------------------------__________________
+  _________________       reset              __________________
+  -----------------__________________________------------------
+*/
+
+static void reset(SFLPoller *poller)
+{
+    SFLDataSource_instance dsi = poller->dsi;
+    sfl_poller_init(poller, poller->agent, &dsi, poller->magic, poller->getCountersFn);
+}
+
+/*_________________---------------------------__________________
+  _________________      MIB access           __________________
+  -----------------___________________________------------------
+*/
+u_int32_t sfl_poller_get_sFlowCpReceiver(SFLPoller *poller) {
+    return poller->sFlowCpReceiver;
+}
+
+void sfl_poller_set_sFlowCpReceiver(SFLPoller *poller, u_int32_t sFlowCpReceiver) {
+    poller->sFlowCpReceiver = sFlowCpReceiver;
+    if(sFlowCpReceiver == 0) reset(poller);
+    else {
+       /* retrieve and cache a direct pointer to my receiver */
+       poller->myReceiver = sfl_agent_getReceiver(poller->agent, poller->sFlowCpReceiver);
+    }
+}
+
+u_int32_t sfl_poller_get_sFlowCpInterval(SFLPoller *poller) {
+    return poller->sFlowCpInterval;
+}
+
+void sfl_poller_set_sFlowCpInterval(SFLPoller *poller, u_int32_t sFlowCpInterval) {
+    poller->sFlowCpInterval = sFlowCpInterval;
+    if(sFlowCpInterval) {
+        /* Set the countersCountdown to be a randomly selected value between 1 and
+          sFlowCpInterval. That way the counter polling will be desynchronised
+          (on a 200-port switch, polling all the counters in one second could be harmful).
+          In a large network, even this might not be ideal if time-synchroniziation
+          between devices is close and counters are always polled on second boundaries. If
+          1000 different devices all send an sFlow datagram on the same second boundary
+          it could result in an antisocial burst.
+          However when counter-samples are packed into the export datagram they do not
+          always result in that datagram being sent immediately. It is more likely that
+          a subsequent packet-sample will be the one that triggers the datagram to be sent.
+          The packet-sample events are not sychronized to any clock, so that results in
+          excellent desynchronization (http://blog.sflow.com/2009/05/measurement-traffic.html).
+          Another smoothing factor is that the tick() function called here is usually
+          driven from a fairly "soft" polling loop rather than a hard real-time event.
+       */
+        poller->countersCountdown = 1 + (random() % sFlowCpInterval);
+    }
+    else {
+        /* Setting sFlowCpInterval to 0 disables counter polling altogether.  Thanks to
+          Andy Kitchingman for spotting this ommission. */
+        poller->countersCountdown = 0;
+    }
+}
+
+/*_________________---------------------------------__________________
+  _________________          bridge port            __________________
+  -----------------_________________________________------------------
+  May need a separate number to reference the local bridge port
+  to get counters if it is not the same as the global ifIndex.
+*/
+
+void sfl_poller_set_bridgePort(SFLPoller *poller, u_int32_t port_no) {
+    poller->bridgePort = port_no;
+}
+
+u_int32_t sfl_poller_get_bridgePort(SFLPoller *poller) {
+    return poller->bridgePort;
+}
+
+/*_________________---------------------------------__________________
+  _________________   sequence number reset         __________________
+  -----------------_________________________________------------------
+  Used to indicate a counter discontinuity
+  so that the sflow collector will know to ignore the next delta.
+*/
+void sfl_poller_resetCountersSeqNo(SFLPoller *poller) {  poller->countersSampleSeqNo = 0; }
+
+/*_________________---------------------------__________________
+  _________________    sfl_poller_tick        __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_poller_tick(SFLPoller *poller, time_t now)
+{
+    if(poller->countersCountdown == 0) return; /* counters retrieval was not enabled */
+    if(poller->sFlowCpReceiver == 0) return;
+
+    if(--poller->countersCountdown == 0) {
+       if(poller->getCountersFn != NULL) {
+           /* call out for counters */
+           SFL_COUNTERS_SAMPLE_TYPE cs;
+           memset(&cs, 0, sizeof(cs));
+           poller->getCountersFn(poller->magic, poller, &cs);
+           /* this countersFn is expected to fill in some counter block elements
+              and then call sfl_poller_writeCountersSample(poller, &cs); */
+       }
+       /* reset the countdown */
+       poller->countersCountdown = poller->sFlowCpInterval;
+    }
+}
+
+/*_________________---------------------------------__________________
+  _________________ sfl_poller_writeCountersSample  __________________
+  -----------------_________________________________------------------
+*/
+
+void sfl_poller_writeCountersSample(SFLPoller *poller, SFL_COUNTERS_SAMPLE_TYPE *cs)
+{
+    /* fill in the rest of the header fields, and send to the receiver */
+    cs->sequence_number = ++poller->countersSampleSeqNo;
+#ifdef SFL_USE_32BIT_INDEX
+    cs->ds_class = SFL_DS_CLASS(poller->dsi);
+    cs->ds_index = SFL_DS_INDEX(poller->dsi);
+#else
+    cs->source_id = SFL_DS_DATASOURCE(poller->dsi);
+#endif
+    /* sent to my receiver */
+    if(poller->myReceiver) sfl_receiver_writeCountersSample(poller->myReceiver, cs);
+}
+
diff --git a/lib/sflow_receiver.c b/lib/sflow_receiver.c
new file mode 100644 (file)
index 0000000..7fccab3
--- /dev/null
@@ -0,0 +1,832 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#include <assert.h>
+#include "sflow_api.h"
+
+static void resetSampleCollector(SFLReceiver *receiver);
+static void sendSample(SFLReceiver *receiver);
+static void sflError(SFLReceiver *receiver, char *errm);
+inline static void putNet32(SFLReceiver *receiver, u_int32_t val);
+inline static void putAddress(SFLReceiver *receiver, SFLAddress *addr);
+#ifdef SFLOW_DO_SOCKET
+static void initSocket(SFLReceiver *receiver);
+#endif
+
+/*_________________--------------------------__________________
+  _________________    sfl_receiver_init     __________________
+  -----------------__________________________------------------
+*/
+
+void sfl_receiver_init(SFLReceiver *receiver, SFLAgent *agent)
+{
+    /* first clear everything */
+    memset(receiver, 0, sizeof(*receiver));
+
+    /* now copy in the parameters */
+    receiver->agent = agent;
+
+    /* set defaults */
+    receiver->sFlowRcvrMaximumDatagramSize = SFL_DEFAULT_DATAGRAM_SIZE;
+    receiver->sFlowRcvrPort = SFL_DEFAULT_COLLECTOR_PORT;
+
+#ifdef SFLOW_DO_SOCKET
+    /* initialize the socket address */
+    initSocket(receiver);
+#endif
+
+    /* preset some of the header fields */
+    receiver->sampleCollector.datap = receiver->sampleCollector.data;
+    putNet32(receiver, SFLDATAGRAM_VERSION5);
+    putAddress(receiver, &agent->myIP);
+    putNet32(receiver, agent->subId);
+
+    /* prepare to receive the first sample */
+    resetSampleCollector(receiver);
+}
+
+/*_________________---------------------------__________________
+  _________________      reset                __________________
+  -----------------___________________________------------------
+
+  called on timeout, or when owner string is cleared
+*/
+
+static void reset(SFLReceiver *receiver) {
+    // ask agent to tell samplers and pollers to stop sending samples
+    sfl_agent_resetReceiver(receiver->agent, receiver);
+    // reinitialize
+    sfl_receiver_init(receiver, receiver->agent);
+}
+
+#ifdef SFLOW_DO_SOCKET
+/*_________________---------------------------__________________
+  _________________      initSocket           __________________
+  -----------------___________________________------------------
+*/
+
+static void initSocket(SFLReceiver *receiver) {
+    if(receiver->sFlowRcvrAddress.type == SFLADDRESSTYPE_IP_V6) {
+       struct sockaddr_in6 *sa6 = &receiver->receiver6;
+       sa6->sin6_port = htons((u_int16_t)receiver->sFlowRcvrPort);
+       sa6->sin6_family = AF_INET6;
+       sa6->sin6_addr = receiver->sFlowRcvrAddress.address.ip_v6;
+    }
+    else {
+       struct sockaddr_in *sa4 = &receiver->receiver4;
+       sa4->sin_port = htons((u_int16_t)receiver->sFlowRcvrPort);
+       sa4->sin_family = AF_INET;
+       sa4->sin_addr = receiver->sFlowRcvrAddress.address.ip_v4;
+    }
+}
+#endif
+
+/*_________________----------------------------------------_____________
+  _________________          MIB Vars                      _____________
+  -----------------________________________________________-------------
+*/
+
+char * sfl_receiver_get_sFlowRcvrOwner(SFLReceiver *receiver) {
+    return receiver->sFlowRcvrOwner;
+}
+void sfl_receiver_set_sFlowRcvrOwner(SFLReceiver *receiver, char *sFlowRcvrOwner) {
+    receiver->sFlowRcvrOwner = sFlowRcvrOwner;
+    if(sFlowRcvrOwner == NULL || sFlowRcvrOwner[0] == '\0') {
+       // reset condition! owner string was cleared
+       reset(receiver);
+    }
+}
+time_t sfl_receiver_get_sFlowRcvrTimeout(SFLReceiver *receiver) {
+    return receiver->sFlowRcvrTimeout;
+}
+void sfl_receiver_set_sFlowRcvrTimeout(SFLReceiver *receiver, time_t sFlowRcvrTimeout) {
+    receiver->sFlowRcvrTimeout =sFlowRcvrTimeout;
+} 
+u_int32_t sfl_receiver_get_sFlowRcvrMaximumDatagramSize(SFLReceiver *receiver) {
+    return receiver->sFlowRcvrMaximumDatagramSize;
+}
+void sfl_receiver_set_sFlowRcvrMaximumDatagramSize(SFLReceiver *receiver, u_int32_t sFlowRcvrMaximumDatagramSize) {
+    u_int32_t mdz = sFlowRcvrMaximumDatagramSize;
+    if(mdz < SFL_MIN_DATAGRAM_SIZE) mdz = SFL_MIN_DATAGRAM_SIZE;
+    receiver->sFlowRcvrMaximumDatagramSize = mdz;
+}
+SFLAddress *sfl_receiver_get_sFlowRcvrAddress(SFLReceiver *receiver) {
+    return &receiver->sFlowRcvrAddress;
+}
+void sfl_receiver_set_sFlowRcvrAddress(SFLReceiver *receiver, SFLAddress *sFlowRcvrAddress) {
+    if(sFlowRcvrAddress) receiver->sFlowRcvrAddress = *sFlowRcvrAddress; // structure copy
+#ifdef SFLOW_DO_SOCKET
+    initSocket(receiver);
+#endif
+}
+u_int32_t sfl_receiver_get_sFlowRcvrPort(SFLReceiver *receiver) {
+    return receiver->sFlowRcvrPort;
+}
+void sfl_receiver_set_sFlowRcvrPort(SFLReceiver *receiver, u_int32_t sFlowRcvrPort) {
+    receiver->sFlowRcvrPort = sFlowRcvrPort;
+    // update the socket structure
+#ifdef SFLOW_DO_SOCKET
+    initSocket(receiver);
+#endif
+}
+
+/*_________________---------------------------__________________
+  _________________   sfl_receiver_tick       __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_receiver_tick(SFLReceiver *receiver, time_t now)
+{
+    // if there are any samples to send, flush them now
+    if(receiver->sampleCollector.numSamples > 0) sendSample(receiver);
+    // check the timeout
+    if(receiver->sFlowRcvrTimeout && (u_int32_t)receiver->sFlowRcvrTimeout != 0xFFFFFFFF) {
+       // count down one tick and reset if we reach 0
+       if(--receiver->sFlowRcvrTimeout == 0) reset(receiver);
+    }
+}
+
+/*_________________-----------------------------__________________
+  _________________   receiver write utilities  __________________
+  -----------------_____________________________------------------
+*/
+inline static void put32(SFLReceiver *receiver, u_int32_t val)
+{
+    *receiver->sampleCollector.datap++ = val;
+}
+
+inline static void putNet32(SFLReceiver *receiver, u_int32_t val)
+{
+    *receiver->sampleCollector.datap++ = htonl(val);
+}
+
+inline static void putNet32_run(SFLReceiver *receiver, void *obj, size_t quads)
+{
+    u_int32_t *from = (u_int32_t *)obj;
+    while(quads--) putNet32(receiver, *from++);
+}
+
+inline static void putNet64(SFLReceiver *receiver, u_int64_t val64)
+{
+    u_int32_t *firstQuadPtr = receiver->sampleCollector.datap;
+    // first copy the bytes in
+    memcpy((u_char *)firstQuadPtr, &val64, 8);
+    if(htonl(1) != 1) {
+       // swap the bytes, and reverse the quads too
+       u_int32_t tmp = *receiver->sampleCollector.datap++;
+       *firstQuadPtr = htonl(*receiver->sampleCollector.datap);
+       *receiver->sampleCollector.datap++ = htonl(tmp);
+    }
+    else receiver->sampleCollector.datap += 2;
+}
+
+inline static void put128(SFLReceiver *receiver, u_char *val)
+{
+    memcpy(receiver->sampleCollector.datap, val, 16);
+    receiver->sampleCollector.datap += 4;
+}
+
+inline static void putString(SFLReceiver *receiver, SFLString *s)
+{
+    putNet32(receiver, s->len);
+    memcpy(receiver->sampleCollector.datap, s->str, s->len);
+    receiver->sampleCollector.datap += (s->len + 3) / 4; /* pad to 4-byte boundary */
+}
+
+inline static u_int32_t stringEncodingLength(SFLString *s) {
+    // answer in bytes,  so remember to mulitply by 4 after rounding up to nearest 4-byte boundary
+    return 4 + (((s->len + 3) / 4) * 4);
+}
+
+inline static void putAddress(SFLReceiver *receiver, SFLAddress *addr)
+{
+    // encode unspecified addresses as IPV4:0.0.0.0 - or should we flag this as an error?
+    if(addr->type == 0) {
+       putNet32(receiver, SFLADDRESSTYPE_IP_V4);
+       put32(receiver, 0);
+    }
+    else {
+       putNet32(receiver, addr->type);
+       if(addr->type == SFLADDRESSTYPE_IP_V4) put32(receiver, addr->address.ip_v4.addr);
+       else put128(receiver, addr->address.ip_v6.addr);
+    }
+}
+
+inline static u_int32_t addressEncodingLength(SFLAddress *addr) {
+    return (addr->type == SFLADDRESSTYPE_IP_V6) ? 20 : 8;  // type + address (unspecified == IPV4)
+}
+
+inline static void putMACAddress(SFLReceiver *receiver, u_int8_t *mac)
+{
+    memcpy(receiver->sampleCollector.datap, mac, 6);
+    receiver->sampleCollector.datap += 2;
+}
+
+inline static void putSwitch(SFLReceiver *receiver, SFLExtended_switch *sw)
+{
+    putNet32(receiver, sw->src_vlan);
+    putNet32(receiver, sw->src_priority);
+    putNet32(receiver, sw->dst_vlan);
+    putNet32(receiver, sw->dst_priority);
+}
+
+inline static void putRouter(SFLReceiver *receiver, SFLExtended_router *router)
+{
+    putAddress(receiver, &router->nexthop);
+    putNet32(receiver, router->src_mask);
+    putNet32(receiver, router->dst_mask);
+}
+
+inline static u_int32_t routerEncodingLength(SFLExtended_router *router) {
+    return addressEncodingLength(&router->nexthop) + 8;
+}
+
+inline static void putGateway(SFLReceiver *receiver, SFLExtended_gateway *gw)
+{
+    putAddress(receiver, &gw->nexthop);
+    putNet32(receiver, gw->as);
+    putNet32(receiver, gw->src_as);
+    putNet32(receiver, gw->src_peer_as);
+    putNet32(receiver, gw->dst_as_path_segments);
+    {
+       u_int32_t seg = 0;
+       for(; seg < gw->dst_as_path_segments; seg++) {
+           putNet32(receiver, gw->dst_as_path[seg].type);
+           putNet32(receiver, gw->dst_as_path[seg].length);
+           putNet32_run(receiver, gw->dst_as_path[seg].as.seq, gw->dst_as_path[seg].length);
+       }
+    }
+    putNet32(receiver, gw->communities_length);
+    putNet32_run(receiver, gw->communities, gw->communities_length);
+    putNet32(receiver, gw->localpref);
+}
+
+inline static u_int32_t gatewayEncodingLength(SFLExtended_gateway *gw) {
+    u_int32_t elemSiz = addressEncodingLength(&gw->nexthop);
+    u_int32_t seg = 0;
+    elemSiz += 16; // as, src_as, src_peer_as, dst_as_path_segments 
+    for(; seg < gw->dst_as_path_segments; seg++) {
+       elemSiz += 8; // type, length 
+       elemSiz += 4 * gw->dst_as_path[seg].length; // set/seq bytes
+    }
+    elemSiz += 4; // communities_length
+    elemSiz += 4 * gw->communities_length; // communities
+    elemSiz += 4; // localpref
+    return elemSiz;
+}
+
+inline static void putUser(SFLReceiver *receiver, SFLExtended_user *user)
+{
+    putNet32(receiver, user->src_charset);
+    putString(receiver, &user->src_user);
+    putNet32(receiver, user->dst_charset);
+    putString(receiver, &user->dst_user);
+}
+
+inline static u_int32_t userEncodingLength(SFLExtended_user *user) {
+    return 4
+       + stringEncodingLength(&user->src_user)
+       + 4
+       + stringEncodingLength(&user->dst_user);
+}
+
+inline static void putUrl(SFLReceiver *receiver, SFLExtended_url *url)
+{
+    putNet32(receiver, url->direction);
+    putString(receiver, &url->url);
+    putString(receiver, &url->host);
+}
+
+inline static u_int32_t urlEncodingLength(SFLExtended_url *url) {
+    return 4
+       + stringEncodingLength(&url->url)
+       + stringEncodingLength(&url->host);
+}
+
+inline static void putLabelStack(SFLReceiver *receiver, SFLLabelStack *labelStack)
+{
+    putNet32(receiver, labelStack->depth);
+    putNet32_run(receiver, labelStack->stack, labelStack->depth);
+}
+
+inline static u_int32_t labelStackEncodingLength(SFLLabelStack *labelStack) {
+    return 4 + (4 * labelStack->depth);
+}
+
+inline static void putMpls(SFLReceiver *receiver, SFLExtended_mpls *mpls)
+{
+    putAddress(receiver, &mpls->nextHop);
+    putLabelStack(receiver, &mpls->in_stack);
+    putLabelStack(receiver, &mpls->out_stack);
+}
+
+inline static u_int32_t mplsEncodingLength(SFLExtended_mpls *mpls) {
+    return addressEncodingLength(&mpls->nextHop)
+       + labelStackEncodingLength(&mpls->in_stack)
+       + labelStackEncodingLength(&mpls->out_stack);
+}
+
+inline static void putNat(SFLReceiver *receiver, SFLExtended_nat *nat)
+{
+    putAddress(receiver, &nat->src);
+    putAddress(receiver, &nat->dst);
+}
+
+inline static u_int32_t natEncodingLength(SFLExtended_nat *nat) {
+    return addressEncodingLength(&nat->src)
+       + addressEncodingLength(&nat->dst);
+}
+
+inline static void putMplsTunnel(SFLReceiver *receiver, SFLExtended_mpls_tunnel *tunnel)
+{
+    putString(receiver, &tunnel->tunnel_lsp_name);
+    putNet32(receiver, tunnel->tunnel_id);
+    putNet32(receiver, tunnel->tunnel_cos);
+}
+
+inline static u_int32_t mplsTunnelEncodingLength(SFLExtended_mpls_tunnel *tunnel) {
+    return stringEncodingLength(&tunnel->tunnel_lsp_name) + 8;
+}
+
+inline static void putMplsVc(SFLReceiver *receiver, SFLExtended_mpls_vc *vc)
+{
+    putString(receiver, &vc->vc_instance_name);
+    putNet32(receiver, vc->vll_vc_id);
+    putNet32(receiver, vc->vc_label_cos);
+}
+
+inline static u_int32_t mplsVcEncodingLength(SFLExtended_mpls_vc *vc) {
+    return stringEncodingLength( &vc->vc_instance_name) + 8;
+}
+
+inline static void putMplsFtn(SFLReceiver *receiver, SFLExtended_mpls_FTN *ftn)
+{
+    putString(receiver, &ftn->mplsFTNDescr);
+    putNet32(receiver, ftn->mplsFTNMask);
+}
+
+inline static u_int32_t mplsFtnEncodingLength(SFLExtended_mpls_FTN *ftn) {
+    return stringEncodingLength( &ftn->mplsFTNDescr) + 4;
+}
+
+inline static void putMplsLdpFec(SFLReceiver *receiver, SFLExtended_mpls_LDP_FEC *ldpfec)
+{
+    putNet32(receiver, ldpfec->mplsFecAddrPrefixLength);
+}
+
+inline static u_int32_t mplsLdpFecEncodingLength(SFLExtended_mpls_LDP_FEC *ldpfec) {
+    return 4;
+}
+
+inline static void putVlanTunnel(SFLReceiver *receiver, SFLExtended_vlan_tunnel *vlanTunnel)
+{
+    putLabelStack(receiver, &vlanTunnel->stack);
+}
+
+inline static u_int32_t vlanTunnelEncodingLength(SFLExtended_vlan_tunnel *vlanTunnel) {
+    return labelStackEncodingLength(&vlanTunnel->stack);
+}
+
+
+inline static void putGenericCounters(SFLReceiver *receiver, SFLIf_counters *counters)
+{
+    putNet32(receiver, counters->ifIndex);
+    putNet32(receiver, counters->ifType);
+    putNet64(receiver, counters->ifSpeed);
+    putNet32(receiver, counters->ifDirection);
+    putNet32(receiver, counters->ifStatus);
+    putNet64(receiver, counters->ifInOctets);
+    putNet32(receiver, counters->ifInUcastPkts);
+    putNet32(receiver, counters->ifInMulticastPkts);
+    putNet32(receiver, counters->ifInBroadcastPkts);
+    putNet32(receiver, counters->ifInDiscards);
+    putNet32(receiver, counters->ifInErrors);
+    putNet32(receiver, counters->ifInUnknownProtos);
+    putNet64(receiver, counters->ifOutOctets);
+    putNet32(receiver, counters->ifOutUcastPkts);
+    putNet32(receiver, counters->ifOutMulticastPkts);
+    putNet32(receiver, counters->ifOutBroadcastPkts);
+    putNet32(receiver, counters->ifOutDiscards);
+    putNet32(receiver, counters->ifOutErrors);
+    putNet32(receiver, counters->ifPromiscuousMode);
+}
+
+
+/*_________________-----------------------------__________________
+  _________________      computeFlowSampleSize  __________________
+  -----------------_____________________________------------------
+*/
+
+static int computeFlowSampleSize(SFLReceiver *receiver, SFL_FLOW_SAMPLE_TYPE *fs)
+{
+    SFLFlow_sample_element *elem = fs->elements;
+#ifdef SFL_USE_32BIT_INDEX
+    u_int siz = 52; /* tag, length, sequence_number, ds_class, ds_index, sampling_rate,
+                      sample_pool, drops, inputFormat, input, outputFormat, output, number of elements */
+#else
+    u_int siz = 40; /* tag, length, sequence_number, source_id, sampling_rate,
+                      sample_pool, drops, input, output, number of elements */
+#endif
+
+    fs->num_elements = 0; /* we're going to count them again even if this was set by the client */
+    for(; elem != NULL; elem = elem->nxt) {
+       u_int elemSiz = 0;
+       fs->num_elements++;
+       siz += 8; /* tag, length */
+       switch(elem->tag) {
+       case SFLFLOW_HEADER:
+           elemSiz = 16; /* header_protocol, frame_length, stripped, header_length */
+           elemSiz += ((elem->flowType.header.header_length + 3) / 4) * 4; /* header, rounded up to nearest 4 bytes */
+           break;
+       case SFLFLOW_ETHERNET: elemSiz = sizeof(SFLSampled_ethernet); break;
+       case SFLFLOW_IPV4: elemSiz = sizeof(SFLSampled_ipv4); break;
+       case SFLFLOW_IPV6: elemSiz = sizeof(SFLSampled_ipv6); break;
+       case SFLFLOW_EX_SWITCH: elemSiz = sizeof(SFLExtended_switch); break;
+       case SFLFLOW_EX_ROUTER: elemSiz = routerEncodingLength(&elem->flowType.router); break;
+       case SFLFLOW_EX_GATEWAY: elemSiz = gatewayEncodingLength(&elem->flowType.gateway); break;
+       case SFLFLOW_EX_USER: elemSiz = userEncodingLength(&elem->flowType.user); break;
+       case SFLFLOW_EX_URL: elemSiz = urlEncodingLength(&elem->flowType.url); break;
+       case SFLFLOW_EX_MPLS: elemSiz = mplsEncodingLength(&elem->flowType.mpls); break;
+       case SFLFLOW_EX_NAT: elemSiz = natEncodingLength(&elem->flowType.nat); break;
+       case SFLFLOW_EX_MPLS_TUNNEL: elemSiz = mplsTunnelEncodingLength(&elem->flowType.mpls_tunnel); break;
+       case SFLFLOW_EX_MPLS_VC: elemSiz = mplsVcEncodingLength(&elem->flowType.mpls_vc); break;
+       case SFLFLOW_EX_MPLS_FTN: elemSiz = mplsFtnEncodingLength(&elem->flowType.mpls_ftn); break;
+       case SFLFLOW_EX_MPLS_LDP_FEC: elemSiz = mplsLdpFecEncodingLength(&elem->flowType.mpls_ldp_fec); break;
+       case SFLFLOW_EX_VLAN_TUNNEL: elemSiz = vlanTunnelEncodingLength(&elem->flowType.vlan_tunnel); break;
+       default:
+           sflError(receiver, "unexpected packet_data_tag");
+           return -1;
+           break;
+       }
+       // cache the element size, and accumulate it into the overall FlowSample size
+       elem->length = elemSiz;
+       siz += elemSiz;
+    }
+
+    return siz;
+}
+
+/*_________________-------------------------------__________________
+  _________________ sfl_receiver_writeFlowSample  __________________
+  -----------------_______________________________------------------
+*/
+
+int sfl_receiver_writeFlowSample(SFLReceiver *receiver, SFL_FLOW_SAMPLE_TYPE *fs)
+{
+    int packedSize;
+    if(fs == NULL) return -1;
+    if((packedSize = computeFlowSampleSize(receiver, fs)) == -1) return -1;
+
+    // check in case this one sample alone is too big for the datagram
+    // in fact - if it is even half as big then we should ditch it. Very
+    // important to avoid overruning the packet buffer.
+    if(packedSize > (int)(receiver->sFlowRcvrMaximumDatagramSize / 2)) {
+       sflError(receiver, "flow sample too big for datagram");
+       return -1;
+    }
+
+    // if the sample pkt is full enough so that this sample might put
+    // it over the limit, then we should send it now before going on.
+    if((receiver->sampleCollector.pktlen + packedSize) >= receiver->sFlowRcvrMaximumDatagramSize)
+       sendSample(receiver);
+    
+    receiver->sampleCollector.numSamples++;
+
+#ifdef SFL_USE_32BIT_INDEX
+    putNet32(receiver, SFLFLOW_SAMPLE_EXPANDED);
+#else
+    putNet32(receiver, SFLFLOW_SAMPLE);
+#endif
+
+    putNet32(receiver, packedSize - 8); // don't include tag and len
+    putNet32(receiver, fs->sequence_number);
+
+#ifdef SFL_USE_32BIT_INDEX
+    putNet32(receiver, fs->ds_class);
+    putNet32(receiver, fs->ds_index);
+#else
+    putNet32(receiver, fs->source_id);
+#endif
+
+    putNet32(receiver, fs->sampling_rate);
+    putNet32(receiver, fs->sample_pool);
+    putNet32(receiver, fs->drops);
+
+#ifdef SFL_USE_32BIT_INDEX
+    putNet32(receiver, fs->inputFormat);
+    putNet32(receiver, fs->input);
+    putNet32(receiver, fs->outputFormat);
+    putNet32(receiver, fs->output);
+#else
+    putNet32(receiver, fs->input);
+    putNet32(receiver, fs->output);
+#endif
+
+    putNet32(receiver, fs->num_elements);
+
+    {
+       SFLFlow_sample_element *elem = fs->elements;
+       for(; elem != NULL; elem = elem->nxt) {
+           
+           putNet32(receiver, elem->tag);
+           putNet32(receiver, elem->length); // length cached in computeFlowSampleSize()
+           
+           switch(elem->tag) {
+           case SFLFLOW_HEADER:
+               putNet32(receiver, elem->flowType.header.header_protocol);
+               putNet32(receiver, elem->flowType.header.frame_length);
+               putNet32(receiver, elem->flowType.header.stripped);
+               putNet32(receiver, elem->flowType.header.header_length);
+               /* the header */
+               memcpy(receiver->sampleCollector.datap, elem->flowType.header.header_bytes, elem->flowType.header.header_length);
+               /* round up to multiple of 4 to preserve alignment */
+               receiver->sampleCollector.datap += ((elem->flowType.header.header_length + 3) / 4);
+               break;
+           case SFLFLOW_ETHERNET:
+               putNet32(receiver, elem->flowType.ethernet.eth_len);
+               putMACAddress(receiver, elem->flowType.ethernet.src_mac);
+               putMACAddress(receiver, elem->flowType.ethernet.dst_mac);
+               putNet32(receiver, elem->flowType.ethernet.eth_type);
+               break;
+           case SFLFLOW_IPV4:
+               putNet32(receiver, elem->flowType.ipv4.length);
+               putNet32(receiver, elem->flowType.ipv4.protocol);
+               put32(receiver, elem->flowType.ipv4.src_ip.addr);
+               put32(receiver, elem->flowType.ipv4.dst_ip.addr);
+               putNet32(receiver, elem->flowType.ipv4.src_port);
+               putNet32(receiver, elem->flowType.ipv4.dst_port);
+               putNet32(receiver, elem->flowType.ipv4.tcp_flags);
+               putNet32(receiver, elem->flowType.ipv4.tos);
+               break;
+           case SFLFLOW_IPV6:
+               putNet32(receiver, elem->flowType.ipv6.length);
+               putNet32(receiver, elem->flowType.ipv6.protocol);
+               put128(receiver, elem->flowType.ipv6.src_ip.addr);
+               put128(receiver, elem->flowType.ipv6.dst_ip.addr);
+               putNet32(receiver, elem->flowType.ipv6.src_port);
+               putNet32(receiver, elem->flowType.ipv6.dst_port);
+               putNet32(receiver, elem->flowType.ipv6.tcp_flags);
+               putNet32(receiver, elem->flowType.ipv6.priority);
+               break;
+           case SFLFLOW_EX_SWITCH: putSwitch(receiver, &elem->flowType.sw); break;
+           case SFLFLOW_EX_ROUTER: putRouter(receiver, &elem->flowType.router); break;
+           case SFLFLOW_EX_GATEWAY: putGateway(receiver, &elem->flowType.gateway); break;
+           case SFLFLOW_EX_USER: putUser(receiver, &elem->flowType.user); break;
+           case SFLFLOW_EX_URL: putUrl(receiver, &elem->flowType.url); break;
+           case SFLFLOW_EX_MPLS: putMpls(receiver, &elem->flowType.mpls); break;
+           case SFLFLOW_EX_NAT: putNat(receiver, &elem->flowType.nat); break;
+           case SFLFLOW_EX_MPLS_TUNNEL: putMplsTunnel(receiver, &elem->flowType.mpls_tunnel); break;
+           case SFLFLOW_EX_MPLS_VC: putMplsVc(receiver, &elem->flowType.mpls_vc); break;
+           case SFLFLOW_EX_MPLS_FTN: putMplsFtn(receiver, &elem->flowType.mpls_ftn); break;
+           case SFLFLOW_EX_MPLS_LDP_FEC: putMplsLdpFec(receiver, &elem->flowType.mpls_ldp_fec); break;
+           case SFLFLOW_EX_VLAN_TUNNEL: putVlanTunnel(receiver, &elem->flowType.vlan_tunnel); break;
+           default:
+               sflError(receiver, "unexpected packet_data_tag");
+               return -1;
+               break;
+           }
+       }
+    }
+
+    // sanity check
+    assert(((u_char *)receiver->sampleCollector.datap
+           - (u_char *)receiver->sampleCollector.data
+           - receiver->sampleCollector.pktlen)  == (u_int32_t)packedSize);
+
+    // update the pktlen
+    receiver->sampleCollector.pktlen = (u_char *)receiver->sampleCollector.datap - (u_char *)receiver->sampleCollector.data;
+    return packedSize;
+}
+
+/*_________________-----------------------------__________________
+  _________________ computeCountersSampleSize   __________________
+  -----------------_____________________________------------------
+*/
+
+static int computeCountersSampleSize(SFLReceiver *receiver, SFL_COUNTERS_SAMPLE_TYPE *cs)
+{
+    SFLCounters_sample_element *elem = cs->elements;
+#ifdef SFL_USE_32BIT_INDEX
+    u_int siz = 24; /* tag, length, sequence_number, ds_class, ds_index, number of elements */
+#else
+    u_int siz = 20; /* tag, length, sequence_number, source_id, number of elements */
+#endif
+
+    cs->num_elements = 0; /* we're going to count them again even if this was set by the client */
+    for(; elem != NULL; elem = elem->nxt) {
+       u_int elemSiz = 0;
+       cs->num_elements++;
+       siz += 8; /* tag, length */
+       switch(elem->tag) {
+       case SFLCOUNTERS_GENERIC:  elemSiz = sizeof(elem->counterBlock.generic); break;
+       case SFLCOUNTERS_ETHERNET: elemSiz = sizeof(elem->counterBlock.ethernet); break;
+       case SFLCOUNTERS_TOKENRING: elemSiz = sizeof(elem->counterBlock.tokenring); break;
+       case SFLCOUNTERS_VG: elemSiz = sizeof(elem->counterBlock.vg); break;
+       case SFLCOUNTERS_VLAN: elemSiz = sizeof(elem->counterBlock.vlan); break;
+       default:
+           sflError(receiver, "unexpected counters_tag");
+           return -1;
+           break;
+       }
+       // cache the element size, and accumulate it into the overall FlowSample size
+       elem->length = elemSiz;
+       siz += elemSiz;
+    }
+    return siz;
+}
+
+/*_________________----------------------------------__________________
+  _________________ sfl_receiver_writeCountersSample __________________
+  -----------------__________________________________------------------
+*/
+
+int sfl_receiver_writeCountersSample(SFLReceiver *receiver, SFL_COUNTERS_SAMPLE_TYPE *cs)
+{
+    int packedSize;
+    if(cs == NULL) return -1;
+    // if the sample pkt is full enough so that this sample might put
+    // it over the limit, then we should send it now.
+    if((packedSize = computeCountersSampleSize(receiver, cs)) == -1) return -1;
+  
+    // check in case this one sample alone is too big for the datagram
+    // in fact - if it is even half as big then we should ditch it. Very
+    // important to avoid overruning the packet buffer.
+    if(packedSize > (int)(receiver->sFlowRcvrMaximumDatagramSize / 2)) {
+       sflError(receiver, "counters sample too big for datagram");
+       return -1;
+    }
+  
+    if((receiver->sampleCollector.pktlen + packedSize) >= receiver->sFlowRcvrMaximumDatagramSize)
+       sendSample(receiver);
+  
+    receiver->sampleCollector.numSamples++;
+  
+#ifdef SFL_USE_32BIT_INDEX
+    putNet32(receiver, SFLCOUNTERS_SAMPLE_EXPANDED);
+#else
+    putNet32(receiver, SFLCOUNTERS_SAMPLE);
+#endif
+
+    putNet32(receiver, packedSize - 8); // tag and length not included
+    putNet32(receiver, cs->sequence_number);
+
+#ifdef SFL_USE_32BIT_INDEX
+    putNet32(receiver, cs->ds_class);
+    putNet32(receiver, cs->ds_index);
+#else
+    putNet32(receiver, cs->source_id);
+#endif
+
+    putNet32(receiver, cs->num_elements);
+  
+    {
+       SFLCounters_sample_element *elem = cs->elements;
+       for(; elem != NULL; elem = elem->nxt) {
+           
+           putNet32(receiver, elem->tag);
+           putNet32(receiver, elem->length); // length cached in computeCountersSampleSize()
+           
+           switch(elem->tag) {
+           case SFLCOUNTERS_GENERIC:
+               putGenericCounters(receiver, &(elem->counterBlock.generic));
+               break;
+           case SFLCOUNTERS_ETHERNET:
+               // all these counters are 32-bit
+               putNet32_run(receiver, &elem->counterBlock.ethernet, sizeof(elem->counterBlock.ethernet) / 4);
+               break;
+           case SFLCOUNTERS_TOKENRING:
+               // all these counters are 32-bit
+               putNet32_run(receiver, &elem->counterBlock.tokenring, sizeof(elem->counterBlock.tokenring) / 4);
+               break;
+           case SFLCOUNTERS_VG:
+               // mixed sizes
+               putNet32(receiver, elem->counterBlock.vg.dot12InHighPriorityFrames);
+               putNet64(receiver, elem->counterBlock.vg.dot12InHighPriorityOctets);
+               putNet32(receiver, elem->counterBlock.vg.dot12InNormPriorityFrames);
+               putNet64(receiver, elem->counterBlock.vg.dot12InNormPriorityOctets);
+               putNet32(receiver, elem->counterBlock.vg.dot12InIPMErrors);
+               putNet32(receiver, elem->counterBlock.vg.dot12InOversizeFrameErrors);
+               putNet32(receiver, elem->counterBlock.vg.dot12InDataErrors);
+               putNet32(receiver, elem->counterBlock.vg.dot12InNullAddressedFrames);
+               putNet32(receiver, elem->counterBlock.vg.dot12OutHighPriorityFrames);
+               putNet64(receiver, elem->counterBlock.vg.dot12OutHighPriorityOctets);
+               putNet32(receiver, elem->counterBlock.vg.dot12TransitionIntoTrainings);
+               putNet64(receiver, elem->counterBlock.vg.dot12HCInHighPriorityOctets);
+               putNet64(receiver, elem->counterBlock.vg.dot12HCInNormPriorityOctets);
+               putNet64(receiver, elem->counterBlock.vg.dot12HCOutHighPriorityOctets);
+               break;
+           case SFLCOUNTERS_VLAN:
+               // mixed sizes
+               putNet32(receiver, elem->counterBlock.vlan.vlan_id);
+               putNet64(receiver, elem->counterBlock.vlan.octets);
+               putNet32(receiver, elem->counterBlock.vlan.ucastPkts);
+               putNet32(receiver, elem->counterBlock.vlan.multicastPkts);
+               putNet32(receiver, elem->counterBlock.vlan.broadcastPkts);
+               putNet32(receiver, elem->counterBlock.vlan.discards);
+               break;
+           default:
+               sflError(receiver, "unexpected counters_tag");
+               return -1;
+               break;
+           }
+       }
+    }
+    // sanity check
+    assert(((u_char *)receiver->sampleCollector.datap
+           - (u_char *)receiver->sampleCollector.data
+           - receiver->sampleCollector.pktlen)  == (u_int32_t)packedSize);
+
+    // update the pktlen
+    receiver->sampleCollector.pktlen = (u_char *)receiver->sampleCollector.datap - (u_char *)receiver->sampleCollector.data;
+    return packedSize;
+}
+
+/*_________________---------------------------------__________________
+  _________________ sfl_receiver_samplePacketsSent  __________________
+  -----------------_________________________________------------------
+*/
+
+u_int32_t sfl_receiver_samplePacketsSent(SFLReceiver *receiver)
+{
+    return receiver->sampleCollector.packetSeqNo;
+}
+
+/*_________________---------------------------__________________
+  _________________     sendSample            __________________
+  -----------------___________________________------------------
+*/
+
+static void sendSample(SFLReceiver *receiver)
+{  
+    /* construct and send out the sample, then reset for the next one... */
+    /* first fill in the header with the latest values */
+    /* version, agent_address and sub_agent_id were pre-set. */
+    u_int32_t hdrIdx = (receiver->agent->myIP.type == SFLADDRESSTYPE_IP_V6) ? 7 : 4;
+    receiver->sampleCollector.data[hdrIdx++] = htonl(++receiver->sampleCollector.packetSeqNo); /* seq no */
+    receiver->sampleCollector.data[hdrIdx++] = htonl((receiver->agent->now - receiver->agent->bootTime) * 1000); /* uptime */
+    receiver->sampleCollector.data[hdrIdx++] = htonl(receiver->sampleCollector.numSamples); /* num samples */
+    /* send */
+    if(receiver->agent->sendFn) (*receiver->agent->sendFn)(receiver->agent->magic,
+                                                          receiver->agent,
+                                                          receiver,
+                                                          (u_char *)receiver->sampleCollector.data, 
+                                                          receiver->sampleCollector.pktlen);
+    else {
+#ifdef SFLOW_DO_SOCKET
+       /* send it myself */
+       if (receiver->sFlowRcvrAddress.type == SFLADDRESSTYPE_IP_V6) {
+           u_int32_t soclen = sizeof(struct sockaddr_in6);
+           int result = sendto(receiver->agent->receiverSocket6,
+                               receiver->sampleCollector.data,
+                               receiver->sampleCollector.pktlen,
+                               0,
+                               (struct sockaddr *)&receiver->receiver6,
+                               soclen);
+           if(result == -1 && errno != EINTR) sfl_agent_sysError(receiver->agent, "receiver", "IPv6 socket sendto error");
+           if(result == 0) sfl_agent_error(receiver->agent, "receiver", "IPv6 socket sendto returned 0");
+       }
+       else {
+           u_int32_t soclen = sizeof(struct sockaddr_in);
+           int result = sendto(receiver->agent->receiverSocket4,
+                               receiver->sampleCollector.data,
+                               receiver->sampleCollector.pktlen,
+                               0,
+                               (struct sockaddr *)&receiver->receiver4,
+                               soclen);
+           if(result == -1 && errno != EINTR) sfl_agent_sysError(receiver->agent, "receiver", "socket sendto error");
+           if(result == 0) sfl_agent_error(receiver->agent, "receiver", "socket sendto returned 0");
+       }
+#endif
+    }
+
+    /* reset for the next time */
+    resetSampleCollector(receiver);
+}
+
+/*_________________---------------------------__________________
+  _________________   resetSampleCollector    __________________
+  -----------------___________________________------------------
+*/
+
+static void resetSampleCollector(SFLReceiver *receiver)
+{
+    receiver->sampleCollector.pktlen = 0;
+    receiver->sampleCollector.numSamples = 0;
+    /* point the datap to just after the header */
+    receiver->sampleCollector.datap = (receiver->agent->myIP.type == SFLADDRESSTYPE_IP_V6) ?
+       (receiver->sampleCollector.data + 10) :  (receiver->sampleCollector.data + 7);
+
+    receiver->sampleCollector.pktlen = (u_char *)receiver->sampleCollector.datap - (u_char *)receiver->sampleCollector.data;
+}
+
+/*_________________---------------------------__________________
+  _________________         sflError          __________________
+  -----------------___________________________------------------
+*/
+
+static void sflError(SFLReceiver *receiver, char *msg)
+{
+    sfl_agent_error(receiver->agent, "receiver", msg);
+    resetSampleCollector(receiver);
+}
diff --git a/lib/sflow_sampler.c b/lib/sflow_sampler.c
new file mode 100644 (file)
index 0000000..c2b4556
--- /dev/null
@@ -0,0 +1,186 @@
+/* Copyright (c) 2002-2009 InMon Corp. Licensed under the terms of the InMon sFlow licence: */
+/* http://www.inmon.com/technology/sflowlicense.txt */
+
+#include "sflow_api.h"
+
+
+/*_________________--------------------------__________________
+  _________________   sfl_sampler_init       __________________
+  -----------------__________________________------------------
+*/
+
+void sfl_sampler_init(SFLSampler *sampler, SFLAgent *agent, SFLDataSource_instance *pdsi)
+{
+    /* copy the dsi in case it points to sampler->dsi, which we are about to clear.
+       (Thanks to Jagjit Choudray of Force 10 Networks for pointing out this bug) */
+    SFLDataSource_instance dsi = *pdsi;
+
+    /* preserve the *nxt pointer too, in case we are resetting this poller and it is
+       already part of the agent's linked list (thanks to Matt Woodly for pointing this out,
+       and to Andy Kitchingman for pointing out that it applies to the hash_nxt ptr too) */
+    SFLSampler *nxtPtr = sampler->nxt;
+    SFLSampler *hashPtr = sampler->hash_nxt;
+  
+    /* clear everything */
+    memset(sampler, 0, sizeof(*sampler));
+  
+    /* restore the linked list and hash-table ptr */
+    sampler->nxt = nxtPtr;
+    sampler->hash_nxt = hashPtr;
+  
+    /* now copy in the parameters */
+    sampler->agent = agent;
+    sampler->dsi = dsi;
+  
+    /* set defaults */
+    sampler->sFlowFsMaximumHeaderSize = SFL_DEFAULT_HEADER_SIZE;
+    sampler->sFlowFsPacketSamplingRate = SFL_DEFAULT_SAMPLING_RATE;
+}
+
+/*_________________--------------------------__________________
+  _________________       reset              __________________
+  -----------------__________________________------------------
+*/
+
+static void reset(SFLSampler *sampler)
+{
+    SFLDataSource_instance dsi = sampler->dsi;
+    sfl_sampler_init(sampler, sampler->agent, &dsi);
+}
+
+/*_________________---------------------------__________________
+  _________________      MIB access           __________________
+  -----------------___________________________------------------
+*/
+u_int32_t sfl_sampler_get_sFlowFsReceiver(SFLSampler *sampler) {
+    return sampler->sFlowFsReceiver;
+}
+void sfl_sampler_set_sFlowFsReceiver(SFLSampler *sampler, u_int32_t sFlowFsReceiver) {
+    sampler->sFlowFsReceiver = sFlowFsReceiver;
+    if(sFlowFsReceiver == 0) reset(sampler);
+    else {
+       /* retrieve and cache a direct pointer to my receiver */
+       sampler->myReceiver = sfl_agent_getReceiver(sampler->agent, sampler->sFlowFsReceiver);
+    }
+}
+u_int32_t sfl_sampler_get_sFlowFsPacketSamplingRate(SFLSampler *sampler) {
+    return sampler->sFlowFsPacketSamplingRate;
+}
+void sfl_sampler_set_sFlowFsPacketSamplingRate(SFLSampler *sampler, u_int32_t sFlowFsPacketSamplingRate) {
+    sampler->sFlowFsPacketSamplingRate = sFlowFsPacketSamplingRate;
+}
+u_int32_t sfl_sampler_get_sFlowFsMaximumHeaderSize(SFLSampler *sampler) {
+    return sampler->sFlowFsMaximumHeaderSize;
+}
+void sfl_sampler_set_sFlowFsMaximumHeaderSize(SFLSampler *sampler, u_int32_t sFlowFsMaximumHeaderSize) {
+    sampler->sFlowFsMaximumHeaderSize = sFlowFsMaximumHeaderSize;
+}
+
+/* call this to set a maximum samples-per-second threshold. If the sampler reaches this
+   threshold it will automatically back off the sampling rate. A value of 0 disables the
+   mechanism */
+void sfl_sampler_set_backoffThreshold(SFLSampler *sampler, u_int32_t samplesPerSecond) {
+    sampler->backoffThreshold = samplesPerSecond;
+}
+u_int32_t sfl_sampler_get_backoffThreshold(SFLSampler *sampler) {
+    return sampler->backoffThreshold;
+}
+u_int32_t sfl_sampler_get_samplesLastTick(SFLSampler *sampler) {
+    return sampler->samplesLastTick;
+}
+
+/*_________________---------------------------------__________________
+  _________________   sequence number reset         __________________
+  -----------------_________________________________------------------
+  Used by the agent to indicate a samplePool discontinuity
+  so that the sflow collector will know to ignore the next delta.
+*/
+void sfl_sampler_resetFlowSeqNo(SFLSampler *sampler) { sampler->flowSampleSeqNo = 0; }
+
+
+/*_________________---------------------------__________________
+  _________________    sfl_sampler_tick       __________________
+  -----------------___________________________------------------
+*/
+
+void sfl_sampler_tick(SFLSampler *sampler, time_t now)
+{
+    if(sampler->backoffThreshold && sampler->samplesThisTick > sampler->backoffThreshold) {
+       /* automatic backoff.  If using hardware sampling then this is where you have to
+        * call out to change the sampling rate and make sure that any other registers/variables
+        * that hold this value are updated.
+        */
+       sampler->sFlowFsPacketSamplingRate *= 2;
+    }
+    sampler->samplesLastTick = sampler->samplesThisTick;
+    sampler->samplesThisTick = 0;
+}
+
+
+
+/*_________________------------------------------__________________
+  _________________ sfl_sampler_writeFlowSample  __________________
+  -----------------______________________________------------------
+*/
+
+void sfl_sampler_writeFlowSample(SFLSampler *sampler, SFL_FLOW_SAMPLE_TYPE *fs)
+{
+    if(fs == NULL) return;
+    sampler->samplesThisTick++;
+    /* increment the sequence number */
+    fs->sequence_number = ++sampler->flowSampleSeqNo;
+    /* copy the other header fields in */
+#ifdef SFL_USE_32BIT_INDEX
+    fs->ds_class = SFL_DS_CLASS(sampler->dsi);
+    fs->ds_index = SFL_DS_INDEX(sampler->dsi);
+#else
+    fs->source_id = SFL_DS_DATASOURCE(sampler->dsi);
+#endif
+    /* the sampling rate may have been set already. */
+    if(fs->sampling_rate == 0) fs->sampling_rate = sampler->sFlowFsPacketSamplingRate;
+    /* the samplePool may be maintained upstream too. */
+    if( fs->sample_pool == 0) fs->sample_pool = sampler->samplePool;
+    /* sent to my receiver */
+    if(sampler->myReceiver) sfl_receiver_writeFlowSample(sampler->myReceiver, fs);
+}
+
+#ifdef SFLOW_SOFTWARE_SAMPLING
+
+/* ================== software sampling ========================*/
+
+/*_________________---------------------------__________________
+  _________________     nextRandomSkip        __________________
+  -----------------___________________________------------------
+*/
+
+inline static u_int32_t nextRandomSkip(u_int32_t mean)
+{
+    if(mean == 0 || mean == 1) return 1;
+    return ((random() % ((2 * mean) - 1)) + 1);
+} 
+
+/*_________________---------------------------__________________
+  _________________  sfl_sampler_takeSample   __________________
+  -----------------___________________________------------------
+*/
+
+int sfl_sampler_takeSample(SFLSampler *sampler)
+{
+    if(sampler->skip == 0) {
+       /* first time - seed the random number generator */
+       srandom(SFL_DS_INDEX(sampler->dsi));
+       sampler->skip = nextRandomSkip(sampler->sFlowFsPacketSamplingRate);
+    }
+
+    /* increment the samplePool */
+    sampler->samplePool++;
+
+    if(--sampler->skip == 0) {
+       /* reached zero. Set the next skip and return true. */
+       sampler->skip = nextRandomSkip(sampler->sFlowFsPacketSamplingRate);
+       return 1;
+    }
+    return 0;
+}
+
+#endif /* SFLOW_SOFTWARE_SAMPLING */
index f73f2d6..205b82f 100644 (file)
@@ -31,7 +31,9 @@
 
 #include <config.h>
 #include "sha1.h"
+#include <ctype.h>
 #include <string.h>
+#include "util.h"
 
 /* a bit faster & bigger, if defined */
 #define UNROLL_LOOPS
@@ -279,3 +281,32 @@ sha1_bytes(const void *data, size_t n, uint8_t digest[SHA1_DIGEST_SIZE])
     sha1_update(&ctx, data, n);
     sha1_final(&ctx, digest);
 }
+
+void
+sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+            char hex[SHA1_HEX_DIGEST_LEN + 1])
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        *hex++ = "0123456789abcdef"[digest[i] >> 4];
+        *hex++ = "0123456789abcdef"[digest[i] & 15];
+    }
+    *hex = '\0';
+}
+
+bool
+sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex)
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        if (!isxdigit(hex[0]) || !isxdigit(hex[1])) {
+            return false;
+        }
+        digest[i] = (hexit_value(hex[0]) << 4) | hexit_value(hex[1]);
+        hex += 2;
+    }
+    return true;
+}
+
index 75d3533..9a37277 100644 (file)
 #ifndef SHA1_H
 #define SHA1_H
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-/* Size of the SHA1 digest. */
-#define SHA1_DIGEST_SIZE 20
+#define SHA1_DIGEST_SIZE 20     /* Size of the SHA1 digest. */
+#define SHA1_HEX_DIGEST_LEN 40  /* Length of SHA1 digest as hex in ASCII. */
 
 /* SHA1 context structure. */
 struct sha1_ctx {
@@ -48,4 +49,18 @@ void sha1_update(struct sha1_ctx *, const void *, size_t);
 void sha1_final(struct sha1_ctx *, uint8_t digest[SHA1_DIGEST_SIZE]);
 void sha1_bytes(const void *, size_t, uint8_t digest[SHA1_DIGEST_SIZE]);
 
+#define SHA1_FMT \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+#define SHA1_ARGS(DIGEST) \
+    ((DIGEST)[0]), ((DIGEST)[1]), ((DIGEST)[2]), ((DIGEST)[3]), \
+    ((DIGEST)[4]), ((DIGEST)[5]), ((DIGEST)[6]), ((DIGEST)[7]), \
+    ((DIGEST)[8]), ((DIGEST)[9]), ((DIGEST)[10]), ((DIGEST)[11]), \
+    ((DIGEST)[12]), ((DIGEST)[13]), ((DIGEST)[14]), ((DIGEST)[15]), \
+    ((DIGEST)[16]), ((DIGEST)[17]), ((DIGEST)[18]), ((DIGEST)[19])
+
+void sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+                 char hex[SHA1_HEX_DIGEST_LEN + 1]);
+bool sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex);
+
 #endif  /* sha1.h */
index 520e189..a5bfecf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,27 +40,63 @@ shash_destroy(struct shash *sh)
     }
 }
 
+void
+shash_swap(struct shash *a, struct shash *b)
+{
+    hmap_swap(&a->map, &b->map);
+}
+
+void
+shash_moved(struct shash *sh)
+{
+    hmap_moved(&sh->map);
+}
+
 void
 shash_clear(struct shash *sh)
 {
     struct shash_node *node, *next;
 
-    HMAP_FOR_EACH_SAFE (node, next, struct shash_node, node, &sh->map) {
+    SHASH_FOR_EACH_SAFE (node, next, sh) {
         hmap_remove(&sh->map, &node->node);
         free(node->name);
         free(node);
     }
 }
 
-/* It is the caller's responsible to avoid duplicate names, if that is
+bool
+shash_is_empty(const struct shash *shash)
+{
+    return hmap_is_empty(&shash->map);
+}
+
+size_t
+shash_count(const struct shash *shash)
+{
+    return hmap_count(&shash->map);
+}
+
+/* It is the caller's responsibility to avoid duplicate names, if that is
  * desirable. */
-void
-shash_add(struct shash *sh, const char *name, void *data)
+struct shash_node *
+shash_add(struct shash *sh, const char *name, const void *data)
 {
     struct shash_node *node = xmalloc(sizeof *node);
     node->name = xstrdup(name);
-    node->data = data;
+    node->data = (void *) data;
     hmap_insert(&sh->map, &node->node, hash_name(name));
+    return node;
+}
+
+bool
+shash_add_once(struct shash *sh, const char *name, const void *data)
+{
+    if (!shash_find(sh, name)) {
+        shash_add(sh, name, data);
+        return true;
+    } else {
+        return false;
+    }
 }
 
 void
@@ -92,3 +128,73 @@ shash_find_data(const struct shash *sh, const char *name)
     struct shash_node *node = shash_find(sh, name);
     return node ? node->data : NULL;
 }
+
+void *
+shash_find_and_delete(struct shash *sh, const char *name)
+{
+    struct shash_node *node = shash_find(sh, name);
+    if (node) {
+        void *data = node->data;
+        shash_delete(sh, node);
+        return data;
+    } else {
+        return NULL;
+    }
+}
+
+struct shash_node *
+shash_first(const struct shash *shash)
+{
+    struct hmap_node *node = hmap_first(&shash->map);
+    return node ? CONTAINER_OF(node, struct shash_node, node) : NULL;
+}
+
+static int
+compare_nodes_by_name(const void *a_, const void *b_)
+{
+    const struct shash_node *const *a = a_;
+    const struct shash_node *const *b = b_;
+    return strcmp((*a)->name, (*b)->name);
+}
+
+const struct shash_node **
+shash_sort(const struct shash *sh)
+{
+    if (shash_is_empty(sh)) {
+        return NULL;
+    } else {
+        const struct shash_node **nodes;
+        struct shash_node *node;
+        size_t i, n;
+
+        n = shash_count(sh);
+        nodes = xmalloc(n * sizeof *nodes);
+        i = 0;
+        SHASH_FOR_EACH (node, sh) {
+            nodes[i++] = node;
+        }
+        assert(i == n);
+
+        qsort(nodes, n, sizeof *nodes, compare_nodes_by_name);
+
+        return nodes;
+    }
+}
+
+/* Returns true if 'a' and 'b' contain the same keys (regardless of their
+ * values), false otherwise. */
+bool
+shash_equal_keys(const struct shash *a, const struct shash *b)
+{
+    struct shash_node *node;
+
+    if (hmap_count(&a->map) != hmap_count(&b->map)) {
+        return false;
+    }
+    SHASH_FOR_EACH (node, a) {
+        if (!shash_find(b, node->name)) {
+            return false;
+        }
+    }
+    return true;
+}
index 9201bb5..3a6e277 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "hmap.h"
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 struct shash_node {
     struct hmap_node node;
     char *name;
@@ -31,12 +35,32 @@ struct shash {
 
 #define SHASH_INITIALIZER(SHASH) { HMAP_INITIALIZER(&(SHASH)->map) }
 
+#define SHASH_FOR_EACH(SHASH_NODE, SHASH)                               \
+    HMAP_FOR_EACH (SHASH_NODE, struct shash_node, node, &(SHASH)->map)
+
+#define SHASH_FOR_EACH_SAFE(SHASH_NODE, NEXT, SHASH)                \
+    HMAP_FOR_EACH_SAFE (SHASH_NODE, NEXT, struct shash_node, node,  \
+                        &(SHASH)->map)
+
 void shash_init(struct shash *);
 void shash_destroy(struct shash *);
+void shash_swap(struct shash *, struct shash *);
+void shash_moved(struct shash *);
 void shash_clear(struct shash *);
-void shash_add(struct shash *, const char *, void *);
+bool shash_is_empty(const struct shash *);
+size_t shash_count(const struct shash *);
+struct shash_node *shash_add(struct shash *, const char *, const void *);
+bool shash_add_once(struct shash *, const char *, const void *);
 void shash_delete(struct shash *, struct shash_node *);
 struct shash_node *shash_find(const struct shash *, const char *);
 void *shash_find_data(const struct shash *, const char *);
+void *shash_find_and_delete(struct shash *, const char *);
+struct shash_node *shash_first(const struct shash *);
+const struct shash_node **shash_sort(const struct shash *);
+bool shash_equal_keys(const struct shash *, const struct shash *);
+
+#ifdef  __cplusplus
+}
+#endif
 
 #endif /* shash.h */
index d8e51b6..eabbcc3 100644 (file)
@@ -98,7 +98,7 @@ bool
 signal_poll(struct signal *s)
 {
     char buf[_POSIX_PIPE_BUF];
-    read(fds[0], buf, sizeof buf);
+    ignore(read(fds[0], buf, sizeof buf));
     if (signaled[s->signr]) {
         signaled[s->signr] = 0;
         return true;
@@ -122,7 +122,7 @@ static void
 signal_handler(int signr)
 {
     if (signr >= 1 && signr < N_SIGNALS) {
-        write(fds[1], "", 1);
+        ignore(write(fds[1], "", 1));
         signaled[signr] = true;
     }
 }
index d13255f..912c47e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
 #include <poll.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/resource.h>
 #include <sys/un.h>
@@ -203,7 +204,7 @@ make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len)
  *
  * Returns the socket's fd if successful, otherwise a negative errno value. */
 int
-make_unix_socket(int style, bool nonblock, bool passcred UNUSED,
+make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED,
                  const char *bind_path, const char *connect_path)
 {
     int error;
@@ -248,6 +249,7 @@ make_unix_socket(int style, bool nonblock, bool passcred UNUSED,
         make_sockaddr_un(connect_path, &un, &un_len);
         if (connect(fd, (struct sockaddr*) &un, un_len)
             && errno != EINPROGRESS) {
+            printf("connect failed with %s\n", strerror(errno));
             goto error;
         }
     }
@@ -264,10 +266,10 @@ make_unix_socket(int style, bool nonblock, bool passcred UNUSED,
     return fd;
 
 error:
+    error = errno == EAGAIN ? EPROTO : errno;
     if (bind_path) {
         fatal_signal_remove_file_to_unlink(bind_path);
     }
-    error = errno;
     close(fd);
     return -error;
 }
@@ -290,6 +292,211 @@ guess_netmask(uint32_t ip)
             : htonl(0));                          /* ??? */
 }
 
+/* Opens a non-blocking IPv4 socket of the specified 'style' and connects to
+ * 'target', which should be a string in the format "<host>[:<port>]".  <host>
+ * is required.  If 'default_port' is nonzero then <port> is optional and
+ * defaults to 'default_port'.
+ *
+ * 'style' should be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP).
+ *
+ * On success, returns 0 (indicating connection complete) or EAGAIN (indicating
+ * connection in progress), in which case the new file descriptor is stored
+ * into '*fdp'.  On failure, returns a positive errno value other than EAGAIN
+ * and stores -1 into '*fdp'.
+ *
+ * If 'sinp' is non-null, then on success the target address is stored into
+ * '*sinp'. */
+int
+inet_open_active(int style, const char *target_, uint16_t default_port,
+                 struct sockaddr_in *sinp, int *fdp)
+{
+    char *target = xstrdup(target_);
+    char *save_ptr = NULL;
+    const char *host_name;
+    const char *port_string;
+    struct sockaddr_in sin;
+    int fd = -1;
+    int error;
+
+    /* Defaults. */
+    memset(&sin, 0, sizeof sin);
+    sin.sin_family = AF_INET;
+    sin.sin_port = htons(default_port);
+
+    /* Tokenize. */
+    host_name = strtok_r(target, ":", &save_ptr);
+    port_string = strtok_r(NULL, ":", &save_ptr);
+    if (!host_name) {
+        ovs_error(0, "%s: bad peer name format", target_);
+        error = EAFNOSUPPORT;
+        goto exit;
+    }
+
+    /* Look up IP, port. */
+    error = lookup_ip(host_name, &sin.sin_addr);
+    if (error) {
+        goto exit;
+    }
+    if (port_string && atoi(port_string)) {
+        sin.sin_port = htons(atoi(port_string));
+    } else if (!default_port) {
+        VLOG_ERR("%s: port number must be specified", target_);
+        error = EAFNOSUPPORT;
+        goto exit;
+    }
+
+    /* Create non-blocking socket. */
+    fd = socket(AF_INET, style, 0);
+    if (fd < 0) {
+        VLOG_ERR("%s: socket: %s", target_, strerror(errno));
+        error = errno;
+        goto exit;
+    }
+    error = set_nonblocking(fd);
+    if (error) {
+        goto exit_close;
+    }
+
+    /* Connect. */
+    error = connect(fd, (struct sockaddr *) &sin, sizeof sin) == 0 ? 0 : errno;
+    if (error == EINPROGRESS) {
+        error = EAGAIN;
+    } else if (error && error != EAGAIN) {
+        goto exit_close;
+    }
+
+    /* Success: error is 0 or EAGAIN. */
+    goto exit;
+
+exit_close:
+    close(fd);
+exit:
+    if (!error || error == EAGAIN) {
+        if (sinp) {
+            *sinp = sin;
+        }
+        *fdp = fd;
+    } else {
+        *fdp = -1;
+    }
+    free(target);
+    return error;
+}
+
+/* Opens a non-blocking IPv4 socket of the specified 'style', binds to
+ * 'target', and listens for incoming connections.  'target' should be a string
+ * in the format "[<port>][:<ip>]":
+ *
+ *      - If 'default_port' is -1, then <port> is required.  Otherwise, if
+ *        <port> is omitted, then 'default_port' is used instead.
+ *
+ *      - If <port> (or 'default_port', if used) is 0, then no port is bound
+ *        and the TCP/IP stack will select a port.
+ *
+ *      - If <ip> is omitted then the IP address is wildcarded.
+ *
+ * 'style' should be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP).
+ *
+ * For TCP, the socket will have SO_REUSEADDR turned on.
+ *
+ * On success, returns a non-negative file descriptor.  On failure, returns a
+ * negative errno value.
+ *
+ * If 'sinp' is non-null, then on success the bound address is stored into
+ * '*sinp'. */
+int
+inet_open_passive(int style, const char *target_, int default_port,
+                  struct sockaddr_in *sinp)
+{
+    char *target = xstrdup(target_);
+    char *string_ptr = target;
+    struct sockaddr_in sin;
+    const char *host_name;
+    const char *port_string;
+    int fd, error, port;
+    unsigned int yes  = 1;
+
+    /* Address defaults. */
+    memset(&sin, 0, sizeof sin);
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+    sin.sin_port = htons(default_port);
+
+    /* Parse optional port number. */
+    port_string = strsep(&string_ptr, ":");
+    if (port_string && str_to_int(port_string, 10, &port)) {
+        sin.sin_port = htons(port);
+    } else if (default_port < 0) {
+        VLOG_ERR("%s: port number must be specified", target_);
+        error = EAFNOSUPPORT;
+        goto exit;
+    }
+
+    /* Parse optional bind IP. */
+    host_name = strsep(&string_ptr, ":");
+    if (host_name && host_name[0]) {
+        error = lookup_ip(host_name, &sin.sin_addr);
+        if (error) {
+            goto exit;
+        }
+    }
+
+    /* Create non-blocking socket, set SO_REUSEADDR. */
+    fd = socket(AF_INET, style, 0);
+    if (fd < 0) {
+        error = errno;
+        VLOG_ERR("%s: socket: %s", target_, strerror(error));
+        goto exit;
+    }
+    error = set_nonblocking(fd);
+    if (error) {
+        goto exit_close;
+    }
+    if (style == SOCK_STREAM
+        && setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) {
+        error = errno;
+        VLOG_ERR("%s: setsockopt(SO_REUSEADDR): %s", target_, strerror(error));
+        goto exit_close;
+    }
+
+    /* Bind. */
+    if (bind(fd, (struct sockaddr *) &sin, sizeof sin) < 0) {
+        error = errno;
+        VLOG_ERR("%s: bind: %s", target_, strerror(error));
+        goto exit_close;
+    }
+
+    /* Listen. */
+    if (listen(fd, 10) < 0) {
+        error = errno;
+        VLOG_ERR("%s: listen: %s", target_, strerror(error));
+        goto exit_close;
+    }
+
+    if (sinp) {
+        socklen_t sin_len = sizeof sin;
+        if (getsockname(fd, (struct sockaddr *) &sin, &sin_len) < 0){
+            error = errno;
+            VLOG_ERR("%s: getsockname: %s", target_, strerror(error));
+            goto exit_close;
+        }
+        if (sin.sin_family != AF_INET || sin_len != sizeof sin) {
+            VLOG_ERR("%s: getsockname: invalid socket name", target_);
+            goto exit_close;
+        }
+        *sinp = sin;
+    }
+
+    error = 0;
+    goto exit;
+
+exit_close:
+    close(fd);
+exit:
+    free(target);
+    return error ? -error : fd;
+}
+
 /* Returns a readable and writable fd for /dev/null, if successful, otherwise
  * a negative errno value.  The caller must not close the returned fd (because
  * the same fd will be handed out to subsequent callers). */
@@ -350,3 +557,34 @@ write_fully(int fd, const void *p_, size_t size, size_t *bytes_written)
     }
     return 0;
 }
+
+/* Given file name 'file_name', fsyncs the directory in which it is contained.
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+fsync_parent_dir(const char *file_name)
+{
+    int error = 0;
+    char *dir;
+    int fd;
+
+    dir = dir_name(file_name);
+    fd = open(dir, O_RDONLY);
+    if (fd >= 0) {
+        if (fsync(fd)) {
+            if (errno == EINVAL || errno == EROFS) {
+                /* This directory does not support synchronization.  Not
+                 * really an error. */
+            } else {
+                error = errno;
+                VLOG_ERR("%s: fsync failed (%s)", dir, strerror(error));
+            }
+        }
+        close(fd);
+    } else {
+        error = errno;
+        VLOG_ERR("%s: open failed (%s)", dir, strerror(error));
+    }
+    free(dir);
+
+    return error;
+}
index 3ba2c47..e489926 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,7 +34,14 @@ int get_unix_name_len(socklen_t sun_len);
 uint32_t guess_netmask(uint32_t ip);
 int get_null_fd(void);
 
+int inet_open_active(int style, const char *target, uint16_t default_port,
+                    struct sockaddr_in *sinp, int *fdp);
+int inet_open_passive(int style, const char *target, int default_port,
+                      struct sockaddr_in *sinp);
+
 int read_fully(int fd, void *, size_t, size_t *bytes_read);
 int write_fully(int fd, const void *, size_t, size_t *bytes_written);
 
+int fsync_parent_dir(const char *file_name);
+
 #endif /* socket-util.h */
diff --git a/lib/sort.c b/lib/sort.c
new file mode 100644 (file)
index 0000000..017b0a9
--- /dev/null
@@ -0,0 +1,70 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "sort.h"
+
+#include "random.h"
+
+static size_t
+partition(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t x = r - 1;
+    size_t i, j;
+
+    i = p;
+    for (j = p; j < x; j++) {
+        if (compare(j, x, aux) <= 0) {
+            swap(i++, j, aux);
+        }
+    }
+    swap(i, x, aux);
+    return i;
+}
+
+static void
+quicksort(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t i, q;
+
+    if (r - p < 2) {
+        return;
+    }
+
+    i = random_range(r - p) + p;
+    if (r - 1 != i) {
+        swap(r - 1, i, aux);
+    }
+
+    q = partition(p, r, compare, swap, aux);
+    quicksort(p, q, compare, swap, aux);
+    quicksort(q, r, compare, swap, aux);
+}
+
+void
+sort(size_t count,
+     int (*compare)(size_t a, size_t b, void *aux),
+     void (*swap)(size_t a, size_t b, void *aux),
+     void *aux)
+{
+    quicksort(0, count, compare, swap, aux);
+}
similarity index 72%
rename from vswitchd/port.h
rename to lib/sort.h
index 2055912..c952f44 100644 (file)
  * limitations under the License.
  */
 
-#ifndef VSWITCHD_PORT_H
-#define VSWITCHD_PORT_H 1
+#ifndef SORT_H
+#define SORT_H 1
 
-void port_init(void);
-void port_reconfigure(void);
+#include <stddef.h>
 
-#endif /* port.h */
+void sort(size_t count,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux);
+
+#endif /* sort.h */
diff --git a/lib/ssl-bootstrap-syn.man b/lib/ssl-bootstrap-syn.man
new file mode 100644 (file)
index 0000000..9425321
--- /dev/null
@@ -0,0 +1,2 @@
+.br
+[\fB\-\-bootstrap\-ca\-cert=\fIcacert.pem]
diff --git a/lib/ssl-bootstrap.man b/lib/ssl-bootstrap.man
new file mode 100644 (file)
index 0000000..68e41c0
--- /dev/null
@@ -0,0 +1,22 @@
+.IP "\fB\-\-bootstrap\-ca\-cert=\fIcacert.pem\fR"
+When \fIcacert.pem\fR exists, this option has the same effect as
+\fB\-C\fR or \fB\-\-ca\-cert\fR.  If it does not exist, then
+\fB\*(PN\fR will attempt to obtain the CA certificate from the
+SSL peer on its first SSL connection and save it to the named PEM
+file.  If it is successful, it will immediately drop the connection
+and reconnect, and from then on all SSL connections must be
+authenticated by a certificate signed by the CA certificate thus
+obtained.
+.IP
+\fBThis option exposes the SSL connection to a man-in-the-middle
+attack obtaining the initial CA certificate\fR, but it may be useful
+for bootstrapping.
+.IP
+This option is only useful if the SSL peer sends its CA certificate as
+part of the SSL certificate chain.  The SSL protocol does not require
+the server to send the CA certificate, but
+\fBovs\-controller\fR(8) can be configured to do so with the
+\fB\-\-peer\-ca\-cert\fR option.
+.IP
+This option is mutually exclusive with \fB-C\fR and
+\fB\-\-ca\-cert\fR.
diff --git a/lib/ssl-peer-ca-cert.man b/lib/ssl-peer-ca-cert.man
new file mode 100644 (file)
index 0000000..183f93e
--- /dev/null
@@ -0,0 +1,12 @@
+.IP "\fB--peer-ca-cert=\fIpeer-cacert.pem\fR"
+Specifies a PEM file that contains one or more additional certificates
+to send to SSL peers.  \fIpeer-cacert.pem\fR should be the CA
+certificate used to sign the \fB\*(PN\fR own certificate (the
+certificate specified on \fB-c\fR or \fB--certificate\fR).
+.IP
+This option is not useful in normal operation, because the SSL peer
+must already have the CA certificate for the peer to have any
+confidence in \fB\*(PN\fR's identity.  However, this option allows a
+newly installed switch to obtain the peer CA certificate on first boot
+using, e.g., the \fB\-\-bootstrap-ca-cert\fR option to
+\fBovs\-openflowd\fR(8).
diff --git a/lib/ssl-syn.man b/lib/ssl-syn.man
new file mode 100644 (file)
index 0000000..4914841
--- /dev/null
@@ -0,0 +1,6 @@
+.IP "Public key infrastructure options:"
+[\fB\-\-private\-key=\fIprivkey.pem\fR]
+.br
+[\fB\-\-certificate=\fIcert.pem\fR]
+.br
+[\fB\-\-ca\-cert=\fIswitch\-cacert.pem\fR]
diff --git a/lib/ssl.man b/lib/ssl.man
new file mode 100644 (file)
index 0000000..12c497c
--- /dev/null
@@ -0,0 +1,19 @@
+.IP "\fB\-p\fR \fIprivkey.pem\fR"
+.IQ "\fB\-\-private\-key=\fIprivkey.pem\fR"
+Specifies a PEM file containing the private key used as \fB\*(PN\fR's
+identity for outgoing SSL connections.
+.
+.IP "\fB\-c\fR \fIcert.pem\fR"
+.IQ "\fB\-\-certificate=\fIcert.pem\fR"
+Specifies a PEM file containing a certificate that certifies the
+private key specified on \fB\-p\fR or \fB\-\-private\-key\fR to be
+trustworthy.  The certificate must be signed by the certificate
+authority (CA) that the peer in SSL connections will use to verify it.
+.
+.IP "\fB\-C\fR \fIcacert.pem\fR"
+.IQ "\fB\-\-ca\-cert=\fIcacert.pem\fR"
+Specifies a PEM file containing the CA certificate that \fB\*(PN\fR
+should use to verify certificates presented to it by SSL peers.  (This
+may be the same certificate that SSL peers use to verify the
+certificate specified on \fB\-c\fR or \fB\-\-certificate\fR, or it may
+be a different one, depending on the PKI design in use.)
index 87230bd..38885c0 100644 (file)
--- a/lib/stp.c
+++ b/lib/stp.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
  * applies to all modifications. */
 
 #include "stp.h"
+#include <sys/types.h>
+#include <netinet/in.h>
 #include <arpa/inet.h>
 #include <assert.h>
 #include <inttypes.h>
@@ -214,7 +216,7 @@ stp_create(const char *name, stp_identifier bridge_id,
     struct stp *stp;
     struct stp_port *p;
 
-    stp = xcalloc(1, sizeof *stp);
+    stp = xzalloc(sizeof *stp);
     stp->name = xstrdup(name);
     stp->bridge_id = bridge_id;
     if (!(stp->bridge_id >> 48)) {
@@ -256,7 +258,10 @@ stp_create(const char *name, stp_identifier bridge_id,
 void
 stp_destroy(struct stp *stp)
 {
-    free(stp);
+    if (stp) {
+        free(stp->name);
+        free(stp);
+    }
 }
 
 /* Runs 'stp' given that 'ms' milliseconds have passed. */
diff --git a/lib/stream-fd.c b/lib/stream-fd.c
new file mode 100644 (file)
index 0000000..94c8434
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-fd.h"
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "fatal-signal.h"
+#include "leak-checker.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_fd
+
+/* Active file descriptor stream. */
+
+struct stream_fd
+{
+    struct stream stream;
+    int fd;
+    char *unlink_path;
+};
+
+static struct stream_class stream_fd_class;
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
+
+static void maybe_unlink_and_free(char *path);
+
+/* Creates a new stream named 'name' that will send and receive data on 'fd'
+ * and stores a pointer to the stream in '*streamp'.  Initial connection status
+ * 'connect_status' is interpreted as described for stream_init().
+ *
+ * When '*streamp' is closed, then 'unlink_path' (if nonnull) will be passed to
+ * fatal_signal_unlink_file_now() and then freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_stream(const char *name, int fd, int connect_status,
+              char *unlink_path, struct stream **streamp)
+{
+    struct stream_fd *s;
+
+    s = xmalloc(sizeof *s);
+    stream_init(&s->stream, &stream_fd_class, connect_status, name);
+    s->fd = fd;
+    s->unlink_path = unlink_path;
+    *streamp = &s->stream;
+    return 0;
+}
+
+static struct stream_fd *
+stream_fd_cast(struct stream *stream)
+{
+    stream_assert_class(stream, &stream_fd_class);
+    return CONTAINER_OF(stream, struct stream_fd, stream);
+}
+
+static void
+fd_close(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    close(s->fd);
+    maybe_unlink_and_free(s->unlink_path);
+    free(s);
+}
+
+static int
+fd_connect(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    return check_connection_completion(s->fd);
+}
+
+static ssize_t
+fd_recv(struct stream *stream, void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = read(s->fd, buffer, n);
+    return retval >= 0 ? retval : -errno;
+}
+
+static ssize_t
+fd_send(struct stream *stream, const void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = write(s->fd, buffer, n);
+    return (retval > 0 ? retval
+            : retval == 0 ? -EAGAIN
+            : -errno);
+}
+
+static void
+fd_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    switch (wait) {
+    case STREAM_CONNECT:
+    case STREAM_SEND:
+        poll_fd_wait(s->fd, POLLOUT);
+        break;
+
+    case STREAM_RECV:
+        poll_fd_wait(s->fd, POLLIN);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct stream_class stream_fd_class = {
+    "fd",                       /* name */
+    NULL,                       /* open */
+    fd_close,                   /* close */
+    fd_connect,                 /* connect */
+    fd_recv,                    /* recv */
+    fd_send,                    /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
+    fd_wait,                    /* wait */
+};
+\f
+/* Passive file descriptor stream. */
+
+struct fd_pstream
+{
+    struct pstream pstream;
+    int fd;
+    int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len,
+                     struct stream **);
+    char *unlink_path;
+};
+
+static struct pstream_class fd_pstream_class;
+
+static struct fd_pstream *
+fd_pstream_cast(struct pstream *pstream)
+{
+    pstream_assert_class(pstream, &fd_pstream_class);
+    return CONTAINER_OF(pstream, struct fd_pstream, pstream);
+}
+
+/* Creates a new pstream named 'name' that will accept new socket connections
+ * on 'fd' and stores a pointer to the stream in '*pstreamp'.
+ *
+ * When a connection has been accepted, 'accept_cb' will be called with the new
+ * socket fd 'fd' and the remote address of the connection 'sa' and 'sa_len'.
+ * accept_cb must return 0 if the connection is successful, in which case it
+ * must initialize '*streamp' to the new stream, or a positive errno value on
+ * error.  In either case accept_cb takes ownership of the 'fd' passed in.
+ *
+ * When '*pstreamp' is closed, then 'unlink_path' (if nonnull) will be passed
+ * to fatal_signal_unlink_file_now() and freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_pstream(const char *name, int fd,
+               int (*accept_cb)(int fd, const struct sockaddr *sa,
+                                size_t sa_len, struct stream **streamp),
+               char *unlink_path, struct pstream **pstreamp)
+{
+    struct fd_pstream *ps = xmalloc(sizeof *ps);
+    pstream_init(&ps->pstream, &fd_pstream_class, name);
+    ps->fd = fd;
+    ps->accept_cb = accept_cb;
+    ps->unlink_path = unlink_path;
+    *pstreamp = &ps->pstream;
+    return 0;
+}
+
+static void
+pfd_close(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    close(ps->fd);
+    maybe_unlink_and_free(ps->unlink_path);
+    free(ps);
+}
+
+static int
+pfd_accept(struct pstream *pstream, struct stream **new_streamp)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    struct sockaddr_storage ss;
+    socklen_t ss_len = sizeof ss;
+    int new_fd;
+    int retval;
+
+    new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len);
+    if (new_fd < 0) {
+        int retval = errno;
+        if (retval != EAGAIN) {
+            VLOG_DBG_RL(&rl, "accept: %s", strerror(retval));
+        }
+        return retval;
+    }
+
+    retval = set_nonblocking(new_fd);
+    if (retval) {
+        close(new_fd);
+        return retval;
+    }
+
+    return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len,
+                         new_streamp);
+}
+
+static void
+pfd_wait(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    poll_fd_wait(ps->fd, POLLIN);
+}
+
+static struct pstream_class fd_pstream_class = {
+    "pstream",
+    NULL,
+    pfd_close,
+    pfd_accept,
+    pfd_wait
+};
+\f
+/* Helper functions. */
+static void
+maybe_unlink_and_free(char *path)
+{
+    if (path) {
+        fatal_signal_unlink_file_now(path);
+        free(path);
+    }
+}
similarity index 58%
rename from lib/vconn-stream.h
rename to lib/stream-fd.h
index 0adac9a..d2a34eb 100644 (file)
  * limitations under the License.
  */
 
-#ifndef VCONN_STREAM_H
-#define VCONN_STREAM_H 1
+#ifndef STREAM_FD_H
+#define STREAM_FD_H 1
 
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-struct vconn;
-struct pvconn;
+struct stream;
+struct pstream;
 struct sockaddr;
 
-int new_stream_vconn(const char *name, int fd, int connect_status,
-                     bool reconnectable, struct vconn **vconnp);
-int new_pstream_pvconn(const char *name, int fd,
-                      int (*accept_cb)(int fd, const struct sockaddr *,
-                                       size_t sa_len, struct vconn **),
-                      struct pvconn **pvconnp);
+int new_fd_stream(const char *name, int fd, int connect_status,
+                      char *unlink_path, struct stream **streamp);
+int new_fd_pstream(const char *name, int fd,
+                   int (*accept_cb)(int fd, const struct sockaddr *,
+                                    size_t sa_len, struct stream **),
+                   char *unlink_path,
+                   struct pstream **pstreamp);
 
-#endif /* vconn-stream.h */
+#endif /* stream-fd.h */
diff --git a/lib/stream-provider.h b/lib/stream-provider.h
new file mode 100644 (file)
index 0000000..d6bf0a2
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_PROVIDER_H
+#define STREAM_PROVIDER_H 1
+
+#include <assert.h>
+#include <sys/types.h>
+#include "stream.h"
+
+/* Active stream connection. */
+
+/* Active stream connection.
+ *
+ * This structure should be treated as opaque by implementation. */
+struct stream {
+    struct stream_class *class;
+    int state;
+    int error;
+    uint32_t remote_ip;
+    uint16_t remote_port;
+    uint32_t local_ip;
+    uint16_t local_port;
+    char *name;
+};
+
+void stream_init(struct stream *, struct stream_class *, int connect_status,
+                 const char *name);
+void stream_set_remote_ip(struct stream *, uint32_t remote_ip);
+void stream_set_remote_port(struct stream *, uint16_t remote_port);
+void stream_set_local_ip(struct stream *, uint32_t local_ip);
+void stream_set_local_port(struct stream *, uint16_t local_port);
+static inline void stream_assert_class(const struct stream *stream,
+                                       const struct stream_class *class)
+{
+    assert(stream->class == class);
+}
+
+struct stream_class {
+    /* Prefix for connection names, e.g. "tcp", "ssl", "unix". */
+    const char *name;
+
+    /* Attempts to connect to a peer.  'name' is the full connection name
+     * provided by the user, e.g. "tcp:1.2.3.4".  This name is useful for error
+     * messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*streamp'.
+     *
+     * The open function must not block waiting for a connection to complete.
+     * If the connection cannot be completed immediately, it should return
+     * EAGAIN (not EINPROGRESS, as returned by the connect system call) and
+     * continue the connection in the background. */
+    int (*open)(const char *name, char *suffix, struct stream **streamp);
+
+    /* Closes 'stream' and frees associated memory. */
+    void (*close)(struct stream *stream);
+
+    /* Tries to complete the connection on 'stream'.  If 'stream''s connection
+     * is complete, returns 0 if the connection was successful or a positive
+     * errno value if it failed.  If the connection is still in progress,
+     * returns EAGAIN.
+     *
+     * The connect function must not block waiting for the connection to
+     * complete; instead, it should return EAGAIN immediately. */
+    int (*connect)(struct stream *stream);
+
+    /* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and
+     * returns:
+     *
+     *     - If successful, the number of bytes received (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - 0, if the connection has been closed in the normal fashion.
+     *
+     * The recv function will not be passed a zero 'n'.
+     *
+     * The recv function must not block waiting for data to arrive.  If no data
+     * have been received, it should return -EAGAIN immediately. */
+    ssize_t (*recv)(struct stream *stream, void *buffer, size_t n);
+
+    /* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+     *
+     *     - If successful, the number of bytes sent (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - Never returns 0.
+     *
+     * The send function will not be passed a zero 'n'.
+     *
+     * The send function must not block.  If no bytes can be immediately
+     * accepted for transmission, it should return -EAGAIN immediately. */
+    ssize_t (*send)(struct stream *stream, const void *buffer, size_t n);
+
+    /* Allows 'stream' to perform maintenance activities, such as flushing
+     * output buffers.
+     *
+     * May be null if 'stream' doesn't have anything to do here. */
+    void (*run)(struct stream *stream);
+
+    /* Arranges for the poll loop to wake up when 'stream' needs to perform
+     * maintenance activities.
+     *
+     * May be null if 'stream' doesn't have anything to do here. */
+    void (*run_wait)(struct stream *stream);
+
+    /* Arranges for the poll loop to wake up when 'stream' is ready to take an
+     * action of the given 'type'. */
+    void (*wait)(struct stream *stream, enum stream_wait_type type);
+};
+\f
+/* Passive listener for incoming stream connections.
+ *
+ * This structure should be treated as opaque by stream implementations. */
+struct pstream {
+    struct pstream_class *class;
+    char *name;
+};
+
+void pstream_init(struct pstream *, struct pstream_class *, const char *name);
+static inline void pstream_assert_class(const struct pstream *pstream,
+                                        const struct pstream_class *class)
+{
+    assert(pstream->class == class);
+}
+
+struct pstream_class {
+    /* Prefix for connection names, e.g. "ptcp", "pssl", "punix". */
+    const char *name;
+
+    /* Attempts to start listening for stream connections.  'name' is the full
+     * connection name provided by the user, e.g. "ptcp:1234".  This name is
+     * useful for error messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*pstreamp'.
+     *
+     * The listen function must not block.  If the connection cannot be
+     * completed immediately, it should return EAGAIN (not EINPROGRESS, as
+     * returned by the connect system call) and continue the connection in the
+     * background. */
+    int (*listen)(const char *name, char *suffix, struct pstream **pstreamp);
+
+    /* Closes 'pstream' and frees associated memory. */
+    void (*close)(struct pstream *pstream);
+
+    /* Tries to accept a new connection on 'pstream'.  If successful, stores
+     * the new connection in '*new_streamp' and returns 0.  Otherwise, returns
+     * a positive errno value.
+     *
+     * The accept function must not block waiting for a connection.  If no
+     * connection is ready to be accepted, it should return EAGAIN. */
+    int (*accept)(struct pstream *pstream, struct stream **new_streamp);
+
+    /* Arranges for the poll loop to wake up when a connection is ready to be
+     * accepted on 'pstream'. */
+    void (*wait)(struct pstream *pstream);
+};
+
+/* Active and passive stream classes. */
+extern struct stream_class tcp_stream_class;
+extern struct pstream_class ptcp_pstream_class;
+extern struct stream_class unix_stream_class;
+extern struct pstream_class punix_pstream_class;
+#ifdef HAVE_OPENSSL
+extern struct stream_class ssl_stream_class;
+extern struct pstream_class pssl_pstream_class;
+#endif
+
+#endif /* stream-provider.h */
similarity index 70%
rename from lib/vconn-ssl.c
rename to lib/stream-ssl.c
index f681bdf..215934d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
  */
 
 #include <config.h>
-#include "vconn-ssl.h"
+#include "stream-ssl.h"
 #include "dhparams.h"
 #include <assert.h>
 #include <ctype.h>
 #include "socket-util.h"
 #include "socket-util.h"
 #include "util.h"
-#include "vconn-provider.h"
-#include "vconn.h"
+#include "stream-provider.h"
+#include "stream.h"
+#include "timeval.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_vconn_ssl
+#define THIS_MODULE VLM_stream_ssl
 
 /* Active SSL. */
 
@@ -57,17 +58,15 @@ enum session_type {
     SERVER
 };
 
-struct ssl_vconn
+struct ssl_stream
 {
-    struct vconn vconn;
+    struct stream stream;
     enum ssl_state state;
     int connect_error;
     enum session_type type;
     int fd;
     SSL *ssl;
-    struct ofpbuf *rxbuf;
     struct ofpbuf *txbuf;
-    struct poll_waiter *tx_waiter;
 
     /* rx_want and tx_want record the result of the last call to SSL_read()
      * and SSL_write(), respectively:
@@ -97,8 +96,8 @@ struct ssl_vconn
      *    - SSL_read() laters succeeds reading from 'fd' and clears out the
      *      whole receive buffer, so rx_want gets SSL_READING.
      *
-     *    - Client calls vconn_wait(WAIT_RECV) and vconn_wait(WAIT_SEND) and
-     *      blocks.
+     *    - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+     *      and blocks.
      *
      *    - Now we're stuck blocking until the peer sends us data, even though
      *      SSL_write() could now succeed, which could easily be a deadlock
@@ -113,8 +112,8 @@ struct ssl_vconn
      *    - SSL_read() blocks, so rx_want gets SSL_READING or SSL_WRITING,
      *      but tx_want gets reset to SSL_NOTHING.
      *
-     *    - Client calls vconn_wait(WAIT_RECV) and vconn_wait(WAIT_SEND) and
-     *      blocks.
+     *    - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+     *      and blocks.
      *
      *    - Client wakes up immediately since SSL_NOTHING in tx_want indicates
      *      that no blocking is necessary.
@@ -131,20 +130,23 @@ struct ssl_vconn
 /* SSL context created by ssl_init(). */
 static SSL_CTX *ctx;
 
-/* Required configuration. */
-static bool has_private_key, has_certificate, has_ca_cert;
+struct ssl_config_file {
+    bool read;                  /* Whether the file was successfully read. */
+    char *file_name;            /* Configured file name, if any. */
+    struct timespec mtime;      /* File mtime as of last time we read it. */
+};
+
+/* SSL configuration files. */
+static struct ssl_config_file private_key;
+static struct ssl_config_file certificate;
+static struct ssl_config_file ca_cert;
 
 /* Ordinarily, we require a CA certificate for the peer to be locally
- * available.  'has_ca_cert' is true when this is the case, and neither of the
- * following variables matter.
- *
- * We can, however, bootstrap the CA certificate from the peer at the beginning
- * of our first connection then use that certificate on all subsequent
- * connections, saving it to a file for use in future runs also.  In this case,
- * 'has_ca_cert' is false, 'bootstrap_ca_cert' is true, and 'ca_cert_file'
- * names the file to be saved. */
+ * available.  We can, however, bootstrap the CA certificate from the peer at
+ * the beginning of our first connection then use that certificate on all
+ * subsequent connections, saving it to a file for use in future runs also.  In
+ * this case, 'bootstrap_ca_cert' is true. */
 static bool bootstrap_ca_cert;
-static char *ca_cert_file;
 
 /* Who knows what can trigger various SSL errors, so let's throttle them down
  * quite a bit. */
@@ -153,13 +155,14 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
 static int ssl_init(void);
 static int do_ssl_init(void);
 static bool ssl_wants_io(int ssl_error);
-static void ssl_close(struct vconn *);
-static void ssl_clear_txbuf(struct ssl_vconn *);
+static void ssl_close(struct stream *);
+static void ssl_clear_txbuf(struct ssl_stream *);
 static int interpret_ssl_error(const char *function, int ret, int error,
                                int *want);
-static void ssl_tx_poll_callback(int fd, short int revents, void *vconn_);
-static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength);
+static DH *tmp_dh_callback(SSL *ssl, int is_export OVS_UNUSED, int keylength);
 static void log_ca_cert(const char *file_name, X509 *cert);
+static void stream_ssl_set_ca_cert_file__(const char *file_name,
+                                          bool bootstrap);
 
 static short int
 want_to_poll_events(int want)
@@ -180,28 +183,28 @@ want_to_poll_events(int want)
 }
 
 static int
-new_ssl_vconn(const char *name, int fd, enum session_type type,
+new_ssl_stream(const char *name, int fd, enum session_type type,
               enum ssl_state state, const struct sockaddr_in *remote,
-              struct vconn **vconnp)
+              struct stream **streamp)
 {
     struct sockaddr_in local;
     socklen_t local_len = sizeof local;
-    struct ssl_vconn *sslv;
+    struct ssl_stream *sslv;
     SSL *ssl = NULL;
     int on = 1;
     int retval;
 
     /* Check for all the needful configuration. */
     retval = 0;
-    if (!has_private_key) {
+    if (!private_key.read) {
         VLOG_ERR("Private key must be configured to use SSL");
         retval = ENOPROTOOPT;
     }
-    if (!has_certificate) {
+    if (!certificate.read) {
         VLOG_ERR("Certificate must be configured to use SSL");
         retval = ENOPROTOOPT;
     }
-    if (!has_ca_cert && !bootstrap_ca_cert) {
+    if (!ca_cert.read && !bootstrap_ca_cert) {
         VLOG_ERR("CA certificate must be configured to use SSL");
         retval = ENOPROTOOPT;
     }
@@ -244,22 +247,20 @@ new_ssl_vconn(const char *name, int fd, enum session_type type,
         SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
     }
 
-    /* Create and return the ssl_vconn. */
+    /* Create and return the ssl_stream. */
     sslv = xmalloc(sizeof *sslv);
-    vconn_init(&sslv->vconn, &ssl_vconn_class, EAGAIN, name, true);
-    vconn_set_remote_ip(&sslv->vconn, remote->sin_addr.s_addr);
-    vconn_set_remote_port(&sslv->vconn, remote->sin_port);
-    vconn_set_local_ip(&sslv->vconn, local.sin_addr.s_addr);
-    vconn_set_local_port(&sslv->vconn, local.sin_port);
+    stream_init(&sslv->stream, &ssl_stream_class, EAGAIN, name);
+    stream_set_remote_ip(&sslv->stream, remote->sin_addr.s_addr);
+    stream_set_remote_port(&sslv->stream, remote->sin_port);
+    stream_set_local_ip(&sslv->stream, local.sin_addr.s_addr);
+    stream_set_local_port(&sslv->stream, local.sin_port);
     sslv->state = state;
     sslv->type = type;
     sslv->fd = fd;
     sslv->ssl = ssl;
-    sslv->rxbuf = NULL;
     sslv->txbuf = NULL;
-    sslv->tx_waiter = NULL;
     sslv->rx_want = sslv->tx_want = SSL_NOTHING;
-    *vconnp = &sslv->vconn;
+    *streamp = &sslv->stream;
     return 0;
 
 error:
@@ -270,78 +271,40 @@ error:
     return retval;
 }
 
-static struct ssl_vconn *
-ssl_vconn_cast(struct vconn *vconn)
+static struct ssl_stream *
+ssl_stream_cast(struct stream *stream)
 {
-    vconn_assert_class(vconn, &ssl_vconn_class);
-    return CONTAINER_OF(vconn, struct ssl_vconn, vconn);
+    stream_assert_class(stream, &ssl_stream_class);
+    return CONTAINER_OF(stream, struct ssl_stream, stream);
 }
 
 static int
-ssl_open(const char *name, char *suffix, struct vconn **vconnp)
+ssl_open(const char *name, char *suffix, struct stream **streamp)
 {
-    char *save_ptr = NULL;
-    char *host_name, *port_string;
     struct sockaddr_in sin;
-    int retval;
-    int fd;
-
-    retval = ssl_init();
-    if (retval) {
-        return retval;
-    }
-
-    host_name = strtok_r(suffix, ":", &save_ptr);
-    port_string = strtok_r(NULL, ":", &save_ptr);
-    if (!host_name) {
-        ovs_error(0, "%s: bad peer name format", name);
-        return EAFNOSUPPORT;
-    }
-
-    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);
+    int error, fd;
 
-    /* Create socket. */
-    fd = socket(AF_INET, SOCK_STREAM, 0);
-    if (fd < 0) {
-        VLOG_ERR("%s: socket: %s", name, strerror(errno));
-        return errno;
-    }
-    retval = set_nonblocking(fd);
-    if (retval) {
-        close(fd);
-        return retval;
+    error = ssl_init();
+    if (error) {
+        return error;
     }
 
-    /* Connect socket. */
-    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
-    if (retval < 0) {
-        if (errno == EINPROGRESS) {
-            return new_ssl_vconn(name, fd, CLIENT, STATE_TCP_CONNECTING,
-                                 &sin, vconnp);
-        } else {
-            int error = errno;
-            VLOG_ERR("%s: connect: %s", name, strerror(error));
-            close(fd);
-            return error;
-        }
+    error = inet_open_active(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin, &fd);
+    if (fd >= 0) {
+        int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
+        return new_ssl_stream(name, fd, CLIENT, state, &sin, streamp);
     } else {
-        return new_ssl_vconn(name, fd, CLIENT, STATE_SSL_CONNECTING,
-                             &sin, vconnp);
+        VLOG_ERR("%s: connect: %s", name, strerror(error));
+        return error;
     }
 }
 
 static int
-do_ca_cert_bootstrap(struct vconn *vconn)
+do_ca_cert_bootstrap(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     STACK_OF(X509) *chain;
-    X509 *ca_cert;
+    X509 *cert;
     FILE *file;
     int error;
     int fd;
@@ -352,11 +315,11 @@ do_ca_cert_bootstrap(struct vconn *vconn)
                  "peer");
         return EPROTO;
     }
-    ca_cert = sk_X509_value(chain, sk_X509_num(chain) - 1);
+    cert = sk_X509_value(chain, sk_X509_num(chain) - 1);
 
-    /* Check that 'ca_cert' is self-signed.  Otherwise it is not a CA
+    /* Check that 'cert' is self-signed.  Otherwise it is not a CA
      * certificate and we should not attempt to use it as one. */
-    error = X509_check_issued(ca_cert, ca_cert);
+    error = X509_check_issued(cert, cert);
     if (error) {
         VLOG_ERR("could not bootstrap CA cert: obtained certificate is "
                  "not self-signed (%s)",
@@ -368,11 +331,19 @@ do_ca_cert_bootstrap(struct vconn *vconn)
         return EPROTO;
     }
 
-    fd = open(ca_cert_file, O_CREAT | O_EXCL | O_WRONLY, 0444);
+    fd = open(ca_cert.file_name, O_CREAT | O_EXCL | O_WRONLY, 0444);
     if (fd < 0) {
-        VLOG_ERR("could not bootstrap CA cert: creating %s failed: %s",
-                 ca_cert_file, strerror(errno));
-        return errno;
+        if (errno == EEXIST) {
+            VLOG_INFO("CA cert %s created by another process",
+                      ca_cert.file_name);
+            /* We'll read it the next time around the main loop because
+             * update_ssl_config() will see that it now exists. */
+            return EPROTO;
+        } else {
+            VLOG_ERR("could not bootstrap CA cert: creating %s failed: %s",
+                     ca_cert.file_name, strerror(errno));
+            return errno;
+        }
     }
 
     file = fdopen(fd, "w");
@@ -380,41 +351,42 @@ do_ca_cert_bootstrap(struct vconn *vconn)
         int error = errno;
         VLOG_ERR("could not bootstrap CA cert: fdopen failed: %s",
                  strerror(error));
-        unlink(ca_cert_file);
+        unlink(ca_cert.file_name);
         return error;
     }
 
-    if (!PEM_write_X509(file, ca_cert)) {
+    if (!PEM_write_X509(file, cert)) {
         VLOG_ERR("could not bootstrap CA cert: PEM_write_X509 to %s failed: "
-                 "%s", ca_cert_file, ERR_error_string(ERR_get_error(), NULL));
+                 "%s", ca_cert.file_name,
+                 ERR_error_string(ERR_get_error(), NULL));
         fclose(file);
-        unlink(ca_cert_file);
+        unlink(ca_cert.file_name);
         return EIO;
     }
 
     if (fclose(file)) {
         int error = errno;
         VLOG_ERR("could not bootstrap CA cert: writing %s failed: %s",
-                 ca_cert_file, strerror(error));
-        unlink(ca_cert_file);
+                 ca_cert.file_name, strerror(error));
+        unlink(ca_cert.file_name);
         return error;
     }
 
-    VLOG_INFO("successfully bootstrapped CA cert to %s", ca_cert_file);
-    log_ca_cert(ca_cert_file, ca_cert);
+    VLOG_INFO("successfully bootstrapped CA cert to %s", ca_cert.file_name);
+    log_ca_cert(ca_cert.file_name, cert);
     bootstrap_ca_cert = false;
-    has_ca_cert = true;
+    ca_cert.read = true;
 
-    /* SSL_CTX_add_client_CA makes a copy of ca_cert's relevant data. */
-    SSL_CTX_add_client_CA(ctx, ca_cert);
+    /* SSL_CTX_add_client_CA makes a copy of cert's relevant data. */
+    SSL_CTX_add_client_CA(ctx, cert);
 
     /* SSL_CTX_use_certificate() takes ownership of the certificate passed in.
-     * 'ca_cert' is owned by sslv->ssl, so we need to duplicate it. */
-    ca_cert = X509_dup(ca_cert);
-    if (!ca_cert) {
+     * 'cert' is owned by sslv->ssl, so we need to duplicate it. */
+    cert = X509_dup(cert);
+    if (!cert) {
         out_of_memory();
     }
-    if (SSL_CTX_load_verify_locations(ctx, ca_cert_file, NULL) != 1) {
+    if (SSL_CTX_load_verify_locations(ctx, ca_cert.file_name, NULL) != 1) {
         VLOG_ERR("SSL_CTX_load_verify_locations: %s",
                  ERR_error_string(ERR_get_error(), NULL));
         return EPROTO;
@@ -424,9 +396,9 @@ do_ca_cert_bootstrap(struct vconn *vconn)
 }
 
 static int
-ssl_connect(struct vconn *vconn)
+ssl_connect(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     int retval;
 
     switch (sslv->state) {
@@ -453,7 +425,7 @@ ssl_connect(struct vconn *vconn)
                 return EPROTO;
             }
         } else if (bootstrap_ca_cert) {
-            return do_ca_cert_bootstrap(vconn);
+            return do_ca_cert_bootstrap(stream);
         } else if ((SSL_get_verify_mode(sslv->ssl)
                     & (SSL_VERIFY_NONE | SSL_VERIFY_PEER))
                    != SSL_VERIFY_PEER) {
@@ -476,12 +448,18 @@ ssl_connect(struct vconn *vconn)
 }
 
 static void
-ssl_close(struct vconn *vconn)
+ssl_close(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    poll_cancel(sslv->tx_waiter);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     ssl_clear_txbuf(sslv);
-    ofpbuf_delete(sslv->rxbuf);
+
+    /* Attempt clean shutdown of the SSL connection.  This will work most of
+     * the time, as long as the kernel send buffer has some free space and the
+     * SSL connection isn't renegotiating, etc.  That has to be good enough,
+     * since we don't have any way to continue the close operation in the
+     * background. */
+    SSL_shutdown(sslv->ssl);
+
     SSL_free(sslv->ssl);
     close(sslv->fd);
     free(sslv);
@@ -562,103 +540,47 @@ interpret_ssl_error(const char *function, int ret, int error,
     return EIO;
 }
 
-static int
-ssl_recv(struct vconn *vconn, struct ofpbuf **bufferp)
+static ssize_t
+ssl_recv(struct stream *stream, void *buffer, size_t n)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    struct ofpbuf *rx;
-    size_t want_bytes;
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     int old_state;
     ssize_t ret;
 
-    if (sslv->rxbuf == NULL) {
-        sslv->rxbuf = ofpbuf_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_RL(&rl, "received too-short ofp_header (%zu bytes)",
-                        length);
-            return EPROTO;
-        }
-        want_bytes = length - rx->size;
-        if (!want_bytes) {
-            *bufferp = rx;
-            sslv->rxbuf = NULL;
-            return 0;
-        }
-    }
-    ofpbuf_prealloc_tailroom(rx, want_bytes);
-
     /* Behavior of zero-byte SSL_read is poorly defined. */
-    assert(want_bytes > 0);
+    assert(n > 0);
 
     old_state = SSL_get_state(sslv->ssl);
-    ret = SSL_read(sslv->ssl, ofpbuf_tail(rx), want_bytes);
+    ret = SSL_read(sslv->ssl, buffer, n);
     if (old_state != SSL_get_state(sslv->ssl)) {
         sslv->tx_want = SSL_NOTHING;
-        if (sslv->tx_waiter) {
-            poll_cancel(sslv->tx_waiter);
-            ssl_tx_poll_callback(sslv->fd, POLLIN, vconn);
-        }
     }
     sslv->rx_want = SSL_NOTHING;
 
     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;
+        return ret;
     } else {
         int error = SSL_get_error(sslv->ssl, ret);
         if (error == SSL_ERROR_ZERO_RETURN) {
-            /* Connection closed (EOF). */
-            if (rx->size) {
-                VLOG_WARN_RL(&rl, "SSL_read: unexpected connection close");
-                return EPROTO;
-            } else {
-                return EOF;
-            }
+            return 0;
         } else {
-            return interpret_ssl_error("SSL_read", ret, error, &sslv->rx_want);
+            return -interpret_ssl_error("SSL_read", ret, error,
+                                        &sslv->rx_want);
         }
     }
 }
 
 static void
-ssl_clear_txbuf(struct ssl_vconn *sslv)
+ssl_clear_txbuf(struct ssl_stream *sslv)
 {
     ofpbuf_delete(sslv->txbuf);
     sslv->txbuf = NULL;
-    sslv->tx_waiter = NULL;
-}
-
-static void
-ssl_register_tx_waiter(struct vconn *vconn)
-{
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    sslv->tx_waiter = poll_fd_callback(sslv->fd,
-                                       want_to_poll_events(sslv->tx_want),
-                                       ssl_tx_poll_callback, vconn);
 }
 
 static int
-ssl_do_tx(struct vconn *vconn)
+ssl_do_tx(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     for (;;) {
         int old_state = SSL_get_state(sslv->ssl);
@@ -685,54 +607,60 @@ ssl_do_tx(struct vconn *vconn)
     }
 }
 
-static void
-ssl_tx_poll_callback(int fd UNUSED, short int revents UNUSED, void *vconn_)
-{
-    struct vconn *vconn = vconn_;
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    int error = ssl_do_tx(vconn);
-    if (error != EAGAIN) {
-        ssl_clear_txbuf(sslv);
-    } else {
-        ssl_register_tx_waiter(vconn);
-    }
-}
-
-static int
-ssl_send(struct vconn *vconn, struct ofpbuf *buffer)
+static ssize_t
+ssl_send(struct stream *stream, const void *buffer, size_t n)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     if (sslv->txbuf) {
-        return EAGAIN;
+        return -EAGAIN;
     } else {
         int error;
 
-        sslv->txbuf = buffer;
-        error = ssl_do_tx(vconn);
+        sslv->txbuf = ofpbuf_clone_data(buffer, n);
+        error = ssl_do_tx(stream);
         switch (error) {
         case 0:
             ssl_clear_txbuf(sslv);
-            return 0;
+            return n;
         case EAGAIN:
             leak_checker_claim(buffer);
-            ssl_register_tx_waiter(vconn);
-            return 0;
+            return n;
         default:
             sslv->txbuf = NULL;
-            return error;
+            return -error;
         }
     }
 }
 
 static void
-ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
+ssl_run(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+    if (sslv->txbuf && ssl_do_tx(stream) != EAGAIN) {
+        ssl_clear_txbuf(sslv);
+    }
+}
+
+static void
+ssl_run_wait(struct stream *stream)
+{
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+    if (sslv->tx_want != SSL_NOTHING) {
+        poll_fd_wait(sslv->fd, want_to_poll_events(sslv->tx_want));
+    }
+}
+
+static void
+ssl_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     switch (wait) {
-    case WAIT_CONNECT:
-        if (vconn_connect(vconn) != EAGAIN) {
+    case STREAM_CONNECT:
+        if (stream_connect(stream) != EAGAIN) {
             poll_immediate_wake();
         } else {
             switch (sslv->state) {
@@ -753,7 +681,7 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
         }
         break;
 
-    case WAIT_RECV:
+    case STREAM_RECV:
         if (sslv->rx_want != SSL_NOTHING) {
             poll_fd_wait(sslv->fd, want_to_poll_events(sslv->rx_want));
         } else {
@@ -761,12 +689,13 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
         }
         break;
 
-    case WAIT_SEND:
+    case STREAM_SEND:
         if (!sslv->txbuf) {
             /* We have room in our tx queue. */
             poll_immediate_wake();
         } else {
-            /* The call to ssl_tx_poll_callback() will wake us up. */
+            /* stream_run_wait() will do the right thing; don't bother with
+             * redundancy. */
         }
         break;
 
@@ -775,106 +704,75 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
     }
 }
 
-struct vconn_class ssl_vconn_class = {
+struct stream_class ssl_stream_class = {
     "ssl",                      /* name */
     ssl_open,                   /* open */
     ssl_close,                  /* close */
     ssl_connect,                /* connect */
     ssl_recv,                   /* recv */
     ssl_send,                   /* send */
+    ssl_run,                    /* run */
+    ssl_run_wait,               /* run_wait */
     ssl_wait,                   /* wait */
 };
 \f
 /* Passive SSL. */
 
-struct pssl_pvconn
+struct pssl_pstream
 {
-    struct pvconn pvconn;
+    struct pstream pstream;
     int fd;
 };
 
-struct pvconn_class pssl_pvconn_class;
+struct pstream_class pssl_pstream_class;
 
-static struct pssl_pvconn *
-pssl_pvconn_cast(struct pvconn *pvconn)
+static struct pssl_pstream *
+pssl_pstream_cast(struct pstream *pstream)
 {
-    pvconn_assert_class(pvconn, &pssl_pvconn_class);
-    return CONTAINER_OF(pvconn, struct pssl_pvconn, pvconn);
+    pstream_assert_class(pstream, &pssl_pstream_class);
+    return CONTAINER_OF(pstream, struct pssl_pstream, pstream);
 }
 
 static int
-pssl_open(const char *name, char *suffix, struct pvconn **pvconnp)
+pssl_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp)
 {
+    struct pssl_pstream *pssl;
     struct sockaddr_in sin;
-    struct pssl_pvconn *pssl;
+    char bound_name[128];
     int retval;
     int fd;
-    unsigned int yes = 1;
 
     retval = ssl_init();
     if (retval) {
         return retval;
     }
 
-    /* Create socket. */
-    fd = socket(AF_INET, SOCK_STREAM, 0);
+    fd = inet_open_passive(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin);
     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) {
-        close(fd);
-        return retval;
+        return -fd;
     }
+    sprintf(bound_name, "pssl:%"PRIu16":"IP_FMT,
+            ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
 
     pssl = xmalloc(sizeof *pssl);
-    pvconn_init(&pssl->pvconn, &pssl_pvconn_class, name);
+    pstream_init(&pssl->pstream, &pssl_pstream_class, bound_name);
     pssl->fd = fd;
-    *pvconnp = &pssl->pvconn;
+    *pstreamp = &pssl->pstream;
     return 0;
 }
 
 static void
-pssl_close(struct pvconn *pvconn)
+pssl_close(struct pstream *pstream)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     close(pssl->fd);
     free(pssl);
 }
 
 static int
-pssl_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
+pssl_accept(struct pstream *pstream, struct stream **new_streamp)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     struct sockaddr_in sin;
     socklen_t sin_len = sizeof sin;
     char name[128];
@@ -900,18 +798,18 @@ pssl_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
     if (sin.sin_port != htons(OFP_SSL_PORT)) {
         sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port));
     }
-    return new_ssl_vconn(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin,
-                         new_vconnp);
+    return new_ssl_stream(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin,
+                         new_streamp);
 }
 
 static void
-pssl_wait(struct pvconn *pvconn)
+pssl_wait(struct pstream *pstream)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     poll_fd_wait(pssl->fd, POLLIN);
 }
 
-struct pvconn_class pssl_pvconn_class = {
+struct pstream_class pssl_pstream_class = {
     "pssl",
     pssl_open,
     pssl_close,
@@ -972,7 +870,7 @@ do_ssl_init(void)
 }
 
 static DH *
-tmp_dh_callback(SSL *ssl UNUSED, int is_export UNUSED, int keylength)
+tmp_dh_callback(SSL *ssl OVS_UNUSED, int is_export OVS_UNUSED, int keylength)
 {
     struct dh {
         int keylength;
@@ -1007,15 +905,60 @@ tmp_dh_callback(SSL *ssl UNUSED, int is_export UNUSED, int keylength)
 
 /* Returns true if SSL is at least partially configured. */
 bool
-vconn_ssl_is_configured(void) 
+stream_ssl_is_configured(void) 
+{
+    return private_key.file_name || certificate.file_name || ca_cert.file_name;
+}
+
+static void
+get_mtime(const char *file_name, struct timespec *mtime)
 {
-    return has_private_key || has_certificate || has_ca_cert;
+    struct stat s;
+
+    if (!stat(file_name, &s)) {
+        mtime->tv_sec = s.st_mtime;
+
+#if HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+        mtime->tv_nsec = s.st_mtim.tv_nsec;
+#elif HAVE_STRUCT_STAT_ST_MTIMENSEC
+        mtime->tv_nsec = s.st_mtimensec;
+#else
+        mtime->tv_nsec = 0;
+#endif
+    } else {
+        mtime->tv_sec = mtime->tv_nsec = 0;
+    }
+}
+
+static bool
+update_ssl_config(struct ssl_config_file *config, const char *file_name)
+{
+    struct timespec mtime;
+
+    if (ssl_init() || !file_name) {
+        return false;
+    }
+
+    /* If the file name hasn't changed and neither has the file contents, stop
+     * here. */
+    get_mtime(file_name, &mtime);
+    if (config->file_name
+        && !strcmp(config->file_name, file_name)
+        && mtime.tv_sec == config->mtime.tv_sec
+        && mtime.tv_nsec == config->mtime.tv_nsec) {
+        return false;
+    }
+
+    config->mtime = mtime;
+    free(config->file_name);
+    config->file_name = xstrdup(file_name);
+    return true;
 }
 
 void
-vconn_ssl_set_private_key_file(const char *file_name)
+stream_ssl_set_private_key_file(const char *file_name)
 {
-    if (ssl_init()) {
+    if (!update_ssl_config(&private_key, file_name)) {
         return;
     }
     if (SSL_CTX_use_PrivateKey_file(ctx, file_name, SSL_FILETYPE_PEM) != 1) {
@@ -1023,13 +966,13 @@ vconn_ssl_set_private_key_file(const char *file_name)
                  ERR_error_string(ERR_get_error(), NULL));
         return;
     }
-    has_private_key = true;
+    private_key.read = true;
 }
 
 void
-vconn_ssl_set_certificate_file(const char *file_name)
+stream_ssl_set_certificate_file(const char *file_name)
 {
-    if (ssl_init()) {
+    if (!update_ssl_config(&certificate, file_name)) {
         return;
     }
     if (SSL_CTX_use_certificate_chain_file(ctx, file_name) != 1) {
@@ -1037,7 +980,7 @@ vconn_ssl_set_certificate_file(const char *file_name)
                  ERR_error_string(ERR_get_error(), NULL));
         return;
     }
-    has_certificate = true;
+    certificate.read = true;
 }
 
 /* Reads the X509 certificate or certificates in file 'file_name'.  On success,
@@ -1108,7 +1051,7 @@ read_cert_file(const char *file_name, X509 ***certs, size_t *n_certs)
  * certificate to the peer, which enables a switch to pick up the controller's
  * CA certificate on its first connection. */
 void
-vconn_ssl_set_peer_ca_cert_file(const char *file_name)
+stream_ssl_set_peer_ca_cert_file(const char *file_name)
 {
     X509 **certs;
     size_t n_certs;
@@ -1157,25 +1100,15 @@ log_ca_cert(const char *file_name, X509 *cert)
     ds_destroy(&fp);
 }
 
-/* Sets 'file_name' as the name of the file from which to read the CA
- * certificate used to verify the peer within SSL connections.  If 'bootstrap'
- * is false, the file must exist.  If 'bootstrap' is false, then the file is
- * read if it is exists; if it does not, then it will be created from the CA
- * certificate received from the peer on the first SSL connection. */
-void
-vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
+static void
+stream_ssl_set_ca_cert_file__(const char *file_name, bool bootstrap)
 {
     X509 **certs;
     size_t n_certs;
     struct stat s;
 
-    if (ssl_init()) {
-        return;
-    }
-
     if (bootstrap && stat(file_name, &s) && errno == ENOENT) {
         bootstrap_ca_cert = true;
-        ca_cert_file = xstrdup(file_name);
     } else if (!read_cert_file(file_name, &certs, &n_certs)) {
         size_t i;
 
@@ -1191,6 +1124,7 @@ vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
             }
             X509_free(certs[i]);
         }
+        free(certs);
 
         /* Set up CAs for OpenSSL to trust in verifying the peer's
          * certificate. */
@@ -1200,6 +1134,24 @@ vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
             return;
         }
 
-        has_ca_cert = true;
+        bootstrap_ca_cert = false;
     }
+    ca_cert.read = true;
 }
+
+/* Sets 'file_name' as the name of the file from which to read the CA
+ * certificate used to verify the peer within SSL connections.  If 'bootstrap'
+ * is false, the file must exist.  If 'bootstrap' is false, then the file is
+ * read if it is exists; if it does not, then it will be created from the CA
+ * certificate received from the peer on the first SSL connection. */
+void
+stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
+{
+    if (!update_ssl_config(&ca_cert, file_name)) {
+        return;
+    }
+
+    stream_ssl_set_ca_cert_file__(file_name, bootstrap);
+}
+
+
similarity index 54%
rename from lib/vconn-ssl.h
rename to lib/stream-ssl.h
index 551e2eb..dd2a16e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef VCONN_SSL_H
-#define VCONN_SSL_H 1
+#ifndef STREAM_SSL_H
+#define STREAM_SSL_H 1
 
 #include <stdbool.h>
 
 #ifdef HAVE_OPENSSL
-bool vconn_ssl_is_configured(void);
-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, bool bootstrap);
-void vconn_ssl_set_peer_ca_cert_file(const char *file_name);
+bool stream_ssl_is_configured(void);
+void stream_ssl_set_private_key_file(const char *file_name);
+void stream_ssl_set_certificate_file(const char *file_name);
+void stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap);
+void stream_ssl_set_peer_ca_cert_file(const char *file_name);
 
-#define VCONN_SSL_LONG_OPTIONS                      \
+/* Define the long options for SSL support.
+ *
+ * Note that the definition includes a final comma, and therefore a comma 
+ * must not be supplied when using the definition.  This is done so that 
+ * compilation succeeds whether or not HAVE_OPENSSL is defined. */
+#define STREAM_SSL_LONG_OPTIONS                      \
         {"private-key", required_argument, 0, 'p'}, \
         {"certificate", required_argument, 0, 'c'}, \
         {"ca-cert",     required_argument, 0, 'C'},
 
-#define VCONN_SSL_OPTION_HANDLERS                       \
+#define STREAM_SSL_OPTION_HANDLERS                      \
         case 'p':                                       \
-            vconn_ssl_set_private_key_file(optarg);     \
+            stream_ssl_set_private_key_file(optarg);    \
             break;                                      \
                                                         \
         case 'c':                                       \
-            vconn_ssl_set_certificate_file(optarg);     \
+            stream_ssl_set_certificate_file(optarg);    \
             break;                                      \
                                                         \
         case 'C':                                       \
-            vconn_ssl_set_ca_cert_file(optarg, false);  \
+            stream_ssl_set_ca_cert_file(optarg, false); \
             break;
 #else /* !HAVE_OPENSSL */
-static inline bool vconn_ssl_is_configured(void) 
+static inline bool stream_ssl_is_configured(void) 
 {
     return false;
 }
-#define VCONN_SSL_LONG_OPTIONS
-#define VCONN_SSL_OPTION_HANDLERS
+#define STREAM_SSL_LONG_OPTIONS
+#define STREAM_SSL_OPTION_HANDLERS
 #endif /* !HAVE_OPENSSL */
 
-#endif /* vconn-ssl.h */
+#endif /* stream-ssl.h */
diff --git a/lib/stream-tcp.c b/lib/stream-tcp.c
new file mode 100644 (file)
index 0000000..a9bcaeb
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "packets.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_tcp
+
+/* Active TCP. */
+
+static int
+new_tcp_stream(const char *name, int fd, int connect_status,
+              const struct sockaddr_in *remote, struct stream **streamp)
+{
+    struct sockaddr_in local;
+    socklen_t local_len = sizeof local;
+    int on = 1;
+    int retval;
+
+    /* Get the local IP and port information */
+    retval = getsockname(fd, (struct sockaddr *)&local, &local_len);
+    if (retval) {
+        memset(&local, 0, sizeof local);
+    }
+
+    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;
+    }
+
+    retval = new_fd_stream(name, fd, connect_status, NULL, streamp);
+    if (!retval) {
+        struct stream *stream = *streamp;
+        stream_set_remote_ip(stream, remote->sin_addr.s_addr);
+        stream_set_remote_port(stream, remote->sin_port);
+        stream_set_local_ip(stream, local.sin_addr.s_addr);
+        stream_set_local_port(stream, local.sin_port);
+    }
+    return retval;
+}
+
+static int
+tcp_open(const char *name, char *suffix, struct stream **streamp)
+{
+    struct sockaddr_in sin;
+    int fd, error;
+
+    error = inet_open_active(SOCK_STREAM, suffix, 0, &sin, &fd);
+    if (fd >= 0) {
+        return new_tcp_stream(name, fd, error, &sin, streamp);
+    } else {
+        VLOG_ERR("%s: connect: %s", name, strerror(error));
+        return error;
+    }
+}
+
+struct stream_class tcp_stream_class = {
+    "tcp",                      /* name */
+    tcp_open,                   /* open */
+    NULL,                       /* close */
+    NULL,                       /* connect */
+    NULL,                       /* recv */
+    NULL,                       /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
+    NULL,                       /* wait */
+};
+\f
+/* Passive TCP. */
+
+static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+                       struct stream **streamp);
+
+static int
+ptcp_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp)
+{
+    struct sockaddr_in sin;
+    char bound_name[128];
+    int fd;
+
+    fd = inet_open_passive(SOCK_STREAM, suffix, -1, &sin);
+    if (fd < 0) {
+        return -fd;
+    }
+
+    sprintf(bound_name, "ptcp:%"PRIu16":"IP_FMT,
+            ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
+    return new_fd_pstream(bound_name, fd, ptcp_accept, NULL, pstreamp);
+}
+
+static int
+ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+            struct stream **streamp)
+{
+    const struct sockaddr_in *sin = (const struct sockaddr_in *) sa;
+    char name[128];
+
+    if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) {
+        sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr));
+        sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
+    } else {
+        strcpy(name, "tcp");
+    }
+    return new_tcp_stream(name, fd, 0, sin, streamp);
+}
+
+struct pstream_class ptcp_pstream_class = {
+    "ptcp",
+    ptcp_open,
+    NULL,
+    NULL,
+    NULL
+};
+
similarity index 66%
rename from lib/vconn-unix.c
rename to lib/stream-unix.c
index e39eaea..33f566b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
  */
 
 #include <config.h>
-#include "vconn.h"
+#include "stream.h"
 #include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "ofpbuf.h"
-#include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "socket-util.h"
 #include "util.h"
-#include "vconn-provider.h"
-#include "vconn-stream.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_vconn_unix
+#define THIS_MODULE VLM_stream_unix
 
 /* Active UNIX socket. */
 
 static int n_unix_sockets;
 
 static int
-unix_open(const char *name, char *suffix, struct vconn **vconnp)
+unix_open(const char *name, char *suffix, struct stream **streamp)
 {
     const char *connect_path = suffix;
-    char bind_path[128];
+    char *bind_path;
     int fd;
 
-    sprintf(bind_path, "/tmp/vconn-unix.%ld.%d",
-            (long int) getpid(), n_unix_sockets++);
+    bind_path = xasprintf("/tmp/stream-unix.%ld.%d",
+                          (long int) getpid(), n_unix_sockets++);
     fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path);
     if (fd < 0) {
         VLOG_ERR("%s: connection to %s failed: %s",
                  bind_path, connect_path, strerror(-fd));
+        free(bind_path);
         return -fd;
     }
 
-    return new_stream_vconn(name, fd, check_connection_completion(fd),
-                            true, vconnp);
+    return new_fd_stream(name, fd, check_connection_completion(fd),
+                         bind_path, streamp);
 }
 
-struct vconn_class unix_vconn_class = {
+struct stream_class unix_stream_class = {
     "unix",                     /* name */
     unix_open,                  /* open */
     NULL,                       /* close */
     NULL,                       /* connect */
     NULL,                       /* recv */
     NULL,                       /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
     NULL,                       /* wait */
 };
 \f
 /* Passive UNIX socket. */
 
 static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-                        struct vconn **vconnp);
+                        struct stream **streamp);
 
 static int
-punix_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
+punix_open(const char *name OVS_UNUSED, char *suffix,
+           struct pstream **pstreamp)
 {
-    int fd;
+    int fd, error;
 
     fd = make_unix_socket(SOCK_STREAM, true, true, suffix, NULL);
     if (fd < 0) {
@@ -89,12 +91,20 @@ punix_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
         return errno;
     }
 
-    return new_pstream_pvconn("punix", fd, punix_accept, pvconnp);
+    if (listen(fd, 10) < 0) {
+        error = errno;
+        VLOG_ERR("%s: listen: %s", name, strerror(error));
+        close(fd);
+        return error;
+    }
+
+    return new_fd_pstream("punix", fd, punix_accept,
+                          xstrdup(suffix), pstreamp);
 }
 
 static int
 punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-             struct vconn **vconnp)
+             struct stream **streamp)
 {
     const struct sockaddr_un *sun = (const struct sockaddr_un *) sa;
     int name_len = get_unix_name_len(sa_len);
@@ -105,10 +115,10 @@ punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
     } else {
         strcpy(name, "unix");
     }
-    return new_stream_vconn(name, fd, 0, true, vconnp);
+    return new_fd_stream(name, fd, 0, NULL, streamp);
 }
 
-struct pvconn_class punix_pvconn_class = {
+struct pstream_class punix_pstream_class = {
     "punix",
     punix_open,
     NULL,
diff --git a/lib/stream.c b/lib/stream.c
new file mode 100644 (file)
index 0000000..db6ec61
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-provider.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include "coverage.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "random.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_stream
+#include "vlog.h"
+
+/* State of an active stream.*/
+enum stream_state {
+    SCS_CONNECTING,             /* Underlying stream is not connected. */
+    SCS_CONNECTED,              /* Connection established. */
+    SCS_DISCONNECTED            /* Connection failed or connection closed. */
+};
+
+static struct stream_class *stream_classes[] = {
+    &tcp_stream_class,
+    &unix_stream_class,
+#ifdef HAVE_OPENSSL
+    &ssl_stream_class,
+#endif
+};
+
+static struct pstream_class *pstream_classes[] = {
+    &ptcp_pstream_class,
+    &punix_pstream_class,
+#ifdef HAVE_OPENSSL
+    &pssl_pstream_class,
+#endif
+};
+
+/* Check the validity of the stream class structures. */
+static void
+check_stream_classes(void)
+{
+#ifndef NDEBUG
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        assert(class->name != NULL);
+        assert(class->open != NULL);
+        if (class->close || class->recv || class->send || class->run
+            || class->run_wait || class->wait) {
+            assert(class->close != NULL);
+            assert(class->recv != NULL);
+            assert(class->send != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        assert(class->name != NULL);
+        assert(class->listen != NULL);
+        if (class->close || class->accept || class->wait) {
+            assert(class->close != NULL);
+            assert(class->accept != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+#endif
+}
+
+/* Prints information on active (if 'active') and passive (if 'passive')
+ * connection methods supported by the stream. */
+void
+stream_usage(const char *name, bool active, bool passive,
+             bool bootstrap OVS_UNUSED)
+{
+    /* Really this should be implemented via callbacks into the stream
+     * providers, but that seems too heavy-weight to bother with at the
+     * moment. */
+
+    printf("\n");
+    if (active) {
+        printf("Active %s connection methods:\n", name);
+        printf("  tcp:IP:PORT             "
+               "PORT at remote IP\n");
+#ifdef HAVE_OPENSSL
+        printf("  ssl:IP:PORT             "
+               "SSL PORT at remote IP\n");
+#endif
+        printf("  unix:FILE               "
+               "Unix domain socket named FILE\n");
+    }
+
+    if (passive) {
+        printf("Passive %s connection methods:\n", name);
+        printf("  ptcp:PORT[:IP]          "
+               "listen to TCP PORT on IP\n");
+#ifdef HAVE_OPENSSL
+        printf("  pssl:PORT[:IP]          "
+               "listen for SSL on PORT on IP\n");
+#endif
+        printf("  punix:FILE              "
+               "listen on Unix domain socket FILE\n");
+    }
+
+#ifdef HAVE_OPENSSL
+    printf("PKI 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");
+    if (bootstrap) {
+        printf("  --bootstrap-ca-cert=FILE  file with peer CA certificate "
+               "to read or create\n");
+    }
+#endif
+}
+
+/* Attempts to connect a stream to a remote peer.  'name' is a connection name
+ * in the form "TYPE:ARGS", where TYPE is an active stream class's name and
+ * ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*streamp', otherwise a null
+ * pointer.  */
+int
+stream_open(const char *name, struct stream **streamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    COVERAGE_INC(stream_open);
+    check_stream_classes();
+
+    *streamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            struct stream *stream;
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->open(name, suffix_copy, &stream);
+            free(suffix_copy);
+            if (!retval) {
+                assert(stream->state != SCS_CONNECTING
+                       || stream->class->connect);
+                *streamp = stream;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+int
+stream_open_block(const char *name, struct stream **streamp)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open(name, &stream);
+    while (error == EAGAIN) {
+        stream_run(stream);
+        stream_run_wait(stream);
+        stream_connect_wait(stream);
+        poll_block();
+        error = stream_connect(stream);
+        assert(error != EINPROGRESS);
+    }
+    if (error) {
+        stream_close(stream);
+        *streamp = NULL;
+    } else {
+        *streamp = stream;
+    }
+    return error;
+}
+
+/* Closes 'stream'. */
+void
+stream_close(struct stream *stream)
+{
+    if (stream != NULL) {
+        char *name = stream->name;
+        (stream->class->close)(stream);
+        free(name);
+    }
+}
+
+/* Returns the name of 'stream', that is, the string passed to
+ * stream_open(). */
+const char *
+stream_get_name(const struct stream *stream)
+{
+    return stream ? stream->name : "(null)";
+}
+
+/* Returns the IP address of the peer, or 0 if the peer is not connected over
+ * an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_remote_ip(const struct stream *stream)
+{
+    return stream->remote_ip;
+}
+
+/* Returns the transport port of the peer, or 0 if the connection does not
+ * contain a port or if the port is not yet known. */
+uint16_t
+stream_get_remote_port(const struct stream *stream)
+{
+    return stream->remote_port;
+}
+
+/* Returns the IP address used to connect to the peer, or 0 if the connection
+ * is not an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_local_ip(const struct stream *stream)
+{
+    return stream->local_ip;
+}
+
+/* Returns the transport port used to connect to the peer, or 0 if the
+ * connection does not contain a port or if the port is not yet known. */
+uint16_t
+stream_get_local_port(const struct stream *stream)
+{
+    return stream->local_port;
+}
+
+static void
+scs_connecting(struct stream *stream)
+{
+    int retval = (stream->class->connect)(stream);
+    assert(retval != EINPROGRESS);
+    if (!retval) {
+        stream->state = SCS_CONNECTED;
+    } else if (retval != EAGAIN) {
+        stream->state = SCS_DISCONNECTED;
+        stream->error = retval;
+    }
+}
+
+/* Tries to complete the connection on 'stream', which must be an active
+ * stream.  If 'stream''s connection is complete, returns 0 if the connection
+ * was successful or a positive errno value if it failed.  If the
+ * connection is still in progress, returns EAGAIN. */
+int
+stream_connect(struct stream *stream)
+{
+    enum stream_state last_state;
+
+    do {
+        last_state = stream->state;
+        switch (stream->state) {
+        case SCS_CONNECTING:
+            scs_connecting(stream);
+            break;
+
+        case SCS_CONNECTED:
+            return 0;
+
+        case SCS_DISCONNECTED:
+            return stream->error;
+
+        default:
+            NOT_REACHED();
+        }
+    } while (stream->state != last_state);
+
+    return EAGAIN;
+}
+
+/* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and returns:
+ *
+ *     - If successful, the number of bytes received (between 1 and 'n').
+ *
+ *     - On error, a negative errno value.
+ *
+ *     - 0, if the connection has been closed in the normal fashion, or if 'n'
+ *       is zero.
+ *
+ * The recv function will not block waiting for a packet to arrive.  If no
+ * data have been received, it returns -EAGAIN immediately. */
+int
+stream_recv(struct stream *stream, void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->recv)(stream, buffer, n));
+}
+
+/* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+ *
+ *     - If successful, the number of bytes sent (between 1 and 'n').  0 is
+ *       only a valid return value if 'n' is 0.
+ *
+ *     - On error, a negative errno value.
+ *
+ * The send function will not block.  If no bytes can be immediately accepted
+ * for transmission, it returns -EAGAIN immediately. */
+int
+stream_send(struct stream *stream, const void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->send)(stream, buffer, n));
+}
+
+/* Allows 'stream' to perform maintenance activities, such as flushing
+ * output buffers. */
+void
+stream_run(struct stream *stream)
+{
+    if (stream->class->run) {
+        (stream->class->run)(stream);
+    }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' needs to perform
+ * maintenance activities. */
+void
+stream_run_wait(struct stream *stream)
+{
+    if (stream->class->run_wait) {
+        (stream->class->run_wait)(stream);
+    }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' is ready to take an
+ * action of the given 'type'. */
+void
+stream_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    assert(wait == STREAM_CONNECT || wait == STREAM_RECV
+           || wait == STREAM_SEND);
+
+    switch (stream->state) {
+    case SCS_CONNECTING:
+        wait = STREAM_CONNECT;
+        break;
+
+    case SCS_DISCONNECTED:
+        poll_immediate_wake();
+        return;
+    }
+    (stream->class->wait)(stream, wait);
+}
+
+void
+stream_connect_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_CONNECT);
+}
+
+void
+stream_recv_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_RECV);
+}
+
+void
+stream_send_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_SEND);
+}
+
+/* Attempts to start listening for remote stream connections.  'name' is a
+ * connection name in the form "TYPE:ARGS", where TYPE is an passive stream
+ * class's name and ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*pstreamp', otherwise a null
+ * pointer.  */
+int
+pstream_open(const char *name, struct pstream **pstreamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    check_stream_classes();
+
+    *pstreamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->listen(name, suffix_copy, pstreamp);
+            free(suffix_copy);
+            if (retval) {
+                *pstreamp = NULL;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+/* Returns the name that was used to open 'pstream'.  The caller must not
+ * modify or free the name. */
+const char *
+pstream_get_name(const struct pstream *pstream)
+{
+    return pstream->name;
+}
+
+/* Closes 'pstream'. */
+void
+pstream_close(struct pstream *pstream)
+{
+    if (pstream != NULL) {
+        char *name = pstream->name;
+        (pstream->class->close)(pstream);
+        free(name);
+    }
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept() will not block waiting for a connection.  If no connection
+ * is ready to be accepted, it returns EAGAIN immediately. */
+int
+pstream_accept(struct pstream *pstream, struct stream **new_stream)
+{
+    int retval = (pstream->class->accept)(pstream, new_stream);
+    if (retval) {
+        *new_stream = NULL;
+    } else {
+        assert((*new_stream)->state != SCS_CONNECTING
+               || (*new_stream)->class->connect);
+    }
+    return retval;
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept_block() blocks until a connection is ready or until an error
+ * occurs.  It will not return EAGAIN. */
+int
+pstream_accept_block(struct pstream *pstream, struct stream **new_stream)
+{
+    int error;
+
+    while ((error = pstream_accept(pstream, new_stream)) == EAGAIN) {
+        pstream_wait(pstream);
+        poll_block();
+    }
+    if (error) {
+        *new_stream = NULL;
+    }
+    return error;
+}
+
+void
+pstream_wait(struct pstream *pstream)
+{
+    (pstream->class->wait)(pstream);
+}
+\f
+/* Initializes 'stream' as a new stream named 'name', implemented via 'class'.
+ * The initial connection status, supplied as 'connect_status', is interpreted
+ * as follows:
+ *
+ *      - 0: 'stream' is connected.  Its 'send' and 'recv' functions may be
+ *        called in the normal fashion.
+ *
+ *      - EAGAIN: 'stream' is trying to complete a connection.  Its 'connect'
+ *        function should be called to complete the connection.
+ *
+ *      - Other positive errno values indicate that the connection failed with
+ *        the specified error.
+ *
+ * After calling this function, stream_close() must be used to destroy
+ * 'stream', otherwise resources will be leaked.
+ *
+ * The caller retains ownership of 'name'. */
+void
+stream_init(struct stream *stream, struct stream_class *class,
+            int connect_status, const char *name)
+{
+    stream->class = class;
+    stream->state = (connect_status == EAGAIN ? SCS_CONNECTING
+                    : !connect_status ? SCS_CONNECTED
+                    : SCS_DISCONNECTED);
+    stream->error = connect_status;
+    stream->name = xstrdup(name);
+    assert(stream->state != SCS_CONNECTING || class->connect);
+}
+
+void
+stream_set_remote_ip(struct stream *stream, uint32_t ip)
+{
+    stream->remote_ip = ip;
+}
+
+void
+stream_set_remote_port(struct stream *stream, uint16_t port)
+{
+    stream->remote_port = port;
+}
+
+void
+stream_set_local_ip(struct stream *stream, uint32_t ip)
+{
+    stream->local_ip = ip;
+}
+
+void
+stream_set_local_port(struct stream *stream, uint16_t port)
+{
+    stream->local_port = port;
+}
+
+void
+pstream_init(struct pstream *pstream, struct pstream_class *class,
+            const char *name)
+{
+    pstream->class = class;
+    pstream->name = xstrdup(name);
+}
diff --git a/lib/stream.h b/lib/stream.h
new file mode 100644 (file)
index 0000000..e7eef36
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_H
+#define STREAM_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct pstream;
+struct stream;
+
+void stream_usage(const char *name, bool active, bool passive, bool bootstrap);
+
+/* Bidirectional byte streams. */
+int stream_open(const char *name, struct stream **);
+int stream_open_block(const char *name, struct stream **);
+void stream_close(struct stream *);
+const char *stream_get_name(const struct stream *);
+uint32_t stream_get_remote_ip(const struct stream *);
+uint16_t stream_get_remote_port(const struct stream *);
+uint32_t stream_get_local_ip(const struct stream *);
+uint16_t stream_get_local_port(const struct stream *);
+int stream_connect(struct stream *);
+int stream_recv(struct stream *, void *buffer, size_t n);
+int stream_send(struct stream *, const void *buffer, size_t n);
+
+void stream_run(struct stream *);
+void stream_run_wait(struct stream *);
+
+enum stream_wait_type {
+    STREAM_CONNECT,
+    STREAM_RECV,
+    STREAM_SEND
+};
+void stream_wait(struct stream *, enum stream_wait_type);
+void stream_connect_wait(struct stream *);
+void stream_recv_wait(struct stream *);
+void stream_send_wait(struct stream *);
+
+/* Passive streams: listeners for incoming stream connections. */
+int pstream_open(const char *name, struct pstream **);
+const char *pstream_get_name(const struct pstream *);
+void pstream_close(struct pstream *);
+int pstream_accept(struct pstream *, struct stream **);
+int pstream_accept_block(struct pstream *, struct stream **);
+void pstream_wait(struct pstream *);
+
+#endif /* stream.h */
index 078b107..bc3df23 100644 (file)
@@ -59,6 +59,12 @@ svec_clear(struct svec *svec)
     svec->n = 0;
 }
 
+bool
+svec_is_empty(const struct svec *svec)
+{
+    return svec->n == 0;
+}
+
 void
 svec_add(struct svec *svec, const char *name)
 {
@@ -366,6 +372,22 @@ svec_join(const struct svec *svec,
     return ds_cstr(&ds);
 }
 
+/* Breaks 's' into tokens at any character in 'delimiters', and appends each
+ * token to 'svec'.  Empty tokens are not added. */
+void
+svec_split(struct svec *svec, const char *s_, const char *delimiters)
+{
+    char *s = xstrdup(s_);
+    char *save_ptr = NULL;
+    char *token;
+
+    for (token = strtok_r(s, delimiters, &save_ptr); token != NULL;
+         token = strtok_r(NULL, delimiters, &save_ptr)) {
+        svec_add(svec, token);
+    }
+    free(s);
+}
+
 const char *
 svec_back(const struct svec *svec)
 {
index e1736bc..d9bb8a7 100644 (file)
 
 #include <stdbool.h>
 #include <stddef.h>
+#ifdef  __cplusplus
+extern "C" {
+#endif
 
 struct svec {
     char **names;
@@ -32,6 +36,7 @@ void svec_init(struct svec *);
 void svec_clone(struct svec *, const struct svec *);
 void svec_destroy(struct svec *);
 void svec_clear(struct svec *);
+bool svec_is_empty(const struct svec *);
 void svec_add(struct svec *, const char *);
 void svec_add_nocopy(struct svec *, char *);
 void svec_del(struct svec *, const char *);
@@ -52,6 +57,7 @@ void svec_swap(struct svec *a, struct svec *b);
 void svec_print(const struct svec *svec, const char *title);
 void svec_parse_words(struct svec *svec, const char *words);
 bool svec_equal(const struct svec *, const struct svec *);
+void svec_split(struct svec *, const char *s, const char *delimiters);
 char *svec_join(const struct svec *,
                 const char *delimiter, const char *terminator);
 const char *svec_back(const struct svec *);
@@ -65,5 +71,9 @@ void svec_pop_back(struct svec *);
           ? (NAME) = (SVEC)->names[INDEX], 1    \
           : 0);                                 \
          (INDEX)++)
+#ifdef  __cplusplus
+}
+#endif
 
 #endif /* svec.h */
index 8ad8d06..ab564a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@ static struct timeval now;
 static time_t deadline = TIME_MIN;
 
 static void set_up_timer(void);
+static void set_up_signal(int flags);
 static void sigalrm_handler(int);
 static void refresh_if_ticked(void);
 static time_t time_add(time_t, time_t);
@@ -51,13 +52,11 @@ static void block_sigalrm(sigset_t *);
 static void unblock_sigalrm(const sigset_t *);
 static void log_poll_interval(long long int last_wakeup,
                               const struct rusage *last_rusage);
-static long long int timeval_to_msec(const struct timeval *);
 
 /* Initializes the timetracking module. */
 void
 time_init(void)
 {
-    struct sigaction sa;
     if (inited) {
         return;
     }
@@ -68,17 +67,49 @@ time_init(void)
     gettimeofday(&now, NULL);
     tick = false;
 
-    /* Set up signal handler. */
+    set_up_signal(SA_RESTART);
+    set_up_timer();
+}
+
+static void
+set_up_signal(int flags)
+{
+    struct sigaction sa;
+
     memset(&sa, 0, sizeof sa);
     sa.sa_handler = sigalrm_handler;
     sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
+    sa.sa_flags = flags;
     if (sigaction(SIGALRM, &sa, NULL)) {
         ovs_fatal(errno, "sigaction(SIGALRM) failed");
     }
+}
 
-    /* Set up periodic signal. */
-    set_up_timer();
+/* Remove SA_RESTART from the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will return EINTR instead of
+ * continuing after the signal handler returns.
+ *
+ * time_disable_restart() and time_enable_restart() may be usefully wrapped
+ * around function calls that might otherwise block forever unless interrupted
+ * by a signal, e.g.:
+ *
+ *   time_disable_restart();
+ *   fcntl(fd, F_SETLKW, &lock);
+ *   time_enable_restart();
+ */
+void
+time_disable_restart(void)
+{
+    set_up_signal(0);
+}
+
+/* Add SA_RESTART to the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will continue after the
+ * signal handler returns instead of returning EINTR. */
+void
+time_enable_restart(void)
+{
+    set_up_signal(SA_RESTART);
 }
 
 static void
@@ -255,7 +286,7 @@ unblock_sigalrm(const sigset_t *oldsigs)
     }
 }
 
-static long long int
+long long int
 timeval_to_msec(const struct timeval *tv)
 {
     return (long long int) tv->tv_sec * 1000 + tv->tv_usec / 1000;
@@ -286,10 +317,10 @@ log_poll_interval(long long int last_wakeup, const struct rusage *last_rusage)
         struct rusage rusage;
 
         getrusage(RUSAGE_SELF, &rusage);
-        VLOG_WARN("%u ms poll interval (%lld ms user, %lld ms system) "
+        VLOG_WARN("%lld ms poll interval (%lld ms user, %lld ms system) "
                   "is over %u times the weighted mean interval %u ms "
                   "(%u samples)",
-                  (interval + 8) / 16,
+                  now - last_wakeup,
                   timeval_diff_msec(&rusage.ru_utime, &last_rusage->ru_utime),
                   timeval_diff_msec(&rusage.ru_stime, &last_rusage->ru_stime),
                   interval / mean_interval,
index 8567d75..89abe80 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "type-props.h"
 #include "util.h"
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 struct pollfd;
 struct timeval;
 
@@ -41,6 +45,8 @@ BUILD_ASSERT_DECL(TYPE_IS_SIGNED(time_t));
 #define TIME_UPDATE_INTERVAL 100
 
 void time_init(void);
+void time_disable_restart(void);
+void time_enable_restart(void);
 void time_postfork(void);
 void time_refresh(void);
 time_t time_now(void);
@@ -49,4 +55,10 @@ void time_timeval(struct timeval *);
 void time_alarm(unsigned int secs);
 int time_poll(struct pollfd *, int n_pollfds, int timeout);
 
+long long int timeval_to_msec(const struct timeval *);
+
+#ifdef  __cplusplus
+}
+#endif
+
 #endif /* timeval.h */
diff --git a/lib/unicode.c b/lib/unicode.c
new file mode 100644 (file)
index 0000000..e8fea86
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "unicode.h"
+
+#include <inttypes.h>
+
+#include "dynamic-string.h"
+#include "util.h"
+
+/* Returns the unicode code point corresponding to leading surrogate 'leading'
+ * and trailing surrogate 'trailing'.  The return value will not make any
+ * sense if 'leading' or 'trailing' are not in the correct ranges for leading
+ * or trailing surrogates. */
+int
+utf16_decode_surrogate_pair(int leading, int trailing)
+{
+    /*
+     *  Leading surrogate:         110110wwwwxxxxxx
+     * Trailing surrogate:         110111xxxxxxxxxx
+     *         Code point: 000uuuuuxxxxxxxxxxxxxxxx
+     */
+    int w = (leading >> 6) & 0xf;
+    int u = w + 1;
+    int x0 = leading & 0x3f;
+    int x1 = trailing & 0x3ff;
+    return (u << 16) | (x0 << 10) | x1;
+}
+
+/* Returns the number of Unicode characters in UTF-8 string 's'. */
+size_t
+utf8_length(const char *s_)
+{
+    const uint8_t *s;
+    size_t length;
+
+    length = 0;
+    for (s = (const uint8_t *) s_; *s != '\0'; s++) {
+        /* The most-significant bits of the first byte in a character are one
+         * of 2#01, 2#00, or 2#11.  2#10 is a continuation byte. */
+        length += (*s & 0xc0) != 0x80;
+    }
+    return length;
+}
+
+static char *
+invalid_utf8_sequence(const uint8_t *s, int n, size_t *lengthp)
+{
+    struct ds msg;
+    int i;
+
+    if (lengthp) {
+        *lengthp = 0;
+    }
+
+    ds_init(&msg);
+    ds_put_cstr(&msg, "invalid UTF-8 sequence");
+    for (i = 0; i < n; i++) {
+        ds_put_format(&msg, " 0x%02"PRIx8, s[i]);
+    }
+    return ds_steal_cstr(&msg);
+}
+
+struct utf8_sequence {
+    uint8_t octets[5][2];
+};
+
+static const struct utf8_sequence *
+lookup_utf8_sequence(uint8_t c)
+{
+    static const struct utf8_sequence seqs[] = {
+        { { { 0x01, 0x7f },
+            { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xc2, 0xdf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xe0, 0xe0 }, { 0xa0, 0xbf }, { 0x80, 0xbf },
+            {0,0}, {0, 0 } } },
+
+        { { { 0xe1, 0xec }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xed, 0xed }, { 0x80, 0x9f }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xee, 0xef }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xf0, 0xf0 }, { 0x90, 0xbf }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+
+        { { { 0xf1, 0xf3 }, { 0x80, 0xbf }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+
+        { { { 0xf4, 0xf4 }, { 0x80, 0x8f }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(seqs); i++) {
+        const uint8_t *o = seqs[i].octets[0];
+        if (c >= o[0] && c <= o[1]) {
+            return &seqs[i];
+        }
+    }
+    return NULL;
+}
+
+/* Checks that 's' is a valid, null-terminated UTF-8 string.  If so, returns a
+ * null pointer and sets '*lengthp' to the number of Unicode characters in
+ * 's'.  If not, returns an error message that the caller must free and sets
+ * '*lengthp' to 0.
+ *
+ * 'lengthp' may be NULL if the length is not needed. */
+char *
+utf8_validate(const char *s_, size_t *lengthp)
+{
+    size_t length = 0;
+    const uint8_t *s;
+
+    for (s = (const uint8_t *) s_; *s != '\0'; ) {
+        length++;
+        if (s[0] < 0x80) {
+            s++;
+        } else {
+            const struct utf8_sequence *seq;
+            int i;
+
+            seq = lookup_utf8_sequence(s[0]);
+            if (!seq) {
+                return invalid_utf8_sequence(s, 1, lengthp);
+            }
+
+            for (i = 1; seq->octets[i][0]; i++) {
+                const uint8_t *o = seq->octets[i];
+                if (s[i] < o[0] || s[i] > o[1]) {
+                    return invalid_utf8_sequence(s, i + 1, lengthp);
+                }
+            }
+            s += i;
+        }
+    }
+    if (lengthp) {
+        *lengthp = length;
+    }
+    return NULL;
+}
diff --git a/lib/unicode.h b/lib/unicode.h
new file mode 100644 (file)
index 0000000..b2078e6
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "compiler.h"
+
+/* Returns true if 'c' is a Unicode code point, otherwise false. */
+static inline bool
+uc_is_code_point(int c)
+{
+    return c >= 0 && c <= 0x10ffff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
+static inline bool
+uc_is_leading_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdbff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
+static inline bool
+uc_is_trailing_surrogate(int c)
+{
+    return c >= 0xdc00 && c <= 0xdfff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading or trailing
+ * surrogate. */
+static inline bool
+uc_is_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdfff;
+}
+
+int utf16_decode_surrogate_pair(int leading, int trailing);
+
+size_t utf8_length(const char *);
+char *utf8_validate(const char *, size_t *lengthp) WARN_UNUSED_RESULT;
+
+#endif /* unicode.h */
index 526c3fe..88fe603 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,7 +44,8 @@
 #include "vlog.h"
 \f
 struct unixctl_command {
-    void (*cb)(struct unixctl_conn *, const char *args);
+    unixctl_cb_func *cb;
+    void *aux;
 };
 
 struct unixctl_conn {
@@ -76,13 +77,14 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
 static void
-unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
+unixctl_help(struct unixctl_conn *conn, const char *args OVS_UNUSED,
+             void *aux OVS_UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     struct shash_node *node;
 
     ds_put_cstr(&ds, "The available commands are:\n");
-    HMAP_FOR_EACH (node, struct shash_node, node, &commands.map) {
+    SHASH_FOR_EACH (node, &commands) {
         ds_put_format(&ds, "\t%s\n", node->name);
     }
     unixctl_command_reply(conn, 214, ds_cstr(&ds));
@@ -90,8 +92,7 @@ unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 void
-unixctl_command_register(const char *name,
-                         void (*cb)(struct unixctl_conn *, const char *args))
+unixctl_command_register(const char *name, unixctl_cb_func *cb, void *aux)
 {
     struct unixctl_command *command;
 
@@ -99,6 +100,7 @@ unixctl_command_register(const char *name,
            || shash_find_data(&commands, name) == cb);
     command = xmalloc(sizeof *command);
     command->cb = cb;
+    command->aux = aux;
     shash_add(&commands, name, command);
 }
 
@@ -168,7 +170,7 @@ unixctl_command_reply(struct unixctl_conn *conn,
  * A program that (optionally) daemonizes itself should call this function
  * *after* daemonization, so that the socket name contains the pid of the
  * daemon instead of the pid of the program that exited.  (Otherwise,
- * "ovs-appctl --target <program>.pid" will fail.)
+ * "ovs-appctl --target=<program>" will fail.)
  *
  * Returns 0 if successful, otherwise a positive errno value.  If successful,
  * sets '*serverp' to the new unixctl_server, otherwise to NULL. */
@@ -178,17 +180,13 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
     struct unixctl_server *server;
     int error;
 
-    unixctl_command_register("help", unixctl_help);
+    unixctl_command_register("help", unixctl_help, NULL);
 
     server = xmalloc(sizeof *server);
     list_init(&server->conns);
 
     if (path) {
-        if (path[0] == '/') {
-            server->path = xstrdup(path);
-        } else {
-            server->path = xasprintf("%s/%s", ovs_rundir, path);
-        }
+        server->path = abs_file_name(ovs_rundir, path);
     } else {
         server->path = xasprintf("%s/%s.%ld.ctl", ovs_rundir,
                                  program_name, (long int) getpid());
@@ -198,22 +196,21 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
                                   NULL);
     if (server->fd < 0) {
         error = -server->fd;
-        fprintf(stderr, "Could not initialize control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "could not initialize control socket %s",
+                  server->path);
         goto error;
     }
 
     if (chmod(server->path, S_IRUSR | S_IWUSR) < 0) {
         error = errno;
-        fprintf(stderr, "Failed to chmod control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "failed to chmod control socket %s", server->path);
         goto error;
     }
 
     if (listen(server->fd, 10) < 0) {
         error = errno;
-        fprintf(stderr, "Failed to listen on control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "Failed to listen on control socket %s",
+                  server->path);
         goto error;
     }
 
@@ -282,7 +279,7 @@ process_command(struct unixctl_conn *conn, char *s)
 
     command = shash_find_data(&commands, name);
     if (command) {
-        command->cb(conn, args);
+        command->cb(conn, args, command->aux);
     } else {
         char *msg = xasprintf("\"%s\" is not a valid command", name);
         unixctl_command_reply(conn, 400, msg);
@@ -438,8 +435,7 @@ unixctl_server_destroy(struct unixctl_server *server)
         }
 
         close(server->fd);
-        unlink(server->path);
-        fatal_signal_remove_file_to_unlink(server->path);
+        fatal_signal_unlink_file_now(server->path);
         free(server->path);
         free(server);
     }
@@ -461,11 +457,7 @@ unixctl_client_create(const char *path, struct unixctl_client **clientp)
 
     /* Determine location. */
     client = xmalloc(sizeof *client);
-    if (path[0] == '/') {
-        client->connect_path = xstrdup(path);
-    } else {
-        client->connect_path = xasprintf("%s/%s", ovs_rundir, path);
-    }
+    client->connect_path = abs_file_name(ovs_rundir, path);
     client->bind_path = xasprintf("/tmp/vlog.%ld.%d",
                                   (long int) getpid(), counter++);
 
@@ -504,8 +496,7 @@ void
 unixctl_client_destroy(struct unixctl_client *client)
 {
     if (client) {
-        unlink(client->bind_path);
-        fatal_signal_remove_file_to_unlink(client->bind_path);
+        fatal_signal_unlink_file_now(client->bind_path);
         free(client->bind_path);
         free(client->connect_path);
         fclose(client->stream);
@@ -553,7 +544,9 @@ unixctl_client_transact(struct unixctl_client *client,
 
         s = ds_cstr(&line);
         if (*reply_code == -1) {
-            if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2])) {
+            if (!isdigit((unsigned char)s[0])
+                    || !isdigit((unsigned char)s[1])
+                    || !isdigit((unsigned char)s[2])) {
                 VLOG_WARN("reply from %s does not start with 3-digit code",
                           client->connect_path);
                 error = EPROTO;
index 18748aa..89199bb 100644 (file)
 
 #ifndef UNIXCTL_H
 #define UNIXCTL_H 1
+#ifdef  __cplusplus
+extern "C" {
+#endif
 
 /* Server for Unix domain socket control connection. */
 struct unixctl_server;
@@ -35,10 +39,15 @@ const char *unixctl_client_target(const struct unixctl_client *);
 
 /* Command registration. */
 struct unixctl_conn;
+typedef void unixctl_cb_func(struct unixctl_conn *,
+                             const char *args, void *aux);
 void unixctl_command_register(const char *name,
-                              void (*cb)(struct unixctl_conn *,
-                                         const char *args));
+                              unixctl_cb_func *cb, void *aux);
 void unixctl_command_reply(struct unixctl_conn *, int code,
                            const char *body);
+#ifdef  __cplusplus
+}
+#endif
 
 #endif /* unixctl.h */
index f766d59..19f13dd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include "coverage.h"
 
+#define THIS_MODULE VLM_util
+#include "vlog.h"
+
 const char *program_name;
 
 void
@@ -42,6 +46,12 @@ xcalloc(size_t count, size_t size)
     return p;
 }
 
+void *
+xzalloc(size_t size)
+{
+    return xcalloc(1, size);
+}
+
 void *
 xmalloc(size_t size) 
 {
@@ -162,8 +172,10 @@ ovs_error(int err_no, const char *format, ...)
     va_start(args, format);
     vfprintf(stderr, format, args);
     va_end(args);
-    if (err_no != 0)
-        fprintf(stderr, " (%s)", strerror(err_no));
+    if (err_no != 0) {
+        fprintf(stderr, " (%s)",
+                err_no == EOF ? "end of file" : strerror(err_no));
+    }
     putc('\n', stderr);
 
     errno = save_errno;
@@ -294,3 +306,148 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
 {
     return str_to_llong(s, base, (long long *) ull);
 }
+
+/* Converts floating-point string 's' into a double.  If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+    int save_errno = errno;
+    char *tail;
+    errno = 0;
+    *d = strtod(s, &tail);
+    if (errno == EINVAL || (errno == ERANGE && *d != 0)
+        || tail == s || *tail != '\0') {
+        errno = save_errno;
+        *d = 0;
+        return false;
+    } else {
+        errno = save_errno;
+        return true;
+    }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+    switch (c) {
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+        return c - '0';
+
+    case 'a': case 'A':
+        return 0xa;
+
+    case 'b': case 'B':
+        return 0xb;
+
+    case 'c': case 'C':
+        return 0xc;
+
+    case 'd': case 'D':
+        return 0xd;
+
+    case 'e': case 'E':
+        return 0xe;
+
+    case 'f': case 'F':
+        return 0xf;
+    }
+
+    NOT_REACHED();
+}
+
+/* Returns the current working directory as a malloc()'d string, or a null
+ * pointer if the current working directory cannot be determined. */
+char *
+get_cwd(void)
+{
+    long int path_max;
+    size_t size;
+
+    /* Get maximum path length or at least a reasonable estimate. */
+    path_max = pathconf(".", _PC_PATH_MAX);
+    size = (path_max < 0 ? 1024
+            : path_max > 10240 ? 10240
+            : path_max);
+
+    /* Get current working directory. */
+    for (;;) {
+        char *buf = xmalloc(size);
+        if (getcwd(buf, size)) {
+            return xrealloc(buf, strlen(buf) + 1);
+        } else {
+            int error = errno;
+            free(buf);
+            if (error != ERANGE) {
+                VLOG_WARN("getcwd failed (%s)", strerror(error));
+                return NULL;
+            }
+            size *= 2;
+        }
+    }
+}
+
+/* Returns the directory name portion of 'file_name' as a malloc()'d string,
+ * similar to the POSIX dirname() function but thread-safe. */
+char *
+dir_name(const char *file_name)
+{
+    size_t len = strlen(file_name);
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] != '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    if (!len) {
+        return xstrdup((file_name[0] == '/'
+                        && file_name[1] == '/'
+                        && file_name[2] != '/') ? "//"
+                       : file_name[0] == '/' ? "/"
+                       : ".");
+    } else {
+        return xmemdup0(file_name, len);
+    }
+}
+
+/* If 'file_name' starts with '/', returns a copy of 'file_name'.  Otherwise,
+ * returns an absolute path to 'file_name' considering it relative to 'dir',
+ * which itself must be absolute.  'dir' may be null or the empty string, in
+ * which case the current working directory is used.
+ *
+ * Returns a null pointer if 'dir' is null and getcwd() fails. */
+char *
+abs_file_name(const char *dir, const char *file_name)
+{
+    if (file_name[0] == '/') {
+        return xstrdup(file_name);
+    } else if (dir && dir[0]) {
+        char *separator = dir[strlen(dir) - 1] == '/' ? "" : "/";
+        return xasprintf("%s%s%s", dir, separator, file_name);
+    } else {
+        char *cwd = get_cwd();
+        if (cwd) {
+            char *abs_name = xasprintf("%s/%s", cwd, file_name);
+            free(cwd);
+            return abs_name;
+        } else {
+            return NULL;
+        }
+    }
+}
+
+
+/* Pass a value to this function if it is marked with
+ * __attribute__((warn_unused_result)) and you genuinely want to ignore 
+ * its return value.  (Note that every scalar type can be implicitly 
+ * converted to bool.) */
+void ignore(bool x OVS_UNUSED) { }
index a945eb2..9df6db5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 extern const char *program_name;
 
+/* Returns the number of elements in ARRAY. */
 #define ARRAY_SIZE(ARRAY) (sizeof ARRAY / sizeof *ARRAY)
-#define ROUND_UP(X, Y) (((X) + ((Y) - 1)) / (Y) * (Y))
+
+/* Returns X / Y, rounding up.  X must be nonnegative to round correctly. */
+#define DIV_ROUND_UP(X, Y) (((X) + ((Y) - 1)) / (Y))
+
+/* Returns X rounded up to the nearest multiple of Y. */
+#define ROUND_UP(X, Y) (DIV_ROUND_UP(X, Y) * (Y))
+
+/* Returns X rounded down to the nearest multiple of Y. */
 #define ROUND_DOWN(X, Y) ((X) / (Y) * (Y))
+
+/* Returns true if X is a power of 2, otherwise false. */
 #define IS_POW2(X) ((X) && !((X) & ((X) - 1)))
 
 #ifndef MIN
@@ -66,8 +76,6 @@ extern const char *program_name;
 #endif
 
 #define NOT_REACHED() abort()
-#define NOT_IMPLEMENTED() abort()
-#define NOT_TESTED() ((void) 0) /* XXX should print a message. */
 
 /* Given POINTER, the address of the given MEMBER in a STRUCT object, returns
    the STRUCT object. */
@@ -88,6 +96,7 @@ void ovs_print_version(char *date, char *time,
 void out_of_memory(void) NO_RETURN;
 void *xmalloc(size_t) MALLOC_LIKE;
 void *xcalloc(size_t, size_t) MALLOC_LIKE;
+void *xzalloc(size_t) MALLOC_LIKE;
 void *xrealloc(void *, size_t);
 void *xmemdup(const void *, size_t) MALLOC_LIKE;
 char *xmemdup0(const char *, size_t) MALLOC_LIKE;
@@ -110,6 +119,16 @@ bool str_to_uint(const char *, int base, unsigned int *);
 bool str_to_ulong(const char *, int base, unsigned long *);
 bool str_to_ullong(const char *, int base, unsigned long long *);
 
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
+char *get_cwd(void);
+char *dir_name(const char *file_name);
+char *abs_file_name(const char *dir, const char *file_name);
+
+void ignore(bool x OVS_UNUSED);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/uuid.c b/lib/uuid.c
new file mode 100644 (file)
index 0000000..9aaa915
--- /dev/null
@@ -0,0 +1,240 @@
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "uuid.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "aes128.h"
+#include "sha1.h"
+#include "socket-util.h"
+#include "util.h"
+
+static struct aes128 key;
+static uint64_t counter[2];
+BUILD_ASSERT_DECL(sizeof counter == 16);
+
+static void do_init(void);
+static void read_urandom(void *buffer, size_t n);
+
+/*
+ * Initialize the UUID module.  Aborts the program with an error message if
+ * initialization fails (which should never happen on a properly configured
+ * machine.)
+ *
+ * Currently initialization is only needed by uuid_generate().  uuid_generate()
+ * will automatically call uuid_init() itself, so it's only necessary to call
+ * this function explicitly if you want to abort the program earlier than the
+ * first UUID generation in case of failure.
+ */
+void
+uuid_init(void)
+{
+    static bool inited;
+    if (!inited) {
+        do_init();
+        inited = true;
+    }
+}
+
+/* Generates a new random UUID in 'uuid'.
+ *
+ * We go to some trouble to ensure as best we can that the generated UUID has
+ * these properties:
+ *
+ *      - Uniqueness.  The random number generator is seeded using both the
+ *        system clock and the system random number generator, plus a few
+ *        other identifiers, which is about as good as we can get in any kind
+ *        of simple way.
+ *
+ *      - Unpredictability.  In some situations it could be bad for an
+ *        adversary to be able to guess the next UUID to be generated with some
+ *        probability of success.  This property may or may not be important
+ *        for our purposes, but it is better if we can get it.
+ *
+ * To ensure both of these, we start by taking our seed data and passing it
+ * through SHA-1.  We use the result as an AES-128 key.  We also generate a
+ * random 16-byte value[*] which we then use as the counter for CTR mode.  To
+ * generate a UUID in a manner compliant with the above goals, we merely
+ * increment the counter and encrypt it.
+ *
+ * [*] It is not actually important that the initial value of the counter be
+ *     random.  AES-128 in counter mode is secure either way.
+ */
+void
+uuid_generate(struct uuid *uuid)
+{
+    uuid_init();
+
+    /* Increment the counter. */
+    if (++counter[1] == 0) {
+        counter[0]++;
+    }
+
+    /* AES output is exactly 16 bytes, so we encrypt directly into 'uuid'. */
+    aes128_encrypt(&key, counter, uuid);
+
+    /* Set bits to indicate a random UUID.  See RFC 4122 section 4.4. */
+    uuid->parts[2] &= ~0xc0000000;
+    uuid->parts[2] |=  0x80000000;
+    uuid->parts[1] &= ~0x0000f000;
+    uuid->parts[1] |=  0x00004000;
+}
+
+/* Sets 'uuid' to all-zero-bits. */
+void
+uuid_zero(struct uuid *uuid)
+{
+    uuid->parts[0] = uuid->parts[1] = uuid->parts[2] = uuid->parts[3] = 0;
+}
+
+/* Returns true if 'uuid' is all zero, otherwise false. */
+bool
+uuid_is_zero(const struct uuid *uuid)
+{
+    return (!uuid->parts[0] && !uuid->parts[1]
+            && !uuid->parts[2] && !uuid->parts[3]);
+}
+
+/* Compares 'a' and 'b'.  Returns a negative value if 'a < b', zero if 'a ==
+ * b', or positive if 'a > b'.  The ordering is lexicographical order of the
+ * conventional way of writing out UUIDs as strings. */
+int
+uuid_compare_3way(const struct uuid *a, const struct uuid *b)
+{
+    if (a->parts[0] != b->parts[0]) {
+        return a->parts[0] > b->parts[0] ? 1 : -1;
+    } else if (a->parts[1] != b->parts[1]) {
+        return a->parts[1] > b->parts[1] ? 1 : -1;
+    } else if (a->parts[2] != b->parts[2]) {
+        return a->parts[2] > b->parts[2] ? 1 : -1;
+    } else if (a->parts[3] != b->parts[3]) {
+        return a->parts[3] > b->parts[3] ? 1 : -1;
+    } else {
+        return 0;
+    }
+}
+
+/* Attempts to convert string 's' into a UUID in 'uuid'.  Returns true if
+ * successful, which will be the case only if 's' has the exact format
+ * specified by RFC 4122.  Returns false on failure.  On failure, 'uuid' will
+ * be set to all-zero-bits. */
+bool
+uuid_from_string(struct uuid *uuid, const char *s)
+{
+    if (!uuid_from_string_prefix(uuid, s)) {
+        return false;
+    } else if (s[UUID_LEN] != '\0') {
+        uuid_zero(uuid);
+        return false;
+    } else {
+        return true;
+    }
+}
+
+/* Same as uuid_from_string() but s[UUID_LEN] is not required to be a null byte
+ * to succeed; that is, 's' need only begin with UUID syntax, not consist
+ * entirely of it. */
+bool
+uuid_from_string_prefix(struct uuid *uuid, const char *s)
+{
+    static const char template[] = "00000000-1111-1111-2222-222233333333";
+    const char *t;
+
+    uuid_zero(uuid);
+    for (t = template; ; t++, s++) {
+        if (*t >= '0' && *t <= '3') {
+            uint32_t *part = &uuid->parts[*t - '0'];
+            if (!isxdigit(*s)) {
+                goto error;
+            }
+            *part = (*part << 4) + hexit_value(*s);
+        } else if (*t == 0) {
+            return true;
+        } else if (*t != *s) {
+            goto error;
+        }
+    }
+
+error:
+    uuid_zero(uuid);
+    return false;
+}
+\f
+static void
+read_urandom(void *buffer, size_t n)
+{
+    static const char urandom[] = "/dev/urandom";
+    size_t bytes_read;
+    int error;
+    int fd;
+
+    fd = open(urandom, O_RDONLY);
+    if (fd < 0) {
+        ovs_fatal(errno, "%s: open failed", urandom);
+    }
+    error = read_fully(fd, buffer, n, &bytes_read);
+    if (error == EOF) {
+        ovs_fatal(0, "%s: unexpected end of file", urandom);
+    } else if (error) {
+        ovs_fatal(error, "%s: read error", urandom);
+    }
+    close(fd);
+}
+
+static void
+do_init(void)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct sha1_ctx sha1_ctx;
+    uint8_t random_seed[16];
+    struct timeval now;
+    pid_t pid, ppid;
+    uid_t uid;
+    gid_t gid;
+
+    /* Get seed data. */
+    read_urandom(random_seed, sizeof random_seed);
+    if (gettimeofday(&now, NULL)) {
+        ovs_fatal(errno, "gettimeofday failed");
+    }
+    pid = getpid();
+    ppid = getppid();
+    uid = getuid();
+    gid = getgid();
+
+    /* Convert seed into key. */
+    sha1_init(&sha1_ctx);
+    sha1_update(&sha1_ctx, random_seed, sizeof random_seed);
+    sha1_update(&sha1_ctx, &pid, sizeof pid);
+    sha1_update(&sha1_ctx, &ppid, sizeof ppid);
+    sha1_update(&sha1_ctx, &uid, sizeof uid);
+    sha1_update(&sha1_ctx, &gid, sizeof gid);
+    sha1_final(&sha1_ctx, sha1);
+
+    /* Generate key. */
+    BUILD_ASSERT(sizeof sha1 >= 16);
+    aes128_schedule(&key, sha1);
+
+    /* Generate initial counter. */
+    read_urandom(counter, sizeof counter);
+}
diff --git a/lib/uuid.h b/lib/uuid.h
new file mode 100644 (file)
index 0000000..8159779
--- /dev/null
@@ -0,0 +1,82 @@
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UUID_H
+#define UUID_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "util.h"
+
+#define UUID_BIT 128            /* Number of bits in a UUID. */
+#define UUID_OCTET (UUID_BIT / 8) /* Number of bytes in a UUID. */
+
+/* A Universally Unique IDentifier (UUID) compliant with RFC 4122.
+ *
+ * Each of the parts is stored in host byte order, but the parts themselves are
+ * ordered from left to right.  That is, (parts[0] >> 24) is the first 8 bits
+ * of the UUID when output in the standard form, and (parts[3] & 0xff) is the
+ * final 8 bits. */
+struct uuid {
+    uint32_t parts[4];
+};
+BUILD_ASSERT_DECL(sizeof(struct uuid) == UUID_OCTET);
+
+/* Formats a UUID as a string, in the conventional format.
+ *
+ * Example:
+ *   struct uuid uuid = ...;
+ *   printf("This UUID is "UUID_FMT"\n", UUID_ARGS(&uuid));
+ *
+ */
+#define UUID_LEN 36
+#define UUID_FMT "%08x-%04x-%04x-%04x-%04x%08x"
+#define UUID_ARGS(UUID)                             \
+    ((unsigned int) ((UUID)->parts[0])),            \
+    ((unsigned int) ((UUID)->parts[1] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[1] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[2] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[2] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[3]))
+
+/* Returns a hash value for 'uuid'.  This hash value is the same regardless of
+ * whether we are running on a 32-bit or 64-bit or big-endian or little-endian
+ * architecture. */
+static inline size_t
+uuid_hash(const struct uuid *uuid)
+{
+    return uuid->parts[0];
+}
+
+/* Returns true if 'a == b', false otherwise. */
+static inline bool
+uuid_equals(const struct uuid *a, const struct uuid *b)
+{
+    return (a->parts[0] == b->parts[0]
+            && a->parts[1] == b->parts[1]
+            && a->parts[2] == b->parts[2]
+            && a->parts[3] == b->parts[3]);
+}
+
+void uuid_init(void);
+void uuid_generate(struct uuid *);
+void uuid_zero(struct uuid *);
+bool uuid_is_zero(const struct uuid *);
+int uuid_compare_3way(const struct uuid *, const struct uuid *);
+bool uuid_from_string(struct uuid *, const char *);
+bool uuid_from_string_prefix(struct uuid *, const char *);
+
+#endif /* vswitchd/cfgdb.h */
diff --git a/lib/vconn-active.man b/lib/vconn-active.man
new file mode 100644 (file)
index 0000000..be96ca8
--- /dev/null
@@ -0,0 +1,13 @@
+.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
+The specified SSL \fIport\fR (default: 6633) on the host at the given
+\fIip\fR, which must be expressed as an IP address (not a DNS name).
+The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.
+.
+.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
+The specified TCP \fIport\fR (default: 6633) on the host at the given
+\fIip\fR, which must be expressed as an IP address (not a DNS name).
+.
+.TP
+\fBunix:\fIfile\fR
+The Unix domain server socket named \fIfile\fR.
diff --git a/lib/vconn-passive.man b/lib/vconn-passive.man
new file mode 100644 (file)
index 0000000..80e7084
--- /dev/null
@@ -0,0 +1,17 @@
+.IP "\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]"
+Listens for OpenFlow SSL connections on \fIport\fR (default: 6633).
+The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.  By
+default, \fB\*(PN\fR listens for connections to any local IP address,
+but \fIip\fR may be specified to listen only for connections to the
+given \fIip\fR.
+.
+.IP "\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]"
+Listens for OpenFlow TCP connections on \fIport\fR (default: 6633).
+By default, \fB\*(PN\fR listens for connections to any local IP
+address, but \fIip\fR may be specified to listen only for connections
+to the given \fIip\Ar.
+.
+.IP "\fBpunix:\fIfile\fR"
+Listens for OpenFlow connections on the Unix domain server socket
+named \fIfile\fR.
index ae025f7..1c8b86d 100644 (file)
@@ -39,11 +39,10 @@ struct vconn {
     uint32_t local_ip;
     uint16_t local_port;
     char *name;
-    bool reconnectable;
 };
 
 void vconn_init(struct vconn *, struct vconn_class *, int connect_status,
-                const char *name, bool reconnectable);
+                const char *name);
 void vconn_set_remote_ip(struct vconn *, uint32_t remote_ip);
 void vconn_set_remote_port(struct vconn *, uint16_t remote_port);
 void vconn_set_local_ip(struct vconn *, uint32_t local_ip);
@@ -109,6 +108,18 @@ struct vconn_class {
      * accepted for transmission, it should return EAGAIN. */
     int (*send)(struct vconn *vconn, struct ofpbuf *msg);
 
+    /* Allows 'vconn' to perform maintenance activities, such as flushing
+     * output buffers.
+     *
+     * May be null if 'vconn' doesn't have anything to do here. */
+    void (*run)(struct vconn *vconn);
+
+    /* Arranges for the poll loop to wake up when 'vconn' needs to perform
+     * maintenance activities.
+     *
+     * May be null if 'vconn' doesn't have anything to do here. */
+    void (*run_wait)(struct vconn *vconn);
+
     /* Arranges for the poll loop to wake up when 'vconn' is ready to take an
      * action of the given 'type'. */
     void (*wait)(struct vconn *vconn, enum vconn_wait_type type);
index 6d38255..aa2e619 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
  */
 
 #include <config.h>
-#include "vconn-stream.h"
 #include <assert.h>
 #include <errno.h>
 #include <poll.h>
 #include <string.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include "fatal-signal.h"
 #include "leak-checker.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "poll-loop.h"
 #include "socket-util.h"
+#include "stream.h"
 #include "util.h"
 #include "vconn-provider.h"
 #include "vconn.h"
 
 /* Active stream socket vconn. */
 
-struct stream_vconn
+struct vconn_stream
 {
     struct vconn vconn;
-    int fd;
+    struct stream *stream;
     struct ofpbuf *rxbuf;
     struct ofpbuf *txbuf;
-    struct poll_waiter *tx_waiter;
 };
 
 static struct vconn_class stream_vconn_class;
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
 
-static void stream_clear_txbuf(struct stream_vconn *);
+static void vconn_stream_clear_txbuf(struct vconn_stream *);
+static int count_fields(const char *);
 
-int
-new_stream_vconn(const char *name, int fd, int connect_status,
-                 bool reconnectable, struct vconn **vconnp)
+static struct vconn *
+vconn_stream_new(struct stream *stream, int connect_status)
 {
-    struct stream_vconn *s;
+    struct vconn_stream *s;
 
     s = xmalloc(sizeof *s);
     vconn_init(&s->vconn, &stream_vconn_class, connect_status,
-               name, reconnectable);
-    s->fd = fd;
+               stream_get_name(stream));
+    s->stream = stream;
     s->txbuf = NULL;
-    s->tx_waiter = NULL;
     s->rxbuf = NULL;
-    *vconnp = &s->vconn;
+    s->vconn.remote_ip = stream_get_remote_ip(stream);
+    s->vconn.remote_port = stream_get_remote_port(stream);
+    s->vconn.local_ip = stream_get_local_ip(stream);
+    s->vconn.local_port = stream_get_local_port(stream);
+    return &s->vconn;
+}
+
+/* Creates a new vconn that will send and receive data on a stream named 'name'
+ * and stores a pointer to the vconn in '*vconnp'.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+vconn_stream_open(const char *name_, char *suffix OVS_UNUSED,
+                  struct vconn **vconnp)
+{
+    struct stream *stream;
+    char *name;
+    int error;
+
+    if (!strncmp(name_, "tcp:", 4) && count_fields(name_) < 3) {
+        name = xasprintf("%s:%d", name_, OFP_TCP_PORT);
+    } else if (!strncmp(name_, "ssl:", 4) && count_fields(name_) < 3) {
+        name = xasprintf("%s:%d", name_, OFP_SSL_PORT);
+    } else {
+        name = xstrdup(name_);
+    }
+    error = stream_open(name, &stream);
+    free(name);
+
+    if (error && error != EAGAIN) {
+        return error;
+    }
+
+    *vconnp = vconn_stream_new(stream, error);
     return 0;
 }
 
-static struct stream_vconn *
-stream_vconn_cast(struct vconn *vconn)
+static struct vconn_stream *
+vconn_stream_cast(struct vconn *vconn)
 {
-    vconn_assert_class(vconn, &stream_vconn_class);
-    return CONTAINER_OF(vconn, struct stream_vconn, vconn);
+    return CONTAINER_OF(vconn, struct vconn_stream, vconn);
 }
 
 static void
-stream_close(struct vconn *vconn)
+vconn_stream_close(struct vconn *vconn)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    poll_cancel(s->tx_waiter);
-    stream_clear_txbuf(s);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    stream_close(s->stream);
+    vconn_stream_clear_txbuf(s);
     ofpbuf_delete(s->rxbuf);
-    close(s->fd);
     free(s);
 }
 
 static int
-stream_connect(struct vconn *vconn)
+vconn_stream_connect(struct vconn *vconn)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    return check_connection_completion(s->fd);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    return stream_connect(s->stream);
 }
 
 static int
-stream_recv(struct vconn *vconn, struct ofpbuf **bufferp)
+vconn_stream_recv(struct vconn *vconn, struct ofpbuf **bufferp)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     struct ofpbuf *rx;
     size_t want_bytes;
     ssize_t retval;
@@ -127,7 +157,7 @@ again:
     }
     ofpbuf_prealloc_tailroom(rx, want_bytes);
 
-    retval = read(s->fd, ofpbuf_tail(rx), want_bytes);
+    retval = stream_recv(s->stream, ofpbuf_tail(rx), want_bytes);
     if (retval > 0) {
         rx->size += retval;
         if (retval == want_bytes) {
@@ -148,199 +178,244 @@ again:
             return EOF;
         }
     } else {
-        return errno;
+        return -retval;
     }
 }
 
 static void
-stream_clear_txbuf(struct stream_vconn *s)
+vconn_stream_clear_txbuf(struct vconn_stream *s)
 {
     ofpbuf_delete(s->txbuf);
     s->txbuf = NULL;
-    s->tx_waiter = NULL;
-}
-
-static void
-stream_do_tx(int fd UNUSED, short int revents UNUSED, void *vconn_)
-{
-    struct vconn *vconn = vconn_;
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    ssize_t n = write(s->fd, s->txbuf->data, s->txbuf->size);
-    if (n < 0) {
-        if (errno != EAGAIN) {
-            VLOG_ERR_RL(&rl, "send: %s", strerror(errno));
-            stream_clear_txbuf(s);
-            return;
-        }
-    } else if (n > 0) {
-        ofpbuf_pull(s->txbuf, n);
-        if (!s->txbuf->size) {
-            stream_clear_txbuf(s);
-            return;
-        }
-    }
-    s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn);
 }
 
 static int
-stream_send(struct vconn *vconn, struct ofpbuf *buffer)
+vconn_stream_send(struct vconn *vconn, struct ofpbuf *buffer)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     ssize_t retval;
 
     if (s->txbuf) {
         return EAGAIN;
     }
 
-    retval = write(s->fd, buffer->data, buffer->size);
+    retval = stream_send(s->stream, buffer->data, buffer->size);
     if (retval == buffer->size) {
         ofpbuf_delete(buffer);
         return 0;
-    } else if (retval >= 0 || errno == EAGAIN) {
+    } else if (retval >= 0 || retval == -EAGAIN) {
         leak_checker_claim(buffer);
         s->txbuf = buffer;
         if (retval > 0) {
             ofpbuf_pull(buffer, retval);
         }
-        s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn);
         return 0;
     } else {
-        return errno;
+        return -retval;
     }
 }
 
 static void
-stream_wait(struct vconn *vconn, enum vconn_wait_type wait)
+vconn_stream_run(struct vconn *vconn)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    ssize_t retval;
+
+    if (!s->txbuf) {
+        return;
+    }
+
+    retval = stream_send(s->stream, s->txbuf->data, s->txbuf->size);
+    if (retval < 0) {
+        if (retval != -EAGAIN) {
+            VLOG_ERR_RL(&rl, "send: %s", strerror(-retval));
+            vconn_stream_clear_txbuf(s);
+            return;
+        }
+    } else if (retval > 0) {
+        ofpbuf_pull(s->txbuf, retval);
+        if (!s->txbuf->size) {
+            vconn_stream_clear_txbuf(s);
+            return;
+        }
+    }
+}
+
+static void
+vconn_stream_run_wait(struct vconn *vconn)
+{
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+
+    if (s->txbuf) {
+        stream_send_wait(s->stream);
+    }
+}
+
+static void
+vconn_stream_wait(struct vconn *vconn, enum vconn_wait_type wait)
+{
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     switch (wait) {
     case WAIT_CONNECT:
-        poll_fd_wait(s->fd, POLLOUT);
+        stream_connect_wait(s->stream);
         break;
 
     case WAIT_SEND:
         if (!s->txbuf) {
-            poll_fd_wait(s->fd, POLLOUT);
+            stream_send_wait(s->stream);
         } else {
-            /* Nothing to do: need to drain txbuf first. */
+            /* Nothing to do: need to drain txbuf first.
+             * vconn_stream_run_wait() will arrange to wake up when there room
+             * to send data, so there's no point in calling poll_fd_wait()
+             * redundantly here. */
         }
         break;
 
     case WAIT_RECV:
-        poll_fd_wait(s->fd, POLLIN);
+        stream_recv_wait(s->stream);
         break;
 
     default:
         NOT_REACHED();
     }
 }
-
-static struct vconn_class stream_vconn_class = {
-    "stream",                   /* name */
-    NULL,                       /* open */
-    stream_close,               /* close */
-    stream_connect,             /* connect */
-    stream_recv,                /* recv */
-    stream_send,                /* send */
-    stream_wait,                /* wait */
-};
 \f
 /* Passive stream socket vconn. */
 
-struct pstream_pvconn
+struct pvconn_pstream
 {
     struct pvconn pvconn;
-    int fd;
-    int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len,
-                     struct vconn **);
+    struct pstream *pstream;
 };
 
 static struct pvconn_class pstream_pvconn_class;
 
-static struct pstream_pvconn *
-pstream_pvconn_cast(struct pvconn *pvconn)
+static struct pvconn_pstream *
+pvconn_pstream_cast(struct pvconn *pvconn)
 {
-    pvconn_assert_class(pvconn, &pstream_pvconn_class);
-    return CONTAINER_OF(pvconn, struct pstream_pvconn, pvconn);
+    return CONTAINER_OF(pvconn, struct pvconn_pstream, pvconn);
 }
 
-int
-new_pstream_pvconn(const char *name, int fd,
-                  int (*accept_cb)(int fd, const struct sockaddr *,
-                                   size_t sa_len, struct vconn **),
-                  struct pvconn **pvconnp)
+/* Creates a new pvconn named 'name' that will accept new connections using
+ * pstream_accept() and stores a pointer to the pvconn in '*pvconnp'.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+static int
+pvconn_pstream_listen(const char *name_, char *suffix OVS_UNUSED,
+                      struct pvconn **pvconnp)
 {
-    struct pstream_pvconn *ps;
-    int retval;
-
-    retval = set_nonblocking(fd);
-    if (retval) {
-        close(fd);
-        return retval;
+    struct pvconn_pstream *ps;
+    struct pstream *pstream;
+    char *name;
+    int error;
+
+    if (!strncmp(name_, "ptcp:", 5) && count_fields(name_) < 2) {
+        name = xasprintf("%s%d", name_, OFP_TCP_PORT);
+    } else if (!strncmp(name_, "pssl:", 5) && count_fields(name_) < 2) {
+        name = xasprintf("%s%d", name_, OFP_SSL_PORT);
+    } else {
+        name = xstrdup(name_);
     }
-
-    if (listen(fd, 10) < 0) {
-        int error = errno;
-        VLOG_ERR("%s: listen: %s", name, strerror(error));
-        close(fd);
+    error = pstream_open(name, &pstream);
+    free(name);
+    if (error) {
         return error;
     }
 
     ps = xmalloc(sizeof *ps);
-    pvconn_init(&ps->pvconn, &pstream_pvconn_class, name);
-    ps->fd = fd;
-    ps->accept_cb = accept_cb;
+    pvconn_init(&ps->pvconn, &pstream_pvconn_class, name_);
+    ps->pstream = pstream;
     *pvconnp = &ps->pvconn;
     return 0;
 }
 
 static void
-pstream_close(struct pvconn *pvconn)
+pvconn_pstream_close(struct pvconn *pvconn)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    close(ps->fd);
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    pstream_close(ps->pstream);
     free(ps);
 }
 
 static int
-pstream_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
+pvconn_pstream_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    struct sockaddr_storage ss;
-    socklen_t ss_len = sizeof ss;
-    int new_fd;
-    int retval;
-
-    new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len);
-    if (new_fd < 0) {
-        int retval = errno;
-        if (retval != EAGAIN) {
-            VLOG_DBG_RL(&rl, "accept: %s", strerror(retval));
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    struct stream *stream;
+    int error;
+
+    error = pstream_accept(ps->pstream, &stream);
+    if (error) {
+        if (error != EAGAIN) {
+            VLOG_DBG_RL(&rl, "%s: accept: %s",
+                        pstream_get_name(ps->pstream), strerror(error));
         }
-        return retval;
-    }
-
-    retval = set_nonblocking(new_fd);
-    if (retval) {
-        close(new_fd);
-        return retval;
+        return error;
     }
 
-    return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len,
-                         new_vconnp);
+    *new_vconnp = vconn_stream_new(stream, 0);
+    return 0;
 }
 
 static void
-pstream_wait(struct pvconn *pvconn)
+pvconn_pstream_wait(struct pvconn *pvconn)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    poll_fd_wait(ps->fd, POLLIN);
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    pstream_wait(ps->pstream);
 }
+\f
+static int
+count_fields(const char *s_)
+{
+    char *s, *field, *save_ptr;
+    int n = 0;
+
+    save_ptr = NULL;
+    s = xstrdup(s_);
+    for (field = strtok_r(s, ":", &save_ptr); field != NULL;
+         field = strtok_r(NULL, ":", &save_ptr)) {
+        n++;
+    }
+    free(s);
 
-static struct pvconn_class pstream_pvconn_class = {
-    "pstream",
-    NULL,
-    pstream_close,
-    pstream_accept,
-    pstream_wait
-};
+    return n;
+}
+\f
+/* Stream-based vconns and pvconns. */
+
+#define DEFINE_VCONN_STREAM_CLASS(NAME)             \
+        struct vconn_class NAME##_vconn_class = {   \
+            #NAME,                                  \
+            vconn_stream_open,                      \
+            vconn_stream_close,                     \
+            vconn_stream_connect,                   \
+            vconn_stream_recv,                      \
+            vconn_stream_send,                      \
+            vconn_stream_run,                       \
+            vconn_stream_run_wait,                  \
+            vconn_stream_wait,                      \
+        };
+
+#define DEFINE_PVCONN_STREAM_CLASS(NAME)            \
+        struct pvconn_class NAME##_pvconn_class = { \
+            #NAME,                                  \
+            pvconn_pstream_listen,                  \
+            pvconn_pstream_close,                   \
+            pvconn_pstream_accept,                  \
+            pvconn_pstream_wait                     \
+        };
+
+static DEFINE_VCONN_STREAM_CLASS(stream);
+static DEFINE_PVCONN_STREAM_CLASS(pstream);
+
+DEFINE_VCONN_STREAM_CLASS(tcp);
+DEFINE_PVCONN_STREAM_CLASS(ptcp);
+
+DEFINE_VCONN_STREAM_CLASS(unix);
+DEFINE_PVCONN_STREAM_CLASS(punix);
+
+#ifdef HAVE_OPENSSL
+DEFINE_VCONN_STREAM_CLASS(ssl);
+DEFINE_PVCONN_STREAM_CLASS(pssl);
+#endif
diff --git a/lib/vconn-tcp.c b/lib/vconn-tcp.c
deleted file mode 100644 (file)
index 1161713..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "vconn.h"
-#include <errno.h>
-#include <inttypes.h>
-#include <sys/types.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include "packets.h"
-#include "socket-util.h"
-#include "util.h"
-#include "openflow/openflow.h"
-#include "vconn-provider.h"
-#include "vconn-stream.h"
-
-#include "vlog.h"
-#define THIS_MODULE VLM_vconn_tcp
-
-/* Active TCP. */
-
-static int
-new_tcp_vconn(const char *name, int fd, int connect_status,
-              const struct sockaddr_in *remote, struct vconn **vconnp)
-{
-    struct sockaddr_in local;
-    socklen_t local_len = sizeof local;
-    int on = 1;
-    int retval;
-
-    /* Get the local IP and port information */
-    retval = getsockname(fd, (struct sockaddr *)&local, &local_len);
-    if (retval) {
-        memset(&local, 0, sizeof local);
-    }
-
-    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;
-    }
-
-    retval = new_stream_vconn(name, fd, connect_status, true, vconnp);
-    if (!retval) {
-        struct vconn *vconn = *vconnp;
-        vconn_set_remote_ip(vconn, remote->sin_addr.s_addr);
-        vconn_set_remote_port(vconn, remote->sin_port);
-        vconn_set_local_ip(vconn, local.sin_addr.s_addr);
-        vconn_set_local_port(vconn, local.sin_port);
-    }
-    return retval;
-}
-
-static int
-tcp_open(const char *name, char *suffix, struct vconn **vconnp)
-{
-    char *save_ptr = NULL;
-    const char *host_name;
-    const char *port_string;
-    struct sockaddr_in sin;
-    int retval;
-    int fd;
-
-    host_name = strtok_r(suffix, ":", &save_ptr);
-    port_string = strtok_r(NULL, ":", &save_ptr);
-    if (!host_name) {
-        ovs_error(0, "%s: bad peer name format", name);
-        return EAFNOSUPPORT;
-    }
-
-    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 ? atoi(port_string) : OFP_TCP_PORT);
-
-    fd = socket(AF_INET, SOCK_STREAM, 0);
-    if (fd < 0) {
-        VLOG_ERR("%s: socket: %s", name, strerror(errno));
-        return errno;
-    }
-
-    retval = set_nonblocking(fd);
-    if (retval) {
-        close(fd);
-        return retval;
-    }
-
-    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
-    if (retval < 0) {
-        if (errno == EINPROGRESS) {
-            return new_tcp_vconn(name, fd, EAGAIN, &sin, vconnp);
-        } else {
-            int error = errno;
-            VLOG_ERR("%s: connect: %s", name, strerror(error));
-            close(fd);
-            return error;
-        }
-    } else {
-        return new_tcp_vconn(name, fd, 0, &sin, vconnp);
-    }
-}
-
-struct vconn_class tcp_vconn_class = {
-    "tcp",                      /* name */
-    tcp_open,                   /* open */
-    NULL,                       /* close */
-    NULL,                       /* connect */
-    NULL,                       /* recv */
-    NULL,                       /* send */
-    NULL,                       /* wait */
-};
-\f
-/* Passive TCP. */
-
-static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-                       struct vconn **vconnp);
-
-static int
-ptcp_open(const char *name, char *suffix, struct pvconn **pvconnp)
-{
-    struct sockaddr_in sin;
-    int retval;
-    int fd;
-    unsigned int yes  = 1;
-
-    fd = socket(AF_INET, SOCK_STREAM, 0);
-    if (fd < 0) {
-        VLOG_ERR("%s: socket: %s", name, strerror(errno));
-        return errno;
-    }
-
-    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) {
-        VLOG_ERR("%s: setsockopt(SO_REUSEADDR): %s", name, strerror(errno));
-        return errno;
-    }
-
-    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_TCP_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;
-    }
-
-    return new_pstream_pvconn("ptcp", fd, ptcp_accept, pvconnp);
-}
-
-static int
-ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-            struct vconn **vconnp)
-{
-    const struct sockaddr_in *sin = (const struct sockaddr_in *) sa;
-    char name[128];
-
-    if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) {
-        sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr));
-        if (sin->sin_port != htons(OFP_TCP_PORT)) {
-            sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
-        }
-    } else {
-        strcpy(name, "tcp");
-    }
-    return new_tcp_vconn(name, fd, 0, sin, vconnp);
-}
-
-struct pvconn_class ptcp_pvconn_class = {
-    "ptcp",
-    ptcp_open,
-    NULL,
-    NULL,
-    NULL
-};
-
index 922198a..d8807fd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -90,7 +90,8 @@ check_vconn_classes(void)
         struct vconn_class *class = vconn_classes[i];
         assert(class->name != NULL);
         assert(class->open != NULL);
-        if (class->close || class->recv || class->send || class->wait) {
+        if (class->close || class->recv || class->send
+            || class->run || class->run_wait || class->wait) {
             assert(class->close != NULL);
             assert(class->recv != NULL);
             assert(class->send != NULL);
@@ -119,7 +120,7 @@ check_vconn_classes(void)
  * connection methods supported by the vconn.  If 'bootstrap' is true, also
  * advertises options to bootstrap the CA certificate. */
 void
-vconn_usage(bool active, bool passive, bool bootstrap UNUSED)
+vconn_usage(bool active, bool passive, bool bootstrap OVS_UNUSED)
 {
     /* Really this should be implemented via callbacks into the vconn
      * providers, but that seems too heavy-weight to bother with at the
@@ -139,12 +140,12 @@ vconn_usage(bool active, bool passive, bool bootstrap UNUSED)
 
     if (passive) {
         printf("Passive OpenFlow connection methods:\n");
-        printf("  ptcp:[PORT]             "
-               "listen to TCP PORT (default: %d)\n",
+        printf("  ptcp:[PORT][:IP]        "
+               "listen to TCP PORT (default: %d) on IP\n",
                OFP_TCP_PORT);
 #ifdef HAVE_OPENSSL
-        printf("  pssl:[PORT]             "
-               "listen for SSL on PORT (default: %d)\n",
+        printf("  pssl:[PORT][:IP]        "
+               "listen for SSL on PORT (default: %d) on IP\n",
                OFP_SSL_PORT);
 #endif
         printf("  punix:FILE              "
@@ -163,6 +164,42 @@ vconn_usage(bool active, bool passive, bool bootstrap UNUSED)
 #endif
 }
 
+/* Given 'name', a connection name in the form "TYPE:ARGS", stores the class
+ * named "TYPE" into '*classp' and returns 0.  Returns EAFNOSUPPORT and stores
+ * a null pointer into '*classp' if 'name' is in the wrong form or if no such
+ * class exists. */
+static int
+vconn_lookup_class(const char *name, struct vconn_class **classp)
+{
+    size_t prefix_len;
+
+    prefix_len = strcspn(name, ":");
+    if (name[prefix_len] != '\0') {
+        size_t i;
+
+        for (i = 0; i < ARRAY_SIZE(vconn_classes); i++) {
+            struct vconn_class *class = vconn_classes[i];
+            if (strlen(class->name) == prefix_len
+                && !memcmp(class->name, name, prefix_len)) {
+                *classp = class;
+                return 0;
+            }
+        }
+    }
+
+    *classp = NULL;
+    return EAFNOSUPPORT;
+}
+
+/* Returns 0 if 'name' is a connection name in the form "TYPE:ARGS" and TYPE is
+ * a supported connection type, otherwise EAFNOSUPPORT.  */
+int
+vconn_verify_name(const char *name)
+{
+    struct vconn_class *class;
+    return vconn_lookup_class(name, &class);
+}
+
 /* Attempts to connect to an OpenFlow device.  'name' is a connection name in
  * the form "TYPE:ARGS", where TYPE is an active vconn class's name and ARGS
  * are vconn class-specific.
@@ -177,35 +214,57 @@ vconn_usage(bool active, bool passive, bool bootstrap UNUSED)
 int
 vconn_open(const char *name, int min_version, struct vconn **vconnp)
 {
-    size_t prefix_len;
-    size_t i;
+    struct vconn_class *class;
+    struct vconn *vconn;
+    char *suffix_copy;
+    int error;
 
     COVERAGE_INC(vconn_open);
     check_vconn_classes();
 
+    /* Look up the class. */
+    error = vconn_lookup_class(name, &class);
+    if (!class) {
+        goto error;
+    }
+
+    /* Call class's "open" function. */
+    suffix_copy = xstrdup(strchr(name, ':') + 1);
+    error = class->open(name, suffix_copy, &vconn);
+    free(suffix_copy);
+    if (error) {
+        goto error;
+    }
+
+    /* Success. */
+    assert(vconn->state != VCS_CONNECTING || vconn->class->connect);
+    vconn->min_version = min_version;
+    *vconnp = vconn;
+    return 0;
+
+error:
     *vconnp = NULL;
-    prefix_len = strcspn(name, ":");
-    if (prefix_len == strlen(name)) {
-        return EAFNOSUPPORT;
+    return error;
+}
+
+/* Allows 'vconn' to perform maintenance activities, such as flushing output
+ * buffers. */
+void
+vconn_run(struct vconn *vconn)
+{
+    if (vconn->class->run) {
+        (vconn->class->run)(vconn);
     }
-    for (i = 0; i < ARRAY_SIZE(vconn_classes); i++) {
-        struct vconn_class *class = vconn_classes[i];
-        if (strlen(class->name) == prefix_len
-            && !memcmp(class->name, name, prefix_len)) {
-            struct vconn *vconn;
-            char *suffix_copy = xstrdup(name + prefix_len + 1);
-            int retval = class->open(name, suffix_copy, &vconn);
-            free(suffix_copy);
-            if (!retval) {
-                assert(vconn->state != VCS_CONNECTING
-                       || vconn->class->connect);
-                vconn->min_version = min_version;
-                *vconnp = vconn;
-            }
-            return retval;
-        }
+}
+
+/* Arranges for the poll loop to wake up when 'vconn' needs to perform
+ * maintenance activities. */
+void
+vconn_run_wait(struct vconn *vconn)
+{
+    if (vconn->class->run_wait) {
+        (vconn->class->run_wait)(vconn);
     }
-    return EAFNOSUPPORT;
 }
 
 int
@@ -216,6 +275,8 @@ vconn_open_block(const char *name, int min_version, struct vconn **vconnp)
 
     error = vconn_open(name, min_version, &vconn);
     while (error == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_connect_wait(vconn);
         poll_block();
         error = vconn_connect(vconn);
@@ -364,7 +425,7 @@ vcs_recv_hello(struct vconn *vconn)
 
     if (retval != EAGAIN) {
         vconn->state = VCS_DISCONNECTED;
-        vconn->error = retval;
+        vconn->error = retval == EOF ? ECONNRESET : retval;
     }
 }
 
@@ -458,10 +519,7 @@ vconn_recv(struct vconn *vconn, struct ofpbuf **msgp)
 static int
 do_recv(struct vconn *vconn, struct ofpbuf **msgp)
 {
-    int retval;
-
-again:
-    retval = (vconn->class->recv)(vconn, msgp);
+    int retval = (vconn->class->recv)(vconn, msgp);
     if (!retval) {
         struct ofp_header *oh;
 
@@ -481,20 +539,6 @@ again:
             && oh->type != OFPT_VENDOR)
         {
             if (vconn->version < 0) {
-                if (oh->type == OFPT_PACKET_IN
-                    || oh->type == OFPT_FLOW_EXPIRED
-                    || oh->type == OFPT_PORT_STATUS) {
-                    /* The kernel datapath is stateless and doesn't really
-                     * support version negotiation, so it can end up sending
-                     * these asynchronous message before version negotiation
-                     * is complete.  Just ignore them.
-                     *
-                     * (After we move OFPT_PORT_STATUS messages from the kernel
-                     * into secchan, we won't get those here, since secchan
-                     * does proper version negotiation.) */
-                    ofpbuf_delete(*msgp);
-                    goto again;
-                }
                 VLOG_ERR_RL(&bad_ofmsg_rl,
                             "%s: received OpenFlow message type %"PRIu8" "
                             "before version negotiation complete",
@@ -564,6 +608,8 @@ vconn_send_block(struct vconn *vconn, struct ofpbuf *msg)
 {
     int retval;
     while ((retval = vconn_send(vconn, msg)) == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_send_wait(vconn);
         poll_block();
     }
@@ -576,6 +622,8 @@ vconn_recv_block(struct vconn *vconn, struct ofpbuf **msgp)
 {
     int retval;
     while ((retval = vconn_recv(vconn, msgp)) == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_recv_wait(vconn);
         poll_block();
     }
@@ -681,6 +729,42 @@ vconn_send_wait(struct vconn *vconn)
     vconn_wait(vconn, WAIT_SEND);
 }
 
+/* Given 'name', a connection name in the form "TYPE:ARGS", stores the class
+ * named "TYPE" into '*classp' and returns 0.  Returns EAFNOSUPPORT and stores
+ * a null pointer into '*classp' if 'name' is in the wrong form or if no such
+ * class exists. */
+static int
+pvconn_lookup_class(const char *name, struct pvconn_class **classp)
+{
+    size_t prefix_len;
+
+    prefix_len = strcspn(name, ":");
+    if (name[prefix_len] != '\0') {
+        size_t i;
+
+        for (i = 0; i < ARRAY_SIZE(pvconn_classes); i++) {
+            struct pvconn_class *class = pvconn_classes[i];
+            if (strlen(class->name) == prefix_len
+                && !memcmp(class->name, name, prefix_len)) {
+                *classp = class;
+                return 0;
+            }
+        }
+    }
+
+    *classp = NULL;
+    return EAFNOSUPPORT;
+}
+
+/* Returns 0 if 'name' is a connection name in the form "TYPE:ARGS" and TYPE is
+ * a supported connection type, otherwise EAFNOSUPPORT.  */
+int
+pvconn_verify_name(const char *name)
+{
+    struct pvconn_class *class;
+    return pvconn_lookup_class(name, &class);
+}
+
 /* Attempts to start listening for OpenFlow connections.  'name' is a
  * connection name in the form "TYPE:ARGS", where TYPE is an passive vconn
  * class's name and ARGS are vconn class-specific.
@@ -691,30 +775,34 @@ vconn_send_wait(struct vconn *vconn)
 int
 pvconn_open(const char *name, struct pvconn **pvconnp)
 {
-    size_t prefix_len;
-    size_t i;
+    struct pvconn_class *class;
+    struct pvconn *pvconn;
+    char *suffix_copy;
+    int error;
 
     check_vconn_classes();
 
-    *pvconnp = NULL;
-    prefix_len = strcspn(name, ":");
-    if (prefix_len == strlen(name)) {
-        return EAFNOSUPPORT;
+    /* Look up the class. */
+    error = pvconn_lookup_class(name, &class);
+    if (!class) {
+        goto error;
     }
-    for (i = 0; i < ARRAY_SIZE(pvconn_classes); i++) {
-        struct pvconn_class *class = pvconn_classes[i];
-        if (strlen(class->name) == prefix_len
-            && !memcmp(class->name, name, prefix_len)) {
-            char *suffix_copy = xstrdup(name + prefix_len + 1);
-            int retval = class->listen(name, suffix_copy, pvconnp);
-            free(suffix_copy);
-            if (retval) {
-                *pvconnp = NULL;
-            }
-            return retval;
-        }
+
+    /* Call class's "open" function. */
+    suffix_copy = xstrdup(strchr(name, ':') + 1);
+    error = class->listen(name, suffix_copy, &pvconn);
+    free(suffix_copy);
+    if (error) {
+        goto error;
     }
-    return EAFNOSUPPORT;
+
+    /* Success. */
+    *pvconnp = pvconn;
+    return 0;
+
+error:
+    *pvconnp = NULL;
+    return error;
 }
 
 /* Returns the name that was used to open 'pvconn'.  The caller must not
@@ -876,16 +964,19 @@ make_flow_mod(uint16_t command, const flow_t *flow, size_t actions_len)
     ofm->header.version = OFP_VERSION;
     ofm->header.type = OFPT_FLOW_MOD;
     ofm->header.length = htons(size);
+    ofm->cookie = 0;
     ofm->match.wildcards = htonl(0);
     ofm->match.in_port = htons(flow->in_port == ODPP_LOCAL ? OFPP_LOCAL
                                : flow->in_port);
     memcpy(ofm->match.dl_src, flow->dl_src, sizeof ofm->match.dl_src);
     memcpy(ofm->match.dl_dst, flow->dl_dst, sizeof ofm->match.dl_dst);
     ofm->match.dl_vlan = flow->dl_vlan;
+    ofm->match.dl_vlan_pcp = flow->dl_vlan_pcp;
     ofm->match.dl_type = flow->dl_type;
     ofm->match.nw_src = flow->nw_src;
     ofm->match.nw_dst = flow->nw_dst;
     ofm->match.nw_proto = flow->nw_proto;
+    ofm->match.nw_tos = flow->nw_tos;
     ofm->match.tp_src = flow->tp_src;
     ofm->match.tp_dst = flow->tp_dst;
     ofm->command = htons(command);
@@ -1062,7 +1153,7 @@ check_ofp_message(const struct ofp_header *msg, uint8_t type, size_t size)
                      "received %s message of length %zu (expected %zu)",
                      type_name, got_size, size);
         free(type_name);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
 
     return 0;
@@ -1098,7 +1189,7 @@ check_ofp_message_array(const struct ofp_header *msg, uint8_t type,
                      "(expected at least %zu)",
                      type_name, got_size, min_size);
         free(type_name);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     if ((got_size - min_size) % array_elt_size) {
         char *type_name = ofp_message_type_to_string(type);
@@ -1109,7 +1200,7 @@ check_ofp_message_array(const struct ofp_header *msg, uint8_t type,
                      type_name, got_size, min_size, got_size - min_size,
                      array_elt_size, (got_size - min_size) % array_elt_size);
         free(type_name);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     if (n_array_elts) {
         *n_array_elts = (got_size - min_size) / array_elt_size;
@@ -1139,13 +1230,13 @@ check_ofp_packet_out(const struct ofp_header *oh, struct ofpbuf *data,
         VLOG_WARN_RL(&bad_ofmsg_rl, "packet-out claims %u bytes of actions "
                      "but message has room for only %zu bytes",
                      actions_len, extra);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     if (actions_len % sizeof(union ofp_action)) {
         VLOG_WARN_RL(&bad_ofmsg_rl, "packet-out claims %u bytes of actions, "
                      "which is not a multiple of %zu",
                      actions_len, sizeof(union ofp_action));
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
 
     n_actions = actions_len / sizeof(union ofp_action);
@@ -1269,30 +1360,17 @@ check_action(const union ofp_action *a, unsigned int len, int max_ports)
 {
     int error;
 
-    if (!len) {
-        VLOG_DBG_RL(&bad_ofmsg_rl, "action has invalid length 0");
-        return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
-    }
-
-    if (len % ACTION_ALIGNMENT) {
-        VLOG_DBG_RL(&bad_ofmsg_rl, "action length %u is not a multiple of %d",
-                    len, ACTION_ALIGNMENT);
-        return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
-    }
-
     switch (ntohs(a->type)) {
     case OFPAT_OUTPUT:
         error = check_action_port(ntohs(a->output.port), max_ports);
-        if (error) {
-            return error;
-        }
-        return check_action_exact_len(a, len, 8);
+        return error ? error : check_action_exact_len(a, len, 8);
 
     case OFPAT_SET_VLAN_VID:
     case OFPAT_SET_VLAN_PCP:
     case OFPAT_STRIP_VLAN:
     case OFPAT_SET_NW_SRC:
     case OFPAT_SET_NW_DST:
+    case OFPAT_SET_NW_TOS:
     case OFPAT_SET_TP_SRC:
     case OFPAT_SET_TP_DST:
         return check_action_exact_len(a, len, 8);
@@ -1302,15 +1380,13 @@ check_action(const union ofp_action *a, unsigned int len, int max_ports)
         return check_action_exact_len(a, len, 16);
 
     case OFPAT_VENDOR:
-        if (a->vendor.vendor == htonl(NX_VENDOR_ID)) {
-            return check_nicira_action(a, len);
-        } else {
-            return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_VENDOR);
-        }
-        break;
+        return (a->vendor.vendor == htonl(NX_VENDOR_ID)
+                ? check_nicira_action(a, len)
+                : ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_VENDOR));
 
     default:
-        VLOG_WARN_RL(&bad_ofmsg_rl, "unknown action type %"PRIu16, a->type);
+        VLOG_WARN_RL(&bad_ofmsg_rl, "unknown action type %"PRIu16,
+                ntohs(a->type));
         return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_TYPE);
     }
 }
@@ -1332,7 +1408,15 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
                         "action requires %u slots but only %u remain",
                         n_slots, slots_left);
             return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
+        } else if (!len) {
+            VLOG_DBG_RL(&bad_ofmsg_rl, "action has invalid length 0");
+            return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
+        } else if (len % ACTION_ALIGNMENT) {
+            VLOG_DBG_RL(&bad_ofmsg_rl, "action length %u is not a multiple "
+                        "of %d", len, ACTION_ALIGNMENT);
+            return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
         }
+
         error = check_action(a, len, max_ports);
         if (error) {
             return error;
@@ -1411,6 +1495,17 @@ normalize_match(struct ofp_match *m)
         if (wc & OFPFW_NW_DST_MASK) {
             m->nw_dst &= flow_nw_bits_to_mask(wc, OFPFW_NW_DST_SHIFT);
         }
+    } else if (m->dl_type == htons(ETH_TYPE_ARP)) {
+        if (wc & OFPFW_NW_PROTO) {
+            m->nw_proto = 0;
+        }
+        if (wc & OFPFW_NW_SRC_MASK) {
+            m->nw_src &= flow_nw_bits_to_mask(wc, OFPFW_NW_SRC_SHIFT);
+        }
+        if (wc & OFPFW_NW_DST_MASK) {
+            m->nw_dst &= flow_nw_bits_to_mask(wc, OFPFW_NW_DST_SHIFT);
+        }
+        m->tp_src = m->tp_dst = 0;
     } else {
         /* Network and transport layer fields will always be extracted as
          * zeros, so we can do an exact-match on those values. */
@@ -1427,9 +1522,26 @@ normalize_match(struct ofp_match *m)
     m->wildcards = htonl(wc);
 }
 
+/* Initializes 'vconn' as a new vconn named 'name', implemented via 'class'.
+ * The initial connection status, supplied as 'connect_status', is interpreted
+ * as follows:
+ *
+ *      - 0: 'vconn' is connected.  Its 'send' and 'recv' functions may be
+ *        called in the normal fashion.
+ *
+ *      - EAGAIN: 'vconn' is trying to complete a connection.  Its 'connect'
+ *        function should be called to complete the connection.
+ *
+ *      - Other positive errno values indicate that the connection failed with
+ *        the specified error.
+ *
+ * After calling this function, vconn_close() must be used to destroy 'vconn',
+ * otherwise resources will be leaked.
+ *
+ * The caller retains ownership of 'name'. */
 void
 vconn_init(struct vconn *vconn, struct vconn_class *class, int connect_status,
-           const char *name, bool reconnectable)
+           const char *name)
 {
     vconn->class = class;
     vconn->state = (connect_status == EAGAIN ? VCS_CONNECTING
@@ -1443,7 +1555,7 @@ vconn_init(struct vconn *vconn, struct vconn_class *class, int connect_status,
     vconn->local_ip = 0;
     vconn->local_port = 0;
     vconn->name = xstrdup(name);
-    vconn->reconnectable = reconnectable;
+    assert(vconn->state != VCS_CONNECTING || class->connect);
 }
 
 void
index 0c13744..1426c1d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ struct vconn;
 void vconn_usage(bool active, bool passive, bool bootstrap);
 
 /* Active vconns: virtual connections to OpenFlow devices. */
+int vconn_verify_name(const char *name);
 int vconn_open(const char *name, int min_version, struct vconn **);
 void vconn_close(struct vconn *);
 const char *vconn_get_name(const struct vconn *);
@@ -48,6 +49,9 @@ int vconn_send(struct vconn *, struct ofpbuf *);
 int vconn_recv_xid(struct vconn *, uint32_t xid, struct ofpbuf **);
 int vconn_transact(struct vconn *, struct ofpbuf *, struct ofpbuf **);
 
+void vconn_run(struct vconn *);
+void vconn_run_wait(struct vconn *);
+
 int vconn_open_block(const char *name, int min_version, struct vconn **);
 int vconn_send_block(struct vconn *, struct ofpbuf *);
 int vconn_recv_block(struct vconn *, struct ofpbuf **);
@@ -63,6 +67,7 @@ void vconn_recv_wait(struct vconn *);
 void vconn_send_wait(struct vconn *);
 
 /* Passive vconns: virtual listeners for incoming OpenFlow connections. */
+int pvconn_verify_name(const char *name);
 int pvconn_open(const char *name, struct pvconn **);
 const char *pvconn_get_name(const struct pvconn *);
 void pvconn_close(struct pvconn *);
index 216111b..dac8162 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,8 +19,7 @@ VLOG_MODULE(backtrace)
 VLOG_MODULE(brcompatd)
 VLOG_MODULE(bridge)
 VLOG_MODULE(chain)
-VLOG_MODULE(cfg)
-VLOG_MODULE(cfg_mod)
+VLOG_MODULE(collectors)
 VLOG_MODULE(controller)
 VLOG_MODULE(coverage)
 VLOG_MODULE(ctlpath)
@@ -30,33 +29,51 @@ VLOG_MODULE(dhcp)
 VLOG_MODULE(dhcp_client)
 VLOG_MODULE(discovery)
 VLOG_MODULE(dpif)
+VLOG_MODULE(dpif_linux)
+VLOG_MODULE(dpif_netdev)
 VLOG_MODULE(dpctl)
-VLOG_MODULE(executer)
 VLOG_MODULE(ezio_term)
 VLOG_MODULE(fail_open)
-VLOG_MODULE(fault)
+VLOG_MODULE(fatal_signal)
 VLOG_MODULE(flow)
 VLOG_MODULE(in_band)
+VLOG_MODULE(jsonrpc)
 VLOG_MODULE(leak_checker)
 VLOG_MODULE(learning_switch)
+VLOG_MODULE(lockfile)
 VLOG_MODULE(mac_learning)
-VLOG_MODULE(mgmt)
 VLOG_MODULE(netdev)
+VLOG_MODULE(netdev_linux)
 VLOG_MODULE(netflow)
 VLOG_MODULE(netlink)
 VLOG_MODULE(ofctl)
 VLOG_MODULE(ovs_discover)
 VLOG_MODULE(ofproto)
+VLOG_MODULE(openflowd)
+VLOG_MODULE(ovsdb_client)
+VLOG_MODULE(ovsdb_error)
+VLOG_MODULE(ovsdb_file)
+VLOG_MODULE(ovsdb_idl)
+VLOG_MODULE(ovsdb_log)
+VLOG_MODULE(ovsdb_jsonrpc_server)
+VLOG_MODULE(ovsdb_server)
+VLOG_MODULE(ovsdb_tool)
 VLOG_MODULE(pktbuf)
 VLOG_MODULE(pcap)
 VLOG_MODULE(poll_loop)
 VLOG_MODULE(port_watcher)
 VLOG_MODULE(proc_net_compat)
 VLOG_MODULE(process)
-VLOG_MODULE(secchan)
 VLOG_MODULE(rconn)
+VLOG_MODULE(reconnect)
+VLOG_MODULE(rtnetlink)
+VLOG_MODULE(sflow)
 VLOG_MODULE(stp)
-VLOG_MODULE(stp_secchan)
+VLOG_MODULE(stream_fd)
+VLOG_MODULE(stream_ssl)
+VLOG_MODULE(stream_tcp)
+VLOG_MODULE(stream_unix)
+VLOG_MODULE(stream)
 VLOG_MODULE(stats)
 VLOG_MODULE(status)
 VLOG_MODULE(svec)
@@ -67,11 +84,13 @@ VLOG_MODULE(tty)
 VLOG_MODULE(socket_util)
 VLOG_MODULE(switchui)
 VLOG_MODULE(unixctl)
+VLOG_MODULE(util)
 VLOG_MODULE(vconn_tcp)
 VLOG_MODULE(vconn_ssl)
 VLOG_MODULE(vconn_stream)
 VLOG_MODULE(vconn_unix)
 VLOG_MODULE(vconn)
+VLOG_MODULE(vsctl)
 VLOG_MODULE(vlog)
 VLOG_MODULE(wcelim)
 VLOG_MODULE(vswitchd)
diff --git a/lib/vlog-syn.man b/lib/vlog-syn.man
new file mode 100644 (file)
index 0000000..873b2f0
--- /dev/null
@@ -0,0 +1,6 @@
+.IP "Logging options:"
+[\fB-v\fR[\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--verbose[=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--log-file\fR[\fB=\fIfile\fR]]
index 5c79875..86eece3 100644 (file)
@@ -7,7 +7,7 @@ Sets the logging level for \fImodule\fR in \fIfacility\fR to
 .RS
 .IP \(bu
 \fImodule\fR may be any valid module name (as displayed by the
-\fB--list\fR action on \fBovs-appctl\fR(8)), or the special name
+\fB--list\fR action on \fBovs\-appctl\fR(8)), or the special name
 \fBANY\fR to set the logging levels for all modules.
 .
 .IP \(bu
@@ -26,7 +26,7 @@ logged.  If it is omitted, \fIlevel\fR defaults to \fBdbg\fR.
 .RE
 .IP "\fBvlog/set PATTERN:\fIfacility\fB:\fIpattern\fR"
 Sets the log pattern for \fIfacility\fR to \fIpattern\fR.  Refer to
-\fBovs-appctl\fR(8) for a description of the valid syntax for \fIpattern\fR.
+\fBovs\-appctl\fR(8) for a description of the valid syntax for \fIpattern\fR.
 .
 .IP "\fBvlog/list\fR"
 Lists the supported logging modules and their current levels.
index 1b95d96..b534d19 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -385,7 +385,8 @@ vlog_set_verbosity(const char *arg)
 }
 
 static void
-vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
+vlog_unixctl_set(struct unixctl_conn *conn,
+                 const char *args, void *aux OVS_UNUSED)
 {
     char *msg = vlog_set_levels_from_string(args);
     unixctl_command_reply(conn, msg ? 501 : 202, msg);
@@ -393,7 +394,8 @@ vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_list(struct unixctl_conn *conn,
+                  const char *args OVS_UNUSED, void *aux OVS_UNUSED)
 {
     char *msg = vlog_get_levels();
     unixctl_command_reply(conn, 200, msg);
@@ -401,7 +403,8 @@ vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 static void
-vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_reopen(struct unixctl_conn *conn,
+                    const char *args OVS_UNUSED, void *aux OVS_UNUSED)
 {
     if (log_file_name) {
         int error = vlog_reopen_log_file();
@@ -435,9 +438,9 @@ vlog_init(void)
         VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now);
     }
 
-    unixctl_command_register("vlog/set", vlog_unixctl_set);
-    unixctl_command_register("vlog/list", vlog_unixctl_list);
-    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen);
+    unixctl_command_register("vlog/set", vlog_unixctl_set, NULL);
+    unixctl_command_register("vlog/list", vlog_unixctl_list, NULL);
+    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen, NULL);
 }
 
 /* Closes the logging subsystem. */
@@ -522,7 +525,7 @@ format_log_message(enum vlog_module module, enum vlog_level level,
             p++;
         }
         field = 0;
-        while (isdigit(*p)) {
+        while (isdigit((unsigned char)*p)) {
             field = (field * 10) + (*p - '0');
             p++;
         }
index d98855a..f50b76a 100644 (file)
 #include <stdbool.h>
 #include <time.h>
 #include "util.h"
+#ifdef  __cplusplus
+extern "C" {
+#endif
 
 /* Logging importance levels. */
 #define VLOG_LEVELS                             \
@@ -187,6 +191,10 @@ void vlog_usage(void);
         }                                                           \
     } while (0)
 extern enum vlog_level min_vlog_levels[VLM_N_MODULES];
+#ifdef  __cplusplus
+}
+#endif
 
 
 #endif /* vlog.h */
index 0bd8a26..ca895b1 100644 (file)
@@ -1,42 +1,42 @@
 .TP
 \fB-v\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]
-
+.
 Sets the logging level for \fImodule\fR in \fIfacility\fR to
 \fIlevel\fR:
-
+.
 .RS
 .IP \(bu
 \fImodule\fR may be any valid module name (as displayed by the
-\fB--list\fR action on \fBovs-appctl\fR(8)), or the special name
+\fB--list\fR action on \fBovs\-appctl\fR(8)), or the special name
 \fBANY\fR to set the logging levels for all modules.
-
+.
 .IP \(bu
 \fIfacility\fR may be \fBsyslog\fR, \fBconsole\fR, or \fBfile\fR to
 set the levels for logging to the system log, the console, or a file
 respectively, or \fBANY\fR to set the logging levels for both
 facilities.  If it is omitted, \fIfacility\fR defaults to \fBANY\fR.
-
+.IP
 Regardless of the log levels set for \fBfile\fR, logging to a file
 will not take place unless \fB--log-file\fR is also specified (see
 below).
-
+.
 .IP \(bu 
 \fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR,
 \fBinfo\fR, or
 \fBdbg\fR, designating the minimum severity of a message for it to be
 logged.  If it is omitted, \fIlevel\fR defaults to \fBdbg\fR.
 .RE
-
+.
 .TP
 \fB-v\fR, \fB--verbose\fR
 Sets the maximum logging verbosity level, equivalent to
 \fB--verbose=ANY:ANY:dbg\fR.
-
+.
 .TP
 \fB-vPATTERN:\fIfacility\fB:\fIpattern\fR, \fB--verbose=PATTERN:\fIfacility\fB:\fIpattern\fR
 Sets the log pattern for \fIfacility\fR to \fIpattern\fR.  Refer to
-\fBovs-appctl\fR(8) for a description of the valid syntax for \fIpattern\fR.
-
+\fBovs\-appctl\fR(8) for a description of the valid syntax for \fIpattern\fR.
+.
 .TP
 \fB--log-file\fR[\fB=\fIfile\fR]
 Enables logging to a file.  If \fIfile\fR is specified, then it is
index ce55311..ccb3a01 100644 (file)
@@ -1,6 +1,6 @@
 # -*- autoconf -*-
 
-# Copyright (c) 2008, 2009 Nicira Networks.
+# Copyright (c) 2008, 2009, 2010 Nicira Networks.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+dnl Checks for --enable-coverage and updates CFLAGS and LDFLAGS appropriately.
+AC_DEFUN([OVS_CHECK_COVERAGE],
+  [AC_REQUIRE([AC_PROG_CC])
+   AC_ARG_ENABLE(
+     [coverage],
+     [AC_HELP_STRING([--enable-coverage], 
+                     [Enable gcov coverage tool.])],
+     [case "${enableval}" in
+        (lcov|yes) coverage=true ;;
+        (no)  coverage=false ;;
+        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-coverage]) ;;
+      esac],
+     [coverage=false])
+   if $coverage; then
+     CFLAGS="$CFLAGS -O0 --coverage"
+     LDFLAGS="$LDFLAGS --coverage"
+   fi])
+
 dnl Checks for --enable-ndebug and defines NDEBUG if it is specified.
 AC_DEFUN([OVS_CHECK_NDEBUG],
   [AC_ARG_ENABLE(
@@ -56,28 +74,25 @@ AC_DEFUN([OVS_CHECK_OPENSSL],
      [ssl=false])
 
    if test "$ssl" = true; then
-   dnl Make sure that pkg-config is installed.
-   m4_pattern_forbid([PKG_CHECK_MODULES])
-   PKG_CHECK_MODULES([SSL], [libssl], 
-     [HAVE_OPENSSL=yes],
-     [HAVE_OPENSSL=no
-      AC_MSG_WARN([Cannot find libssl:
-
-   $SSL_PKG_ERRORS
+       dnl Make sure that pkg-config is installed.
+       m4_pattern_forbid([PKG_CHECK_MODULES])
+       PKG_CHECK_MODULES([SSL], [libssl], 
+         [HAVE_OPENSSL=yes],
+         [HAVE_OPENSSL=no
+          AC_MSG_WARN([Cannot find libssl:
 
-   OpenFlow connections over SSL will not be supported.])])
+$SSL_PKG_ERRORS
 
+OpenFlow connections over SSL will not be supported.])])
+   else
+       HAVE_OPENSSL=no
    fi
+   AC_SUBST([HAVE_OPENSSL])
    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])
 
-dnl Checks for libraries needed by lib/fault.c.
-AC_DEFUN([OVS_CHECK_FAULT_LIBS],
-  [AC_CHECK_LIB([dl], [dladdr], [FAULT_LIBS=-ldl])
-   AC_SUBST([FAULT_LIBS])])
-
 dnl Checks for libraries needed by lib/socket-util.c.
 AC_DEFUN([OVS_CHECK_SOCKET_LIBS],
   [AC_CHECK_LIB([socket], [connect])
@@ -200,11 +215,51 @@ AC_DEFUN([OVS_CHECK_LINUX_VT_H],
    fi])
 
 dnl Checks for libpcre.
+dnl
+dnl ezio-term wants libpcre that supports the PCRE_PARTIAL feature,
+dnl which is libpcre 7.2 or later.
 AC_DEFUN([OVS_CHECK_PCRE],
   [dnl Make sure that pkg-config is installed.
    m4_pattern_forbid([PKG_CHECK_MODULES])
-   PKG_CHECK_MODULES([PCRE], [libpcre], [HAVE_PCRE=yes], [HAVE_PCRE=no])
-   AM_CONDITIONAL([HAVE_PCRE], [test "$HAVE_PCRE" = yes])
-   if test "$HAVE_PCRE" = yes; then
-      AC_DEFINE([HAVE_PCRE], [1], [Define to 1 if libpcre is installed.])
-   fi])
+   PKG_CHECK_MODULES([PCRE],
+                     [libpcre >= 7.2], 
+                     [HAVE_PCRE_PARTIAL=yes],
+                     [HAVE_PCRE_PARTIAL=no])
+   AM_CONDITIONAL([HAVE_PCRE_PARTIAL], [test "$HAVE_PCRE_PARTIAL" = yes])
+   AC_SUBST([HAVE_PCRE_PARTIAL])
+])
+
+dnl Checks for Python 2.x, x >= 4.
+AC_DEFUN([OVS_CHECK_PYTHON],
+  [AC_CACHE_CHECK(
+     [for Python 2.x for x >= 4],
+     [ovs_cv_python],
+     [if test -n "$PYTHON"; then
+        ovs_cv_python=$PYTHON
+      else
+        ovs_cv_python=no
+        for binary in python python2.4 python2.5; do
+          ovs_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+          for dir in $PATH; do
+            IFS=$ovs_save_IFS
+            test -z "$dir" && dir=.
+            if test -x $dir/$binary && $dir/$binary -c 'import sys
+if sys.hexversion >= 0x02040000 and sys.hexversion < 0x03000000:
+    sys.exit(0)
+else:
+    sys.exit(1)'; then
+              ovs_cv_python=$dir/$binary
+              break 2
+            fi
+          done
+        done
+      fi])
+   AC_SUBST([HAVE_PYTHON])
+   AM_MISSING_PROG([PYTHON], [python])
+   if test $ovs_cv_python != no; then
+     PYTHON=$ovs_cv_python
+     HAVE_PYTHON=yes
+   else
+     HAVE_PYTHON=no
+   fi
+   AM_CONDITIONAL([HAVE_PYTHON], [test "$HAVE_PYTHON" = yes])])
similarity index 53%
rename from secchan/.gitignore
rename to ofproto/.gitignore
index ada6566..b336cc7 100644 (file)
@@ -1,4 +1,2 @@
 /Makefile
 /Makefile.in
-/secchan
-/secchan.8
diff --git a/ofproto/automake.mk b/ofproto/automake.mk
new file mode 100644 (file)
index 0000000..0c99b49
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (C) 2009 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.  This file is offered as-is,
+# without warranty of any kind.
+
+noinst_LIBRARIES += ofproto/libofproto.a
+ofproto_libofproto_a_SOURCES = \
+       ofproto/collectors.c \
+       ofproto/collectors.h \
+       ofproto/discovery.c \
+       ofproto/discovery.h \
+       ofproto/fail-open.c \
+       ofproto/fail-open.h \
+       ofproto/in-band.c \
+       ofproto/in-band.h \
+       ofproto/netflow.c \
+       ofproto/netflow.h \
+       ofproto/ofproto.c \
+       ofproto/ofproto.h \
+       ofproto/ofproto-sflow.c \
+       ofproto/ofproto-sflow.h \
+       ofproto/pktbuf.c \
+       ofproto/pktbuf.h \
+       ofproto/pinsched.c \
+       ofproto/pinsched.h \
+       ofproto/status.c \
+       ofproto/status.h
diff --git a/ofproto/collectors.c b/ofproto/collectors.c
new file mode 100644 (file)
index 0000000..f063983
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "collectors.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "socket-util.h"
+#include "svec.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_collectors
+#include "vlog.h"
+
+struct collectors {
+    int *fds;                     /* Sockets. */
+    size_t n_fds;                 /* Number of sockets. */
+};
+
+/* Opens the targets specified in 'targets' for sending UDP packets.  This is
+ * useful for e.g. sending NetFlow or sFlow packets.  Returns 0 if successful,
+ * otherwise a positive errno value if opening at least one collector failed.
+ *
+ * Each target in 'targets' should be a string in the format "<host>[:<port>]".
+ * <port> may be omitted if 'default_port' is nonzero, in which case it
+ * defaults to 'default_port'.
+ *
+ * '*collectorsp' is set to a null pointer if no targets were successfully
+ * added, otherwise to a new collectors object if at least one was successfully
+ * added.  Thus, even on a failure return, it is possible that '*collectorsp'
+ * is nonnull, and even on a successful return, it is possible that
+ * '*collectorsp' is null, if 'target's is an empty svec. */
+int
+collectors_create(const struct svec *targets_, uint16_t default_port,
+                  struct collectors **collectorsp)
+{
+    struct collectors *c;
+    struct svec targets;
+    int retval = 0;
+    size_t i;
+
+    svec_clone(&targets, targets_);
+    svec_sort_unique(&targets);
+
+    c = xmalloc(sizeof *c);
+    c->fds = xmalloc(sizeof *c->fds * targets.n);
+    c->n_fds = 0;
+    for (i = 0; i < targets.n; i++) {
+        const char *name = targets.names[i];
+        int error;
+        int fd;
+
+        error = inet_open_active(SOCK_DGRAM, name, default_port, NULL, &fd);
+        if (fd >= 0) {
+            c->fds[c->n_fds++] = fd;
+        } else {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+            VLOG_WARN_RL(&rl, "couldn't open connection to collector %s (%s)",
+                         name, strerror(error));
+            if (!retval) {
+                retval = error;
+            }
+        }
+    }
+    svec_destroy(&targets);
+
+    if (c->n_fds) {
+        *collectorsp = c;
+    } else {
+        collectors_destroy(c);
+        *collectorsp = NULL;
+    }
+
+    return retval;
+}
+
+/* Destroys 'c'. */
+void
+collectors_destroy(struct collectors *c)
+{
+    if (c) {
+        size_t i;
+
+        for (i = 0; i < c->n_fds; i++) {
+            close(c->fds[i]);
+        }
+        free(c->fds);
+        free(c);
+    }
+}
+
+/* Sends the 'n'-byte 'payload' to each of the collectors in 'c'. */
+void
+collectors_send(const struct collectors *c, const void *payload, size_t n)
+{
+    if (c) {
+        size_t i;
+
+        for (i = 0; i < c->n_fds; i++) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            if (send(c->fds[i], payload, n, 0) == -1) {
+                VLOG_WARN_RL(&rl, "sending to collector failed: %s",
+                             strerror(errno));
+            }
+        }
+    }
+}
+
+int
+collectors_count(const struct collectors *c)
+{
+    return c ? c->n_fds : 0;
+}
similarity index 55%
rename from vswitchd/ovs-vswitchd.h
rename to ofproto/collectors.h
index 3877f88..ac70f37 100644 (file)
@@ -1,4 +1,5 @@
-/* Copyright (c) 2009 Nicira Networks
+/*
+ * Copyright (c) 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef VSWITCHD_H
-#define VSWITCHD_H 1
+#ifndef COLLECTORS_H
+#define COLLECTORS_H 1
 
-void reconfigure(void);
+#include <stdint.h>
+#include "svec.h"
 
-#endif /* ovs-vswitchd.h */
+struct collectors;
+
+int collectors_create(const struct svec *targets, uint16_t default_port,
+                      struct collectors **);
+void collectors_destroy(struct collectors *);
+
+void collectors_send(const struct collectors *, const void *, size_t);
+
+int collectors_count(const struct collectors *);
+
+#endif /* collectors.h */
similarity index 95%
rename from secchan/discovery.c
rename to ofproto/discovery.c
index da52482..ae36b4d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "status.h"
-#include "vconn-ssl.h"
+#include "stream-ssl.h"
 
 #define THIS_MODULE VLM_discovery
 #include "vlog.h"
@@ -102,7 +102,7 @@ discovery_create(const char *re, bool update_resolv_conf,
     char local_name[IF_NAMESIZE];
     int error;
 
-    d = xcalloc(1, sizeof *d);
+    d = xzalloc(sizeof *d);
 
     /* Controller regular expression. */
     error = discovery_set_accept_controller_re(d, re);
@@ -112,7 +112,8 @@ discovery_create(const char *re, bool update_resolv_conf,
     d->update_resolv_conf = update_resolv_conf;
 
     /* Initialize DHCP client. */
-    error = dpif_get_name(dpif, local_name, sizeof local_name);
+    error = dpif_port_get_name(dpif, ODPP_LOCAL,
+                               local_name, sizeof local_name);
     if (error) {
         VLOG_ERR("failed to query datapath local port: %s", strerror(error));
         goto error_regfree;
@@ -168,7 +169,7 @@ discovery_set_accept_controller_re(struct discovery *d, const char *re_)
     int error;
     char *re;
 
-    re = (!re_ ? xstrdup(vconn_ssl_is_configured() ? "^ssl:.*" : ".*")
+    re = (!re_ ? xstrdup(stream_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*")
           : re_[0] == '^' ? xstrdup(re_) : xasprintf("^%s", re_));
     regex = xmalloc(sizeof *regex);
     error = regcomp(regex, re, REG_NOSUB | REG_EXTENDED);
@@ -243,7 +244,7 @@ discovery_wait(struct discovery *d)
 }
 
 static void
-modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
+modify_dhcp_request(struct dhcp_msg *msg, void *aux OVS_UNUSED)
 {
     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
 }
similarity index 100%
rename from secchan/discovery.h
rename to ofproto/discovery.h
similarity index 95%
rename from secchan/fail-open.c
rename to ofproto/fail-open.c
index 48f7069..ff77de8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -76,6 +76,8 @@ struct fail_open {
     struct rconn_packet_counter *bogus_packet_counter;
 };
 
+static void fail_open_recover(struct fail_open *);
+
 /* Returns true if 'fo' should be in fail-open mode, otherwise false. */
 static inline bool
 should_fail_open(const struct fail_open *fo)
@@ -99,7 +101,7 @@ send_bogus_packet_in(struct fail_open *fo)
 
     /* Compose ofp_packet_in. */
     ofpbuf_init(&b, 128);
-    eth_addr_random(mac);
+    eth_addr_nicira_random(mac);
     compose_benign_packet(&b, "Open vSwitch Controller Probe", 0xa033, mac);
     opi = make_packet_in(pktbuf_get_null(), OFPP_LOCAL, OFPR_NO_MATCH, &b, 64);
     ofpbuf_uninit(&b);
@@ -155,7 +157,15 @@ fail_open_run(struct fail_open *fo)
 void
 fail_open_maybe_recover(struct fail_open *fo)
 {
-    if (fail_open_is_active(fo) && rconn_is_admitted(fo->controller)) {
+    if (rconn_is_admitted(fo->controller)) {
+        fail_open_recover(fo);
+    }
+}
+
+static void
+fail_open_recover(struct fail_open *fo)
+{
+    if (fail_open_is_active(fo)) {
         flow_t flow;
 
         VLOG_WARN("No longer in fail-open mode");
@@ -235,6 +245,7 @@ void
 fail_open_destroy(struct fail_open *fo)
 {
     if (fo) {
+        fail_open_recover(fo);
         /* We don't own fo->controller. */
         switch_status_unregister(fo->ss_cat);
         rconn_packet_counter_destroy(fo->bogus_packet_counter);
similarity index 100%
rename from secchan/fail-open.h
rename to ofproto/fail-open.h
similarity index 90%
rename from secchan/in-band.c
rename to ofproto/in-band.c
index c8f7779..857618f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -225,12 +225,13 @@ struct in_band {
 
     /* Keep track of local port's information. */
     uint8_t local_mac[ETH_ADDR_LEN];       /* Current MAC. */
-    char local_name[IF_NAMESIZE];          /* Local device name. */
+    struct netdev *local_netdev;           /* Local port's network device. */
     time_t next_local_refresh;
 
     /* Keep track of controller and next hop's information. */
     uint32_t controller_ip;                /* Controller IP, 0 if unknown. */
     uint8_t remote_mac[ETH_ADDR_LEN];      /* Remote MAC. */
+    struct netdev *remote_netdev;
     uint8_t last_remote_mac[ETH_ADDR_LEN]; /* Previous remote MAC. */
     time_t next_remote_refresh;
 
@@ -245,14 +246,17 @@ get_remote_mac(struct in_band *ib)
 {
     int retval;
     bool have_mac;
-    struct in_addr c_in4, r_in4;
-    char *dev_name;
+    struct in_addr c_in4;   /* Controller's IP address. */
+    struct in_addr r_in4;   /* Next hop IP address. */
+    char *next_hop_dev;
     time_t now = time_now();
 
     if (now >= ib->next_remote_refresh) {
+        /* Find the next-hop IP address. */
         c_in4.s_addr = ib->controller_ip;
         memset(ib->remote_mac, 0, sizeof ib->remote_mac);
-        retval = netdev_get_next_hop(&c_in4, &r_in4, &dev_name);
+        retval = netdev_get_next_hop(ib->local_netdev,
+                                     &c_in4, &r_in4, &next_hop_dev);
         if (retval) {
             VLOG_WARN("cannot find route for controller ("IP_FMT"): %s",
                     IP_ARGS(&ib->controller_ip), strerror(retval));
@@ -263,17 +267,34 @@ get_remote_mac(struct in_band *ib)
             r_in4.s_addr = c_in4.s_addr;
         }
 
-        retval = netdev_nodev_arp_lookup(dev_name, r_in4.s_addr, 
-                                         ib->remote_mac);
+        /* Get the next-hop IP and network device. */
+        if (!ib->remote_netdev
+            || strcmp(netdev_get_name(ib->remote_netdev), next_hop_dev))
+        {
+            netdev_close(ib->remote_netdev);
+
+            retval = netdev_open_default(next_hop_dev, &ib->remote_netdev);
+            if (retval) {
+                VLOG_WARN_RL(&rl, "cannot open netdev %s (next hop "
+                             "to controller "IP_FMT"): %s",
+                             next_hop_dev, IP_ARGS(&ib->controller_ip),
+                             strerror(retval));
+                ib->next_remote_refresh = now + 1;
+                return NULL;
+            }
+        }
+
+        /* Look up the MAC address of the next-hop IP address. */
+        retval = netdev_arp_lookup(ib->remote_netdev, r_in4.s_addr,
+                                   ib->remote_mac);
         if (retval) {
             VLOG_DBG_RL(&rl, "cannot look up remote MAC address ("IP_FMT"): %s",
                         IP_ARGS(&r_in4.s_addr), strerror(retval));
         }
         have_mac = !eth_addr_is_zero(ib->remote_mac);
-        free(dev_name);
-
-        if (have_mac 
-                && !eth_addr_equals(ib->last_remote_mac, ib->remote_mac)) {
+        free(next_hop_dev);
+        if (have_mac
+            && !eth_addr_equals(ib->last_remote_mac, ib->remote_mac)) {
             VLOG_DBG("remote MAC address changed from "ETH_ADDR_FMT" to "
                      ETH_ADDR_FMT,
                      ETH_ADDR_ARGS(ib->last_remote_mac),
@@ -299,7 +320,7 @@ get_local_mac(struct in_band *ib)
     time_t now = time_now();
     if (now >= ib->next_local_refresh) {
         uint8_t ea[ETH_ADDR_LEN];
-        if (!netdev_nodev_get_etheraddr(ib->local_name, ea)) {
+        if (ib->local_netdev && !netdev_get_etheraddr(ib->local_netdev, ea)) {
             memcpy(ib->local_mac, ea, ETH_ADDR_LEN);
         }
         ib->next_local_refresh = now + 1;
@@ -578,30 +599,44 @@ in_band_flushed(struct in_band *in_band)
     }
 }
 
-void
+int
 in_band_create(struct ofproto *ofproto, struct dpif *dpif,
                struct switch_status *ss, struct rconn *controller, 
                struct in_band **in_bandp)
 {
     struct in_band *in_band;
+    char local_name[IF_NAMESIZE];
+    struct netdev *local_netdev;
     int error;
 
-    in_band = xcalloc(1, sizeof *in_band);
-    error = dpif_port_get_name(dpif, ODPP_LOCAL, in_band->local_name, 
-                               sizeof in_band->local_name);
+    error = dpif_port_get_name(dpif, ODPP_LOCAL,
+                               local_name, sizeof local_name);
     if (error) {
-        free(in_band);
-        return;
+        VLOG_ERR("failed to initialize in-band control: cannot get name "
+                 "of datapath local port (%s)", strerror(error));
+        return error;
     }
 
+    error = netdev_open_default(local_name, &local_netdev);
+    if (error) {
+        VLOG_ERR("failed to initialize in-band control: cannot open "
+                 "datapath local port %s (%s)", local_name, strerror(error));
+        return error;
+    }
+
+    in_band = xzalloc(sizeof *in_band);
     in_band->ofproto = ofproto;
     in_band->controller = controller;
     in_band->ss_cat = switch_status_register(ss, "in-band",
                                              in_band_status_cb, in_band);
-    in_band->next_remote_refresh = TIME_MIN;
+    in_band->local_netdev = local_netdev;
     in_band->next_local_refresh = TIME_MIN;
+    in_band->remote_netdev = NULL;
+    in_band->next_remote_refresh = TIME_MIN;
 
     *in_bandp = in_band;
+
+    return 0;
 }
 
 void
@@ -609,6 +644,8 @@ in_band_destroy(struct in_band *in_band)
 {
     if (in_band) {
         switch_status_unregister(in_band->ss_cat);
+        netdev_close(in_band->local_netdev);
+        netdev_close(in_band->remote_netdev);
         /* We don't own the rconn. */
     }
 }
similarity index 88%
rename from secchan/in-band.h
rename to ofproto/in-band.h
index 736ad76..ddbc5e5 100644 (file)
@@ -24,12 +24,11 @@ struct in_band;
 struct odp_actions;
 struct ofproto;
 struct rconn;
-struct secchan;
 struct settings;
 struct switch_status;
 
-void in_band_create(struct ofproto *, struct dpif *, struct switch_status *,
-                    struct rconn *controller, struct in_band **);
+int in_band_create(struct ofproto *, struct dpif *, struct switch_status *,
+                   struct rconn *controller, struct in_band **);
 void in_band_destroy(struct in_band *);
 void in_band_run(struct in_band *);
 bool in_band_msg_in_hook(struct in_band *, const flow_t *, 
similarity index 75%
rename from secchan/netflow.c
rename to ofproto/netflow.c
index 0505cd3..34d571f 100644 (file)
@@ -20,7 +20,7 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include "cfg.h"
+#include "collectors.h"
 #include "flow.h"
 #include "netflow.h"
 #include "ofpbuf.h"
@@ -37,8 +37,6 @@
 
 #define NETFLOW_V5_VERSION 5
 
-static const int ACTIVE_TIMEOUT_DEFAULT = 600;
-
 /* Every NetFlow v5 message contains the header that follows.  This is
  * followed by up to thirty records that describe a terminating flow.
  * We only send a single record per NetFlow message.
@@ -95,8 +93,7 @@ struct netflow {
     uint8_t engine_type;          /* Value of engine_type to use. */
     uint8_t engine_id;            /* Value of engine_id to use. */
     long long int boot_time;      /* Time when netflow_create() was called. */
-    int *fds;                     /* Sockets for NetFlow collectors. */
-    size_t n_fds;                 /* Number of Netflow collectors. */
+    struct collectors *collectors; /* NetFlow collectors. */
     bool add_id_to_iface;         /* Put the 7 least signficiant bits of 
                                    * 'engine_id' into the most signficant 
                                    * bits of the interface fields. */
@@ -106,63 +103,6 @@ struct netflow {
     long long int reconfig_time;  /* When we reconfigured the timeouts. */
 };
 
-static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
-static int
-open_collector(char *dst)
-{
-    char *save_ptr = NULL;
-    const char *host_name;
-    const char *port_string;
-    struct sockaddr_in sin;
-    int retval;
-    int fd;
-
-    /* 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(dst, ":", &save_ptr);
-    port_string = strtok_r(NULL, ":", &save_ptr);
-    if (!host_name) {
-        ovs_error(0, "%s: bad peer name format", dst);
-        return -EAFNOSUPPORT;
-    }
-    if (!port_string) {
-        ovs_error(0, "%s: bad port format", dst);
-        return -EAFNOSUPPORT;
-    }
-
-    memset(&sin, 0, sizeof sin);
-    sin.sin_family = AF_INET;
-    if (lookup_ip(host_name, &sin.sin_addr)) {
-        return -ENOENT;
-    }
-    sin.sin_port = htons(atoi(port_string));
-
-    fd = socket(AF_INET, SOCK_DGRAM, 0);
-    if (fd < 0) {
-        VLOG_ERR("%s: socket: %s", dst, strerror(errno));
-        return -errno;
-    }
-
-    retval = set_nonblocking(fd);
-    if (retval) {
-        close(fd);
-        return -retval;
-    }
-
-    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
-    if (retval < 0) {
-        int error = errno;
-        VLOG_ERR("%s: connect: %s", dst, strerror(error));
-        close(fd);
-        return -error;
-    }
-
-    return fd;
-}
-
 void
 netflow_expire(struct netflow *nf, struct netflow_flow *nf_flow,
                struct ofexpired *expired)
@@ -247,76 +187,31 @@ netflow_expire(struct netflow *nf, struct netflow_flow *nf_flow,
 void
 netflow_run(struct netflow *nf)
 {
-    size_t i;
-
-    if (!nf->packet.size) {
-        return;
-    }
-
-    for (i = 0; i < nf->n_fds; i++) {
-        if (send(nf->fds[i], nf->packet.data, nf->packet.size, 0) == -1) {
-            VLOG_WARN_RL(&rl, "netflow message send failed: %s",
-                         strerror(errno));
-        }
-    }
-    nf->packet.size = 0;
-}
-
-static void
-clear_collectors(struct netflow *nf)
-{
-    size_t i;
-
-    for (i = 0; i < nf->n_fds; i++) {
-        close(nf->fds[i]);
+    if (nf->packet.size) {
+        collectors_send(nf->collectors, nf->packet.data, nf->packet.size);
+        nf->packet.size = 0;
     }
-    free(nf->fds);
-    nf->fds = NULL;
-    nf->n_fds = 0;
 }
 
 int
 netflow_set_options(struct netflow *nf,
                     const struct netflow_options *nf_options)
 {
-    struct svec collectors;
     int error = 0;
-    size_t i;
     long long int old_timeout;
 
     nf->engine_type = nf_options->engine_type;
     nf->engine_id = nf_options->engine_id;
     nf->add_id_to_iface = nf_options->add_id_to_iface;
 
-    clear_collectors(nf);
-
-    svec_clone(&collectors, &nf_options->collectors);
-    svec_sort_unique(&collectors);
-
-    nf->fds = xmalloc(sizeof *nf->fds * collectors.n);
-    for (i = 0; i < collectors.n; i++) {
-        const char *name = collectors.names[i];
-        char *tmpname = xstrdup(name);
-        int fd = open_collector(tmpname);
-        free(tmpname);
-        if (fd >= 0) {
-            nf->fds[nf->n_fds++] = fd;
-        } else {
-            VLOG_WARN("couldn't open connection to collector (%s), "
-                      "ignoring %s\n", strerror(-fd), name);
-            if (!error) {
-                error = -fd;
-            }
-        }
-    }
-
-    svec_destroy(&collectors);
+    collectors_destroy(nf->collectors);
+    collectors_create(&nf_options->collectors, 0, &nf->collectors);
 
     old_timeout = nf->active_timeout;
-    if (nf_options->active_timeout != -1) {
+    if (nf_options->active_timeout >= 0) {
         nf->active_timeout = nf_options->active_timeout;
     } else {
-        nf->active_timeout = ACTIVE_TIMEOUT_DEFAULT;
+        nf->active_timeout = NF_ACTIVE_TIMEOUT_DEFAULT;
     }
     nf->active_timeout *= 1000;
     if (old_timeout != nf->active_timeout) {
@@ -333,8 +228,7 @@ netflow_create(void)
     nf->engine_type = 0;
     nf->engine_id = 0;
     nf->boot_time = time_msec();
-    nf->fds = NULL;
-    nf->n_fds = 0;
+    nf->collectors = NULL;
     nf->add_id_to_iface = false;
     nf->netflow_cnt = 0;
     ofpbuf_init(&nf->packet, 1500);
@@ -346,7 +240,7 @@ netflow_destroy(struct netflow *nf)
 {
     if (nf) {
         ofpbuf_uninit(&nf->packet);
-        clear_collectors(nf);
+        collectors_destroy(nf->collectors);
         free(nf);
     }
 }
similarity index 94%
rename from secchan/netflow.h
rename to ofproto/netflow.h
index cc7b960..701ffd4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef NETFLOW_H
 #define NETFLOW_H 1
 
+#include <stdint.h>
 #include "flow.h"
 #include "svec.h"
 
+static const int NF_ACTIVE_TIMEOUT_DEFAULT = 600;
+
 struct ofexpired;
 
 struct netflow_options {
diff --git a/ofproto/ofproto-sflow.c b/ofproto/ofproto-sflow.c
new file mode 100644 (file)
index 0000000..4537200
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+ * Copyright (c) 2009, 2010 InMon Corp.
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "ofproto-sflow.h"
+#include <inttypes.h>
+#include <stdlib.h>
+#include "collectors.h"
+#include "dpif.h"
+#include "compiler.h"
+#include "netdev.h"
+#include "ofpbuf.h"
+#include "ofproto.h"
+#include "poll-loop.h"
+#include "port-array.h"
+#include "sflow_api.h"
+#include "socket-util.h"
+#include "timeval.h"
+
+#define THIS_MODULE VLM_sflow
+#include "vlog.h"
+
+struct ofproto_sflow_port {
+    struct netdev *netdev;      /* Underlying network device, for stats. */
+    SFLDataSource_instance dsi; /* sFlow library's notion of port number. */
+};
+
+struct ofproto_sflow {
+    struct ofproto *ofproto;
+    struct collectors *collectors;
+    SFLAgent *sflow_agent;
+    struct ofproto_sflow_options *options;
+    struct dpif *dpif;
+    time_t next_tick;
+    size_t n_flood, n_all;
+    struct port_array ports;    /* Indexed by ODP port number. */
+};
+
+#define RECEIVER_INDEX 1
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+static bool
+nullable_string_is_equal(const char *a, const char *b)
+{
+    return a ? b && !strcmp(a, b) : !b;
+}
+
+static bool
+ofproto_sflow_options_equal(const struct ofproto_sflow_options *a,
+                            const struct ofproto_sflow_options *b)
+{
+    return (svec_equal(&a->targets, &b->targets)
+            && a->sampling_rate == b->sampling_rate
+            && a->polling_interval == b->polling_interval
+            && a->header_len == b->header_len
+            && a->sub_id == b->sub_id
+            && nullable_string_is_equal(a->agent_device, b->agent_device)
+            && nullable_string_is_equal(a->control_ip, b->control_ip));
+}
+
+static struct ofproto_sflow_options *
+ofproto_sflow_options_clone(const struct ofproto_sflow_options *old)
+{
+    struct ofproto_sflow_options *new = xmemdup(old, sizeof *old);
+    svec_clone(&new->targets, &old->targets);
+    new->agent_device = old->agent_device ? xstrdup(old->agent_device) : NULL;
+    new->control_ip = old->control_ip ? xstrdup(old->control_ip) : NULL;
+    return new;
+}
+
+static void
+ofproto_sflow_options_destroy(struct ofproto_sflow_options *options)
+{
+    if (options) {
+        svec_destroy(&options->targets);
+        free(options->agent_device);
+        free(options->control_ip);
+        free(options);
+    }
+}
+
+/* sFlow library callback to allocate memory. */
+static void *
+sflow_agent_alloc_cb(void *magic OVS_UNUSED, SFLAgent *agent OVS_UNUSED,
+                     size_t bytes)
+{
+    return calloc(1, bytes);
+}
+
+/* sFlow library callback to free memory. */
+static int
+sflow_agent_free_cb(void *magic OVS_UNUSED, SFLAgent *agent OVS_UNUSED,
+                    void *obj)
+{
+    free(obj);
+    return 0;
+}
+
+/* sFlow library callback to report error. */
+static void
+sflow_agent_error_cb(void *magic OVS_UNUSED, SFLAgent *agent OVS_UNUSED,
+                     char *msg)
+{
+    VLOG_WARN("sFlow agent error: %s", msg);
+}
+
+/* sFlow library callback to send datagram. */
+static void
+sflow_agent_send_packet_cb(void *os_, SFLAgent *agent OVS_UNUSED,
+                           SFLReceiver *receiver OVS_UNUSED, u_char *pkt,
+                           uint32_t pktLen)
+{
+    struct ofproto_sflow *os = os_;
+    collectors_send(os->collectors, pkt, pktLen);
+}
+
+static void
+sflow_agent_get_counters(void *os_, SFLPoller *poller,
+                         SFL_COUNTERS_SAMPLE_TYPE *cs)
+{
+    struct ofproto_sflow *os = os_;
+    SFLCounters_sample_element elem;
+    struct ofproto_sflow_port *osp;
+    SFLIf_counters *counters;
+    struct netdev_stats stats;
+    enum netdev_flags flags;
+    uint32_t current;
+
+    osp = port_array_get(&os->ports, poller->bridgePort);
+    if (!osp) {
+        return;
+    }
+
+    elem.tag = SFLCOUNTERS_GENERIC;
+    counters = &elem.counterBlock.generic;
+    counters->ifIndex = SFL_DS_INDEX(poller->dsi);
+    counters->ifType = 6;
+    if (!netdev_get_features(osp->netdev, &current, NULL, NULL, NULL)) {
+      /* The values of ifDirection come from MAU MIB (RFC 2668): 0 = unknown,
+         1 = full-duplex, 2 = half-duplex, 3 = in, 4=out */
+        counters->ifSpeed = netdev_features_to_bps(current);
+        counters->ifDirection = (netdev_features_is_full_duplex(current)
+                                 ? 1 : 2);
+    } else {
+        counters->ifSpeed = 100000000;
+        counters->ifDirection = 0;
+    }
+    if (!netdev_get_flags(osp->netdev, &flags) && flags & NETDEV_UP) {
+        bool carrier;
+
+        counters->ifStatus = 1; /* ifAdminStatus up. */
+        if (!netdev_get_carrier(osp->netdev, &carrier) && carrier) {
+            counters->ifStatus |= 2; /* ifOperStatus us. */
+        }
+    } else {
+        counters->ifStatus = 0;  /* Down. */
+    }
+
+    /* XXX
+       1. Is the multicast counter filled in?
+       2. Does the multicast counter include broadcasts?
+       3. Does the rx_packets counter include multicasts/broadcasts?
+    */
+    netdev_get_stats(osp->netdev, &stats);
+    counters->ifInOctets = stats.rx_bytes;
+    counters->ifInUcastPkts = stats.rx_packets;
+    counters->ifInMulticastPkts = stats.multicast;
+    counters->ifInBroadcastPkts = -1;
+    counters->ifInDiscards = stats.rx_dropped;
+    counters->ifInErrors = stats.rx_errors;
+    counters->ifInUnknownProtos = -1;
+    counters->ifOutOctets = stats.tx_bytes;
+    counters->ifOutUcastPkts = stats.tx_packets;
+    counters->ifOutMulticastPkts = -1;
+    counters->ifOutBroadcastPkts = -1;
+    counters->ifOutDiscards = stats.tx_dropped;
+    counters->ifOutErrors = stats.tx_errors;
+    counters->ifPromiscuousMode = 0;
+
+    SFLADD_ELEMENT(cs, &elem);
+    sfl_poller_writeCountersSample(poller, cs);
+}
+
+/* Obtains an address to use for the local sFlow agent and stores it into
+ * '*agent_addr'.  Returns true if successful, false on failure.
+ *
+ * The sFlow agent address should be a local IP address that is persistent and
+ * reachable over the network, if possible.  The IP address associated with
+ * 'agent_device' is used if it has one, and otherwise 'control_ip', the IP
+ * address used to talk to the controller. */
+static bool
+sflow_choose_agent_address(const char *agent_device, const char *control_ip,
+                           SFLAddress *agent_addr)
+{
+    struct in_addr in4;
+
+    memset(agent_addr, 0, sizeof *agent_addr);
+    agent_addr->type = SFLADDRESSTYPE_IP_V4;
+
+    if (agent_device) {
+        struct netdev *netdev;
+
+        if (!netdev_open_default(agent_device, &netdev)) {
+            int error = netdev_get_in4(netdev, &in4, NULL);
+            netdev_close(netdev);
+            if (!error) {
+                goto success;
+            }
+        }
+    }
+
+    if (control_ip && !lookup_ip(control_ip, &in4)) {
+        goto success;
+    }
+
+    VLOG_ERR("could not determine IP address for sFlow agent");
+    return false;
+
+success:
+    agent_addr->address.ip_v4.addr = in4.s_addr;
+    return true;
+}
+
+void
+ofproto_sflow_clear(struct ofproto_sflow *os)
+{
+    struct ofproto_sflow_port *osp;
+    unsigned int odp_port;
+
+    if (os->sflow_agent) {
+        sfl_agent_release(os->sflow_agent);
+        os->sflow_agent = NULL;
+    }
+    collectors_destroy(os->collectors);
+    os->collectors = NULL;
+    ofproto_sflow_options_destroy(os->options);
+    os->options = NULL;
+
+    PORT_ARRAY_FOR_EACH (osp, &os->ports, odp_port) {
+        ofproto_sflow_del_port(os, odp_port);
+    }
+    port_array_clear(&os->ports);
+
+    /* Turn off sampling to save CPU cycles. */
+    dpif_set_sflow_probability(os->dpif, 0);
+}
+
+bool
+ofproto_sflow_is_enabled(const struct ofproto_sflow *os)
+{
+    return os->collectors != NULL;
+}
+
+struct ofproto_sflow *
+ofproto_sflow_create(struct dpif *dpif)
+{
+    struct ofproto_sflow *os;
+
+    os = xcalloc(1, sizeof *os);
+    os->dpif = dpif;
+    os->next_tick = time_now() + 1;
+    port_array_init(&os->ports);
+    return os;
+}
+
+void
+ofproto_sflow_destroy(struct ofproto_sflow *os)
+{
+    if (os) {
+        ofproto_sflow_clear(os);
+        port_array_destroy(&os->ports);
+        free(os);
+    }
+}
+
+static void
+ofproto_sflow_add_poller(struct ofproto_sflow *os,
+                         struct ofproto_sflow_port *osp, uint16_t odp_port)
+{
+    SFLPoller *poller = sfl_agent_addPoller(os->sflow_agent, &osp->dsi, os,
+                                            sflow_agent_get_counters);
+    sfl_poller_set_sFlowCpInterval(poller, os->options->polling_interval);
+    sfl_poller_set_sFlowCpReceiver(poller, RECEIVER_INDEX);
+    sfl_poller_set_bridgePort(poller, odp_port);
+}
+
+static void
+ofproto_sflow_add_sampler(struct ofproto_sflow *os,
+                         struct ofproto_sflow_port *osp,
+                         u_int32_t sampling_rate, u_int32_t header_len)
+{
+    SFLSampler *sampler = sfl_agent_addSampler(os->sflow_agent, &osp->dsi);
+    sfl_sampler_set_sFlowFsPacketSamplingRate(sampler, sampling_rate);
+    sfl_sampler_set_sFlowFsMaximumHeaderSize(sampler, header_len);
+    sfl_sampler_set_sFlowFsReceiver(sampler, RECEIVER_INDEX);
+}
+
+void
+ofproto_sflow_add_port(struct ofproto_sflow *os, uint16_t odp_port,
+                       const char *netdev_name)
+{
+    struct ofproto_sflow_port *osp;
+    struct netdev *netdev;
+    uint32_t ifindex;
+    int error;
+
+    ofproto_sflow_del_port(os, odp_port);
+
+    /* Open network device. */
+    error = netdev_open_default(netdev_name, &netdev);
+    if (error) {
+        VLOG_WARN_RL(&rl, "failed to open network device \"%s\": %s",
+                     netdev_name, strerror(error));
+        return;
+    }
+
+    /* Add to table of ports. */
+    osp = xmalloc(sizeof *osp);
+    osp->netdev = netdev;
+    ifindex = netdev_get_ifindex(netdev);
+    if (ifindex <= 0) {
+        ifindex = (os->sflow_agent->subId << 16) + odp_port;
+    }
+    SFL_DS_SET(osp->dsi, 0, ifindex, 0);
+    port_array_set(&os->ports, odp_port, osp);
+
+    /* Add poller. */
+    if (os->sflow_agent) {
+        ofproto_sflow_add_poller(os, osp, odp_port);
+    }
+}
+
+void
+ofproto_sflow_del_port(struct ofproto_sflow *os, uint16_t odp_port)
+{
+    struct ofproto_sflow_port *osp = port_array_get(&os->ports, odp_port);
+    if (osp) {
+        if (os->sflow_agent) {
+            sfl_agent_removePoller(os->sflow_agent, &osp->dsi);
+            sfl_agent_removeSampler(os->sflow_agent, &osp->dsi);
+        }
+        netdev_close(osp->netdev);
+        free(osp);
+        port_array_set(&os->ports, odp_port, NULL);
+    }
+}
+
+void
+ofproto_sflow_set_options(struct ofproto_sflow *os,
+                          const struct ofproto_sflow_options *options)
+{
+    struct ofproto_sflow_port *osp;
+    bool options_changed;
+    SFLReceiver *receiver;
+    unsigned int odp_port;
+    SFLAddress agentIP;
+    time_t now;
+
+    if (!options->targets.n || !options->sampling_rate) {
+        /* No point in doing any work if there are no targets or nothing to
+         * sample. */
+        ofproto_sflow_clear(os);
+        return;
+    }
+
+    options_changed = (!os->options
+                       || !ofproto_sflow_options_equal(options, os->options));
+
+    /* Configure collectors if options have changed or if we're shortchanged in
+     * collectors (which indicates that opening one or more of the configured
+     * collectors failed, so that we should retry). */
+    if (options_changed
+        || collectors_count(os->collectors) < options->targets.n) {
+        collectors_destroy(os->collectors);
+        collectors_create(&options->targets, SFL_DEFAULT_COLLECTOR_PORT,
+                          &os->collectors);
+        if (os->collectors == NULL) {
+            VLOG_WARN_RL(&rl, "no collectors could be initialized, "
+                         "sFlow disabled");
+            ofproto_sflow_clear(os);
+            return;
+        }
+    }
+
+    /* Avoid reconfiguring if options didn't change. */
+    if (!options_changed) {
+        return;
+    }
+    ofproto_sflow_options_destroy(os->options);
+    os->options = ofproto_sflow_options_clone(options);
+
+    /* Choose agent IP address. */
+    if (!sflow_choose_agent_address(options->agent_device,
+                                    options->control_ip, &agentIP)) {
+        ofproto_sflow_clear(os);
+        return;
+    }
+
+    /* Create agent. */
+    VLOG_INFO("creating sFlow agent %d", options->sub_id);
+    if (os->sflow_agent) {
+        sfl_agent_release(os->sflow_agent);
+    }
+    os->sflow_agent = xcalloc(1, sizeof *os->sflow_agent);
+    now = time_now();
+    sfl_agent_init(os->sflow_agent,
+                   &agentIP,
+                   options->sub_id,
+                   now,         /* Boot time. */
+                   now,         /* Current time. */
+                   os,          /* Pointer supplied to callbacks. */
+                   sflow_agent_alloc_cb,
+                   sflow_agent_free_cb,
+                   sflow_agent_error_cb,
+                   sflow_agent_send_packet_cb);
+
+    receiver = sfl_agent_addReceiver(os->sflow_agent);
+    sfl_receiver_set_sFlowRcvrOwner(receiver, "Open vSwitch sFlow");
+    sfl_receiver_set_sFlowRcvrTimeout(receiver, 0xffffffff);
+
+    /* Set the sampling_rate down in the datapath. */
+    dpif_set_sflow_probability(os->dpif,
+                               MAX(1, UINT32_MAX / options->sampling_rate));
+
+    /* Add samplers and pollers for the currently known ports. */
+    PORT_ARRAY_FOR_EACH (osp, &os->ports, odp_port) {
+        ofproto_sflow_add_sampler(os, osp,
+                                  options->sampling_rate, options->header_len);
+    }
+}
+
+static int
+ofproto_sflow_odp_port_to_ifindex(const struct ofproto_sflow *os,
+                                  uint16_t odp_port)
+{
+    struct ofproto_sflow_port *osp = port_array_get(&os->ports, odp_port);
+    return osp ? SFL_DS_INDEX(osp->dsi) : 0;
+}
+
+void
+ofproto_sflow_received(struct ofproto_sflow *os, struct odp_msg *msg)
+{
+    SFL_FLOW_SAMPLE_TYPE fs;
+    SFLFlow_sample_element hdrElem;
+    SFLSampled_header *header;
+    SFLFlow_sample_element switchElem;
+    SFLSampler *sampler;
+    const struct odp_sflow_sample_header *hdr;
+    const union odp_action *actions;
+    struct ofpbuf payload;
+    size_t n_actions, n_outputs;
+    size_t min_size;
+    flow_t flow;
+    size_t i;
+
+    /* Get odp_sflow_sample_header. */
+    min_size = sizeof *msg + sizeof *hdr;
+    if (min_size > msg->length) {
+        VLOG_WARN_RL(&rl, "sFlow packet too small (%"PRIu32" < %zu)",
+                     msg->length, min_size);
+        return;
+    }
+    hdr = (const struct odp_sflow_sample_header *) (msg + 1);
+
+    /* Get actions. */
+    n_actions = hdr->n_actions;
+    if (n_actions > 65536 / sizeof *actions) {
+        VLOG_WARN_RL(&rl, "too many actions in sFlow packet (%zu > %zu)",
+                     65536 / sizeof *actions, n_actions);
+        return;
+    }
+    min_size += n_actions * sizeof *actions;
+    if (min_size > msg->length) {
+        VLOG_WARN_RL(&rl, "sFlow packet with %zu actions too small "
+                     "(%"PRIu32" < %zu)",
+                     n_actions, msg->length, min_size);
+        return;
+    }
+    actions = (const union odp_action *) (hdr + 1);
+
+    /* Get packet payload and extract flow. */
+    payload.data = (union odp_action *) (actions + n_actions);
+    payload.size = msg->length - min_size;
+    flow_extract(&payload, msg->port, &flow);
+
+    /* Build a flow sample */
+    memset(&fs, 0, sizeof fs);
+    fs.input = ofproto_sflow_odp_port_to_ifindex(os, msg->port);
+    fs.output = 0;              /* Filled in correctly below. */
+    fs.sample_pool = hdr->sample_pool;
+
+    /* We are going to give it to the sampler that represents this input port.
+     * By implementing "ingress-only" sampling like this we ensure that we
+     * never have to offer the same sample to more than one sampler. */
+    sampler = sfl_agent_getSamplerByIfIndex(os->sflow_agent, fs.input);
+    if (!sampler) {
+        VLOG_WARN_RL(&rl, "no sampler for input ifIndex (%"PRIu32")",
+                     fs.input);
+        return;
+    }
+
+    /* Sampled header. */
+    memset(&hdrElem, 0, sizeof hdrElem);
+    hdrElem.tag = SFLFLOW_HEADER;
+    header = &hdrElem.flowType.header;
+    header->header_protocol = SFLHEADER_ETHERNET_ISO8023;
+    header->frame_length = payload.size;
+    header->stripped = 4; /* Ethernet FCS stripped off. */
+    header->header_length = MIN(payload.size,
+                                sampler->sFlowFsMaximumHeaderSize);
+    header->header_bytes = payload.data;
+
+    /* Add extended switch element. */
+    memset(&switchElem, 0, sizeof(switchElem));
+    switchElem.tag = SFLFLOW_EX_SWITCH;
+    switchElem.flowType.sw.src_vlan = ntohs(flow.dl_vlan);
+    switchElem.flowType.sw.src_priority = -1; /* XXX */
+    switchElem.flowType.sw.dst_vlan = -1;     /* Filled in correctly below. */
+    switchElem.flowType.sw.dst_priority = switchElem.flowType.sw.src_priority;
+
+    /* Figure out the output ports. */
+    n_outputs = 0;
+    for (i = 0; i < n_actions; i++) {
+        const union odp_action *a = &actions[i];
+
+        switch (a->type) {
+        case ODPAT_OUTPUT:
+            fs.output = ofproto_sflow_odp_port_to_ifindex(os, a->output.port);
+            n_outputs++;
+            break;
+
+        case ODPAT_OUTPUT_GROUP:
+            n_outputs += (a->output_group.group == DP_GROUP_FLOOD ? os->n_flood
+                          : a->output_group.group == DP_GROUP_ALL ? os->n_all
+                          : 0);
+            break;
+
+        case ODPAT_SET_VLAN_VID:
+            switchElem.flowType.sw.dst_vlan = ntohs(a->vlan_vid.vlan_vid);
+            break;
+
+        case ODPAT_SET_VLAN_PCP:
+            switchElem.flowType.sw.dst_priority = a->vlan_pcp.vlan_pcp;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    /* Set output port, as defined by http://www.sflow.org/sflow_version_5.txt
+       (search for "Input/output port information"). */
+    if (!n_outputs) {
+        /* This value indicates that the packet was dropped for an unknown
+         * reason. */
+        fs.output = 0x40000000 | 256;
+    } else if (n_outputs > 1 || !fs.output) {
+        /* Setting the high bit means "multiple output ports". */
+        fs.output = 0x80000000 | n_outputs;
+    }
+
+    /* Submit the flow sample to be encoded into the next datagram. */
+    SFLADD_ELEMENT(&fs, &hdrElem);
+    SFLADD_ELEMENT(&fs, &switchElem);
+    sfl_sampler_writeFlowSample(sampler, &fs);
+}
+
+void
+ofproto_sflow_set_group_sizes(struct ofproto_sflow *os,
+                              size_t n_flood, size_t n_all)
+{
+    os->n_flood = n_flood;
+    os->n_all = n_all;
+}
+
+void
+ofproto_sflow_run(struct ofproto_sflow *os)
+{
+    if (ofproto_sflow_is_enabled(os)) {
+        time_t now = time_now();
+        if (now >= os->next_tick) {
+            sfl_agent_tick(os->sflow_agent, now);
+            os->next_tick = now + 1;
+        }
+    }
+}
+
+void
+ofproto_sflow_wait(struct ofproto_sflow *os)
+{
+    if (ofproto_sflow_is_enabled(os)) {
+        poll_timer_wait(os->next_tick * 1000 - time_msec());
+    }
+}
diff --git a/ofproto/ofproto-sflow.h b/ofproto/ofproto-sflow.h
new file mode 100644 (file)
index 0000000..ec86d11
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009 InMon Corp.
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OFPROTO_SFLOW_H
+#define OFPROTO_SFLOW_H 1
+
+#include <stdint.h>
+#include "svec.h"
+
+struct dpif;
+struct odp_msg;
+struct ofproto_sflow_options;
+
+struct ofproto_sflow *ofproto_sflow_create(struct dpif *);
+void ofproto_sflow_destroy(struct ofproto_sflow *);
+void ofproto_sflow_set_options(struct ofproto_sflow *,
+                               const struct ofproto_sflow_options *);
+void ofproto_sflow_clear(struct ofproto_sflow *);
+bool ofproto_sflow_is_enabled(const struct ofproto_sflow *);
+
+void ofproto_sflow_add_port(struct ofproto_sflow *, uint16_t odp_port,
+                            const char *netdev_name);
+void ofproto_sflow_del_port(struct ofproto_sflow *, uint16_t odp_port);
+void ofproto_sflow_set_group_sizes(struct ofproto_sflow *,
+                                   size_t n_flood, size_t n_all);
+
+void ofproto_sflow_run(struct ofproto_sflow *);
+void ofproto_sflow_wait(struct ofproto_sflow *);
+
+void ofproto_sflow_received(struct ofproto_sflow *, struct odp_msg *);
+
+#endif /* ofproto/ofproto-sflow.h */
similarity index 84%
rename from secchan/ofproto.c
rename to ofproto/ofproto.c
index a34120e..9efc96e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
 #include "discovery.h"
 #include "dpif.h"
 #include "dynamic-string.h"
-#include "executer.h"
 #include "fail-open.h"
 #include "in-band.h"
 #include "mac-learning.h"
 #include "netflow.h"
 #include "odp-util.h"
 #include "ofp-print.h"
+#include "ofproto-sflow.h"
 #include "ofpbuf.h"
 #include "openflow/nicira-ext.h"
 #include "openflow/openflow.h"
-#include "openflow/openflow-mgmt.h"
 #include "openvswitch/datapath-protocol.h"
 #include "packets.h"
 #include "pinsched.h"
 #include "shash.h"
 #include "status.h"
 #include "stp.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "tag.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "vconn.h"
-#include "vconn-ssl.h"
 #include "xtoxll.h"
 
 #define THIS_MODULE VLM_ofproto
 #include "vlog.h"
 
-enum {
-    DP_GROUP_FLOOD = 0,
-    DP_GROUP_ALL = 1
-};
+#include "sflow_api.h"
 
 enum {
     TABLEID_HASH = 0,
@@ -87,8 +83,11 @@ static int xlate_actions(const union ofp_action *in, size_t n_in,
 struct rule {
     struct cls_rule cr;
 
+    uint64_t flow_cookie;       /* Controller-issued identifier. 
+                                   (Kept in network-byte order.) */
     uint16_t idle_timeout;      /* In seconds from time of last use. */
     uint16_t hard_timeout;      /* In seconds from time of creation. */
+    bool send_flow_removed;     /* Send a flow removed message? */
     long long int used;         /* Last-used time (0 if never used). */
     long long int created;      /* Creation time. */
     uint64_t packet_count;      /* Number of packets received. */
@@ -134,7 +133,7 @@ rule_is_hidden(const struct rule *rule)
         return true;
     }
 
-    /* Rules with priority higher than UINT16_MAX are set up by secchan itself
+    /* Rules with priority higher than UINT16_MAX are set up by ofproto itself
      * (e.g. by in-band control) and are intentionally hidden from the
      * controller. */
     if (rule->cr.priority > UINT16_MAX) {
@@ -146,7 +145,8 @@ rule_is_hidden(const struct rule *rule)
 
 static struct rule *rule_create(struct ofproto *, struct rule *super,
                                 const union ofp_action *, size_t n_actions,
-                                uint16_t idle_timeout, uint16_t hard_timeout);
+                                uint16_t idle_timeout, uint16_t hard_timeout,
+                                uint64_t flow_cookie, bool send_flow_removed);
 static void rule_free(struct rule *);
 static void rule_destroy(struct ofproto *, struct rule *);
 static struct rule *rule_from_cls_rule(const struct cls_rule *);
@@ -159,12 +159,13 @@ static void rule_install(struct ofproto *, struct rule *,
                          struct rule *displaced_rule);
 static void rule_uninstall(struct ofproto *, struct rule *);
 static void rule_post_uninstall(struct ofproto *, struct rule *);
+static void send_flow_removed(struct ofproto *p, struct rule *rule,
+                              long long int now, uint8_t reason);
 
 struct ofconn {
     struct list node;
     struct rconn *rconn;
     struct pktbuf *pktbuf;
-    bool send_flow_exp;
     int miss_send_len;
 
     struct rconn_packet_counter *packet_in_counter;
@@ -176,7 +177,7 @@ struct ofconn {
 };
 
 static struct ofconn *ofconn_create(struct ofproto *, struct rconn *);
-static void ofconn_destroy(struct ofconn *, struct ofproto *);
+static void ofconn_destroy(struct ofconn *);
 static void ofconn_run(struct ofconn *, struct ofproto *);
 static void ofconn_wait(struct ofconn *);
 static void queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn,
@@ -186,15 +187,15 @@ struct ofproto {
     /* Settings. */
     uint64_t datapath_id;       /* Datapath ID. */
     uint64_t fallback_dpid;     /* Datapath ID if no better choice found. */
-    uint64_t mgmt_id;           /* Management channel identifier. */
-    char *manufacturer;         /* Manufacturer. */
-    char *hardware;             /* Hardware. */
-    char *software;             /* Software version. */
-    char *serial;               /* Serial number. */
+    char *mfr_desc;             /* Manufacturer. */
+    char *hw_desc;              /* Hardware. */
+    char *sw_desc;              /* Software version. */
+    char *serial_desc;          /* Serial number. */
+    char *dp_desc;              /* Datapath description. */
 
     /* Datapath. */
-    struct dpif dpif;
-    struct dpifmon *dpifmon;
+    struct dpif *dpif;
+    struct netdev_monitor *netdev_monitor;
     struct port_array ports;    /* Index is ODP port nr; ofport->opp.port_no is
                                  * OFP port nr. */
     struct shash port_by_name;
@@ -207,8 +208,8 @@ struct ofproto {
     struct discovery *discovery;
     struct fail_open *fail_open;
     struct pinsched *miss_sched, *action_sched;
-    struct executer *executer;
     struct netflow *netflow;
+    struct ofproto_sflow *sflow;
 
     /* Flow table. */
     struct classifier cls;
@@ -236,7 +237,7 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
 static const struct ofhooks default_ofhooks;
 
-static uint64_t pick_datapath_id(struct dpif *, uint64_t fallback_dpid);
+static uint64_t pick_datapath_id(const struct ofproto *);
 static uint64_t pick_fallback_dpid(void);
 static void send_packet_in_miss(struct ofpbuf *, void *ofproto);
 static void send_packet_in_action(struct ofpbuf *, void *ofproto);
@@ -253,68 +254,60 @@ static void handle_odp_msg(struct ofproto *, struct ofpbuf *);
 static void handle_openflow(struct ofconn *, struct ofproto *,
                             struct ofpbuf *);
 
-static void refresh_port_group(struct ofproto *, unsigned int group);
+static void refresh_port_groups(struct ofproto *);
+
 static void update_port(struct ofproto *, const char *devname);
 static int init_ports(struct ofproto *);
 static void reinit_ports(struct ofproto *);
 
 int
-ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
+ofproto_create(const char *datapath, const char *datapath_type,
+               const struct ofhooks *ofhooks, void *aux,
                struct ofproto **ofprotop)
 {
-    struct dpifmon *dpifmon;
     struct odp_stats stats;
     struct ofproto *p;
-    struct dpif dpif;
+    struct dpif *dpif;
     int error;
 
     *ofprotop = NULL;
 
     /* Connect to datapath and start listening for messages. */
-    error = dpif_open(datapath, &dpif);
+    error = dpif_open(datapath, datapath_type, &dpif);
     if (error) {
         VLOG_ERR("failed to open datapath %s: %s", datapath, strerror(error));
         return error;
     }
-    error = dpif_get_dp_stats(&dpif, &stats);
+    error = dpif_get_dp_stats(dpif, &stats);
     if (error) {
         VLOG_ERR("failed to obtain stats for datapath %s: %s",
                  datapath, strerror(error));
-        dpif_close(&dpif);
+        dpif_close(dpif);
         return error;
     }
-    error = dpif_set_listen_mask(&dpif, ODPL_MISS | ODPL_ACTION);
+    error = dpif_recv_set_mask(dpif, ODPL_MISS | ODPL_ACTION | ODPL_SFLOW);
     if (error) {
         VLOG_ERR("failed to listen on datapath %s: %s",
                  datapath, strerror(error));
-        dpif_close(&dpif);
-        return error;
-    }
-    dpif_flow_flush(&dpif);
-    dpif_purge(&dpif);
-
-    /* Start monitoring datapath ports for status changes. */
-    error = dpifmon_create(datapath, &dpifmon);
-    if (error) {
-        VLOG_ERR("failed to starting monitoring datapath %s: %s",
-                 datapath, strerror(error));
-        dpif_close(&dpif);
+        dpif_close(dpif);
         return error;
     }
+    dpif_flow_flush(dpif);
+    dpif_recv_purge(dpif);
 
     /* Initialize settings. */
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->fallback_dpid = pick_fallback_dpid();
-    p->datapath_id = pick_datapath_id(&dpif, p->fallback_dpid);
-    VLOG_INFO("using datapath ID %012"PRIx64, p->datapath_id);
-    p->manufacturer = xstrdup("Nicira Networks, Inc.");
-    p->hardware = xstrdup("Reference Implementation");
-    p->software = xstrdup(VERSION BUILDNR);
-    p->serial = xstrdup("None");
+    p->datapath_id = p->fallback_dpid;
+    p->mfr_desc = xstrdup(DEFAULT_MFR_DESC);
+    p->hw_desc = xstrdup(DEFAULT_HW_DESC);
+    p->sw_desc = xstrdup(DEFAULT_SW_DESC);
+    p->serial_desc = xstrdup(DEFAULT_SERIAL_DESC);
+    p->dp_desc = xstrdup(DEFAULT_DP_DESC);
 
     /* Initialize datapath. */
     p->dpif = dpif;
-    p->dpifmon = dpifmon;
+    p->netdev_monitor = netdev_monitor_create();
     port_array_init(&p->ports);
     shash_init(&p->port_by_name);
     p->max_ports = stats.max_ports;
@@ -325,8 +318,8 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     p->discovery = NULL;
     p->fail_open = NULL;
     p->miss_sched = p->action_sched = NULL;
-    p->executer = NULL;
     p->netflow = NULL;
+    p->sflow = NULL;
 
     /* Initialize flow table. */
     classifier_init(&p->cls);
@@ -359,12 +352,9 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     p->ss_cat = switch_status_register(p->switch_status, "remote",
                                        rconn_status_cb, p->controller->rconn);
 
-    /* Almost done... */
-    error = init_ports(p);
-    if (error) {
-        ofproto_destroy(p);
-        return error;
-    }
+    /* Pick final datapath ID. */
+    p->datapath_id = pick_datapath_id(p);
+    VLOG_INFO("using datapath ID %016"PRIx64, p->datapath_id);
 
     *ofprotop = p;
     return 0;
@@ -374,21 +364,13 @@ void
 ofproto_set_datapath_id(struct ofproto *p, uint64_t datapath_id)
 {
     uint64_t old_dpid = p->datapath_id;
-    p->datapath_id = (datapath_id
-                      ? datapath_id
-                      : pick_datapath_id(&p->dpif, p->fallback_dpid));
+    p->datapath_id = datapath_id ? datapath_id : pick_datapath_id(p);
     if (p->datapath_id != old_dpid) {
-        VLOG_INFO("datapath ID changed to %012"PRIx64, p->datapath_id);
+        VLOG_INFO("datapath ID changed to %016"PRIx64, p->datapath_id);
         rconn_reconnect(p->controller->rconn);
     }
 }
 
-void
-ofproto_set_mgmt_id(struct ofproto *p, uint64_t mgmt_id)
-{
-    p->mgmt_id = mgmt_id;
-}
-
 void
 ofproto_set_probe_interval(struct ofproto *p, int probe_interval)
 {
@@ -408,24 +390,52 @@ ofproto_set_max_backoff(struct ofproto *p, int max_backoff)
 
 void
 ofproto_set_desc(struct ofproto *p,
-                 const char *manufacturer, const char *hardware,
-                 const char *software, const char *serial)
+                 const char *mfr_desc, const char *hw_desc,
+                 const char *sw_desc, const char *serial_desc,
+                 const char *dp_desc)
 {
-    if (manufacturer) {
-        free(p->manufacturer);
-        p->manufacturer = xstrdup(manufacturer);
+    struct ofp_desc_stats *ods;
+
+    if (mfr_desc) {
+        if (strlen(mfr_desc) >= sizeof ods->mfr_desc) {
+            VLOG_WARN("truncating mfr_desc, must be less than %zu characters",
+                    sizeof ods->mfr_desc);
+        }
+        free(p->mfr_desc);
+        p->mfr_desc = xstrdup(mfr_desc);
     }
-    if (hardware) {
-        free(p->hardware);
-        p->hardware = xstrdup(hardware);
+    if (hw_desc) {
+        if (strlen(hw_desc) >= sizeof ods->hw_desc) {
+            VLOG_WARN("truncating hw_desc, must be less than %zu characters",
+                    sizeof ods->hw_desc);
+        }
+        free(p->hw_desc);
+        p->hw_desc = xstrdup(hw_desc);
     }
-    if (software) {
-        free(p->software);
-        p->software = xstrdup(software);
+    if (sw_desc) {
+        if (strlen(sw_desc) >= sizeof ods->sw_desc) {
+            VLOG_WARN("truncating sw_desc, must be less than %zu characters",
+                    sizeof ods->sw_desc);
+        }
+        free(p->sw_desc);
+        p->sw_desc = xstrdup(sw_desc);
+    }
+    if (serial_desc) {
+        if (strlen(serial_desc) >= sizeof ods->serial_num) {
+            VLOG_WARN("truncating serial_desc, must be less than %zu "
+                    "characters",
+                    sizeof ods->serial_num);
+        }
+        free(p->serial_desc);
+        p->serial_desc = xstrdup(serial_desc);
     }
-    if (serial) {
-        free(p->serial);
-        p->serial = xstrdup(serial);
+    if (dp_desc) {
+        if (strlen(dp_desc) >= sizeof ods->dp_desc) {
+            VLOG_WARN("truncating dp_desc, must be less than %zu characters",
+                    sizeof ods->dp_desc);
+        }
+        free(p->dp_desc);
+        p->dp_desc = xstrdup(dp_desc);
     }
 }
 
@@ -434,9 +444,8 @@ ofproto_set_in_band(struct ofproto *p, bool in_band)
 {
     if (in_band != (p->in_band != NULL)) {
         if (in_band) {
-            in_band_create(p, &p->dpif, p->switch_status, 
-                           p->controller->rconn, &p->in_band);
-            return 0;
+            return in_band_create(p, p->dpif, p->switch_status,
+                                  p->controller->rconn, &p->in_band);
         } else {
             ofproto_set_discovery(p, false, NULL, true);
             in_band_destroy(p->in_band);
@@ -458,7 +467,7 @@ ofproto_set_discovery(struct ofproto *p, bool discovery,
                 return error;
             }
             error = discovery_create(re, update_resolv_conf,
-                                     &p->dpif, p->switch_status,
+                                     p->dpif, p->switch_status,
                                      &p->discovery);
             if (error) {
                 return error;
@@ -546,7 +555,7 @@ int
 ofproto_set_netflow(struct ofproto *ofproto,
                     const struct netflow_options *nf_options)
 {
-    if (nf_options->collectors.n) {
+    if (nf_options && nf_options->collectors.n) {
         if (!ofproto->netflow) {
             ofproto->netflow = netflow_create();
         }
@@ -558,6 +567,30 @@ ofproto_set_netflow(struct ofproto *ofproto,
     }
 }
 
+void
+ofproto_set_sflow(struct ofproto *ofproto,
+                  const struct ofproto_sflow_options *oso)
+{
+    struct ofproto_sflow *os = ofproto->sflow;
+    if (oso) {
+        if (!os) {
+            struct ofport *ofport;
+            unsigned int odp_port;
+
+            os = ofproto->sflow = ofproto_sflow_create(ofproto->dpif);
+            refresh_port_groups(ofproto);
+            PORT_ARRAY_FOR_EACH (ofport, &ofproto->ports, odp_port) {
+                ofproto_sflow_add_port(os, odp_port,
+                                       netdev_get_name(ofport->netdev));
+            }
+        }
+        ofproto_sflow_set_options(os, oso);
+    } else {
+        ofproto_sflow_destroy(os);
+        ofproto->sflow = NULL;
+    }
+}
+
 void
 ofproto_set_failure(struct ofproto *ofproto, bool fail_open)
 {
@@ -602,7 +635,7 @@ ofproto_set_rate_limit(struct ofproto *ofproto,
 }
 
 int
-ofproto_set_stp(struct ofproto *ofproto UNUSED, bool enable_stp)
+ofproto_set_stp(struct ofproto *ofproto OVS_UNUSED, bool enable_stp)
 {
     /* XXX */
     if (enable_stp) {
@@ -613,36 +646,12 @@ ofproto_set_stp(struct ofproto *ofproto UNUSED, bool enable_stp)
     }
 }
 
-int
-ofproto_set_remote_execution(struct ofproto *ofproto, const char *command_acl,
-                             const char *command_dir)
-{
-    if (command_acl) {
-        if (!ofproto->executer) {
-            return executer_create(command_acl, command_dir,
-                                   &ofproto->executer);
-        } else {
-            executer_set_acl(ofproto->executer, command_acl, command_dir);
-        }
-    } else {
-        executer_destroy(ofproto->executer);
-        ofproto->executer = NULL;
-    }
-    return 0;
-}
-
 uint64_t
 ofproto_get_datapath_id(const struct ofproto *ofproto)
 {
     return ofproto->datapath_id;
 }
 
-uint64_t
-ofproto_get_mgmt_id(const struct ofproto *ofproto)
-{
-    return ofproto->mgmt_id;
-}
-
 int
 ofproto_get_probe_interval(const struct ofproto *ofproto)
 {
@@ -705,16 +714,19 @@ ofproto_destroy(struct ofproto *p)
         return;
     }
 
+    /* Destroy fail-open early, because it touches the classifier. */
+    ofproto_set_failure(p, false);
+
     ofproto_flush_flows(p);
     classifier_destroy(&p->cls);
 
     LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
                         &p->all_conns) {
-        ofconn_destroy(ofconn, p);
+        ofconn_destroy(ofconn);
     }
 
-    dpif_close(&p->dpif);
-    dpifmon_destroy(p->dpifmon);
+    dpif_close(p->dpif);
+    netdev_monitor_destroy(p->netdev_monitor);
     PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) {
         ofport_free(ofport);
     }
@@ -723,11 +735,10 @@ ofproto_destroy(struct ofproto *p)
     switch_status_destroy(p->switch_status);
     in_band_destroy(p->in_band);
     discovery_destroy(p->discovery);
-    fail_open_destroy(p->fail_open);
     pinsched_destroy(p->miss_sched);
     pinsched_destroy(p->action_sched);
-    executer_destroy(p->executer);
     netflow_destroy(p->netflow);
+    ofproto_sflow_destroy(p->sflow);
 
     switch_status_unregister(p->ss_cat);
 
@@ -743,6 +754,14 @@ ofproto_destroy(struct ofproto *p)
 
     mac_learning_destroy(p->ml);
 
+    free(p->mfr_desc);
+    free(p->hw_desc);
+    free(p->sw_desc);
+    free(p->serial_desc);
+    free(p->dp_desc);
+
+    port_array_destroy(&p->ports);
+
     free(p);
 }
 
@@ -756,6 +775,17 @@ ofproto_run(struct ofproto *p)
     return error;
 }
 
+static void
+process_port_change(struct ofproto *ofproto, int error, char *devname)
+{
+    if (error == ENOBUFS) {
+        reinit_ports(ofproto);
+    } else if (!error) {
+        update_port(ofproto, devname);
+        free(devname);
+    }
+}
+
 int
 ofproto_run1(struct ofproto *p)
 {
@@ -764,19 +794,23 @@ ofproto_run1(struct ofproto *p)
     int error;
     int i;
 
+    if (shash_is_empty(&p->port_by_name)) {
+        init_ports(p);
+    }
+
     for (i = 0; i < 50; i++) {
         struct ofpbuf *buf;
         int error;
 
-        error = dpif_recv(&p->dpif, &buf);
+        error = dpif_recv(p->dpif, &buf);
         if (error) {
             if (error == ENODEV) {
                 /* Someone destroyed the datapath behind our back.  The caller
                  * better destroy us and give up, because we're just going to
                  * spin from here on out. */
-                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-                VLOG_ERR_RL(&rl, "dp%u: datapath was destroyed externally",
-                            dpif_id(&p->dpif));
+                static struct vlog_rate_limit rl2 = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_ERR_RL(&rl2, "%s: datapath was destroyed externally",
+                            dpif_name(p->dpif));
                 return ENODEV;
             }
             break;
@@ -785,13 +819,12 @@ ofproto_run1(struct ofproto *p)
         handle_odp_msg(p, buf);
     }
 
-    while ((error = dpifmon_poll(p->dpifmon, &devname)) != EAGAIN) {
-        if (error == ENOBUFS) {
-            reinit_ports(p);
-        } else if (!error) {
-            update_port(p, devname);
-            free(devname);
-        }
+    while ((error = dpif_port_poll(p->dpif, &devname)) != EAGAIN) {
+        process_port_change(p, error, devname);
+    }
+    while ((error = netdev_monitor_poll(p->netdev_monitor,
+                                        &devname)) != EAGAIN) {
+        process_port_change(p, error, devname);
     }
 
     if (p->in_band) {
@@ -812,9 +845,6 @@ ofproto_run1(struct ofproto *p)
     }
     pinsched_run(p->miss_sched, send_packet_in_miss, p);
     pinsched_run(p->action_sched, send_packet_in_action, p);
-    if (p->executer) {
-        executer_run(p->executer);
-    }
 
     LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
                         &p->all_conns) {
@@ -870,6 +900,9 @@ ofproto_run1(struct ofproto *p)
     if (p->netflow) {
         netflow_run(p->netflow);
     }
+    if (p->sflow) {
+        ofproto_sflow_run(p->sflow);
+    }
 
     return 0;
 }
@@ -906,8 +939,9 @@ ofproto_wait(struct ofproto *p)
     struct ofconn *ofconn;
     size_t i;
 
-    dpif_recv_wait(&p->dpif);
-    dpifmon_wait(p->dpifmon);
+    dpif_recv_wait(p->dpif);
+    dpif_port_poll_wait(p->dpif);
+    netdev_monitor_poll_wait(p->netdev_monitor);
     LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
         ofconn_wait(ofconn);
     }
@@ -922,8 +956,8 @@ ofproto_wait(struct ofproto *p)
     }
     pinsched_wait(p->miss_sched);
     pinsched_wait(p->action_sched);
-    if (p->executer) {
-        executer_wait(p->executer);
+    if (p->sflow) {
+        ofproto_sflow_wait(p->sflow);
     }
     if (!tag_set_is_empty(&p->revalidate_set)) {
         poll_immediate_wake();
@@ -977,7 +1011,7 @@ ofproto_send_packet(struct ofproto *p, const flow_t *flow,
 
     /* XXX Should we translate the dpif_execute() errno value into an OpenFlow
      * error code? */
-    dpif_execute(&p->dpif, flow->in_port, odp_actions.actions,
+    dpif_execute(p->dpif, flow->in_port, odp_actions.actions,
                  odp_actions.n_actions, packet);
     return 0;
 }
@@ -990,7 +1024,8 @@ ofproto_add_flow(struct ofproto *p,
 {
     struct rule *rule;
     rule = rule_create(p, NULL, actions, n_actions,
-                       idle_timeout >= 0 ? idle_timeout : 5 /* XXX */, 0);
+                       idle_timeout >= 0 ? idle_timeout : 5 /* XXX */, 
+                       0, 0, false);
     cls_rule_from_flow(&rule->cr, flow, wildcards, priority);
     rule_insert(p, rule, NULL, 0);
 }
@@ -1029,7 +1064,7 @@ ofproto_flush_flows(struct ofproto *ofproto)
 {
     COVERAGE_INC(ofproto_flush);
     classifier_for_each(&ofproto->cls, CLS_INC_ALL, destroy_rule, ofproto);
-    dpif_flow_flush(&ofproto->dpif);
+    dpif_flow_flush(ofproto->dpif);
     if (ofproto->in_band) {
         in_band_flushed(ofproto->in_band);
     }
@@ -1052,7 +1087,7 @@ reinit_ports(struct ofproto *p)
     PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) {
         svec_add (&devnames, (char *) ofport->opp.name);
     }
-    dpif_port_list(&p->dpif, &odp_ports, &n_odp_ports);
+    dpif_port_list(p->dpif, &odp_ports, &n_odp_ports);
     for (i = 0; i < n_odp_ports; i++) {
         svec_add (&devnames, odp_ports[i].devname);
     }
@@ -1065,7 +1100,7 @@ reinit_ports(struct ofproto *p)
     svec_destroy(&devnames);
 }
 
-static void
+static size_t
 refresh_port_group(struct ofproto *p, unsigned int group)
 {
     uint16_t *ports;
@@ -1082,27 +1117,38 @@ refresh_port_group(struct ofproto *p, unsigned int group)
             ports[n_ports++] = port_no;
         }
     }
-    dpif_port_group_set(&p->dpif, group, ports, n_ports);
+    dpif_port_group_set(p->dpif, group, ports, n_ports);
     free(ports);
+
+    return n_ports;
 }
 
 static void
 refresh_port_groups(struct ofproto *p)
 {
-    refresh_port_group(p, DP_GROUP_FLOOD);
-    refresh_port_group(p, DP_GROUP_ALL);
+    size_t n_flood = refresh_port_group(p, DP_GROUP_FLOOD);
+    size_t n_all = refresh_port_group(p, DP_GROUP_ALL);
+    if (p->sflow) {
+        ofproto_sflow_set_group_sizes(p->sflow, n_flood, n_all);
+    }
 }
 
 static struct ofport *
 make_ofport(const struct odp_port *odp_port)
 {
+    struct netdev_options netdev_options;
     enum netdev_flags flags;
     struct ofport *ofport;
     struct netdev *netdev;
     bool carrier;
     int error;
 
-    error = netdev_open(odp_port->devname, NETDEV_ETH_TYPE_NONE, &netdev);
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = odp_port->devname;
+    netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
+    netdev_options.may_open = true;
+
+    error = netdev_open(&netdev_options, &netdev);
     if (error) {
         VLOG_WARN_RL(&rl, "ignoring port %s (%"PRIu16") because netdev %s "
                      "cannot be opened (%s)",
@@ -1114,7 +1160,7 @@ make_ofport(const struct odp_port *odp_port)
     ofport = xmalloc(sizeof *ofport);
     ofport->netdev = netdev;
     ofport->opp.port_no = odp_port_to_ofp_port(odp_port->port);
-    memcpy(ofport->opp.hw_addr, netdev_get_etheraddr(netdev), ETH_ALEN);
+    netdev_get_etheraddr(netdev, ofport->opp.hw_addr);
     memcpy(ofport->opp.name, odp_port->devname,
            MIN(sizeof ofport->opp.name, sizeof odp_port->devname));
     ofport->opp.name[sizeof ofport->opp.name - 1] = '\0';
@@ -1189,17 +1235,29 @@ send_port_status(struct ofproto *p, const struct ofport *ofport,
 static void
 ofport_install(struct ofproto *p, struct ofport *ofport)
 {
-    port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no),
-                   ofport);
-    shash_add(&p->port_by_name, (char *) ofport->opp.name, ofport);
+    uint16_t odp_port = ofp_port_to_odp_port(ofport->opp.port_no);
+    const char *netdev_name = (const char *) ofport->opp.name;
+
+    netdev_monitor_add(p->netdev_monitor, ofport->netdev);
+    port_array_set(&p->ports, odp_port, ofport);
+    shash_add(&p->port_by_name, netdev_name, ofport);
+    if (p->sflow) {
+        ofproto_sflow_add_port(p->sflow, odp_port, netdev_name);
+    }
 }
 
 static void
 ofport_remove(struct ofproto *p, struct ofport *ofport)
 {
-    port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), NULL);
+    uint16_t odp_port = ofp_port_to_odp_port(ofport->opp.port_no);
+
+    netdev_monitor_remove(p->netdev_monitor, ofport->netdev);
+    port_array_set(&p->ports, odp_port, NULL);
     shash_delete(&p->port_by_name,
                  shash_find(&p->port_by_name, (char *) ofport->opp.name));
+    if (p->sflow) {
+        ofproto_sflow_del_port(p->sflow, odp_port);
+    }
 }
 
 static void
@@ -1222,7 +1280,7 @@ update_port(struct ofproto *p, const char *devname)
     COVERAGE_INC(ofproto_update_port);
 
     /* Query the datapath for port information. */
-    error = dpif_port_query_by_name(&p->dpif, devname, &odp_port);
+    error = dpif_port_query_by_name(p->dpif, devname, &odp_port);
 
     /* Find the old ofport. */
     old_ofport = shash_find_data(&p->port_by_name, devname);
@@ -1291,7 +1349,7 @@ init_ports(struct ofproto *p)
     size_t i;
     int error;
 
-    error = dpif_port_list(&p->dpif, &ports, &n_ports);
+    error = dpif_port_list(p->dpif, &ports, &n_ports);
     if (error) {
         return error;
     }
@@ -1317,7 +1375,6 @@ ofconn_create(struct ofproto *p, struct rconn *rconn)
     list_push_back(&p->all_conns, &ofconn->node);
     ofconn->rconn = rconn;
     ofconn->pktbuf = NULL;
-    ofconn->send_flow_exp = false;
     ofconn->miss_send_len = 0;
     ofconn->packet_in_counter = rconn_packet_counter_create ();
     ofconn->reply_counter = rconn_packet_counter_create ();
@@ -1325,12 +1382,8 @@ ofconn_create(struct ofproto *p, struct rconn *rconn)
 }
 
 static void
-ofconn_destroy(struct ofconn *ofconn, struct ofproto *p)
+ofconn_destroy(struct ofconn *ofconn)
 {
-    if (p->executer) {
-        executer_rconn_closing(p->executer, ofconn->rconn);
-    }
-
     list_remove(&ofconn->node);
     rconn_destroy(ofconn->rconn);
     rconn_packet_counter_destroy(ofconn->packet_in_counter);
@@ -1363,7 +1416,7 @@ ofconn_run(struct ofconn *ofconn, struct ofproto *p)
     }
 
     if (ofconn != p->controller && !rconn_is_alive(ofconn->rconn)) {
-        ofconn_destroy(ofconn, p);
+        ofconn_destroy(ofconn);
     }
 }
 
@@ -1383,12 +1436,15 @@ ofconn_wait(struct ofconn *ofconn)
 static struct rule *
 rule_create(struct ofproto *ofproto, struct rule *super,
             const union ofp_action *actions, size_t n_actions,
-            uint16_t idle_timeout, uint16_t hard_timeout)
+            uint16_t idle_timeout, uint16_t hard_timeout,
+            uint64_t flow_cookie, bool send_flow_removed)
 {
-    struct rule *rule = xcalloc(1, sizeof *rule);
+    struct rule *rule = xzalloc(sizeof *rule);
     rule->idle_timeout = idle_timeout;
     rule->hard_timeout = hard_timeout;
+    rule->flow_cookie = flow_cookie;
     rule->used = rule->created = time_msec();
+    rule->send_flow_removed = send_flow_removed;
     rule->super = super;
     if (super) {
         list_push_back(&super->list, &rule->list);
@@ -1499,7 +1555,7 @@ rule_execute(struct ofproto *ofproto, struct rule *rule,
     }
 
     /* Execute the ODP actions. */
-    if (!dpif_execute(&ofproto->dpif, flow->in_port,
+    if (!dpif_execute(ofproto->dpif, flow->in_port,
                       actions, n_actions, packet)) {
         struct odp_flow_stats stats;
         flow_extract_stats(flow, packet, &stats);
@@ -1548,7 +1604,8 @@ rule_create_subrule(struct ofproto *ofproto, struct rule *rule,
                     const flow_t *flow)
 {
     struct rule *subrule = rule_create(ofproto, rule, NULL, 0,
-                                       rule->idle_timeout, rule->hard_timeout);
+                                       rule->idle_timeout, rule->hard_timeout,
+                                       0, false);
     COVERAGE_INC(ofproto_subrule_create);
     cls_rule_from_flow(&subrule->cr, flow, 0,
                        (rule->cr.priority <= UINT16_MAX ? UINT16_MAX
@@ -1610,7 +1667,7 @@ do_put_flow(struct ofproto *ofproto, struct rule *rule, int flags,
     put->flow.actions = rule->odp_actions;
     put->flow.n_actions = rule->n_odp_actions;
     put->flags = flags;
-    return dpif_flow_put(&ofproto->dpif, put);
+    return dpif_flow_put(ofproto->dpif, put);
 }
 
 static void
@@ -1704,7 +1761,7 @@ rule_uninstall(struct ofproto *p, struct rule *rule)
         odp_flow.key = rule->cr.flow;
         odp_flow.actions = NULL;
         odp_flow.n_actions = 0;
-        if (!dpif_flow_del(&p->dpif, &odp_flow)) {
+        if (!dpif_flow_del(p->dpif, &odp_flow)) {
             update_stats(p, rule, &odp_flow.stats);
         }
         rule->installed = false;
@@ -1834,7 +1891,7 @@ handle_features_request(struct ofproto *p, struct ofconn *ofconn,
     osf->n_buffers = htonl(pktbuf_capacity());
     osf->n_tables = 2;
     osf->capabilities = htonl(OFPC_FLOW_STATS | OFPC_TABLE_STATS |
-                              OFPC_PORT_STATS | OFPC_MULTI_PHY_TX);
+                              OFPC_PORT_STATS | OFPC_ARP_MATCH_IP);
     osf->actions = htonl((1u << OFPAT_OUTPUT) |
                          (1u << OFPAT_SET_VLAN_VID) |
                          (1u << OFPAT_SET_VLAN_PCP) |
@@ -1843,6 +1900,7 @@ handle_features_request(struct ofproto *p, struct ofconn *ofconn,
                          (1u << OFPAT_SET_DL_DST) |
                          (1u << OFPAT_SET_NW_SRC) |
                          (1u << OFPAT_SET_NW_DST) |
+                         (1u << OFPAT_SET_NW_TOS) |
                          (1u << OFPAT_SET_TP_SRC) |
                          (1u << OFPAT_SET_TP_DST));
 
@@ -1864,11 +1922,8 @@ handle_get_config_request(struct ofproto *p, struct ofconn *ofconn,
     bool drop_frags;
 
     /* Figure out flags. */
-    dpif_get_drop_frags(&p->dpif, &drop_frags);
+    dpif_get_drop_frags(p->dpif, &drop_frags);
     flags = drop_frags ? OFPC_FRAG_DROP : OFPC_FRAG_NORMAL;
-    if (ofconn->send_flow_exp) {
-        flags |= OFPC_SEND_FLOW_EXP;
-    }
 
     /* Send reply. */
     osc = make_openflow_xid(sizeof *osc, OFPT_GET_CONFIG_REPLY, oh->xid, &buf);
@@ -1892,15 +1947,13 @@ handle_set_config(struct ofproto *p, struct ofconn *ofconn,
     }
     flags = ntohs(osc->flags);
 
-    ofconn->send_flow_exp = (flags & OFPC_SEND_FLOW_EXP) != 0;
-
     if (ofconn == p->controller) {
         switch (flags & OFPC_FRAG_MASK) {
         case OFPC_FRAG_NORMAL:
-            dpif_set_drop_frags(&p->dpif, false);
+            dpif_set_drop_frags(p->dpif, false);
             break;
         case OFPC_FRAG_DROP:
-            dpif_set_drop_frags(&p->dpif, true);
+            dpif_set_drop_frags(p->dpif, true);
             break;
         default:
             VLOG_WARN_RL(&rl, "requested bad fragment mode (flags=%"PRIx16")",
@@ -2157,11 +2210,26 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
             oa->nw_addr.nw_addr = ia->nw_addr.nw_addr;
             break;
 
+        case OFPAT_SET_NW_DST:
+            oa = odp_actions_add(ctx->out, ODPAT_SET_NW_DST);
+            oa->nw_addr.nw_addr = ia->nw_addr.nw_addr;
+            break;
+
+        case OFPAT_SET_NW_TOS:
+            oa = odp_actions_add(ctx->out, ODPAT_SET_NW_TOS);
+            oa->nw_tos.nw_tos = ia->nw_tos.nw_tos;
+            break;
+
         case OFPAT_SET_TP_SRC:
             oa = odp_actions_add(ctx->out, ODPAT_SET_TP_SRC);
             oa->tp_port.tp_port = ia->tp_port.tp_port;
             break;
 
+        case OFPAT_SET_TP_DST:
+            oa = odp_actions_add(ctx->out, ODPAT_SET_TP_DST);
+            oa->tp_port.tp_port = ia->tp_port.tp_port;
+            break;
+
         case OFPAT_VENDOR:
             xlate_nicira_action(ctx, (const struct nx_action_header *) ia);
             break;
@@ -2250,7 +2318,7 @@ handle_packet_out(struct ofproto *p, struct ofconn *ofconn,
         return error;
     }
 
-    dpif_execute(&p->dpif, flow.in_port, actions.actions, actions.n_actions,
+    dpif_execute(p->dpif, flow.in_port, actions.actions, actions.n_actions,
                  &payload);
     ofpbuf_delete(buffer);
 
@@ -2278,7 +2346,7 @@ update_port_config(struct ofproto *p, struct ofport *port,
 #undef REVALIDATE_BITS
     if (mask & OFPPC_NO_FLOOD) {
         port->opp.config ^= OFPPC_NO_FLOOD;
-        refresh_port_group(p, DP_GROUP_FLOOD);
+        refresh_port_groups(p);
     }
     if (mask & OFPPC_NO_PACKET_IN) {
         port->opp.config ^= OFPPC_NO_PACKET_IN;
@@ -2355,10 +2423,12 @@ handle_desc_stats_request(struct ofproto *p, struct ofconn *ofconn,
 
     msg = start_stats_reply(request, sizeof *ods);
     ods = append_stats_reply(sizeof *ods, ofconn, &msg);
-    strncpy(ods->mfr_desc, p->manufacturer, sizeof ods->mfr_desc);
-    strncpy(ods->hw_desc, p->hardware, sizeof ods->hw_desc);
-    strncpy(ods->sw_desc, p->software, sizeof ods->sw_desc);
-    strncpy(ods->serial_num, p->serial, sizeof ods->serial_num);
+    memset(ods, 0, sizeof *ods);
+    ovs_strlcpy(ods->mfr_desc, p->mfr_desc, sizeof ods->mfr_desc);
+    ovs_strlcpy(ods->hw_desc, p->hw_desc, sizeof ods->hw_desc);
+    ovs_strlcpy(ods->sw_desc, p->sw_desc, sizeof ods->sw_desc);
+    ovs_strlcpy(ods->serial_num, p->serial_desc, sizeof ods->serial_num);
+    ovs_strlcpy(ods->dp_desc, p->dp_desc, sizeof ods->dp_desc);
     queue_tx(msg, ofconn, ofconn->reply_counter);
 
     return 0;
@@ -2393,7 +2463,7 @@ handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn,
     n_wild = classifier_count(&p->cls) - classifier_count_exact(&p->cls);
 
     /* Hash table. */
-    dpif_get_dp_stats(&p->dpif, &dpstats);
+    dpif_get_dp_stats(p->dpif, &dpstats);
     ots = append_stats_reply(sizeof *ots, ofconn, &msg);
     memset(ots, 0, sizeof *ots);
     ots->table_id = TABLEID_HASH;
@@ -2420,39 +2490,62 @@ handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn,
     return 0;
 }
 
+static void
+append_port_stat(struct ofport *port, uint16_t port_no, struct ofconn *ofconn, 
+                 struct ofpbuf *msg)
+{
+    struct netdev_stats stats;
+    struct ofp_port_stats *ops;
+
+    /* Intentionally ignore return value, since errors will set 
+     * 'stats' to all-1s, which is correct for OpenFlow, and 
+     * netdev_get_stats() will log errors. */
+    netdev_get_stats(port->netdev, &stats);
+
+    ops = append_stats_reply(sizeof *ops, ofconn, &msg);
+    ops->port_no = htons(odp_port_to_ofp_port(port_no));
+    memset(ops->pad, 0, sizeof ops->pad);
+    ops->rx_packets = htonll(stats.rx_packets);
+    ops->tx_packets = htonll(stats.tx_packets);
+    ops->rx_bytes = htonll(stats.rx_bytes);
+    ops->tx_bytes = htonll(stats.tx_bytes);
+    ops->rx_dropped = htonll(stats.rx_dropped);
+    ops->tx_dropped = htonll(stats.tx_dropped);
+    ops->rx_errors = htonll(stats.rx_errors);
+    ops->tx_errors = htonll(stats.tx_errors);
+    ops->rx_frame_err = htonll(stats.rx_frame_errors);
+    ops->rx_over_err = htonll(stats.rx_over_errors);
+    ops->rx_crc_err = htonll(stats.rx_crc_errors);
+    ops->collisions = htonll(stats.collisions);
+}
+
 static int
 handle_port_stats_request(struct ofproto *p, struct ofconn *ofconn,
-                          struct ofp_stats_request *request)
+                          struct ofp_stats_request *osr,
+                          size_t arg_size)
 {
+    struct ofp_port_stats_request *psr;
     struct ofp_port_stats *ops;
     struct ofpbuf *msg;
     struct ofport *port;
     unsigned int port_no;
 
-    msg = start_stats_reply(request, sizeof *ops * 16);
-    PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
-        struct netdev_stats stats;
-
-        /* Intentionally ignore return value, since errors will set 'stats' to
-         * all-1s, which is correct for OpenFlow, and netdev_get_stats() will
-         * log errors. */
-        netdev_get_stats(port->netdev, &stats);
-
-        ops = append_stats_reply(sizeof *ops, ofconn, &msg);
-        ops->port_no = htons(odp_port_to_ofp_port(port_no));
-        memset(ops->pad, 0, sizeof ops->pad);
-        ops->rx_packets = htonll(stats.rx_packets);
-        ops->tx_packets = htonll(stats.tx_packets);
-        ops->rx_bytes = htonll(stats.rx_bytes);
-        ops->tx_bytes = htonll(stats.tx_bytes);
-        ops->rx_dropped = htonll(stats.rx_dropped);
-        ops->tx_dropped = htonll(stats.tx_dropped);
-        ops->rx_errors = htonll(stats.rx_errors);
-        ops->tx_errors = htonll(stats.tx_errors);
-        ops->rx_frame_err = htonll(stats.rx_frame_errors);
-        ops->rx_over_err = htonll(stats.rx_over_errors);
-        ops->rx_crc_err = htonll(stats.rx_crc_errors);
-        ops->collisions = htonll(stats.collisions);
+    if (arg_size != sizeof *psr) {
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
+    }
+    psr = (struct ofp_port_stats_request *) osr->body;
+
+    msg = start_stats_reply(osr, sizeof *ops * 16);
+    if (psr->port_no != htons(OFPP_NONE)) {
+        port = port_array_get(&p->ports, 
+                ofp_port_to_odp_port(ntohs(psr->port_no)));
+        if (port) {
+            append_port_stat(port, ntohs(psr->port_no), ofconn, msg);
+        }
+    } else {
+        PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
+            append_port_stat(port, port_no, ofconn, msg);
+        }
     }
 
     queue_tx(msg, ofconn, ofconn->reply_counter);
@@ -2466,6 +2559,9 @@ struct flow_stats_cbdata {
     struct ofpbuf *msg;
 };
 
+/* Obtains statistic counters for 'rule' within 'p' and stores them into
+ * '*packet_countp' and '*byte_countp'.  If 'rule' is a wildcarded rule, the
+ * returned statistic include statistics for all of 'rule''s subrules. */
 static void
 query_stats(struct ofproto *p, struct rule *rule,
             uint64_t *packet_countp, uint64_t *byte_countp)
@@ -2475,11 +2571,21 @@ query_stats(struct ofproto *p, struct rule *rule,
     struct odp_flow *odp_flows;
     size_t n_odp_flows;
 
+    /* Start from historical data for 'rule' itself that are no longer tracked
+     * by the datapath.  This counts, for example, subrules that have
+     * expired. */
     packet_count = rule->packet_count;
     byte_count = rule->byte_count;
 
+    /* Prepare to ask the datapath for statistics on 'rule', or if it is
+     * wildcarded then on all of its subrules.
+     *
+     * Also, add any statistics that are not tracked by the datapath for each
+     * subrule.  This includes, for example, statistics for packets that were
+     * executed "by hand" by ofproto via dpif_execute() but must be accounted
+     * to a flow. */
     n_odp_flows = rule->cr.wc.wildcards ? list_size(&rule->list) : 1;
-    odp_flows = xcalloc(1, n_odp_flows * sizeof *odp_flows);
+    odp_flows = xzalloc(n_odp_flows * sizeof *odp_flows);
     if (rule->cr.wc.wildcards) {
         size_t i = 0;
         LIST_FOR_EACH (subrule, struct rule, list, &rule->list) {
@@ -2491,7 +2597,8 @@ query_stats(struct ofproto *p, struct rule *rule,
         odp_flows[0].key = rule->cr.flow;
     }
 
-    if (!dpif_flow_get_multiple(&p->dpif, odp_flows, n_odp_flows)) {
+    /* Fetch up-to-date statistics from the datapath and add them in. */
+    if (!dpif_flow_get_multiple(p->dpif, odp_flows, n_odp_flows)) {
         size_t i;
         for (i = 0; i < n_odp_flows; i++) {
             struct odp_flow *odp_flow = &odp_flows[i];
@@ -2501,6 +2608,7 @@ query_stats(struct ofproto *p, struct rule *rule,
     }
     free(odp_flows);
 
+    /* Return the stats to the caller. */
     *packet_countp = packet_count;
     *byte_countp = byte_count;
 }
@@ -2513,6 +2621,9 @@ flow_stats_cb(struct cls_rule *rule_, void *cbdata_)
     struct ofp_flow_stats *ofs;
     uint64_t packet_count, byte_count;
     size_t act_len, len;
+    long long int tdiff = time_msec() - rule->created;
+    uint32_t sec = tdiff / 1000;
+    uint32_t msec = tdiff - (sec * 1000);
 
     if (rule_is_hidden(rule) || !rule_has_out_port(rule, cbdata->out_port)) {
         return;
@@ -2528,7 +2639,9 @@ flow_stats_cb(struct cls_rule *rule_, void *cbdata_)
     ofs->table_id = rule->cr.wc.wildcards ? TABLEID_CLASSIFIER : TABLEID_HASH;
     ofs->pad = 0;
     flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofs->match);
-    ofs->duration = htonl((time_msec() - rule->created) / 1000);
+    ofs->duration_sec = htonl(sec);
+    ofs->duration_nsec = htonl(msec * 1000000);
+    ofs->cookie = rule->flow_cookie;
     ofs->priority = htons(rule->cr.priority);
     ofs->idle_timeout = htons(rule->idle_timeout);
     ofs->hard_timeout = htons(rule->hard_timeout);
@@ -2557,7 +2670,7 @@ handle_flow_stats_request(struct ofproto *p, struct ofconn *ofconn,
     struct cls_rule target;
 
     if (arg_size != sizeof *fsr) {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     fsr = (struct ofp_flow_stats_request *) osr->body;
 
@@ -2595,7 +2708,7 @@ flow_stats_ds_cb(struct cls_rule *rule_, void *cbdata_)
     }
 
     query_stats(cbdata->ofproto, rule, &packet_count, &byte_count);
-    flow_to_ovs_match(&rule->cr.flow, rule->cr.wc.wildcards, &match);
+    flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &match);
 
     ds_put_format(results, "duration=%llds, ",
                   (time_msec() - rule->created) / 1000);
@@ -2665,7 +2778,7 @@ handle_aggregate_stats_request(struct ofproto *p, struct ofconn *ofconn,
     struct ofpbuf *msg;
 
     if (arg_size != sizeof *asr) {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     asr = (struct ofp_aggregate_stats_request *) osr->body;
 
@@ -2718,7 +2831,7 @@ handle_stats_request(struct ofproto *p, struct ofconn *ofconn,
         return handle_table_stats_request(p, ofconn, osr);
 
     case OFPST_PORT:
-        return handle_port_stats_request(p, ofconn, osr);
+        return handle_port_stats_request(p, ofconn, osr, arg_size);
 
     case OFPST_VENDOR:
         return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR);
@@ -2741,6 +2854,9 @@ update_time(struct ofproto *ofproto, struct rule *rule,
     long long int used = msec_from_nsec(stats->used_sec, stats->used_nsec);
     if (used > rule->used) {
         rule->used = used;
+        if (rule->super && used > rule->super->used) {
+            rule->super->used = used;
+        }
         netflow_flow_update_time(ofproto->netflow, &rule->nf_flow, used);
     }
 }
@@ -2767,16 +2883,30 @@ add_flow(struct ofproto *p, struct ofconn *ofconn,
     uint16_t in_port;
     int error;
 
+    if (ofm->flags & htons(OFPFF_CHECK_OVERLAP)) {
+        flow_t flow;
+        uint32_t wildcards;
+
+        flow_from_match(&flow, &wildcards, &ofm->match);
+        if (classifier_rule_overlaps(&p->cls, &flow, wildcards,
+                                     ntohs(ofm->priority))) {
+            return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_OVERLAP);
+        }
+    }
+
     rule = rule_create(p, NULL, (const union ofp_action *) ofm->actions,
                        n_actions, ntohs(ofm->idle_timeout),
-                       ntohs(ofm->hard_timeout));
+                       ntohs(ofm->hard_timeout),  ofm->cookie,
+                       ofm->flags & htons(OFPFF_SEND_FLOW_REM));
     cls_rule_from_match(&rule->cr, &ofm->match, ntohs(ofm->priority));
 
-    packet = NULL;
     error = 0;
     if (ofm->buffer_id != htonl(UINT32_MAX)) {
         error = pktbuf_retrieve(ofconn->pktbuf, ntohl(ofm->buffer_id),
                                 &packet, &in_port);
+    } else {
+        packet = NULL;
+        in_port = UINT16_MAX;
     }
 
     rule_insert(p, rule, packet, in_port);
@@ -2793,10 +2923,13 @@ modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm,
     }
 
     if (command == OFPFC_DELETE) {
+        long long int now = time_msec();
+        send_flow_removed(p, rule, now, OFPRR_DELETE);
         rule_remove(p, rule);
     } else {
         size_t actions_len = n_actions * sizeof *rule->actions;
 
+        rule->flow_cookie = ofm->cookie;
         if (n_actions == rule->n_actions
             && !memcmp(ofm->actions, rule->actions, actions_len))
         {
@@ -2900,6 +3033,14 @@ handle_flow_mod(struct ofproto *p, struct ofconn *ofconn,
         return error;
     }
 
+    /* We do not support the emergency flow cache.  It will hopefully
+     * get dropped from OpenFlow in the near future. */
+    if (ofm->flags & htons(OFPFF_EMERG)) {
+        /* There isn't a good fit for an error code, so just state that the
+         * flow table is full. */
+        return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_ALL_TABLES_FULL);
+    }
+
     normalize_match(&ofm->match);
     if (!ofm->match.wildcards) {
         ofm->priority = htons(UINT16_MAX);
@@ -2932,58 +3073,6 @@ handle_flow_mod(struct ofproto *p, struct ofconn *ofconn,
     }
 }
 
-static void
-send_capability_reply(struct ofproto *p, struct ofconn *ofconn, uint32_t xid)
-{
-    struct ofmp_capability_reply *ocr;
-    struct ofpbuf *b;
-    char capabilities[] = "com.nicira.mgmt.manager=false\n";
-
-    ocr = make_openflow_xid(sizeof(*ocr), OFPT_VENDOR, xid, &b);
-    ocr->header.header.vendor = htonl(NX_VENDOR_ID);
-    ocr->header.header.subtype = htonl(NXT_MGMT);
-    ocr->header.type = htons(OFMPT_CAPABILITY_REPLY);
-
-    ocr->format = htonl(OFMPCOF_SIMPLE);
-    ocr->mgmt_id = htonll(p->mgmt_id);
-
-    ofpbuf_put(b, capabilities, strlen(capabilities));
-
-    queue_tx(b, ofconn, ofconn->reply_counter);
-}
-
-static int
-handle_ofmp(struct ofproto *p, struct ofconn *ofconn, 
-            struct ofmp_header *ofmph)
-{
-    size_t msg_len = ntohs(ofmph->header.header.length);
-    if (msg_len < sizeof(*ofmph)) {
-        VLOG_WARN_RL(&rl, "dropping short managment message: %zu\n", msg_len);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
-    }
-
-    if (ofmph->type == htons(OFMPT_CAPABILITY_REQUEST)) {
-        struct ofmp_capability_request *ofmpcr;
-
-        if (msg_len < sizeof(struct ofmp_capability_request)) {
-            VLOG_WARN_RL(&rl, "dropping short capability request: %zu\n",
-                    msg_len);
-            return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
-        }
-
-        ofmpcr = (struct ofmp_capability_request *)ofmph;
-        if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) {
-            /* xxx Find a better type than bad subtype */
-            return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
-        }
-
-        send_capability_reply(p, ofconn, ofmph->header.header.xid);
-        return 0;
-    } else {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
-    }
-}
-
 static int
 handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg)
 {
@@ -2991,13 +3080,13 @@ handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg)
     struct nicira_header *nh;
 
     if (ntohs(ovh->header.length) < sizeof(struct ofp_vendor_header)) {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
     if (ovh->vendor != htonl(NX_VENDOR_ID)) {
         return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR);
     }
     if (ntohs(ovh->header.length) < sizeof(struct nicira_header)) {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
     }
 
     nh = msg;
@@ -3005,26 +3094,24 @@ handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg)
     case NXT_STATUS_REQUEST:
         return switch_status_handle_request(p->switch_status, ofconn->rconn,
                                             msg);
-
-    case NXT_ACT_SET_CONFIG:
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */
-
-    case NXT_ACT_GET_CONFIG:
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */
-
-    case NXT_COMMAND_REQUEST:
-        if (p->executer) {
-            return executer_handle_request(p->executer, ofconn->rconn, msg);
-        }
-        break;
-
-    case NXT_MGMT:
-        return handle_ofmp(p, ofconn, msg);
     }
 
     return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
 }
 
+static int
+handle_barrier_request(struct ofconn *ofconn, struct ofp_header *oh)
+{
+    struct ofp_header *ob;
+    struct ofpbuf *buf;
+
+    /* Currently, everything executes synchronously, so we can just
+     * immediately send the barrier reply. */
+    ob = make_openflow_xid(sizeof *ob, OFPT_BARRIER_REPLY, oh->xid, &buf);
+    queue_tx(buf, ofconn, ofconn->reply_counter);
+    return 0;
+}
+
 static void
 handle_openflow(struct ofconn *ofconn, struct ofproto *p,
                 struct ofpbuf *ofp_msg)
@@ -3074,6 +3161,10 @@ handle_openflow(struct ofconn *ofconn, struct ofproto *p,
         error = handle_vendor(p, ofconn, ofp_msg->data);
         break;
 
+    case OFPT_BARRIER_REQUEST:
+        error = handle_barrier_request(ofconn, oh);
+        break;
+
     default:
         if (VLOG_IS_WARN_ENABLED()) {
             char *s = ofp_to_string(oh, ntohs(oh->length), 2);
@@ -3090,7 +3181,7 @@ handle_openflow(struct ofconn *ofconn, struct ofproto *p,
 }
 \f
 static void
-handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
+handle_odp_miss_msg(struct ofproto *p, struct ofpbuf *packet)
 {
     struct odp_msg *msg = packet->data;
     uint16_t in_port = odp_port_to_ofp_port(msg->port);
@@ -3098,14 +3189,6 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
     struct ofpbuf payload;
     flow_t flow;
 
-    /* Handle controller actions. */
-    if (msg->type == _ODPL_ACTION_NR) {
-        COVERAGE_INC(ofproto_ctlr_action);
-        pinsched_send(p->action_sched, in_port, packet,
-                      send_packet_in_action, p);
-        return;
-    }
-
     payload.data = msg + 1;
     payload.size = msg->length - sizeof *msg;
     flow_extract(&payload, msg->port, &flow);
@@ -3118,7 +3201,7 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
         memset(&action, 0, sizeof(action));
         action.output.type = ODPAT_OUTPUT;
         action.output.port = ODPP_LOCAL;
-        dpif_execute(&p->dpif, flow.in_port, &action, 1, &payload);
+        dpif_execute(p->dpif, flow.in_port, &action, 1, &payload);
     }
 
     rule = lookup_valid_rule(p, &flow);
@@ -3175,6 +3258,36 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
         ofpbuf_delete(packet);
     }
 }
+
+static void
+handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
+{
+    struct odp_msg *msg = packet->data;
+
+    switch (msg->type) {
+    case _ODPL_ACTION_NR:
+        COVERAGE_INC(ofproto_ctlr_action);
+        pinsched_send(p->action_sched, odp_port_to_ofp_port(msg->port), packet,
+                      send_packet_in_action, p);
+        break;
+
+    case _ODPL_SFLOW_NR:
+        if (p->sflow) {
+            ofproto_sflow_received(p->sflow, msg);
+        }
+        ofpbuf_delete(packet);
+        break;
+
+    case _ODPL_MISS_NR:
+        handle_odp_miss_msg(p, packet);
+        break;
+
+    default:
+        VLOG_WARN_RL(&rl, "received ODP message of unexpected type %"PRIu32,
+                     msg->type);
+        break;
+    }
+}
 \f
 static void
 revalidate_cb(struct cls_rule *sub_, void *cbdata_)
@@ -3218,29 +3331,47 @@ revalidate_rule(struct ofproto *p, struct rule *rule)
 }
 
 static struct ofpbuf *
-compose_flow_exp(const struct rule *rule, long long int now, uint8_t reason)
+compose_flow_removed(const struct rule *rule, long long int now, uint8_t reason)
 {
-    struct ofp_flow_expired *ofe;
+    struct ofp_flow_removed *ofr;
     struct ofpbuf *buf;
-
-    ofe = make_openflow(sizeof *ofe, OFPT_FLOW_EXPIRED, &buf);
-    flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofe->match);
-    ofe->priority = htons(rule->cr.priority);
-    ofe->reason = reason;
-    ofe->duration = htonl((now - rule->created) / 1000);
-    ofe->packet_count = htonll(rule->packet_count);
-    ofe->byte_count = htonll(rule->byte_count);
+    long long int tdiff = now - rule->created;
+    uint32_t sec = tdiff / 1000;
+    uint32_t msec = tdiff - (sec * 1000);
+
+    ofr = make_openflow(sizeof *ofr, OFPT_FLOW_REMOVED, &buf);
+    flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofr->match);
+    ofr->cookie = rule->flow_cookie;
+    ofr->priority = htons(rule->cr.priority);
+    ofr->reason = reason;
+    ofr->duration_sec = htonl(sec);
+    ofr->duration_nsec = htonl(msec * 1000000);
+    ofr->idle_timeout = htons(rule->idle_timeout);
+    ofr->packet_count = htonll(rule->packet_count);
+    ofr->byte_count = htonll(rule->byte_count);
 
     return buf;
 }
 
 static void
-send_flow_exp(struct ofproto *p, struct rule *rule,
-              long long int now, uint8_t reason)
+uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule)
+{
+    assert(rule->installed);
+    assert(!rule->cr.wc.wildcards);
+
+    if (rule->super) {
+        rule_remove(ofproto, rule);
+    } else {
+        rule_uninstall(ofproto, rule);
+    }
+}
+static void
+send_flow_removed(struct ofproto *p, struct rule *rule,
+                  long long int now, uint8_t reason)
 {
     struct ofconn *ofconn;
     struct ofconn *prev;
-    struct ofpbuf *buf;
+    struct ofpbuf *buf = NULL;
 
     /* We limit the maximum number of queued flow expirations it by accounting
      * them under the counter for replies.  That works because preventing
@@ -3250,11 +3381,11 @@ send_flow_exp(struct ofproto *p, struct rule *rule,
 
     prev = NULL;
     LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
-        if (ofconn->send_flow_exp && rconn_is_connected(ofconn->rconn)) {
+        if (rule->send_flow_removed && rconn_is_connected(ofconn->rconn)) {
             if (prev) {
                 queue_tx(ofpbuf_clone(buf), prev, prev->reply_counter);
             } else {
-                buf = compose_flow_exp(rule, now, reason);
+                buf = compose_flow_removed(rule, now, reason);
             }
             prev = ofconn;
         }
@@ -3264,18 +3395,6 @@ send_flow_exp(struct ofproto *p, struct rule *rule,
     }
 }
 
-static void
-uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule)
-{
-    assert(rule->installed);
-    assert(!rule->cr.wc.wildcards);
-
-    if (rule->super) {
-        rule_remove(ofproto, rule);
-    } else {
-        rule_uninstall(ofproto, rule);
-    }
-}
 
 static void
 expire_rule(struct cls_rule *cls_rule, void *p_)
@@ -3318,9 +3437,9 @@ expire_rule(struct cls_rule *cls_rule, void *p_)
     }
 
     if (!rule_is_hidden(rule)) {
-        send_flow_exp(p, rule, now,
-                      (now >= hard_expire
-                       ? OFPER_HARD_TIMEOUT : OFPER_IDLE_TIMEOUT));
+        send_flow_removed(p, rule, now,
+                          (now >= hard_expire
+                           ? OFPRR_HARD_TIMEOUT : OFPRR_IDLE_TIMEOUT));
     }
     rule_remove(p, rule);
 }
@@ -3338,7 +3457,7 @@ active_timeout(struct ofproto *ofproto, struct rule *rule)
         if (rule->installed) {
             odp_flow.key = rule->cr.flow;
             odp_flow.flags = ODPFF_ZERO_TCP_FLAGS;
-            dpif_flow_get(&ofproto->dpif, &odp_flow);
+            dpif_flow_get(ofproto->dpif, &odp_flow);
 
             if (odp_flow.stats.n_packets) {
                 update_time(ofproto, rule, &odp_flow.stats);
@@ -3369,7 +3488,7 @@ update_used(struct ofproto *p)
     size_t i;
     int error;
 
-    error = dpif_flow_list_all(&p->dpif, &flows, &n_flows);
+    error = dpif_flow_list_all(p->dpif, &flows, &n_flows);
     if (error) {
         return;
     }
@@ -3382,7 +3501,7 @@ update_used(struct ofproto *p)
             classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX));
         if (!rule || !rule->installed) {
             COVERAGE_INC(ofproto_unexpected_rule);
-            dpif_flow_del(&p->dpif, f);
+            dpif_flow_del(p->dpif, f);
             continue;
         }
 
@@ -3457,33 +3576,30 @@ send_packet_in_miss(struct ofpbuf *packet, void *p_)
 }
 
 static uint64_t
-pick_datapath_id(struct dpif *dpif, uint64_t fallback_dpid)
+pick_datapath_id(const struct ofproto *ofproto)
 {
-    char local_name[IF_NAMESIZE];
-    uint8_t ea[ETH_ADDR_LEN];
-    int error;
+    const struct ofport *port;
 
-    error = dpif_get_name(dpif, local_name, sizeof local_name);
-    if (!error) {
-        error = netdev_nodev_get_etheraddr(local_name, ea);
+    port = port_array_get(&ofproto->ports, ODPP_LOCAL);
+    if (port) {
+        uint8_t ea[ETH_ADDR_LEN];
+        int error;
+
+        error = netdev_get_etheraddr(port->netdev, ea);
         if (!error) {
             return eth_addr_to_uint64(ea);
         }
         VLOG_WARN("could not get MAC address for %s (%s)",
-                  local_name, strerror(error));
+                  netdev_get_name(port->netdev), strerror(error));
     }
-
-    return fallback_dpid;
+    return ofproto->fallback_dpid;
 }
 
 static uint64_t
 pick_fallback_dpid(void)
 {
     uint8_t ea[ETH_ADDR_LEN];
-    eth_addr_random(ea);
-    ea[0] = 0x00;               /* Set Nicira OUI. */
-    ea[1] = 0x23;
-    ea[2] = 0x20;
+    eth_addr_nicira_random(ea);
     return eth_addr_to_uint64(ea);
 }
 \f
similarity index 81%
rename from secchan/ofproto.h
rename to ofproto/ofproto.h
index 50dd5d5..d9e71d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "netflow.h"
 #include "tag.h"
 
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 struct odp_actions;
 struct ofhooks;
 struct ofproto;
 struct svec;
 
+enum {
+    DP_GROUP_FLOOD = 0,
+    DP_GROUP_ALL = 1
+};
+
 struct ofexpired {
     flow_t flow;
     uint64_t packet_count;      /* Packets from subrules. */
@@ -36,7 +45,24 @@ struct ofexpired {
     long long int used;         /* Last-used time (0 if never used). */
 };
 
-int ofproto_create(const char *datapath, const struct ofhooks *, void *aux,
+struct ofproto_sflow_options {
+    struct svec targets;
+    uint32_t sampling_rate;
+    uint32_t polling_interval;
+    uint32_t header_len;
+    uint32_t sub_id;
+    char *agent_device;
+    char *control_ip;
+};
+
+#define DEFAULT_MFR_DESC "Nicira Networks, Inc."
+#define DEFAULT_HW_DESC "Open vSwitch"
+#define DEFAULT_SW_DESC VERSION BUILDNR
+#define DEFAULT_SERIAL_DESC "None"
+#define DEFAULT_DP_DESC "None"
+
+int ofproto_create(const char *datapath, const char *datapath_type,
+                   const struct ofhooks *, void *aux,
                    struct ofproto **ofprotop);
 void ofproto_destroy(struct ofproto *);
 int ofproto_run(struct ofproto *);
@@ -47,12 +73,12 @@ bool ofproto_is_alive(const struct ofproto *);
 
 /* Configuration. */
 void ofproto_set_datapath_id(struct ofproto *, uint64_t datapath_id);
-void ofproto_set_mgmt_id(struct ofproto *, uint64_t mgmt_id);
 void ofproto_set_probe_interval(struct ofproto *, int probe_interval);
 void ofproto_set_max_backoff(struct ofproto *, int max_backoff);
 void ofproto_set_desc(struct ofproto *,
-                      const char *manufacturer, const char *hardware,
-                      const char *software, const char *serial);
+                      const char *mfr_desc, const char *hw_desc,
+                      const char *sw_desc, const char *serial_desc,
+                      const char *dp_desc);
 int ofproto_set_in_band(struct ofproto *, bool in_band);
 int ofproto_set_discovery(struct ofproto *, bool discovery,
                           const char *accept_controller_re,
@@ -62,15 +88,13 @@ int ofproto_set_listeners(struct ofproto *, const struct svec *listeners);
 int ofproto_set_snoops(struct ofproto *, const struct svec *snoops);
 int ofproto_set_netflow(struct ofproto *,
                         const struct netflow_options *nf_options);
+void ofproto_set_sflow(struct ofproto *, const struct ofproto_sflow_options *);
 void ofproto_set_failure(struct ofproto *, bool fail_open);
 void ofproto_set_rate_limit(struct ofproto *, int rate_limit, int burst_limit);
 int ofproto_set_stp(struct ofproto *, bool enable_stp);
-int ofproto_set_remote_execution(struct ofproto *, const char *command_acl,
-                                 const char *command_dir);
 
 /* Configuration querying. */
 uint64_t ofproto_get_datapath_id(const struct ofproto *);
-uint64_t ofproto_get_mgmt_id(const struct ofproto *);
 int ofproto_get_probe_interval(const struct ofproto *);
 int ofproto_get_max_backoff(const struct ofproto *);
 bool ofproto_get_in_band(const struct ofproto *);
@@ -107,4 +131,8 @@ struct ofhooks {
 void ofproto_revalidate(struct ofproto *, tag_type);
 struct tag_set *ofproto_get_revalidate_set(struct ofproto *);
 
+#ifdef  __cplusplus
+}
+#endif
+
 #endif /* ofproto.h */
similarity index 98%
rename from secchan/pinsched.c
rename to ofproto/pinsched.c
index 0afd22f..b9c6371 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <config.h>
 #include "pinsched.h"
+#include <sys/types.h>
+#include <netinet/in.h>
 #include <arpa/inet.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
@@ -227,7 +230,7 @@ pinsched_create(int rate_limit, int burst_limit, struct switch_status *ss)
 {
     struct pinsched *ps;
 
-    ps = xcalloc(1, sizeof *ps);
+    ps = xzalloc(sizeof *ps);
     port_array_init(&ps->queues);
     ps->n_queued = 0;
     ps->last_tx_port = PORT_ARRAY_SIZE;
similarity index 100%
rename from secchan/pinsched.h
rename to ofproto/pinsched.h
similarity index 93%
rename from secchan/pktbuf.c
rename to ofproto/pktbuf.c
index 450cc3b..c103c7f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ pktbuf_capacity(void)
 struct pktbuf *
 pktbuf_create(void)
 {
-    return xcalloc(1, sizeof *pktbuf_create());
+    return xzalloc(sizeof *pktbuf_create());
 }
 
 void
@@ -151,9 +151,9 @@ pktbuf_get_null(void)
  * datapath port number on which the packet was received in '*in_port'.  The
  * caller becomes responsible for freeing the buffer.  However, if 'id'
  * identifies a "null" packet buffer (created with pktbuf_get_null()), stores
- * NULL in '*bufferp' and -1 in '*in_port'.
+ * NULL in '*bufferp' and UINT16_max in '*in_port'.
  *
- * On failure, stores NULL in in '*bufferp' and -1 in '*in_port'. */
+ * On failure, stores NULL in in '*bufferp' and UINT16_MAX in '*in_port'. */
 int
 pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp,
                 uint16_t *in_port)
@@ -165,7 +165,7 @@ pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp,
     if (!pb) {
         VLOG_WARN_RL(&rl, "attempt to send buffered packet via connection "
                      "without buffers");
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE);
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BUFFER_UNKNOWN);
     }
 
     p = &pb->packets[id & PKTBUF_MASK];
@@ -183,10 +183,10 @@ pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp,
             error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BUFFER_EMPTY);
         }
     } else if (id >> PKTBUF_BITS != COOKIE_MAX) {
-        COVERAGE_INC(pktbuf_bad_cookie);
+        COVERAGE_INC(pktbuf_buffer_unknown);
         VLOG_WARN_RL(&rl, "cookie mismatch: %08"PRIx32" != %08"PRIx32,
                      id, (id & PKTBUF_MASK) | (p->cookie << PKTBUF_BITS));
-        error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE);
+        error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BUFFER_UNKNOWN);
     } else {
         COVERAGE_INC(pktbuf_null_cookie);
         VLOG_INFO_RL(&rl, "Received null cookie %08"PRIx32" (this is normal "
@@ -194,7 +194,7 @@ pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp,
         error = 0;
     }
     *bufferp = NULL;
-    *in_port = -1;
+    *in_port = UINT16_MAX;
     return error;
 }
 
similarity index 100%
rename from secchan/pktbuf.h
rename to ofproto/pktbuf.h
similarity index 95%
rename from secchan/status.c
rename to ofproto/status.c
index b2cb935..01cda01 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,19 +130,14 @@ static void
 config_status_cb(struct status_reply *sr, void *ofproto_)
 {
     const struct ofproto *ofproto = ofproto_;
-    uint64_t datapath_id, mgmt_id;
+    uint64_t datapath_id;
     struct svec listeners;
     int probe_interval, max_backoff;
     size_t i;
 
     datapath_id = ofproto_get_datapath_id(ofproto);
     if (datapath_id) {
-        status_reply_put(sr, "datapath-id=%"PRIx64, datapath_id);
-    }
-
-    mgmt_id = ofproto_get_mgmt_id(ofproto);
-    if (mgmt_id) {
-        status_reply_put(sr, "mgmt-id=%"PRIx64, mgmt_id);
+        status_reply_put(sr, "datapath-id=%016"PRIx64, datapath_id);
     }
 
     svec_init(&listeners);
@@ -177,7 +172,7 @@ switch_status_cb(struct status_reply *sr, void *ss_)
 struct switch_status *
 switch_status_create(const struct ofproto *ofproto)
 {
-    struct switch_status *ss = xcalloc(1, sizeof *ss);
+    struct switch_status *ss = xzalloc(sizeof *ss);
     ss->booted = time_now();
     list_init(&ss->categories);
     ss->config_cat = switch_status_register(ss, "config", config_status_cb,
similarity index 98%
rename from secchan/status.h
rename to ofproto/status.h
index 7856674..1186fa5 100644 (file)
@@ -21,7 +21,6 @@
 
 struct nicira_header;
 struct rconn;
-struct secchan;
 struct ofproto;
 struct status_reply;
 
diff --git a/ovsdb/.gitignore b/ovsdb/.gitignore
new file mode 100644 (file)
index 0000000..d9c12ac
--- /dev/null
@@ -0,0 +1,7 @@
+/ovsdb-client
+/ovsdb-client.1
+/ovsdb-idlc
+/ovsdb-server
+/ovsdb-server.1
+/ovsdb-tool
+/ovsdb-tool.1
diff --git a/ovsdb/OVSDB.py b/ovsdb/OVSDB.py
new file mode 100644 (file)
index 0000000..f01c45b
--- /dev/null
@@ -0,0 +1,495 @@
+import re
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def getMember(json, name, validTypes, description, default=None):
+    if name in json:
+        member = json[name]
+        if len(validTypes) and type(member) not in validTypes:
+            raise Error("%s: type mismatch for '%s' member"
+                        % (description, name))
+        return member
+    return default
+
+def mustGetMember(json, name, expectedType, description):
+    member = getMember(json, name, expectedType, description)
+    if member == None:
+        raise Error("%s: missing '%s' member" % (description, name))
+    return member
+
+class DbSchema:
+    def __init__(self, name, tables):
+        self.name = name
+        self.tables = tables
+
+    @staticmethod
+    def fromJson(json):
+        name = mustGetMember(json, 'name', [unicode], 'database')
+        tablesJson = mustGetMember(json, 'tables', [dict], 'database')
+        tables = {}
+        for tableName, tableJson in tablesJson.iteritems():
+            tables[tableName] = TableSchema.fromJson(tableJson,
+                                                     "%s table" % tableName)
+        return DbSchema(name, tables)
+
+class IdlSchema(DbSchema):
+    def __init__(self, name, tables, idlPrefix, idlHeader):
+        DbSchema.__init__(self, name, tables)
+        self.idlPrefix = idlPrefix
+        self.idlHeader = idlHeader
+
+    @staticmethod
+    def fromJson(json):
+        schema = DbSchema.fromJson(json)
+        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+        return IdlSchema(schema.name, schema.tables, idlPrefix, idlHeader)
+
+class TableSchema:
+    def __init__(self, columns):
+        self.columns = columns
+
+    @staticmethod
+    def fromJson(json, description):
+        columnsJson = mustGetMember(json, 'columns', [dict], description)
+        columns = {}
+        for name, json in columnsJson.iteritems():
+            columns[name] = ColumnSchema.fromJson(
+                json, "column %s in %s" % (name, description))
+        return TableSchema(columns)
+
+class ColumnSchema:
+    def __init__(self, type, persistent):
+        self.type = type
+        self.persistent = persistent
+
+    @staticmethod
+    def fromJson(json, description):
+        type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
+                                           description),
+                             'type of %s' % description)
+        ephemeral = getMember(json, 'ephemeral', [bool], description)
+        persistent = ephemeral != True
+        return ColumnSchema(type, persistent)
+
+def escapeCString(src):
+    dst = ""
+    for c in src:
+        if c in "\\\"":
+            dst += "\\" + c
+        elif ord(c) < 32:
+            if c == '\n':
+                dst += '\\n'
+            elif c == '\r':
+                dst += '\\r'
+            elif c == '\a':
+                dst += '\\a'
+            elif c == '\b':
+                dst += '\\b'
+            elif c == '\f':
+                dst += '\\f'
+            elif c == '\t':
+                dst += '\\t'
+            elif c == '\v':
+                dst += '\\v'
+            else:
+                dst += '\\%03o' % ord(c)
+        else:
+            dst += c
+    return dst
+
+def returnUnchanged(x):
+    return x
+
+class UUID:
+    x = "[0-9a-fA-f]"
+    uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
+                        % (x, x, x, x, x, x))
+
+    def __init__(self, value):
+        self.value = value
+
+    @staticmethod
+    def fromString(s):
+        if not uuidRE.match(s):
+            raise Error("%s is not a valid UUID" % s)
+        return UUID(s)
+
+    @staticmethod
+    def fromJson(json):
+        if UUID.isValidJson(json):
+            return UUID(json[1])
+        else:
+            raise Error("%s is not valid JSON for a UUID" % json)
+
+    @staticmethod
+    def isValidJson(json):
+        return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
+            
+    def toJson(self):
+        return ["uuid", self.value]
+
+    def cInitUUID(self, var):
+        m = re.match(self.value)
+        return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
+                "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
+                "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
+                "%s.parts[3] = 0x%s;" % (var, m.group(6))]
+
+class Atom:
+    def __init__(self, type, value):
+        self.type = type
+        self.value = value
+
+    @staticmethod
+    def fromJson(type_, json):
+        if ((type_ == 'integer' and type(json) in [int, long])
+            or (type_ == 'real' and type(json) in [int, long, float])
+            or (type_ == 'boolean' and json in [True, False])
+            or (type_ == 'string' and type(json) in [str, unicode])):
+            return Atom(type_, json)
+        elif type_ == 'uuid':
+            return UUID.fromJson(json)
+        else:
+            raise Error("%s is not valid JSON for type %s" % (json, type_))
+
+    def toJson(self):
+        if self.type == 'uuid':
+            return self.value.toString()
+        else:
+            return self.value
+
+    def cInitAtom(self, var):
+        if self.type == 'integer':
+            return ['%s.integer = %d;' % (var, self.value)]
+        elif self.type == 'real':
+            return ['%s.real = %.15g;' % (var, self.value)]
+        elif self.type == 'boolean':
+            if self.value:
+                return ['%s.boolean = true;']
+            else:
+                return ['%s.boolean = false;']
+        elif self.type == 'string':
+            return ['%s.string = xstrdup("%s");'
+                    % (var, escapeCString(self.value))]
+        elif self.type == 'uuid':
+            return self.value.cInitUUID(var)
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        if self.type == 'integer':
+            return '%d' % self.value
+        elif self.type == 'real':
+            return '%.15g' % self.value
+        elif self.type == 'boolean':
+            if self.value:
+                return 'true'
+            else:
+                return 'false'
+        elif self.type == 'string':
+            return escapeLiteral(self.value)
+        elif self.type == 'uuid':
+            return self.value.value
+
+class BaseType:
+    def __init__(self, type,
+                 enum=None,
+                 refTable=None, refType="strong",
+                 minInteger=None, maxInteger=None,
+                 minReal=None, maxReal=None,
+                 minLength=None, maxLength=None):
+        self.type = type
+        self.enum = enum
+        self.refTable = refTable
+        self.refType = refType
+        self.minInteger = minInteger
+        self.maxInteger = maxInteger
+        self.minReal = minReal
+        self.maxReal = maxReal
+        self.minLength = minLength
+        self.maxLength = maxLength
+
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return BaseType(json)
+        else:
+            atomicType = mustGetMember(json, 'type', [unicode], description)
+            enum = getMember(json, 'enum', [], description)
+            if enum:
+                enumType = Type(atomicType, None, 0, 'unlimited')
+                enum = Datum.fromJson(enumType, enum)
+            refTable = getMember(json, 'refTable', [unicode], description)
+            refType = getMember(json, 'refType', [unicode], description)
+            if refType == None:
+                refType = "strong"
+            minInteger = getMember(json, 'minInteger', [int, long], description)
+            maxInteger = getMember(json, 'maxInteger', [int, long], description)
+            minReal = getMember(json, 'minReal', [int, long, float], description)
+            maxReal = getMember(json, 'maxReal', [int, long, float], description)
+            minLength = getMember(json, 'minLength', [int], description)
+            maxLength = getMember(json, 'minLength', [int], description)
+            return BaseType(atomicType, enum, refTable, refType, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        if self.type == 'uuid' and self.refTable:
+            s = escapeLiteral(self.refTable)
+            if self.refType == 'weak':
+                s = "weak reference to " + s
+            return s
+        else:
+            return self.type
+
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
+        if self.enum:
+            literals = [value.toEnglish(escapeLiteral)
+                        for value in self.enum.values]
+            if len(literals) == 2:
+                return 'either %s or %s' % (literals[0], literals[1])
+            else:
+                return 'one of %s, %s, or %s' % (literals[0],
+                                                 ', '.join(literals[1:-1]),
+                                                 literals[-1])
+        elif self.minInteger != None and self.maxInteger != None:
+            return 'in range [%d,%d]' % (self.minInteger, self.maxInteger)
+        elif self.minInteger != None:
+            return 'at least %d' % self.minInteger
+        elif self.maxInteger != None:
+            return 'at most %d' % self.maxInteger
+        elif self.minReal != None and self.maxReal != None:
+            return 'in range [%g, %g]' % (self.minReal, self.maxReal)
+        elif self.minReal != None:
+            return 'at least %g' % self.minReal
+        elif self.maxReal != None:
+            return 'at most %g' % self.maxReal
+        elif self.minLength != None and self.maxLength != None:
+            if self.minLength == self.maxLength:
+                return 'exactly %d characters long' % (self.minLength)
+            else:
+                return 'between %d and %d characters long' % (self.minLength, self.maxLength)
+        elif self.minLength != None:
+            return 'at least %d characters long' % self.minLength
+        elif self.maxLength != None:
+            return 'at most %d characters long' % self.maxLength
+        else:
+            return ''
+
+    def toCType(self, prefix):
+        if self.refTable:
+            return "struct %s%s *" % (prefix, self.refTable.lower())
+        else:
+            return {'integer': 'int64_t ',
+                    'real': 'double ',
+                    'uuid': 'struct uuid ',
+                    'boolean': 'bool ',
+                    'string': 'char *'}[self.type]
+
+    def copyCValue(self, dst, src):
+        args = {'dst': dst, 'src': src}
+        if self.refTable:
+            return ("%(dst)s = %(src)s->header_.uuid;") % args
+        elif self.type == 'string':
+            return "%(dst)s = xstrdup(%(src)s);" % args
+        else:
+            return "%(dst)s = %(src)s;" % args
+
+    def initCDefault(self, var, isOptional):
+        if self.refTable:
+            return "%s = NULL;" % var
+        elif self.type == 'string' and not isOptional:
+            return "%s = \"\";" % var
+        else:
+            return {'integer': '%s = 0;',
+                    'real': '%s = 0.0;',
+                    'uuid': 'uuid_zero(&%s);',
+                    'boolean': '%s = false;',
+                    'string': '%s = NULL;'}[self.type] % var
+
+    def cInitBaseType(self, indent, var):
+        stmts = []
+        stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
+                var, self.type.upper()),)
+        if self.enum:
+            stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
+                         % (var, var))
+            stmts += self.enum.cInitDatum("%s.enum_" % var)
+        if self.type == 'integer':
+            if self.minInteger != None:
+                stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
+            if self.maxInteger != None:
+                stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger))
+        elif self.type == 'real':
+            if self.minReal != None:
+                stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
+            if self.maxReal != None:
+                stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
+        elif self.type == 'string':
+            if self.minLength != None:
+                stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
+            if self.maxLength != None:
+                stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+        elif self.type == 'uuid':
+            if self.refTable != None:
+                stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
+        return '\n'.join([indent + stmt for stmt in stmts])
+
+class Type:
+    def __init__(self, key, value=None, min=1, max=1):
+        self.key = key
+        self.value = value
+        self.min = min
+        self.max = max
+    
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return Type(BaseType(json))
+        else:
+            keyJson = mustGetMember(json, 'key', [dict, unicode], description)
+            key = BaseType.fromJson(keyJson, 'key in %s' % description)
+
+            valueJson = getMember(json, 'value', [dict, unicode], description)
+            if valueJson:
+                value = BaseType.fromJson(valueJson,
+                                          'value in %s' % description)
+            else:
+                value = None
+
+            min = getMember(json, 'min', [int], description, 1)
+            max = getMember(json, 'max', [int, unicode], description, 1)
+            return Type(key, value, min, max)
+
+    def isScalar(self):
+        return self.min == 1 and self.max == 1 and not self.value
+
+    def isOptional(self):
+        return self.min == 0 and self.max == 1
+
+    def isOptionalPointer(self):
+        return (self.min == 0 and self.max == 1 and not self.value
+                and (self.key.type == 'string' or self.key.refTable))
+
+    def toEnglish(self, escapeLiteral=returnUnchanged):
+        keyName = self.key.toEnglish(escapeLiteral)
+        if self.value:
+            valueName = self.value.toEnglish(escapeLiteral)
+
+        if self.isScalar():
+            return keyName
+        elif self.isOptional():
+            if self.value:
+                return "optional %s-%s pair" % (keyName, valueName)
+            else:
+                return "optional %s" % keyName
+        else:
+            if self.max == "unlimited":
+                if self.min:
+                    quantity = "%d or more " % self.min
+                else:
+                    quantity = ""
+            elif self.min:
+                quantity = "%d to %d " % (self.min, self.max)
+            else:
+                quantity = "up to %d " % self.max
+
+            if self.value:
+                return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
+            else:
+                if keyName.endswith('s'):
+                    plural = keyName + "es"
+                else:
+                    plural = keyName + "s"
+                return "set of %s%s" % (quantity, plural)
+
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
+        s = ""
+
+        constraints = []
+        keyConstraints = self.key.constraintsToEnglish(escapeLiteral)
+        if keyConstraints:
+            if self.value:
+                constraints += ['key ' + keyConstraints]
+            else:
+                constraints += [keyConstraints]
+
+        if self.value:
+            valueConstraints = self.value.constraintsToEnglish(escapeLiteral)
+            if valueConstraints:
+                constraints += ['value ' + valueConstraints]
+
+        return ', '.join(constraints)
+                
+    def cDeclComment(self):
+        if self.min == 1 and self.max == 1 and self.key.type == "string":
+            return "\t/* Always nonnull. */"
+        else:
+            return ""
+
+    def cInitType(self, indent, var):
+        initKey = self.key.cInitBaseType(indent, "%s.key" % var)
+        if self.value:
+            initValue = self.value.cInitBaseType(indent, "%s.value" % var)
+        else:
+            initValue = ('%sovsdb_base_type_init(&%s.value, '
+                         'OVSDB_TYPE_VOID);' % (indent, var))
+        initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
+        if self.max == "unlimited":
+            max = "UINT_MAX"
+        else:
+            max = self.max
+        initMax = "%s%s.n_max = %s;" % (indent, var, max)
+        return "\n".join((initKey, initValue, initMin, initMax))
+
+class Datum:
+    def __init__(self, type, values):
+        self.type = type
+        self.values = values
+
+    @staticmethod
+    def fromJson(type_, json):
+        if not type_.value:
+            if len(json) == 2 and json[0] == "set":
+                values = []
+                for atomJson in json[1]:
+                    values += [Atom.fromJson(type_.key, atomJson)]
+            else:
+                values = [Atom.fromJson(type_.key, json)]
+        else:
+            if len(json) != 2 or json[0] != "map":
+                raise Error("%s is not valid JSON for a map" % json)
+            values = []
+            for pairJson in json[1]:
+                values += [(Atom.fromJson(type_.key, pairJson[0]),
+                            Atom.fromJson(type_.value, pairJson[1]))]
+        return Datum(type_, values)
+
+    def cInitDatum(self, var):
+        if len(self.values) == 0:
+            return ["ovsdb_datum_init_empty(%s);" % var]
+
+        s = ["%s->n = %d;" % (var, len(self.values))]
+        s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
+              % (var, len(self.values), var)]
+
+        for i in range(len(self.values)):
+            key = self.values[i]
+            if self.type.value:
+                key = key[0]
+            s += key.cInitAtom("%s->keys[%d]" % (var, i))
+        
+        if self.type.value:
+            s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
+                  % (var, len(self.values), var)]
+            for i in range(len(self.values)):
+                value = self.values[i][1]
+                s += key.cInitAtom("%s->values[%d]" % (var, i))
+        else:
+            s += ["%s->values = NULL;" % var]
+
+        if len(self.values) > 1:
+            s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
+                  % (var, self.type.key.upper())]
+
+        return s
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644 (file)
index 0000000..c926e21
--- /dev/null
@@ -0,0 +1,1109 @@
+         ===================================================
+          Open vSwitch Configuration Database Specification
+         ===================================================
+
+Basic Notation
+--------------
+
+The descriptions below use the following shorthand notations for JSON
+values.  Additional notation is presented later.
+
+<string>
+
+    A JSON string.  Any Unicode string is allowed, as specified by RFC
+    4627.  Implementations may disallow null bytes.
+
+<id>
+
+    A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*.
+
+    <id>s that begin with _ are reserved to the implementation and may
+    not be used by the user.
+
+<boolean>
+
+    A JSON true or false value.
+
+<number>
+
+    A JSON number.
+
+<integer>
+
+    A JSON number with an integer value, within a certain range
+    (currently -2**63...+2**63-1).
+
+<json-value>
+
+    Any JSON value.
+
+<nonnull-json-value>
+
+    Any JSON value except null.
+
+<error>
+
+    A JSON object with the following members:
+
+        "error": <string>                       required
+        "details": <string>                     optional
+
+    The value of the "error" member is a short string, specified in
+    this document, that broadly indicates the class of the error.
+    Most "error" strings are specific to contexts described elsewhere
+    in this document, but the following "error" strings may appear in
+    any context where an <error> is permitted:
+
+    "error": "resources exhausted"
+
+        The operation requires more resources (memory, disk, CPU,
+        etc.) than are currently available to the database server.
+
+    "error": "I/O error"
+
+        Problems accessing the disk, network, or other required
+        resources prevented the operation from completing.
+
+    Database implementations may use "error" strings not specified
+    in this document to indicate errors that do not fit into any of
+    the specified categories.
+
+    Optionally, an <error> may include a "details" member, whose value
+    is a string that describes the error in more detail for the
+    benefit of a human user or administrator.  This document does not
+    specify the format or content of the "details" string.
+
+    An <error> may also have other members that describe the error in
+    more detail.  This document does not specify the names or values
+    of these members.
+
+Schema Format
+-------------
+
+An Open vSwitch configuration database consists of a set of tables,
+each of which has a number of columns and zero or more rows.  A schema
+is represented by <database-schema>, as described below.
+
+<database-schema>
+
+    A JSON object with the following members:
+
+        "name": <id>                            required
+        "tables": {<id>: <table-schema>, ...}   required
+
+    The "name" identifies the database as a whole.  It must be
+    provided to most JSON-RPC requests to identify the database being
+    operated on.  The value of "tables" is a JSON object whose names
+    are table names and whose values are <table-schema>s.
+
+<table-schema>
+
+    A JSON object with the following members:
+
+        "columns": {<id>: <column-schema>, ...}   required
+        "maxRows": <integer>                      optional
+
+    The value of "columns" is a JSON object whose names are column
+    names and whose values are <column-schema>s.
+
+    Every table has the following columns whose definitions are not
+    included in the schema:
+
+        "_uuid": This column, which contains exactly one UUID value,
+        is initialized to a random value by the database engine when
+        it creates a row.  It is read-only, and its value never
+        changes during the lifetime of a row.
+
+        "_version": Like "_uuid", this column contains exactly one
+        UUID value, initialized to a random value by the database
+        engine when it creates a row, and it is read-only.  However,
+        its value changes to a new random value whenever any other
+        field in the row changes.  Furthermore, its value is
+        ephemeral: when the database is closed and reopened, or when
+        the database process is stopped and then started again, each
+        "_version" also changes to a new random value.
+
+    If "maxRows" is specified, as a positive integer, it limits the
+    maximum number of rows that may be present in the table.  This is
+    a "deferred" constraint, enforced only at transaction commit time
+    (see the "transact" request below).  If "maxRows" is not
+    specified, the size of the table is limited only by the resources
+    available to the database server.
+
+<column-schema>
+
+    A JSON object with the following members:
+
+        "type": <type>                            required
+        "ephemeral": <boolean>                    optional
+
+    The "type" specifies the type of data stored in this column.  If
+    "ephemeral" is specified as true, then this column's values are
+    not guaranteed to be durable; they may be lost when the database
+    restarts.
+
+<type>
+
+    The type of a database column.  Either an <atomic-type> or a JSON
+    object that describes the type of a database column, with the
+    following members:
+
+        "key": <base-type>                 required
+        "value": <base-type>               optional
+        "min": <integer>                   optional
+        "max": <integer> or "unlimited"    optional
+
+    If "min" or "max" is not specified, each defaults to 1.  If "max"
+    is specified as "unlimited", then there is no specified maximum
+    number of elements, although the implementation will enforce some
+    limit.  After considering defaults, "min" must be exactly 0 or
+    exactly 1, "max" must be at least 1, and "max" must be greater
+    than or equal to "min".
+
+    If "min" and "max" are both 1 and "value" is not specified, the
+    type is the scalar type specified by "key".
+
+    If "min" is not 1 or "max" is not 1, or both, and "value" is not
+    specified, the type is a set of scalar type "key".
+
+    If "value" is specified, the type is a map from type "key" to type
+    "value".
+
+<base-type>
+
+    The type of a key or value in a database column.  Either an
+    <atomic-type> or a JSON object with the following members:
+
+        "type": <atomic-type>              required
+        "enum": <value>                    optional
+        "minInteger": <integer>            optional, integers only
+        "maxInteger": <integer>            optional, integers only
+        "minReal": <real>                  optional, reals only
+        "maxReal": <real>                  optional, reals only 
+        "minLength": <integer>             optional, strings only
+        "maxLength": <integer>             optional, strings only
+        "refTable": <id>                   optional, uuids only
+        "refType": "strong" or "weak"      optional, only with "refTable"
+
+    An <atomic-type> by itself is equivalent to a JSON object with a
+    single member "type" whose value is the <atomic-type>.
+
+    "enum" may be specified as a <value> whose type is a set of one
+    or more values specified for the member "type".  If "enum" is
+    specified, then the valid values of the <base-type> are limited to
+    those in the <value>.
+
+    "enum" is mutually exclusive with the following constraints.
+
+    If "type" is "integer", then "minInteger" or "maxInteger" or both
+    may also be specified, restricting the valid integer range.  If
+    both are specified, then the maxInteger must be greater than or
+    equal to minInteger.
+
+    If "type" is "real", then "minReal" or "maxReal" or both may also
+    be specified, restricting the valid real range.  If both are
+    specified, then the maxReal must be greater than or equal to
+    minReal.
+
+    If "type" is "string", then "minLength" and "maxLength" or both
+    may be specified, restricting the valid length of value strings.
+    If both are specified, then maxLength must be greater than or
+    equal to minLength.  String length is measured in characters (not
+    bytes or UTF-16 code units).
+
+    If "type" is "uuid", then "refTable", if present, must be the name
+    of a table within this database.  If "refTable" is specified, then
+    "refType" may also be specified.  If "refTable" is set, the effect
+    depends on "refType":
+
+        - If "refType" is "strong" or if "refType" is omitted, the
+          allowed UUIDs are limited to UUIDs for rows in the named
+          table.
+
+        - If "refType" is "weak", then any UUIDs are allowed, but
+          UUIDs that do not correspond to rows in the named table will
+          be automatically deleted.
+
+    "refTable" constraints are "deferred" constraints: they are
+    enforced only at transaction commit time (see the "transact"
+    request below).  The other contraints on <base-type> are
+    "immediate", enforced immediately by each operation.
+
+<atomic-type>
+
+    One of the strings "integer", "real", "boolean", "string", or
+    "uuid", representing the specified scalar type.
+
+Wire Protocol
+-------------
+
+The database wire protocol is implemented in JSON-RPC 1.0.  We
+encourage use of JSON-RPC over stream connections instead of JSON-RPC
+over HTTP, for these reasons:
+
+    * JSON-RPC is a peer-to-peer protocol, but HTTP is a client-server
+      protocol, which is a poor match.  Thus, JSON-RPC over HTTP
+      requires the client to periodically poll the server to receive
+      server requests.
+
+    * HTTP is more complicated than stream connections and doesn't
+      provide any corresponding advantage.
+
+    * The JSON-RPC specification for HTTP transport is incomplete.
+
+The database wire protocol consists of the following JSON-RPC methods:
+
+list_dbs
+........
+
+Request object members:
+
+    "method": "list_dbs"              required
+    "params": []                      required
+    "id": <nonnull-json-value>        required
+
+Response object members:
+
+    "result": [<db-name>, ...]
+    "error": null
+    "id": same "id" as request
+
+This operation retrieves an array whose elements are <db-name>s
+that name the databases that can be accessed over this JSON-RPC
+connection.
+
+get_schema
+..........
+
+Request object members:
+
+    "method": "get_schema"            required
+    "params": [<db-name>]             required
+    "id": <nonnull-json-value>        required
+
+Response object members:
+
+    "result": <database-schema>
+    "error": null
+    "id": same "id" as request
+
+This operation retrieves a <database-schema> that describes hosted
+database <db-name>.
+
+transact
+........
+
+Request object members:
+
+    "method": "transact"                  required
+    "params": [<db-name>, <operation>*]   required
+    "id": <nonnull-json-value>            required
+
+Response object members:
+
+    "result": [<object>*]
+    "error": null
+    "id": same "id" as request
+
+The "params" array for this method consists of a <db-name> that
+identifies the database to which the transaction applies, followed by
+zero or more JSON objects, each of which represents a single database
+operation.  The "Operations" section below describes the valid
+operations.
+
+The value of "id" must be unique among all in-flight transactions
+within the current JSON-RPC session.  Otherwise, the server may return
+a JSON-RPC error.
+
+The database server executes each of the specified operations in the
+specified order, except that if an operation fails, then the remaining
+operations are not executed.
+
+The set of operations is executed as a single atomic, consistent,
+isolated transaction.  The transaction is committed only if every
+operation succeeds.  Durability of the commit is not guaranteed unless
+the "commit" operation, with "durable" set to true, is included in the
+operation set (see below).
+
+Regardless of whether errors occur, the response is always a JSON-RPC
+response with null "error" and a "result" member that is an array with
+the same number of elements as "params".  Each element of the "result"
+array corresponds to the same element of the "params" array.  The
+"result" array elements may be interpreted as follows:
+
+    - A JSON object that does not contain an "error" member indicates
+      that the operation completed successfully.  The specific members
+      of the object are specified below in the descriptions of
+      individual operations.  Some operations do not produce any
+      results, in which case the object will have no members.
+
+    - An <error>, which indicates that the operation completed with an
+      error.
+
+    - A JSON null value indicates that the operation was not attempted
+      because a prior operation failed.
+
+In general, "result" contains some number of successful results,
+possibly followed by an error, in turn followed by enough JSON null
+values to match the number of elements in "params".  There is one
+exception: if all of the operations succeed, but the results cannot be
+committed, then "result" will have one more element than "params",
+with the additional element an <error>.  The possible "error" strings
+include at least the following:
+
+    "error": "referential integrity violation"
+
+        When the commit was attempted, a column's value referenced the
+        UUID for a row that did not exist in the table named by the
+        column's <base-type> key or value "refTable" that has a
+        "refType" of "strong".  (This can be caused by inserting a row
+        that references a nonexistent row, by deleting a row that is
+        still referenced by another row, by specifying the UUID for a
+        row in the wrong table, and other ways.)
+
+    "error": "constraint violation"
+
+        A column with a <base-type> key or value "refTable" whose
+        "refType" is "weak" became empty due to deletion(s) caused
+        because the rows that it referenced were deleted (or never
+        existed, if the column's row was inserted within the
+        transaction), and this column is not allowed to be empty
+        because its <type> has a "min" of 1.
+
+    "error": "constraint violation"
+
+        The number of rows in a table exceeds the maximum number
+        permitted by the table's "maxRows" value (see <table-schema>).
+
+If "params" contains one or more "wait" operations, then the
+transaction may take an arbitrary amount of time to complete.  The
+database implementation must be capable of accepting, executing, and
+replying to other transactions and other JSON-RPC requests while a
+transaction or transactions containing "wait" operations are
+outstanding on the same or different JSON-RPC sessions.
+
+The section "Notation for the Wire Protocol" below describes
+additional notation for use with the wire protocol.  After that, the
+"Operations" section describes each operation.
+
+cancel
+......
+
+Request object members:
+
+    "method": "cancel"                              required
+    "params": [the "id" for an outstanding request] required
+    "id": null                                      required
+
+Response object members:
+
+    <no response>
+
+This JSON-RPC notification instructs the database server to
+immediately complete or cancel the "transact" request whose "id" is
+the same as the notification's "params" value.  
+
+If the "transact" request can be completed immediately, then the
+server sends a response in the form described for "transact", above.
+Otherwise, the server sends a JSON-RPC error response of the following
+form:
+
+    "result": null
+    "error": "canceled"
+    "id": the request "id" member
+
+The "cancel" notification itself has no reply.
+
+monitor
+.......
+
+Request object members:
+
+    "method": "monitor"                                       required
+    "params": [<db-name>, <json-value>, <monitor-requests>]   required
+    "id": <nonnull-json-value>                                required
+
+<monitor-requests> is an object that maps from a table name to a
+<monitor-request>.
+
+Each <monitor-request> is an object with the following members:
+
+    "columns": [<column>*]            optional
+    "select": <monitor-select>        optional
+
+<monitor-select> is an object with the following members:
+
+    "initial": <boolean>              optional
+    "insert": <boolean>               optional
+    "delete": <boolean>               optional
+    "modify": <boolean>               optional
+
+Response object members:
+
+    "result": <table-updates>
+    "error": null
+    "id": same "id" as request
+
+This JSON-RPC request enables a client to replicate tables or subsets
+of tables within database <db-name>.  Each <monitor-request> specifies
+a table to be replicated.  The JSON-RPC response to the "monitor"
+includes the initial contents of each table.  Afterward, when changes
+to those tables are committed, the changes are automatically sent to
+the client using the "update" monitor notification.  This monitoring
+persists until the JSON-RPC session terminates or until the client
+sends a "monitor_cancel" JSON-RPC request.
+
+Each <monitor-request> describes how to monitor a table:
+
+    The circumstances in which an "update" notification is sent for a
+    row within the table are determined by <monitor-select>:
+
+        If "initial" is omitted or true, every row in the table is
+        sent as part of the reply to the "monitor" request.
+
+        If "insert" is omitted or true, "update" notifications are
+        sent for rows newly inserted into the table.
+
+        If "delete" is omitted or true, "update" notifications are
+        sent for rows deleted from the table.
+
+        If "modify" is omitted or true, "update" notifications are
+        sent whenever when a row in the table is modified.
+
+    The "columns" member specifies the columns whose values are
+    monitored.  If "columns" is omitted, all columns in the table,
+    except for "_uuid", are monitored.
+
+The "result" in the JSON-RPC response to the "monitor" request is a
+<table-updates> object (see below) that contains the contents of the
+tables for which "initial" rows are selected.  If no tables' initial
+contents are requested, then "result" is an empty object.
+
+update
+......
+
+Notification object members:
+
+    "method": "update"
+    "params": [<json-value>, <table-updates>]
+    "id": null
+
+The <json-value> in "params" is the same as the value passed as the
+<json-value> in "params" for the "monitor" request.
+
+<table-updates> is an object that maps from a table name to a
+<table-update>.
+
+A <table-update> is an object that maps from the row's UUID (as a
+36-byte string) to a <row-update> object.
+
+A <row-update> is an object with the following members:
+
+    "old": <row>         present for "delete" and "modify" updates
+    "new": <row>         present for "initial", "insert", and "modify" updates
+
+This JSON-RPC notification is sent from the server to the client to
+tell it about changes to a monitored table (or the initial state of a
+modified table).  Each table in which one or more rows has changed (or
+whose initial view is being presented) is represented in "updates".
+Each row that has changed (or whose initial view is being presented)
+is represented in its <table-update> as a member with its name taken
+from the row's _uuid member.  The corresponding value is a
+<row-update>:
+
+    The "old" member is present for "delete" and "modify" updates.
+    For "delete" updates, each monitored column is included.  For
+    "modify" updates, the prior value of each monitored column whose
+    value has changed is included (monitored columns that have not
+    changed are represented in "new").
+
+    The "new" member is present for "initial", "insert", and "modify"
+    updates.  For "initial" and "insert" updates, each monitored
+    column is included.  For "modify" updates, the new value of each
+    monitored column is included.
+
+monitor_cancel
+..............
+
+Request object members:
+
+    "method": "monitor_cancel"                              required
+    "params": [<json-value>]                                required
+    "id": <nonnull-json-value>                              required
+
+Response object members:
+
+    "result": {}
+    "error": null
+    "id": the request "id" member
+
+Cancels the ongoing table monitor request, identified by the
+<json-value> in "params" matching the <json-value> in "params" for an
+ongoing "monitor" request.  No more "update" messages will be sent for
+this table monitor.
+
+echo
+....
+
+Request object members:
+
+    "method": "echo"                                required
+    "params": JSON array with any contents          required
+    "id": <json-value>                              required
+
+Response object members:
+
+    "result": same as "params"
+    "error": null
+    "id": the request "id" member
+
+Both the JSON-RPC client and the server must implement this request.
+
+This JSON-RPC request and response can be used to implement connection
+keepalives, by allowing the server to check that the client is still
+there or vice versa.
+
+
+Notation for the Wire Protocol
+------------------------------
+
+<db-name>
+
+    An <id> that names a database.  The valid <db-name>s can be
+    obtained using a "list-db" request.  The <db-name> is taken from
+    the "name" member of <database-schema>.
+
+<table>
+
+    An <id> that names a table.
+
+<column>
+
+    An <id> that names a table column.
+
+<row>
+
+    A JSON object that describes a table row or a subset of a table
+    row.  Each member is the name of a table column paired with the
+    <value> of that column.
+
+<value>
+
+    A JSON value that represents the value of a column in a table row,
+    one of <atom>, a <set>, or a <map>.
+
+<atom>
+
+    A JSON value that represents a scalar value for a column, one of
+    <string>, <number>, <boolean>, <uuid>, <named-uuid>.
+
+<set>
+
+    Either an <atom>, representing a set with exactly one element, or
+    a 2-element JSON array that represents a database set value.  The
+    first element of the array must be the string "set" and the second
+    element must be an array of zero or more <atom>s giving the values
+    in the set.  All of the <atom>s must have the same type.
+
+<map>
+
+    A 2-element JSON array that represents a database map value.  The
+    first element of the array must be the string "map" and the second
+    element must be an array of zero or more <pair>s giving the values
+    in the map.  All of the <pair>s must have the same key and value
+    types.
+
+    (JSON objects are not used to represent <map> because JSON only
+    allows string names in an object.)
+
+<pair>
+
+    A 2-element JSON array that represents a pair within a database
+    map.  The first element is an <atom> that represents the key, the
+    second element is an <atom> that represents the value.
+
+<uuid>
+
+    A 2-element JSON array that represents a UUID.  The first element
+    of the array must be the string "uuid" and the second element must
+    be a 36-character string giving the UUID in the format described
+    by RFC 4122.  For example, the following <uuid> represents the
+    UUID 550e8400-e29b-41d4-a716-446655440000:
+
+        ["uuid", "550e8400-e29b-41d4-a716-446655440000"]
+
+<named-uuid>
+
+    A 2-element JSON array that represents the UUID of a row inserted
+    in an "insert" operation within the same transaction.  The first
+    element of the array must be the string "named-uuid" and the
+    second element should be the string specified as the "uuid-name"
+    for an "insert" operation within the same transaction.  For
+    example, if an "insert" operation within this transaction
+    specifies a "uuid-name" of "myrow", the following <named-uuid>
+    represents the UUID created by that operation:
+
+        ["named-uuid", "myrow"]
+
+    A <named-uuid> may be used anywhere a <uuid> is valid.
+
+<condition>
+
+    A 3-element JSON array of the form [<column>, <function>,
+    <value>] that represents a test on a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <function> must be "<", "<=", "==", "!=", ">=", ">",
+            "includes", or "excludes".
+
+            The test is true if the column's value satisfies the
+            relation <function> <value>, e.g. if the column has value
+            1 and <value> is 2, the test is true if <function> is "<",
+            "<=" or "!=", but not otherwise.
+
+            "includes" is equivalent to "=="; "excludes" is equivalent
+            to "!=".
+
+        boolean
+        string
+        uuid
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==" or "includes", the test is true if
+            the column's value equals <value>.  If <function> is "!="
+            or "excludes", the test is inverted.
+
+        set
+        map
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==", the test is true if the column's
+            value contains exactly the same values (for sets) or pairs
+            (for maps).  If <function> is "!=", the test is inverted.
+
+            If <function> is "includes", the test is true if the
+            column's value contains all of the values (for sets) or
+            pairs (for maps) in <value>.  The column's value may also
+            contain other values or pairs.
+
+            If <function> is "excludes", the test is true if the
+            column's value does not contain any of the values (for
+            sets) or pairs (for maps) in <value>.  The column's value
+            may contain other values or pairs not in <value>.
+
+            If <function> is "includes" or "excludes", then the
+            required type of <value> is slightly relaxed, in that it
+            may have fewer than the minimum number of elements
+            specified by the column's type.  If <function> is
+            "excludes", then the required type is additionally relaxed
+            in that <value> may have more than the maximum number of
+            elements specified by the column's type.
+
+<function>
+
+    One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
+
+<mutation>
+
+    A 3-element JSON array of the form [<column>, <mutator>, <value>]
+    that represents a change to a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <mutator> must be "+=", "-=", "*=", "/=" or (integer only)
+            "%=".  The value of <column> is changed to the sum,
+            difference, product, quotient, or remainder, respectively,
+            of <column> and <value>.
+
+            Constraints on <column> are ignored when parsing <value>.
+
+        boolean
+        string
+        uuid
+
+            No valid <mutator>s are currently defined for these types.
+
+        set
+
+            Any <mutator> valid for the set's element type may be
+            applied to the set, in which case the mutation is applied
+            to each member of the set individually.  <value> must be a
+            scalar value of the same type as the set's element type,
+            except that contraints are ignored.
+
+            If <mutator> is "insert", then each of the values in the
+            set in <value> is added to <column> if it is not already
+            present.  The required type of <value> is slightly
+            relaxed, in that it may have fewer than the minimum number
+            of elements specified by the column's type.
+
+            If <mutator> is "delete", then each of the values in the
+            set in <value> is removed from <column> if it is present
+            there.  The required type is slightly relaxed in that
+            <value> may have more or less than the maximum number of
+            elements specified by the column's type.
+
+        map
+
+            <mutator> must be "insert" or "delete".
+
+            If <mutator> is "insert", then each of the key-value pairs
+            in the map in <value> is added to <column> only if its key
+            is not already present.  The required type of <value> is
+            slightly relaxed, in that it may have fewer than the
+            minimum number of elements specified by the column's type.
+
+            If <mutator> is "delete", then <value> may have the same
+            type as <column> (a map type) or it may be a set whose
+            element type is the same as <column>'s key type:
+
+                - If <value> is a map, the mutation deletes each
+                  key-value pair in <column> whose key and value equal
+                  one of the key-value pairs in <value>.
+
+                - If <value> is a set, the mutation deletes each
+                  key-value pair in <column> whose key equals one of
+                  the values in <value>.
+
+            For "delete", <value> may have any number of elements,
+            regardless of restrictions on the number of elements in
+            <column>.
+
+<mutator>
+
+    One of "+=", "-=", "*=", "/=", "%=", "insert", "delete".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+    "op": "insert"          required
+    "table": <table>        required
+    "row": <row>            required
+    "uuid-name": <id>       optional
+
+Result object members:
+
+    "uuid": <uuid>
+
+Semantics:
+
+    Inserts "row" into "table".
+
+    If "row" does not specify values for all the columns in "table",
+    those columns receive default values.  The default value for a
+    column depends on its type.  The default for a column whose <type>
+    specifies a "min" of 0 is an empty set or empty map.  Otherwise,
+    the default is a single value or a single key-value pair, whose
+    value(s) depend on its <atomic-type>:
+
+        - "integer" or "real": 0
+
+        - "boolean": false
+
+        - "string": "" (the empty string)
+
+        - "uuid": 00000000-0000-0000-0000-000000000000
+
+    The new row receives a new, randomly generated UUID.
+
+    If "uuid-name" is supplied, then it is an error if <id> is not
+    unique among the "uuid-name"s supplied on all the "insert"
+    operations within this transaction.
+
+    The UUID for the new row is returned as the "uuid" member of the
+    result.
+
+Errors:
+
+    "error": "duplicate uuid-name"
+
+        The same "uuid-name" appears on another "insert" operation
+        within this transaction.
+
+    "error": "constraint violation"
+
+        One of the values in "row" does not satisfy the immediate
+        constraints for its column's <base-type>.  This error will
+        occur for columns that are not explicitly set by "row" if the
+        default value does not satisfy the column's constraints.
+
+select
+......
+
+Request object members:
+
+    "op": "select"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "columns": [<column>*]        optional
+
+Result object members:
+
+    "rows": [<row>*]
+
+Semantics:
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  If "where" is an empty array, every row in "table" is
+    selected.
+
+    The "rows" member of the result is an array of objects.  Each
+    object corresponds to a matching row, with each column
+    specified in "columns" as a member, the column's name as the
+    member name and its value as the member value.  If "columns"
+    is not specified, all the table's columns are included.  If
+    two rows of the result have the same values for all included
+    columns, only one copy of that row is included in "rows".
+    Specifying "_uuid" within "columns" will avoid dropping
+    duplicates, since every row has a unique UUID.
+
+    The ordering of rows within "rows" is unspecified.
+
+update
+......
+
+Request object members:
+
+    "op": "update"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "row": <row>                  required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Updates rows in a table.
+
+    Searches "table" for rows that match all the conditions
+    specified in "where".  For each matching row, changes the
+    value of each column specified in "row" to the value for that
+    column specified in "row".
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    updated with this operation.  Columns designated read-only in the 
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+Errors:
+
+    "error": "constraint violation"
+
+        One of the values in "row" does not satisfy the immediate
+        constraints for its column's <base-type>.
+mutate
+......
+
+Request object members:
+
+    "op": "mutate"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "mutations": [<mutation>*]    required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Mutates rows in a table.
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  For each matching row, mutates its columns as
+    specified by each <mutation> in "mutations", in the order
+    specified.
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    modified with this operation.  Columns designated read-only in the
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+Errors:
+
+    "error": "domain error"
+
+        The result of the mutation is not mathematically defined,
+        e.g. division by zero.
+
+    "error": "range error"
+
+        The result of the mutation is not representable within the
+        database's format, e.g. an integer result outside the range
+        INT64_MIN...INT64_MAX or a real result outside the range
+        -DBL_MAX...DBL_MAX.
+
+    "error": "constraint violation"
+
+        The mutation caused the column's value to violate a
+        constraint, e.g. it caused a column to have more or fewer
+        values than are allowed, an arithmetic operation caused a set
+        or map to have duplicate elements, or it violated a constraint
+        specified by a column's <base-type>.
+
+delete
+......
+
+Request object members:
+
+    "op": "delete"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Deletes all the rows from "table" that match all the conditions
+    specified in "where".
+
+    The "count" member of the result specifies the number of deleted
+    rows.
+
+wait
+....
+
+Request object members:
+
+    "op": "wait"                        required
+    "timeout": <integer>                optional
+    "table": <table>                    required
+    "where": [<condition>*]             required
+    "columns": [<column>*]              required
+    "until": "==" or "!="               required
+    "rows": [<row>*]                    required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Waits until a condition becomes true.
+
+    If "until" is "==", checks whether the query on "table" specified
+    by "where" and "columns", which is evaluated in the same way as
+    specified for "select", returns the result set specified by
+    "rows".  If it does, then the operation completes successfully.
+    Otherwise, the entire transaction rolls back.  It is automatically
+    restarted later, after a change in the database makes it possible
+    for the operation to succeed.  The client will not receive a
+    response until the operation permanently succeeds or fails.
+    
+    If "until" is "!=", the sense of the test is negated.  That is, as
+    long as the query on "table" specified by "where" and "columns"
+    returns "rows", the transaction will be rolled back and restarted
+    later.
+
+    If "timeout" is specified, then the transaction aborts after the
+    specified number of milliseconds.  The transaction is guaranteed
+    to be attempted at least once before it aborts.  A "timeout" of 0
+    will abort the transaction on the first mismatch.
+
+Errors:
+
+    "error": "not supported"
+
+        One or more of the columns in this table do not support
+        triggers.  This error will not occur if "timeout" is 0.
+
+    "error": "timed out"
+
+        The "timeout" was reached before the transaction was able to
+        complete.
+
+commit
+......
+
+Request object members:
+
+    "op": "commit"                      required
+    "durable": <boolean>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    If "durable" is specified as true, then the transaction, if it
+    commits, will be stored durably (to disk) before the reply is sent
+    to the client.
+
+Errors:
+
+    "error": "not supported"
+
+        When "durable" is true, this database implementation does not
+        support durable commits.
+
+abort
+.....
+
+Request object members:
+
+    "op": "abort"                      required
+
+Result object members:
+
+    (never succeeds)
+
+Semantics:
+
+    Aborts the transaction with an error.  This may be useful for
+    testing.
+
+Errors:
+
+    "error": "aborted"
+
+        This operation always fails with this error.
+
+comment
+.......
+
+
+Request object members:
+
+    "op": "comment"                    required
+    "comment": <string>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Provides information to a database administrator on the purpose of
+    a transaction.  The OVSDB server, for example, adds comments in
+    transactions that modify the database to the database journal.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644 (file)
index 0000000..7d578a8
--- /dev/null
@@ -0,0 +1,116 @@
+# libovsdb
+noinst_LIBRARIES += ovsdb/libovsdb.a
+ovsdb_libovsdb_a_SOURCES = \
+       ovsdb/column.c \
+       ovsdb/column.h \
+       ovsdb/condition.c \
+       ovsdb/condition.h \
+       ovsdb/execution.c \
+       ovsdb/file.c \
+       ovsdb/file.h \
+       ovsdb/jsonrpc-server.c \
+       ovsdb/jsonrpc-server.h \
+       ovsdb/log.c \
+       ovsdb/log.h \
+       ovsdb/mutation.c \
+       ovsdb/mutation.h \
+       ovsdb/ovsdb-server.c \
+       ovsdb/ovsdb.c \
+       ovsdb/ovsdb.h \
+       ovsdb/query.c \
+       ovsdb/query.h \
+       ovsdb/row.c \
+       ovsdb/row.h \
+       ovsdb/table.c \
+       ovsdb/table.h \
+       ovsdb/trigger.c \
+       ovsdb/trigger.h \
+       ovsdb/transaction.c \
+       ovsdb/transaction.h
+EXTRA_DIST += \
+       ovsdb/remote-active.man \
+       ovsdb/remote-passive.man
+
+# ovsdb-tool
+bin_PROGRAMS += ovsdb/ovsdb-tool
+ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+# ovsdb-tool.1
+man_MANS += ovsdb/ovsdb-tool.1
+DISTCLEANFILES += ovsdb/ovsdb-tool.1
+EXTRA_DIST += ovsdb/ovsdb-tool.1.in
+
+# ovsdb-client
+bin_PROGRAMS += ovsdb/ovsdb-client
+ovsdb_ovsdb_client_SOURCES = ovsdb/ovsdb-client.c
+ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+# ovsdb-client.1
+man_MANS += ovsdb/ovsdb-client.1
+DISTCLEANFILES += ovsdb/ovsdb-client.1
+EXTRA_DIST += ovsdb/ovsdb-client.1.in
+
+# ovsdb-server
+sbin_PROGRAMS += ovsdb/ovsdb-server
+ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+# ovsdb-server.1
+man_MANS += ovsdb/ovsdb-server.1
+DISTCLEANFILES += ovsdb/ovsdb-server.1
+EXTRA_DIST += ovsdb/ovsdb-server.1.in
+
+# ovsdb-idlc
+EXTRA_DIST += \
+       ovsdb/simplejson/__init__.py \
+       ovsdb/simplejson/_speedups.c                            \
+       ovsdb/simplejson/decoder.py                             \
+       ovsdb/simplejson/encoder.py                             \
+       ovsdb/simplejson/scanner.py                             \
+       ovsdb/simplejson/tests/__init__.py                      \
+       ovsdb/simplejson/tests/test_check_circular.py           \
+       ovsdb/simplejson/tests/test_decode.py                   \
+       ovsdb/simplejson/tests/test_default.py                  \
+       ovsdb/simplejson/tests/test_dump.py                     \
+       ovsdb/simplejson/tests/test_encode_basestring_ascii.py  \
+       ovsdb/simplejson/tests/test_fail.py                     \
+       ovsdb/simplejson/tests/test_float.py                    \
+       ovsdb/simplejson/tests/test_indent.py                   \
+       ovsdb/simplejson/tests/test_pass1.py                    \
+       ovsdb/simplejson/tests/test_pass2.py                    \
+       ovsdb/simplejson/tests/test_pass3.py                    \
+       ovsdb/simplejson/tests/test_recursion.py                \
+       ovsdb/simplejson/tests/test_scanstring.py               \
+       ovsdb/simplejson/tests/test_separators.py               \
+       ovsdb/simplejson/tests/test_unicode.py                  \
+       ovsdb/simplejson/tool.py
+noinst_SCRIPTS += ovsdb/ovsdb-idlc 
+EXTRA_DIST += \
+       ovsdb/ovsdb-idlc.in \
+       ovsdb/ovsdb-idlc.1
+DISTCLEANFILES += ovsdb/ovsdb-idlc
+SUFFIXES += .ovsidl
+OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in
+.ovsidl.c:
+       $(OVSDB_IDLC) c-idl-source $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.h:
+       $(OVSDB_IDLC) c-idl-header $< > $@.tmp
+       mv $@.tmp $@
+
+EXTRA_DIST += $(OVSIDL_BUILT)
+BUILT_SOURCES += $(OVSIDL_BUILT)
+
+# This must be done late: macros in targets are expanded when the
+# target line is read, so if this file were to be included before some
+# other file that added to OVSIDL_BUILT, then those files wouldn't get
+# the dependency.
+#
+# However, current versions of Automake seem to output all variable
+# assignments before any targets, so it doesn't seem to be a problem,
+# at least for now.
+$(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in
+
+# ovsdb-doc
+EXTRA_DIST += ovsdb/ovsdb-doc.in
+noinst_SCRIPTS += ovsdb/ovsdb-doc
+DISTCLEANFILES += ovsdb/ovsdb-doc
+OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644 (file)
index 0000000..a22e1a2
--- /dev/null
@@ -0,0 +1,261 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb/column.h"
+
+#include <stdlib.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "util.h"
+
+struct ovsdb_column *
+ovsdb_column_create(const char *name,
+                    bool mutable, bool persistent,
+                    const struct ovsdb_type *type)
+{
+    /* Doesn't set the new column's 'index': the caller must do that. */
+    struct ovsdb_column *column;
+
+    column = xzalloc(sizeof *column);
+    column->name = xstrdup(name);
+    column->mutable = mutable;
+    column->persistent = persistent;
+    ovsdb_type_clone(&column->type, type);
+
+    return column;
+}
+
+struct ovsdb_column *
+ovsdb_column_clone(const struct ovsdb_column *old)
+{
+    /* Doesn't copy the column's 'index': the caller must do that. */
+    return ovsdb_column_create(old->name,
+                               old->mutable, old->persistent,
+                               &old->type);
+}
+
+void
+ovsdb_column_destroy(struct ovsdb_column *column)
+{
+    ovsdb_type_destroy(&column->type);
+    free(column->name);
+    free(column);
+}
+
+struct ovsdb_error *
+ovsdb_column_from_json(const struct json *json, const char *name,
+                       struct ovsdb_column **columnp)
+{
+    const struct json *mutable, *ephemeral, *type_json;
+    struct ovsdb_error *error;
+    struct ovsdb_type type;
+    struct ovsdb_parser parser;
+    bool persistent;
+
+    *columnp = NULL;
+
+    ovsdb_parser_init(&parser, json, "schema for column %s", name);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    ephemeral = ovsdb_parser_member(&parser, "ephemeral",
+                                    OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_type_from_json(&type, type_json);
+    if (error) {
+        return error;
+    }
+
+    persistent = ephemeral ? !json_boolean(ephemeral) : true;
+    *columnp = ovsdb_column_create(name,
+                                   mutable ? json_boolean(mutable) : true,
+                                   persistent, &type);
+
+    ovsdb_type_destroy(&type);
+
+    return NULL;
+}
+
+struct json *
+ovsdb_column_to_json(const struct ovsdb_column *column)
+{
+    struct json *json = json_object_create();
+    if (!column->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+    if (!column->persistent) {
+        json_object_put(json, "ephemeral", json_boolean_create(true));
+    }
+    json_object_put(json, "type", ovsdb_type_to_json(&column->type));
+    return json;
+}
+\f
+void
+ovsdb_column_set_init(struct ovsdb_column_set *set)
+{
+    set->columns = NULL;
+    set->n_columns = set->allocated_columns = 0;
+}
+
+void
+ovsdb_column_set_destroy(struct ovsdb_column_set *set)
+{
+    free(set->columns);
+}
+
+void
+ovsdb_column_set_clone(struct ovsdb_column_set *new,
+                       const struct ovsdb_column_set *old)
+{
+    new->columns = xmemdup(old->columns,
+                           old->n_columns * sizeof *old->columns);
+    new->n_columns = new->allocated_columns = old->n_columns;
+}
+
+struct ovsdb_error *
+ovsdb_column_set_from_json(const struct json *json,
+                           const struct ovsdb_table *table,
+                           struct ovsdb_column_set *set)
+{
+    ovsdb_column_set_init(set);
+    if (!json) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    } else {
+        struct ovsdb_error *error = NULL;
+        size_t i;
+
+        if (json->type != JSON_ARRAY) {
+            goto error;
+        }
+
+        /* XXX this is O(n**2) */
+        for (i = 0; i < json->u.array.n; i++) {
+            const struct ovsdb_column *column;
+            const char *s;
+
+            if (json->u.array.elems[i]->type != JSON_STRING) {
+                goto error;
+            }
+
+            s = json->u.array.elems[i]->u.string;
+            column = shash_find_data(&table->schema->columns, s);
+            if (!column) {
+                error = ovsdb_syntax_error(json, NULL, "%s is not a valid "
+                                           "column name", s);
+                goto error;
+            } else if (ovsdb_column_set_contains(set, column->index)) {
+                goto error;
+            }
+            ovsdb_column_set_add(set, column);
+        }
+        return NULL;
+
+    error:
+        ovsdb_column_set_destroy(set);
+        ovsdb_column_set_init(set);
+        if (!error) {
+            error = ovsdb_syntax_error(json, NULL, "array of distinct column "
+                                       "names expected");
+        }
+        return error;
+    }
+}
+
+struct json *
+ovsdb_column_set_to_json(const struct ovsdb_column_set *set)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_array_create_empty();
+    for (i = 0; i < set->n_columns; i++) {
+        json_array_add(json, json_string_create(set->columns[i]->name));
+    }
+    return json;
+}
+
+void
+ovsdb_column_set_add(struct ovsdb_column_set *set,
+                     const struct ovsdb_column *column)
+{
+    if (set->n_columns >= set->allocated_columns) {
+        set->columns = x2nrealloc(set->columns, &set->allocated_columns,
+                                  sizeof *set->columns);
+    }
+    set->columns[set->n_columns++] = column;
+}
+
+void
+ovsdb_column_set_add_all(struct ovsdb_column_set *set,
+                         const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_column_set_add(set, column);
+    }
+}
+
+bool
+ovsdb_column_set_contains(const struct ovsdb_column_set *set,
+                          unsigned int column_index)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_columns; i++) {
+        if (set->columns[i]->index == column_index) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* This comparison is sensitive to ordering of columns within a set, but that's
+ * good: the only existing caller wants to make sure that hash values are
+ * comparable, which is only true if column ordering is the same. */
+bool
+ovsdb_column_set_equals(const struct ovsdb_column_set *a,
+                        const struct ovsdb_column_set *b)
+{
+    size_t i;
+
+    if (a->n_columns != b->n_columns) {
+        return false;
+    }
+    for (i = 0; i < a->n_columns; i++) {
+        if (a->columns[i] != b->columns[i]) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/ovsdb/column.h b/ovsdb/column.h
new file mode 100644 (file)
index 0000000..b6f324c
--- /dev/null
@@ -0,0 +1,85 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_COLUMN_H
+#define OVSDB_COLUMN_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_table;
+
+/* A column or a column schema (currently there is no distinction). */
+struct ovsdb_column {
+    unsigned int index;
+    char *name;
+
+    bool mutable;
+    bool persistent;
+    struct ovsdb_type type;
+};
+
+/* A few columns appear in every table with standardized column indexes.
+ * These macros define those columns' indexes.
+ *
+ * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID
+ * having value 0. */
+enum {
+    OVSDB_COL_UUID = 0,         /* UUID for the row. */
+    OVSDB_COL_VERSION = 1,      /* Version number for the row. */
+    OVSDB_N_STD_COLUMNS
+};
+
+struct ovsdb_column *ovsdb_column_create(
+    const char *name, bool mutable, bool persistent,
+    const struct ovsdb_type *);
+struct ovsdb_column *ovsdb_column_clone(const struct ovsdb_column *);
+void ovsdb_column_destroy(struct ovsdb_column *);
+
+struct ovsdb_error *ovsdb_column_from_json(const struct json *,
+                                           const char *name,
+                                           struct ovsdb_column **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_column_to_json(const struct ovsdb_column *);
+\f
+/* An unordered set of distinct columns. */
+
+struct ovsdb_column_set {
+    const struct ovsdb_column **columns;
+    size_t n_columns, allocated_columns;
+};
+
+#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_column_set_init(struct ovsdb_column_set *);
+void ovsdb_column_set_destroy(struct ovsdb_column_set *);
+void ovsdb_column_set_clone(struct ovsdb_column_set *,
+                            const struct ovsdb_column_set *);
+struct ovsdb_error *ovsdb_column_set_from_json(const struct json *,
+                                               const struct ovsdb_table *,
+                                               struct ovsdb_column_set *);
+struct json *ovsdb_column_set_to_json(const struct ovsdb_column_set *);
+
+void ovsdb_column_set_add(struct ovsdb_column_set *,
+                          const struct ovsdb_column *);
+void ovsdb_column_set_add_all(struct ovsdb_column_set *,
+                              const struct ovsdb_table *);
+bool ovsdb_column_set_contains(const struct ovsdb_column_set *,
+                               unsigned int column_index);
+bool ovsdb_column_set_equals(const struct ovsdb_column_set *,
+                             const struct ovsdb_column_set *);
+
+#endif /* column.h */
diff --git a/ovsdb/condition.c b/ovsdb/condition.c
new file mode 100644 (file)
index 0000000..59f742c
--- /dev/null
@@ -0,0 +1,283 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "condition.h"
+
+#include <limits.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_function_from_string(const char *name, enum ovsdb_function *function)
+{
+#define OVSDB_FUNCTION(ENUM, NAME)              \
+    if (!strcmp(name, NAME)) {                  \
+        *function = ENUM;                       \
+        return NULL;                            \
+    }
+    OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+
+    return ovsdb_syntax_error(NULL, "unknown function",
+                              "No function named %s.", name);
+}
+
+const char *
+ovsdb_function_to_string(enum ovsdb_function function)
+{
+    switch (function) {
+#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+    }
+
+    return NULL;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
+                       const struct json *json,
+                       struct ovsdb_symbol_table *symtab,
+                       struct ovsdb_clause *clause)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *function_name;
+    const char *column_name;
+    struct ovsdb_type type;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in condition.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    clause->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!clause->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    type = clause->column->type;
+
+    function_name = json_string(array->elems[1]);
+    error = ovsdb_function_from_string(function_name, &clause->function);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (clause->function) {
+    case OVSDB_F_LT:
+    case OVSDB_F_LE:
+    case OVSDB_F_GT:
+    case OVSDB_F_GE:
+        /* XXX should we also allow these operators for types with n_min == 0,
+         * n_max == 1?  (They would always be "false" if the value was
+         * missing.) */
+        if (!ovsdb_type_is_scalar(&type)
+            || (type.key.type != OVSDB_TYPE_INTEGER
+                && type.key.type != OVSDB_TYPE_REAL)) {
+            char *s = ovsdb_type_to_english(&type);
+            error = ovsdb_syntax_error(
+                json, NULL, "Type mismatch: \"%s\" operator may not be "
+                "applied to column %s of type %s.",
+                ovsdb_function_to_string(clause->function),
+                clause->column->name, s);
+            free(s);
+            return error;
+        }
+        break;
+
+    case OVSDB_F_EQ:
+    case OVSDB_F_NE:
+        break;
+
+    case OVSDB_F_EXCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+            type.n_max = UINT_MAX;
+        }
+        break;
+
+    case OVSDB_F_INCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+        }
+        break;
+    }
+    return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab);
+}
+
+static void
+ovsdb_clause_free(struct ovsdb_clause *clause)
+{
+    ovsdb_datum_destroy(&clause->arg, &clause->column->type);
+}
+
+static int
+compare_clauses_3way(const void *a_, const void *b_)
+{
+    const struct ovsdb_clause *a = a_;
+    const struct ovsdb_clause *b = b_;
+
+    if (a->function != b->function) {
+        /* Bring functions to the front based on the fraction of table rows
+         * that they are (heuristically) expected to leave in the query
+         * results.  Note that "enum ovsdb_function" is intentionally ordered
+         * to make this trivial. */
+        return a->function < b->function ? -1 : 1;
+    } else if (a->column->index != b->column->index) {
+        if (a->column->index < OVSDB_N_STD_COLUMNS
+            || b->column->index < OVSDB_N_STD_COLUMNS) {
+            /* Bring the standard columns and in particular the UUID column
+             * (since OVSDB_COL_UUID has value 0) to the front.  We have an
+             * index on the UUID column, so that makes our queries cheaper. */
+            return a->column->index < b->column->index ? -1 : 1;
+        } else {
+            /* Order clauses predictably to make testing easier. */
+            return strcmp(a->column->name, b->column->name);
+        }
+    } else {
+        return 0;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_condition_from_json(const struct ovsdb_table_schema *ts,
+                          const struct json *json,
+                          struct ovsdb_symbol_table *symtab,
+                          struct ovsdb_condition *cnd)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses);
+    cnd->n_clauses = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_clause_from_json(ts, array->elems[i], symtab,
+                                       &cnd->clauses[i]);
+        if (error) {
+            ovsdb_condition_destroy(cnd);
+            cnd->clauses = NULL;
+            cnd->n_clauses = 0;
+            return error;
+        }
+        cnd->n_clauses++;
+    }
+
+    /* A real database would have a query optimizer here. */
+    qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses,
+          compare_clauses_3way);
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_clause_to_json(const struct ovsdb_clause *clause)
+{
+    return json_array_create_3(
+        json_string_create(clause->column->name),
+        json_string_create(ovsdb_function_to_string(clause->function)),
+        ovsdb_datum_to_json(&clause->arg, &clause->column->type));
+}
+
+struct json *
+ovsdb_condition_to_json(const struct ovsdb_condition *cnd)
+{
+    struct json **clauses;
+    size_t i;
+
+    clauses = xmalloc(cnd->n_clauses * sizeof *clauses);
+    for (i = 0; i < cnd->n_clauses; i++) {
+        clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]);
+    }
+    return json_array_create(clauses, cnd->n_clauses);
+}
+
+bool
+ovsdb_condition_evaluate(const struct ovsdb_row *row,
+                         const struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        const struct ovsdb_clause *c = &cnd->clauses[i];
+        const struct ovsdb_datum *field = &row->fields[c->column->index];
+        const struct ovsdb_datum *arg = &cnd->clauses[i].arg;
+        const struct ovsdb_type *type = &c->column->type;
+
+        if (ovsdb_type_is_scalar(type)) {
+            int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
+                                              type->key.type);
+            switch (c->function) {
+            case OVSDB_F_LT:
+                return cmp < 0;
+            case OVSDB_F_LE:
+                return cmp <= 0;
+            case OVSDB_F_EQ:
+            case OVSDB_F_INCLUDES:
+                return cmp == 0;
+            case OVSDB_F_NE:
+            case OVSDB_F_EXCLUDES:
+                return cmp != 0;
+            case OVSDB_F_GE:
+                return cmp >= 0;
+            case OVSDB_F_GT:
+                return cmp > 0;
+            }
+        } else {
+            switch (c->function) {
+            case OVSDB_F_EQ:
+                return ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_NE:
+                return !ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_INCLUDES:
+                return ovsdb_datum_includes_all(arg, field, type);
+            case OVSDB_F_EXCLUDES:
+                return ovsdb_datum_excludes_all(arg, field, type);
+            case OVSDB_F_LT:
+            case OVSDB_F_LE:
+            case OVSDB_F_GE:
+            case OVSDB_F_GT:
+                NOT_REACHED();
+            }
+        }
+        NOT_REACHED();
+    }
+
+    return true;
+}
+
+void
+ovsdb_condition_destroy(struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        ovsdb_clause_free(&cnd->clauses[i]);
+    }
+    free(cnd->clauses);
+}
diff --git a/ovsdb/condition.h b/ovsdb/condition.h
new file mode 100644 (file)
index 0000000..4716150
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_CONDITION_H
+#define OVSDB_CONDITION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_FUNCTIONS                         \
+    OVSDB_FUNCTION(OVSDB_F_EQ, "==")                  \
+    OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes")      \
+    OVSDB_FUNCTION(OVSDB_F_LE, "<=")                  \
+    OVSDB_FUNCTION(OVSDB_F_LT, "<")                   \
+    OVSDB_FUNCTION(OVSDB_F_GE, ">=")                  \
+    OVSDB_FUNCTION(OVSDB_F_GT, ">")                   \
+    OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes")      \
+    OVSDB_FUNCTION(OVSDB_F_NE, "!=")
+
+enum ovsdb_function {
+#define OVSDB_FUNCTION(ENUM, NAME) ENUM,
+    OVSDB_FUNCTIONS
+#undef OVSDB_FUNCTION
+};
+
+struct ovsdb_error *ovsdb_function_from_string(const char *,
+                                               enum ovsdb_function *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_function_to_string(enum ovsdb_function);
+
+struct ovsdb_clause {
+    enum ovsdb_function function;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+};
+
+struct ovsdb_condition {
+    struct ovsdb_clause *clauses;
+    size_t n_clauses;
+};
+
+#define OVSDB_CONDITION_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_condition_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, struct ovsdb_symbol_table *,
+    struct ovsdb_condition *) WARN_UNUSED_RESULT;
+struct json *ovsdb_condition_to_json(const struct ovsdb_condition *);
+void ovsdb_condition_destroy(struct ovsdb_condition *);
+bool ovsdb_condition_evaluate(const struct ovsdb_row *,
+                              const struct ovsdb_condition *);
+
+#endif /* ovsdb/condition.h */
diff --git a/ovsdb/execution.c b/ovsdb/execution.c
new file mode 100644 (file)
index 0000000..5b6762f
--- /dev/null
@@ -0,0 +1,706 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "mutation.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+    struct ovsdb *db;
+    struct ovsdb_txn *txn;
+    struct ovsdb_symbol_table *symtab;
+    bool durable;
+
+    /* Triggers. */
+    long long int elapsed_msec;
+    long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+                                                     struct ovsdb_parser *,
+                                                     struct json *result);
+
+static ovsdb_operation_executor ovsdb_execute_insert;
+static ovsdb_operation_executor ovsdb_execute_select;
+static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_mutate;
+static ovsdb_operation_executor ovsdb_execute_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+static ovsdb_operation_executor ovsdb_execute_comment;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+    struct ovsdb_operation {
+        const char *name;
+        ovsdb_operation_executor *executor;
+    };
+
+    static const struct ovsdb_operation operations[] = {
+        { "insert", ovsdb_execute_insert },
+        { "select", ovsdb_execute_select },
+        { "update", ovsdb_execute_update },
+        { "mutate", ovsdb_execute_mutate },
+        { "delete", ovsdb_execute_delete },
+        { "wait", ovsdb_execute_wait },
+        { "commit", ovsdb_execute_commit },
+        { "abort", ovsdb_execute_abort },
+        { "comment", ovsdb_execute_comment },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(operations); i++) {
+        const struct ovsdb_operation *c = &operations[i];
+        if (!strcmp(c->name, name)) {
+            return c->executor;
+        }
+    }
+    return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+              long long int elapsed_msec, long long int *timeout_msec)
+{
+    struct ovsdb_execution x;
+    struct ovsdb_error *error;
+    struct json *results;
+    size_t n_operations;
+    size_t i;
+
+    if (params->type != JSON_ARRAY
+        || !params->u.array.n
+        || params->u.array.elems[0]->type != JSON_STRING
+        || strcmp(params->u.array.elems[0]->u.string, db->schema->name)) {
+        struct ovsdb_error *error;
+
+        if (params->type != JSON_ARRAY) {
+            error = ovsdb_syntax_error(params, NULL, "array expected");
+        } else {
+            error = ovsdb_syntax_error(params, NULL, "database name expected "
+                                       "as first parameter");
+        }
+
+        results = ovsdb_error_to_json(error);
+        ovsdb_error_destroy(error);
+        return results;
+    }
+
+    x.db = db;
+    x.txn = ovsdb_txn_create(db);
+    x.symtab = ovsdb_symbol_table_create();
+    x.durable = false;
+    x.elapsed_msec = elapsed_msec;
+    x.timeout_msec = LLONG_MAX;
+    results = NULL;
+
+    results = json_array_create_empty();
+    n_operations = params->u.array.n - 1;
+    error = NULL;
+    for (i = 1; i <= n_operations; i++) {
+        struct json *operation = params->u.array.elems[i];
+        struct ovsdb_error *parse_error;
+        struct ovsdb_parser parser;
+        struct json *result;
+        const struct json *op;
+
+        /* Parse and execute operation. */
+        ovsdb_parser_init(&parser, operation,
+                          "ovsdb operation %zu of %zu", i, n_operations);
+        op = ovsdb_parser_member(&parser, "op", OP_ID);
+        result = json_object_create();
+        if (op) {
+            const char *op_name = json_string(op);
+            ovsdb_operation_executor *executor = lookup_executor(op_name);
+            if (executor) {
+                error = executor(&x, &parser, result);
+            } else {
+                ovsdb_parser_raise_error(&parser, "No operation \"%s\"",
+                                         op_name);
+            }
+        } else {
+            assert(ovsdb_parser_has_error(&parser));
+        }
+
+        /* A parse error overrides any other error.
+         * An error overrides any other result. */
+        parse_error = ovsdb_parser_finish(&parser);
+        if (parse_error) {
+            ovsdb_error_destroy(error);
+            error = parse_error;
+        }
+        if (error) {
+            json_destroy(result);
+            result = ovsdb_error_to_json(error);
+        }
+        if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+            && timeout_msec) {
+            ovsdb_txn_abort(x.txn);
+            *timeout_msec = x.timeout_msec;
+
+            json_destroy(result);
+            json_destroy(results);
+            results = NULL;
+            goto exit;
+        }
+
+        /* Add result to array. */
+        json_array_add(results, result);
+        if (error) {
+            break;
+        }
+    }
+
+    if (!error) {
+        error = ovsdb_txn_commit(x.txn, x.durable);
+        if (error) {
+            json_array_add(results, ovsdb_error_to_json(error));
+        }
+    } else {
+        ovsdb_txn_abort(x.txn);
+    }
+
+    while (json_array(results)->n < n_operations) {
+        json_array_add(results, json_null_create());
+    }
+
+exit:
+    ovsdb_error_destroy(error);
+    ovsdb_symbol_table_destroy(x.symtab);
+
+    return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result OVS_UNUSED)
+{
+    const struct json *durable;
+
+    durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+    if (durable && json_boolean(durable)) {
+        x->durable = true;
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_abort(struct ovsdb_execution *x OVS_UNUSED,
+                    struct ovsdb_parser *parser OVS_UNUSED,
+                    struct json *result OVS_UNUSED)
+{
+    return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+            struct ovsdb_parser *parser, const char *member)
+{
+    struct ovsdb_table *table;
+    const char *table_name;
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_ID);
+    if (!json) {
+        return NULL;
+    }
+    table_name = json_string(json);
+
+    table = shash_find_data(&x->db->tables, table_name);
+    if (!table) {
+        ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+    }
+    return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+          const struct ovsdb_table *table,
+          struct ovsdb_symbol_table *symtab,
+          struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+    struct ovsdb_error *error;
+    const struct json *json;
+    struct ovsdb_row *row;
+
+    *rowp = NULL;
+
+    if (!table) {
+        return OVSDB_BUG("null table");
+    }
+    json = ovsdb_parser_member(parser, member, OP_OBJECT);
+    if (!json) {
+        return OVSDB_BUG("null row member");
+    }
+
+    row = ovsdb_row_create(table);
+    error = ovsdb_row_from_json(row, json, symtab, columns);
+    if (error) {
+        ovsdb_row_destroy(row);
+        return error;
+    } else {
+        *rowp = row;
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    struct ovsdb_row *row = NULL;
+    const struct json *uuid_name;
+    struct ovsdb_error *error;
+    struct uuid row_uuid;
+
+    table = parse_table(x, parser, "table");
+    uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+    error = ovsdb_parser_get_error(parser);
+
+    if (uuid_name) {
+        struct ovsdb_symbol *symbol;
+
+        symbol = ovsdb_symbol_table_insert(x->symtab, json_string(uuid_name));
+        if (symbol->used) {
+            return ovsdb_syntax_error(uuid_name, "duplicate uuid-name",
+                                      "This \"uuid-name\" appeared on an "
+                                      "earlier \"insert\" operation.");
+        }
+        row_uuid = symbol->uuid;
+        symbol->used = true;
+    } else {
+        uuid_generate(&row_uuid);
+    }
+
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+    }
+    if (!error) {
+        /* Check constraints for columns not included in "row", in case the
+         * default values do not satisfy the constraints.  We could check only
+         * the columns that have their default values by supplying an
+         * ovsdb_column_set to parse_row() above, but I suspect that this is
+         * cheaper.  */
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_datum *datum = &row->fields[column->index];
+
+            /* If there are 0 keys or pairs, there's nothing to check.
+             * If there is 1, it might be a default value.
+             * If there are more, it can't be a default value, so the value has
+             * already been checked. */
+            if (datum->n == 1) {
+                error = ovsdb_datum_check_constraints(datum, &column->type);
+                if (error) {
+                    ovsdb_row_destroy(row);
+                    break;
+                }
+            }
+        }
+    }
+    if (!error) {
+        *ovsdb_row_get_uuid_rw(row) = row_uuid;
+        ovsdb_txn_row_insert(x->txn, row);
+        json_object_put(result, "uuid",
+                        ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+                                            &ovsdb_type_uuid));
+    }
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where, *columns_json, *sort_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(sort_json, table, &sort);
+    }
+    if (!error) {
+        struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+        ovsdb_query_distinct(table, &condition, &columns, &rows);
+        ovsdb_row_set_sort(&rows, &sort);
+        json_object_put(result, "rows",
+                        ovsdb_row_set_to_json(&rows, &columns));
+
+        ovsdb_row_set_destroy(&rows);
+    }
+
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_column_set_destroy(&sort);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct update_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_row *row;
+    const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+    struct update_row_cbdata *ur = ur_;
+
+    ur->n_matches++;
+    if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+        ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+                                 ur->row, ur->columns);
+    }
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct update_row_cbdata ur;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        ur.n_matches = 0;
+        ur.txn = x->txn;
+        ur.row = row;
+        ur.columns = &columns;
+        ovsdb_query(table, &condition, update_row_cb, &ur);
+        json_object_put(result, "count", json_integer_create(ur.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct mutate_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_mutation_set *mutations;
+};
+
+static bool
+mutate_row_cb(const struct ovsdb_row *row, void *mr_)
+{
+    struct mutate_row_cbdata *mr = mr_;
+
+    mr->n_matches++;
+    ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row),
+                               mr->mutations);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    const struct json *mutations_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_mutation_set mutations = OVSDB_MUTATION_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct mutate_row_cbdata mr;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    mutations_json = ovsdb_parser_member(parser, "mutations", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_mutation_set_from_json(table->schema, mutations_json,
+                                             x->symtab, &mutations);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        mr.n_matches = 0;
+        mr.txn = x->txn;
+        mr.mutations = &mutations;
+        ovsdb_query(table, &condition, mutate_row_cb, &mr);
+        json_object_put(result, "count", json_integer_create(mr.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_mutation_set_destroy(&mutations);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct delete_row_cbdata {
+    size_t n_matches;
+    const struct ovsdb_table *table;
+    struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+    struct delete_row_cbdata *dr = dr_;
+
+    dr->n_matches++;
+    ovsdb_txn_row_delete(dr->txn, row);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_error *error;
+
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        struct delete_row_cbdata dr;
+
+        dr.n_matches = 0;
+        dr.table = table;
+        dr.txn = x->txn;
+        ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+        json_object_put(result, "count", json_integer_create(dr.n_matches));
+    }
+
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct wait_auxdata {
+    struct ovsdb_row_hash *actual;
+    struct ovsdb_row_hash *expected;
+    bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+    struct wait_auxdata *aux = aux_;
+
+    if (ovsdb_row_hash_contains(aux->expected, row)) {
+        ovsdb_row_hash_insert(aux->actual, row);
+        return true;
+    } else {
+        /* The query row isn't in the expected result set, so the actual and
+         * expected results sets definitely differ and we can short-circuit the
+         * rest of the query. */
+        *aux->equal = false;
+        return false;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                   struct json *result OVS_UNUSED)
+{
+    struct ovsdb_table *table;
+    const struct json *timeout, *where, *columns_json, *until, *rows;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+    struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+    struct ovsdb_error *error;
+    struct wait_auxdata aux;
+    long long int timeout_msec = 0;
+    size_t i;
+
+    timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    until = ovsdb_parser_member(parser, "until", OP_STRING);
+    rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        if (timeout) {
+            timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+            if (timeout_msec < 0) {
+                error = ovsdb_syntax_error(timeout, NULL,
+                                           "timeout must be nonnegative");
+            } else if (timeout_msec < x->timeout_msec) {
+                x->timeout_msec = timeout_msec;
+            }
+        } else {
+            timeout_msec = LLONG_MAX;
+        }
+        if (strcmp(json_string(until), "==")
+            && strcmp(json_string(until), "!=")) {
+            error = ovsdb_syntax_error(until, NULL,
+                                       "\"until\" must be \"==\" or \"!=\"");
+        }
+    }
+    if (!error) {
+        /* Parse "rows" into 'expected'. */
+        ovsdb_row_hash_init(&expected, &columns);
+        for (i = 0; i < rows->u.array.n; i++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_create(table);
+            error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+                                        NULL);
+            if (error) {
+                break;
+            }
+
+            if (!ovsdb_row_hash_insert(&expected, row)) {
+                /* XXX Perhaps we should abort with an error or log a
+                 * warning. */
+                ovsdb_row_destroy(row);
+            }
+        }
+    }
+    if (!error) {
+        /* Execute query. */
+        bool equal = true;
+        ovsdb_row_hash_init(&actual, &columns);
+        aux.actual = &actual;
+        aux.expected = &expected;
+        aux.equal = &equal;
+        ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+        if (equal) {
+            /* We know that every row in 'actual' is also in 'expected'.  We
+             * also know that all of the rows in 'actual' are distinct and that
+             * all of the rows in 'expected' are distinct.  Therefore, if
+             * 'actual' and 'expected' have the same number of rows, then they
+             * have the same content. */
+            size_t n_actual = ovsdb_row_hash_count(&actual);
+            size_t n_expected = ovsdb_row_hash_count(&expected);
+            equal = n_actual == n_expected;
+        }
+        if (!strcmp(json_string(until), "==") != equal) {
+            if (timeout && x->elapsed_msec >= timeout_msec) {
+                if (x->elapsed_msec) {
+                    error = ovsdb_error("timed out",
+                                        "\"wait\" timed out after %lld ms",
+                                        x->elapsed_msec);
+                } else {
+                    error = ovsdb_error("timed out", "\"wait\" timed out");
+                }
+            } else {
+                /* ovsdb_execute() will change this, if triggers really are
+                 * supported. */
+                error = ovsdb_error("not supported", "triggers not supported");
+            }
+        }
+    }
+
+
+    ovsdb_row_hash_destroy(&expected, true);
+    ovsdb_row_hash_destroy(&actual, false);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_comment(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                      struct json *result OVS_UNUSED)
+{
+    const struct json *comment;
+
+    comment = ovsdb_parser_member(parser, "comment", OP_STRING);
+    if (!comment) {
+        return NULL;
+    }
+    ovsdb_txn_add_comment(x->txn, json_string(comment));
+
+    return NULL;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644 (file)
index 0000000..cf5bb41
--- /dev/null
@@ -0,0 +1,756 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "file.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "bitmap.h"
+#include "column.h"
+#include "log.h"
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "socket-util.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "uuid.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+/* Minimum number of milliseconds between database compactions. */
+#define COMPACT_MIN_MSEC        (10 * 60 * 1000) /* 10 minutes. */
+
+/* Minimum number of milliseconds between trying to compact the database if
+ * compacting fails. */
+#define COMPACT_RETRY_MSEC      (60 * 1000)      /* 1 minute. */
+
+/* A transaction being converted to JSON for writing to a file. */
+struct ovsdb_file_txn {
+    struct json *json;          /* JSON for the whole transaction. */
+    struct json *table_json;    /* JSON for 'table''s transaction. */
+    struct ovsdb_table *table;  /* Table described in 'table_json'.  */
+};
+
+static void ovsdb_file_txn_init(struct ovsdb_file_txn *);
+static void ovsdb_file_txn_add_row(struct ovsdb_file_txn *,
+                                   const struct ovsdb_row *old,
+                                   const struct ovsdb_row *new,
+                                   const unsigned long int *changed);
+static struct ovsdb_error *ovsdb_file_txn_commit(struct json *,
+                                                 const char *comment,
+                                                 bool durable,
+                                                 struct ovsdb_log *);
+
+static struct ovsdb_error *ovsdb_file_open__(const char *file_name,
+                                             const struct ovsdb_schema *,
+                                             bool read_only, struct ovsdb **,
+                                             struct ovsdb_file **);
+static struct ovsdb_error *ovsdb_file_txn_from_json(
+    struct ovsdb *, const struct json *, bool converting,
+    long long int *date, struct ovsdb_txn **);
+static struct ovsdb_error *ovsdb_file_create(struct ovsdb *,
+                                             struct ovsdb_log *,
+                                             const char *file_name,
+                                             long long int oldest_commit,
+                                             unsigned int n_transactions,
+                                             struct ovsdb_file **filep);
+
+/* Opens database 'file_name' and stores a pointer to the new database in
+ * '*dbp'.  If 'read_only' is false, then the database will be locked and
+ * changes to the database will be written to disk.  If 'read_only' is true,
+ * the database will not be locked and changes to the database will persist
+ * only as long as the "struct ovsdb".
+ *
+ * If 'filep' is nonnull and 'read_only' is false, then on success sets
+ * '*filep' to an ovsdb_file that represents the open file.  This ovsdb_file
+ * persists until '*dbp' is destroyed.
+ *
+ * On success, returns NULL.  On failure, returns an ovsdb_error (which the
+ * caller must destroy) and sets '*dbp' and '*filep' to NULL. */
+struct ovsdb_error *
+ovsdb_file_open(const char *file_name, bool read_only,
+                struct ovsdb **dbp, struct ovsdb_file **filep)
+{
+    return ovsdb_file_open__(file_name, NULL, read_only, dbp, filep);
+}
+
+/* Opens database 'file_name' with an alternate schema.  The specified 'schema'
+ * is used to interpret the data in 'file_name', ignoring the schema actually
+ * stored in the file.  Data in the file for tables or columns that do not
+ * exist in 'schema' are ignored, but the ovsdb file format must otherwise be
+ * observed, including column constraints.
+ *
+ * This function can be useful for upgrading or downgrading databases to
+ * "almost-compatible" formats.
+ *
+ * The database will not be locked.  Changes to the database will persist only
+ * as long as the "struct ovsdb".
+ *
+ * On success, stores a pointer to the new database in '*dbp' and returns a
+ * null pointer.  On failure, returns an ovsdb_error (which the caller must
+ * destroy) and sets '*dbp' to NULL. */
+struct ovsdb_error *
+ovsdb_file_open_as_schema(const char *file_name,
+                          const struct ovsdb_schema *schema,
+                          struct ovsdb **dbp)
+{
+    return ovsdb_file_open__(file_name, schema, true, dbp, NULL);
+}
+
+static struct ovsdb_error *
+ovsdb_file_open__(const char *file_name,
+                  const struct ovsdb_schema *alternate_schema,
+                  bool read_only, struct ovsdb **dbp,
+                  struct ovsdb_file **filep)
+{
+    enum ovsdb_log_open_mode open_mode;
+    long long int oldest_commit;
+    unsigned int n_transactions;
+    struct ovsdb_schema *schema = NULL;
+    struct ovsdb_error *error;
+    struct ovsdb_log *log = NULL;
+    struct json *json;
+    struct ovsdb *db = NULL;
+
+    /* In read-only mode there is no ovsdb_file so 'filep' must be null. */
+    assert(!(read_only && filep));
+
+    open_mode = read_only ? OVSDB_LOG_READ_ONLY : OVSDB_LOG_READ_WRITE;
+    error = ovsdb_log_open(file_name, open_mode, -1, &log);
+    if (error) {
+        goto error;
+    }
+
+    error = ovsdb_log_read(log, &json);
+    if (error) {
+        goto error;
+    } else if (!json) {
+        error = ovsdb_io_error(EOF, "%s: database file contains no schema",
+                               file_name);
+        goto error;
+    }
+
+    if (alternate_schema) {
+        schema = ovsdb_schema_clone(alternate_schema);
+    } else {
+        error = ovsdb_schema_from_json(json, &schema);
+        if (error) {
+            json_destroy(json);
+            error = ovsdb_wrap_error(error,
+                                     "failed to parse \"%s\" as ovsdb schema",
+                                     file_name);
+            goto error;
+        }
+    }
+    json_destroy(json);
+
+    db = ovsdb_create(schema);
+    schema = NULL;
+
+    oldest_commit = LLONG_MAX;
+    n_transactions = 0;
+    while ((error = ovsdb_log_read(log, &json)) == NULL && json) {
+        struct ovsdb_txn *txn;
+        long long int date;
+
+        error = ovsdb_file_txn_from_json(db, json, alternate_schema != NULL,
+                                         &date, &txn);
+        json_destroy(json);
+        if (error) {
+            break;
+        }
+
+        n_transactions++;
+        if (date < oldest_commit) {
+            oldest_commit = date;
+        }
+
+        ovsdb_txn_commit(txn, false);
+    }
+    if (error) {
+        /* Log error but otherwise ignore it.  Probably the database just got
+         * truncated due to power failure etc. and we should use its current
+         * contents. */
+        char *msg = ovsdb_error_to_string(error);
+        VLOG_WARN("%s", msg);
+        free(msg);
+
+        ovsdb_error_destroy(error);
+    }
+
+    if (!read_only) {
+        struct ovsdb_file *file;
+
+        error = ovsdb_file_create(db, log, file_name, oldest_commit,
+                                  n_transactions, &file);
+        if (error) {
+            goto error;
+        }
+        if (filep) {
+            *filep = file;
+        }
+    } else {
+        ovsdb_log_close(log);
+    }
+
+    *dbp = db;
+    return NULL;
+
+error:
+    *dbp = NULL;
+    if (filep) {
+        *filep = NULL;
+    }
+    ovsdb_destroy(db);
+    ovsdb_schema_destroy(schema);
+    ovsdb_log_close(log);
+    return error;
+}
+
+static struct ovsdb_error *
+ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
+                                const struct json *json)
+{
+    struct ovsdb_table_schema *schema = row->table->schema;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+    }
+
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
+
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            if (converting) {
+                continue;
+            }
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
+        if (error) {
+            return error;
+        }
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                             bool converting,
+                             const struct uuid *row_uuid, struct json *json)
+{
+    const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
+    if (json->type == JSON_NULL) {
+        if (!row) {
+            return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
+                                      "row "UUID_FMT" that does not exist",
+                                      UUID_ARGS(row_uuid));
+        }
+        ovsdb_txn_row_delete(txn, row);
+        return NULL;
+    } else if (row) {
+        return ovsdb_file_update_row_from_json(ovsdb_txn_row_modify(txn, row),
+                                               converting, json);
+    } else {
+        struct ovsdb_error *error;
+        struct ovsdb_row *new;
+
+        new = ovsdb_row_create(table);
+        *ovsdb_row_get_uuid_rw(new) = *row_uuid;
+        error = ovsdb_file_update_row_from_json(new, converting, json);
+        if (error) {
+            ovsdb_row_destroy(new);
+        }
+
+        ovsdb_txn_row_insert(txn, new);
+
+        return error;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
+                               struct ovsdb_table *table,
+                               bool converting, struct json *json)
+{
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *uuid_string = node->name;
+        struct json *txn_row_json = node->data;
+        struct ovsdb_error *error;
+        struct uuid row_uuid;
+
+        if (!uuid_from_string(&row_uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+
+        error = ovsdb_file_txn_row_from_json(txn, table, converting,
+                                             &row_uuid, txn_row_json);
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+
+/* Converts 'json' to an ovsdb_txn for 'db', storing the new transaction in
+ * '*txnp'.  Returns NULL if successful, otherwise an error.
+ *
+ * If 'converting' is true, then unknown table and column names are ignored
+ * (which can ease upgrading and downgrading schemas); otherwise, they are
+ * treated as errors.
+ *
+ * If successful, the date associated with the transaction, as the number of
+ * milliseconds since the epoch, is stored in '*date'.  If the transaction does
+ * not include a date, LLONG_MAX is stored. */
+static struct ovsdb_error *
+ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
+                         bool converting, long long int *date,
+                         struct ovsdb_txn **txnp)
+{
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_txn *txn;
+
+    *txnp = NULL;
+    *date = LLONG_MAX;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    txn = ovsdb_txn_create(db);
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *table_name = node->name;
+        struct json *node_json = node->data;
+        struct ovsdb_table *table;
+
+        table = shash_find_data(&db->tables, table_name);
+        if (!table) {
+            if (!strcmp(table_name, "_date")
+                && node_json->type == JSON_INTEGER) {
+                if (date) {
+                    *date = json_integer(node_json);
+                }
+                continue;
+            } else if (!strcmp(table_name, "_comment") || converting) {
+                continue;
+            }
+
+            error = ovsdb_syntax_error(json, "unknown table",
+                                       "No table named %s.", table_name);
+            goto error;
+        }
+
+        error = ovsdb_file_txn_table_from_json(txn, table, converting,
+                                               node_json);
+        if (error) {
+            goto error;
+        }
+    }
+    *txnp = txn;
+    return NULL;
+
+error:
+    ovsdb_txn_abort(txn);
+    return error;
+}
+
+static struct ovsdb_error *
+ovsdb_file_save_copy__(const char *file_name, int locking,
+                       const char *comment, const struct ovsdb *db,
+                       struct ovsdb_log **logp)
+{
+    const struct shash_node *node;
+    struct ovsdb_file_txn ftxn;
+    struct ovsdb_error *error;
+    struct ovsdb_log *log;
+    struct json *json;
+
+    error = ovsdb_log_open(file_name, OVSDB_LOG_CREATE, locking, &log);
+    if (error) {
+        return error;
+    }
+
+    /* Write schema. */
+    json = ovsdb_schema_to_json(db->schema);
+    error = ovsdb_log_write(log, json);
+    json_destroy(json);
+    if (error) {
+        goto exit;
+    }
+
+    /* Write data. */
+    ovsdb_file_txn_init(&ftxn);
+    SHASH_FOR_EACH (node, &db->tables) {
+        const struct ovsdb_table *table = node->data;
+        const struct ovsdb_row *row;
+
+        HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
+            ovsdb_file_txn_add_row(&ftxn, NULL, row, NULL);
+        }
+    }
+    error = ovsdb_file_txn_commit(ftxn.json, comment, true, log);
+
+exit:
+    if (logp) {
+        if (!error) {
+            *logp = log;
+            log = NULL;
+        } else {
+            *logp = NULL;
+        }
+    }
+    ovsdb_log_close(log);
+    if (error) {
+        remove(file_name);
+    }
+    return error;
+}
+
+/* Saves a snapshot of 'db''s current contents as 'file_name'.  If 'comment' is
+ * nonnull, then it is added along with the data contents and can be viewed
+ * with "ovsdb-tool show-log".
+ *
+ * 'locking' is passed along to ovsdb_log_open() untouched. */
+struct ovsdb_error *
+ovsdb_file_save_copy(const char *file_name, int locking,
+                     const char *comment, const struct ovsdb *db)
+{
+    return ovsdb_file_save_copy__(file_name, locking, comment, db, NULL);
+}
+\f
+/* Replica implementation. */
+
+struct ovsdb_file {
+    struct ovsdb_replica replica;
+    struct ovsdb *db;
+    struct ovsdb_log *log;
+    char *file_name;
+    long long int oldest_commit;
+    long long int next_compact;
+    unsigned int n_transactions;
+};
+
+static const struct ovsdb_replica_class ovsdb_file_class;
+
+static struct ovsdb_error *
+ovsdb_file_create(struct ovsdb *db, struct ovsdb_log *log,
+                  const char *file_name,
+                  long long int oldest_commit,
+                  unsigned int n_transactions,
+                  struct ovsdb_file **filep)
+{
+    long long int now = time_msec();
+    struct ovsdb_file *file;
+    char *abs_name;
+
+    /* Use the absolute name of the file because ovsdb-server opens its
+     * database before daemonize() chdirs to "/". */
+    abs_name = abs_file_name(NULL, file_name);
+    if (!abs_name) {
+        *filep = NULL;
+        return ovsdb_io_error(0, "could not determine current "
+                              "working directory");
+    }
+
+    file = xmalloc(sizeof *file);
+    ovsdb_replica_init(&file->replica, &ovsdb_file_class);
+    file->db = db;
+    file->log = log;
+    file->file_name = abs_name;
+    file->oldest_commit = MIN(oldest_commit, now);
+    file->next_compact = file->oldest_commit + COMPACT_MIN_MSEC;
+    file->n_transactions = n_transactions;
+    ovsdb_add_replica(db, &file->replica);
+
+    *filep = file;
+    return NULL;
+}
+
+static struct ovsdb_file *
+ovsdb_file_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_file_class);
+    return CONTAINER_OF(replica, struct ovsdb_file, replica);
+}
+
+static bool
+ovsdb_file_change_cb(const struct ovsdb_row *old,
+                     const struct ovsdb_row *new,
+                     const unsigned long int *changed,
+                     void *ftxn_)
+{
+    struct ovsdb_file_txn *ftxn = ftxn_;
+    ovsdb_file_txn_add_row(ftxn, old, new, changed);
+    return true;
+}
+
+static struct ovsdb_error *
+ovsdb_file_commit(struct ovsdb_replica *replica,
+                  const struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_file *file = ovsdb_file_cast(replica);
+    struct ovsdb_file_txn ftxn;
+    struct ovsdb_error *error;
+
+    ovsdb_file_txn_init(&ftxn);
+    ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);
+    if (!ftxn.json) {
+        /* Nothing to commit. */
+        return NULL;
+    }
+
+    error = ovsdb_file_txn_commit(ftxn.json, ovsdb_txn_get_comment(txn),
+                                  durable, file->log);
+    if (error) {
+        return error;
+    }
+    file->n_transactions++;
+
+    /* If it has been at least COMPACT_MIN_MSEC millseconds since the last time
+     * we compacted (or at least COMPACT_RETRY_MSEC since the last time we
+     * tried), and if there are at least 100 transactions in the database, and
+     * if the database is at least 1 MB, then compact the database. */
+    if (time_msec() >= file->next_compact
+        && file->n_transactions >= 100
+        && ovsdb_log_get_offset(file->log) >= 10 * 1024 * 1024)
+    {
+        error = ovsdb_file_compact(file);
+        if (error) {
+            char *s = ovsdb_error_to_string(error);
+            ovsdb_error_destroy(error);
+            VLOG_WARN("%s: compacting database failed (%s), retrying in "
+                      "60 seconds", file->file_name, s);
+            free(s);
+
+            file->next_compact = time_msec() + COMPACT_RETRY_MSEC;
+        }
+    }
+
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_file_compact(struct ovsdb_file *file)
+{
+    struct ovsdb_log *new_log = NULL;
+    struct lockfile *tmp_lock = NULL;
+    struct ovsdb_error *error;
+    char *tmp_name = NULL;
+    char *comment = NULL;
+    int retval;
+
+    comment = xasprintf("compacting database online "
+                        "(%.3f seconds old, %u transactions, %llu bytes)",
+                        (time_msec() - file->oldest_commit) / 1000.0,
+                        file->n_transactions,
+                        (unsigned long long) ovsdb_log_get_offset(file->log));
+    VLOG_INFO("%s: %s", file->file_name, comment);
+
+    /* Commit the old version, so that we can be assured that we'll eventually
+     * have either the old or the new version. */
+    error = ovsdb_log_commit(file->log);
+    if (error) {
+        goto exit;
+    }
+
+    /* Lock temporary file. */
+    tmp_name = xasprintf("%s.tmp", file->file_name);
+    retval = lockfile_lock(tmp_name, 0, &tmp_lock);
+    if (retval) {
+        error = ovsdb_io_error(retval, "could not get lock on %s", tmp_name);
+        goto exit;
+    }
+
+    /* Remove temporary file.  (It might not exist.) */
+    if (unlink(tmp_name) < 0 && errno != ENOENT) {
+        error = ovsdb_io_error(errno, "failed to remove %s", tmp_name);
+        goto exit;
+    }
+
+    /* Save a copy. */
+    error = ovsdb_file_save_copy__(tmp_name, false, comment, file->db,
+                                   &new_log);
+    if (error) {
+        goto exit;
+    }
+
+    /* Replace original by temporary. */
+    if (rename(tmp_name, file->file_name)) {
+        error = ovsdb_io_error(errno, "failed to rename \"%s\" to \"%s\"",
+                               tmp_name, file->file_name);
+        goto exit;
+    }
+    fsync_parent_dir(file->file_name);
+
+exit:
+    if (!error) {
+        ovsdb_log_close(file->log);
+        file->log = new_log;
+        file->oldest_commit = time_msec();
+        file->next_compact = file->oldest_commit + COMPACT_MIN_MSEC;
+        file->n_transactions = 1;
+    } else {
+        ovsdb_log_close(new_log);
+        if (tmp_lock) {
+            unlink(tmp_name);
+        }
+    }
+
+    lockfile_unlock(tmp_lock);
+    free(tmp_name);
+    free(comment);
+
+    return error;
+}
+
+static void
+ovsdb_file_destroy(struct ovsdb_replica *replica)
+{
+    struct ovsdb_file *file = ovsdb_file_cast(replica);
+
+    ovsdb_log_close(file->log);
+    free(file->file_name);
+    free(file);
+}
+
+static const struct ovsdb_replica_class ovsdb_file_class = {
+    ovsdb_file_commit,
+    ovsdb_file_destroy
+};
+\f
+static void
+ovsdb_file_txn_init(struct ovsdb_file_txn *ftxn)
+{
+    ftxn->json = NULL;
+    ftxn->table_json = NULL;
+    ftxn->table = NULL;
+}
+
+static void
+ovsdb_file_txn_add_row(struct ovsdb_file_txn *ftxn,
+                       const struct ovsdb_row *old,
+                       const struct ovsdb_row *new,
+                       const unsigned long int *changed)
+{
+    struct json *row;
+
+    if (!new) {
+        row = json_null_create();
+    } else {
+        struct shash_node *node;
+
+        row = old ? NULL : json_object_create();
+        SHASH_FOR_EACH (node, &new->table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_type *type = &column->type;
+            unsigned int idx = column->index;
+
+            if (idx != OVSDB_COL_UUID && column->persistent
+                && (old
+                    ? bitmap_is_set(changed, idx)
+                    : !ovsdb_datum_is_default(&new->fields[idx], type)))
+            {
+                if (!row) {
+                    row = json_object_create();
+                }
+                json_object_put(row, column->name,
+                                ovsdb_datum_to_json(&new->fields[idx], type));
+            }
+        }
+    }
+
+    if (row) {
+        struct ovsdb_table *table = new ? new->table : old->table;
+        char uuid[UUID_LEN + 1];
+
+        if (table != ftxn->table) {
+            /* Create JSON object for transaction overall. */
+            if (!ftxn->json) {
+                ftxn->json = json_object_create();
+            }
+
+            /* Create JSON object for transaction on this table. */
+            ftxn->table_json = json_object_create();
+            ftxn->table = table;
+            json_object_put(ftxn->json, table->schema->name, ftxn->table_json);
+        }
+
+        /* Add row to transaction for this table. */
+        snprintf(uuid, sizeof uuid,
+                 UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+        json_object_put(ftxn->table_json, uuid, row);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_commit(struct json *json, const char *comment,
+                      bool durable, struct ovsdb_log *log)
+{
+    struct ovsdb_error *error;
+
+    if (!json) {
+        json = json_object_create();
+    }
+    if (comment) {
+        json_object_put_string(json, "_comment", comment);
+    }
+    json_object_put(json, "_date", json_integer_create(time_now()));
+
+    error = ovsdb_log_write(log, json);
+    json_destroy(json);
+    if (error) {
+        return ovsdb_wrap_error(error, "writing transaction failed");
+    }
+
+    if (durable) {
+        error = ovsdb_log_commit(log);
+        if (error) {
+            return ovsdb_wrap_error(error, "committing transaction failed");
+        }
+    }
+
+    return NULL;
+}
diff --git a/ovsdb/file.h b/ovsdb/file.h
new file mode 100644 (file)
index 0000000..b28c318
--- /dev/null
@@ -0,0 +1,43 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_FILE_H
+#define OVSDB_FILE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "log.h"
+
+struct ovsdb;
+struct ovsdb_file;
+struct ovsdb_schema;
+
+struct ovsdb_error *ovsdb_file_open(const char *file_name, bool read_only,
+                                    struct ovsdb **, struct ovsdb_file **)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_file_open_as_schema(const char *file_name,
+                                              const struct ovsdb_schema *,
+                                              struct ovsdb **)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_file_save_copy(const char *file_name, int locking,
+                                         const char *comment,
+                                         const struct ovsdb *)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_file_compact(struct ovsdb_file *);
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644 (file)
index 0000000..73c3c1c
--- /dev/null
@@ -0,0 +1,982 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc-server.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include "bitmap.h"
+#include "column.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "reconnect.h"
+#include "row.h"
+#include "stream.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_remote;
+struct ovsdb_jsonrpc_session;
+
+/* Message rate-limiting. */
+struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+/* Sessions. */
+static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create(
+    struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *);
+static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *);
+
+/* Triggers. */
+static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
+                                         struct json *id, struct json *params);
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+    struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_trigger_complete_done(
+    struct ovsdb_jsonrpc_session *);
+
+/* Monitors. */
+static struct json *ovsdb_jsonrpc_monitor_create(
+    struct ovsdb_jsonrpc_session *, struct json *params);
+static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel(
+    struct ovsdb_jsonrpc_session *,
+    struct json_array *params,
+    const struct json *request_id);
+static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *);
+\f
+/* JSON-RPC database server. */
+
+struct ovsdb_jsonrpc_server {
+    struct ovsdb *db;
+    unsigned int n_sessions, max_sessions;
+    struct shash remotes;      /* Contains "struct ovsdb_jsonrpc_remote *"s. */
+};
+
+/* A configured remote.  This is either a passive stream listener plus a list
+ * of the currently connected sessions, or a list of exactly one active
+ * session. */
+struct ovsdb_jsonrpc_remote {
+    struct ovsdb_jsonrpc_server *server;
+    struct pstream *listener;   /* Listener, if passive. */
+    struct list sessions;       /* List of "struct ovsdb_jsonrpc_session"s. */
+};
+
+static void ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *,
+                                            const char *name);
+static void ovsdb_jsonrpc_server_del_remote(struct shash_node *);
+
+struct ovsdb_jsonrpc_server *
+ovsdb_jsonrpc_server_create(struct ovsdb *db)
+{
+    struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server);
+    server->db = db;
+    server->max_sessions = 64;
+    shash_init(&server->remotes);
+    return server;
+}
+
+void
+ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+        ovsdb_jsonrpc_server_del_remote(node);
+    }
+    shash_destroy(&svr->remotes);
+    free(svr);
+}
+
+/* Sets 'svr''s current set of remotes to the names in 'new_remotes'.  The data
+ * values in 'new_remotes' are ignored.
+ *
+ * A remote is an active or passive stream connection method, e.g. "pssl:" or
+ * "tcp:1.2.3.4". */
+void
+ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *svr,
+                                 const struct shash *new_remotes)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+        if (!shash_find(new_remotes, node->name)) {
+            ovsdb_jsonrpc_server_del_remote(node);
+        }
+    }
+    SHASH_FOR_EACH (node, new_remotes) {
+        if (!shash_find(&svr->remotes, node->name)) {
+            ovsdb_jsonrpc_server_add_remote(svr, node->name);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr,
+                                const char *name)
+{
+    struct ovsdb_jsonrpc_remote *remote;
+    struct pstream *listener;
+    int error;
+
+    error = pstream_open(name, &listener);
+    if (error && error != EAFNOSUPPORT) {
+        VLOG_ERR_RL(&rl, "%s: listen failed: %s", name, strerror(error));
+        return;
+    }
+
+    remote = xmalloc(sizeof *remote);
+    remote->server = svr;
+    remote->listener = listener;
+    list_init(&remote->sessions);
+    shash_add(&svr->remotes, name, remote);
+
+    if (!listener) {
+        ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name));
+    }
+}
+
+static void
+ovsdb_jsonrpc_server_del_remote(struct shash_node *node)
+{
+    struct ovsdb_jsonrpc_remote *remote = node->data;
+
+    ovsdb_jsonrpc_session_close_all(remote);
+    pstream_close(remote->listener);
+    shash_delete(&remote->server->remotes, node);
+    free(remote);
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &svr->remotes) {
+        struct ovsdb_jsonrpc_remote *remote = node->data;
+
+        if (remote->listener && svr->n_sessions < svr->max_sessions) {
+            struct stream *stream;
+            int error;
+
+            error = pstream_accept(remote->listener, &stream);
+            if (!error) {
+                struct jsonrpc_session *js;
+                js = jsonrpc_session_open_unreliably(jsonrpc_open(stream));
+                ovsdb_jsonrpc_session_create(remote, js);
+            } else if (error != EAGAIN) {
+                VLOG_WARN_RL(&rl, "%s: accept failed: %s",
+                             pstream_get_name(remote->listener),
+                             strerror(error));
+            }
+        }
+
+        ovsdb_jsonrpc_session_run_all(remote);
+    }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &svr->remotes) {
+        struct ovsdb_jsonrpc_remote *remote = node->data;
+
+        if (remote->listener && svr->n_sessions < svr->max_sessions) {
+            pstream_wait(remote->listener);
+        }
+
+        ovsdb_jsonrpc_session_wait_all(remote);
+    }
+}
+\f
+/* JSON-RPC database server session. */
+
+struct ovsdb_jsonrpc_session {
+    struct ovsdb_jsonrpc_remote *remote;
+    struct list node;           /* Element in remote's sessions list. */
+
+    /* Triggers. */
+    struct hmap triggers;       /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */
+    struct list completions;    /* Completed triggers. */
+
+    /* Monitors. */
+    struct hmap monitors;       /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */
+
+    /* Network connectivity. */
+    struct jsonrpc_session *js;  /* JSON-RPC session. */
+    unsigned int js_seqno;       /* Last jsonrpc_session_get_seqno() value. */
+};
+
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+
+static struct ovsdb_jsonrpc_session *
+ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote,
+                             struct jsonrpc_session *js)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    s = xzalloc(sizeof *s);
+    s->remote = remote;
+    list_push_back(&remote->sessions, &s->node);
+    hmap_init(&s->triggers);
+    hmap_init(&s->monitors);
+    list_init(&s->completions);
+    s->js = js;
+    s->js_seqno = jsonrpc_session_get_seqno(js);
+
+    remote->server->n_sessions++;
+
+    return s;
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_monitor_remove_all(s);
+    jsonrpc_session_close(s->js);
+    list_remove(&s->node);
+    s->remote->server->n_sessions--;
+    free(s);
+}
+
+static int
+ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s)
+{
+    jsonrpc_session_run(s->js);
+    if (s->js_seqno != jsonrpc_session_get_seqno(s->js)) {
+        s->js_seqno = jsonrpc_session_get_seqno(s->js);
+        ovsdb_jsonrpc_trigger_complete_all(s);
+        ovsdb_jsonrpc_monitor_remove_all(s);
+    }
+
+    ovsdb_jsonrpc_trigger_complete_done(s);
+
+    if (!jsonrpc_session_get_backlog(s->js)) {
+        struct jsonrpc_msg *msg = jsonrpc_session_recv(s->js);
+        if (msg) {
+            if (msg->type == JSONRPC_REQUEST) {
+                ovsdb_jsonrpc_session_got_request(s, msg);
+            } else if (msg->type == JSONRPC_NOTIFY) {
+                ovsdb_jsonrpc_session_got_notify(s, msg);
+            } else {
+                VLOG_WARN("%s: received unexpected %s message",
+                          jsonrpc_session_get_name(s->js),
+                          jsonrpc_msg_type_to_string(msg->type));
+                jsonrpc_session_force_reconnect(s->js);
+                jsonrpc_msg_destroy(msg);
+            }
+        }
+    }
+    return jsonrpc_session_is_alive(s->js) ? 0 : ETIMEDOUT;
+}
+
+static void
+ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &remote->sessions) {
+        int error = ovsdb_jsonrpc_session_run(s);
+        if (error) {
+            ovsdb_jsonrpc_session_close(s);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s)
+{
+    jsonrpc_session_wait(s->js);
+    if (!jsonrpc_session_get_backlog(s->js)) {
+        jsonrpc_session_recv_wait(s->js);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &remote->sessions) {
+        ovsdb_jsonrpc_session_wait(s);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &remote->sessions) {
+        ovsdb_jsonrpc_session_close(s);
+    }
+}
+
+static const char *
+get_db_name(const struct ovsdb_jsonrpc_session *s)
+{
+    return s->remote->server->db->schema->name;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_check_db_name(const struct ovsdb_jsonrpc_session *s,
+                            const struct jsonrpc_msg *request)
+{
+    struct json_array *params;
+    const char *want_db_name;
+    const char *have_db_name;
+    struct ovsdb_error *error;
+    struct jsonrpc_msg *reply;
+
+    params = json_array(request->params);
+    if (!params->n || params->elems[0]->type != JSON_STRING) {
+        error = ovsdb_syntax_error(
+            request->params, NULL,
+            "%s request params must begin with <db-name>", request->method);
+        goto error;
+    }
+
+    want_db_name = params->elems[0]->u.string;
+    have_db_name = get_db_name(s);
+    if (strcmp(want_db_name, have_db_name)) {
+        error = ovsdb_syntax_error(
+            request->params, "unknown database",
+            "%s request specifies unknown database %s",
+            request->method, want_db_name);
+        goto error;
+    }
+
+    return NULL;
+
+error:
+    reply = jsonrpc_create_reply(ovsdb_error_to_json(error), request->id);
+    ovsdb_error_destroy(error);
+    return reply;
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+                    struct jsonrpc_msg *request)
+{
+    ovsdb_jsonrpc_trigger_create(s, request->id, request->params);
+    request->id = NULL;
+    request->params = NULL;
+    jsonrpc_msg_destroy(request);
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+                                  struct jsonrpc_msg *request)
+{
+    struct jsonrpc_msg *reply;
+
+    if (!strcmp(request->method, "transact")) {
+        reply = ovsdb_jsonrpc_check_db_name(s, request);
+        if (!reply) {
+            reply = execute_transaction(s, request);
+        }
+    } else if (!strcmp(request->method, "monitor")) {
+        reply = ovsdb_jsonrpc_check_db_name(s, request);
+        if (!reply) {
+            reply = jsonrpc_create_reply(
+                ovsdb_jsonrpc_monitor_create(s, request->params), request->id);
+        }
+    } else if (!strcmp(request->method, "monitor_cancel")) {
+        reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params),
+                                             request->id);
+    } else if (!strcmp(request->method, "get_schema")) {
+        reply = ovsdb_jsonrpc_check_db_name(s, request);
+        if (!reply) {
+            reply = jsonrpc_create_reply(
+                ovsdb_schema_to_json(s->remote->server->db->schema),
+                request->id);
+        }
+    } else if (!strcmp(request->method, "list_dbs")) {
+        reply = jsonrpc_create_reply(
+            json_array_create_1(json_string_create(get_db_name(s))),
+            request->id);
+    } else if (!strcmp(request->method, "echo")) {
+        reply = jsonrpc_create_reply(json_clone(request->params), request->id);
+    } else {
+        reply = jsonrpc_create_error(json_string_create("unknown method"),
+                                     request->id);
+    }
+
+    if (reply) {
+        jsonrpc_msg_destroy(request);
+        jsonrpc_session_send(s->js, reply);
+    }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+    if (json_array(request->params)->n == 1) {
+        struct ovsdb_jsonrpc_trigger *t;
+        struct json *id;
+
+        id = request->params->u.array.elems[0];
+        t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0));
+        if (t) {
+            ovsdb_jsonrpc_trigger_complete(t);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+                                 struct jsonrpc_msg *request)
+{
+    if (!strcmp(request->method, "cancel")) {
+        execute_cancel(s, request);
+    }
+    jsonrpc_msg_destroy(request);
+}
+\f
+/* JSON-RPC database server triggers.
+ *
+ * (Every transaction is treated as a trigger even if it doesn't actually have
+ * any "wait" operations.) */
+
+struct ovsdb_jsonrpc_trigger {
+    struct ovsdb_trigger trigger;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node hmap_node; /* In session's "triggers" hmap. */
+    struct json *id;
+};
+
+static void
+ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *id, struct json *params)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+    size_t hash;
+
+    /* Check for duplicate ID. */
+    hash = json_hash(id, 0);
+    t = ovsdb_jsonrpc_trigger_find(s, id, hash);
+    if (t) {
+        struct jsonrpc_msg *msg;
+
+        msg = jsonrpc_create_error(json_string_create("duplicate request ID"),
+                                   id);
+        jsonrpc_session_send(s->js, msg);
+        json_destroy(id);
+        json_destroy(params);
+        return;
+    }
+
+    /* Insert into trigger table. */
+    t = xmalloc(sizeof *t);
+    ovsdb_trigger_init(s->remote->server->db,
+                       &t->trigger, params, &s->completions,
+                       time_msec());
+    t->session = s;
+    t->id = id;
+    hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+    /* Complete early if possible. */
+    if (ovsdb_trigger_is_complete(&t->trigger)) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *id, size_t hash)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+
+    HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+                             &s->triggers) {
+        if (json_equal(t->id, id)) {
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+    struct ovsdb_jsonrpc_session *s = t->session;
+
+    if (jsonrpc_session_is_connected(s->js)) {
+        struct jsonrpc_msg *reply;
+        struct json *result;
+
+        result = ovsdb_trigger_steal_result(&t->trigger);
+        if (result) {
+            reply = jsonrpc_create_reply(result, t->id);
+        } else {
+            reply = jsonrpc_create_error(json_string_create("canceled"),
+                                         t->id);
+        }
+        jsonrpc_session_send(s->js, reply);
+    }
+
+    json_destroy(t->id);
+    ovsdb_trigger_destroy(&t->trigger);
+    hmap_remove(&s->triggers, &t->hmap_node);
+    free(t);
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_trigger *t, *next;
+    HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+                        &s->triggers) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)
+{
+    while (!list_is_empty(&s->completions)) {
+        struct ovsdb_jsonrpc_trigger *t
+            = CONTAINER_OF(s->completions.next,
+                           struct ovsdb_jsonrpc_trigger, trigger.node);
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+\f
+/* JSON-RPC database table monitors. */
+
+enum ovsdb_jsonrpc_monitor_selection {
+    OJMS_INITIAL = 1 << 0,      /* All rows when monitor is created. */
+    OJMS_INSERT = 1 << 1,       /* New rows. */
+    OJMS_DELETE = 1 << 2,       /* Deleted rows. */
+    OJMS_MODIFY = 1 << 3        /* Modified rows. */
+};
+
+struct ovsdb_jsonrpc_monitor_table {
+    const struct ovsdb_table *table;
+    enum ovsdb_jsonrpc_monitor_selection select;
+    struct ovsdb_column_set columns;
+};
+
+struct ovsdb_jsonrpc_monitor {
+    struct ovsdb_replica replica;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node node;      /* In ovsdb_jsonrpc_session's "monitors". */
+
+    struct json *monitor_id;
+    struct shash tables;     /* Holds "struct ovsdb_jsonrpc_monitor_table"s. */
+};
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class;
+
+struct ovsdb_jsonrpc_monitor *ovsdb_jsonrpc_monitor_find(
+    struct ovsdb_jsonrpc_session *, const struct json *monitor_id);
+static void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *);
+static struct json *ovsdb_jsonrpc_monitor_get_initial(
+    const struct ovsdb_jsonrpc_monitor *);
+
+static bool
+parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value)
+{
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL);
+    return json ? json_boolean(json) : default_value;
+}
+
+struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *monitor_id)
+{
+    struct ovsdb_jsonrpc_monitor *m;
+
+    HMAP_FOR_EACH_WITH_HASH (m, struct ovsdb_jsonrpc_monitor, node,
+                             json_hash(monitor_id, 0), &s->monitors) {
+        if (json_equal(m->monitor_id, monitor_id)) {
+            return m;
+        }
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *params)
+{
+    struct ovsdb_jsonrpc_monitor *m = NULL;
+    struct json *monitor_id, *monitor_requests;
+    struct ovsdb_error *error = NULL;
+    struct shash_node *node;
+    struct json *json;
+
+    if (json_array(params)->n != 3) {
+        error = ovsdb_syntax_error(params, NULL, "invalid parameters");
+        goto error;
+    }
+    monitor_id = params->u.array.elems[1];
+    monitor_requests = params->u.array.elems[2];
+    if (monitor_requests->type != JSON_OBJECT) {
+        error = ovsdb_syntax_error(monitor_requests, NULL,
+                                   "monitor-requests must be object");
+        goto error;
+    }
+
+    if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) {
+        error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID");
+        goto error;
+    }
+
+    m = xzalloc(sizeof *m);
+    ovsdb_replica_init(&m->replica, &ovsdb_jsonrpc_replica_class);
+    ovsdb_add_replica(s->remote->server->db, &m->replica);
+    m->session = s;
+    hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0));
+    m->monitor_id = json_clone(monitor_id);
+    shash_init(&m->tables);
+
+    SHASH_FOR_EACH (node, json_object(monitor_requests)) {
+        const struct ovsdb_table *table;
+        struct ovsdb_jsonrpc_monitor_table *mt;
+        const struct json *columns_json, *select_json;
+        struct ovsdb_parser parser;
+
+        table = ovsdb_get_table(s->remote->server->db, node->name);
+        if (!table) {
+            error = ovsdb_syntax_error(NULL, NULL,
+                                       "no table named %s", node->name);
+            goto error;
+        }
+
+        mt = xzalloc(sizeof *mt);
+        mt->table = table;
+        mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
+        ovsdb_column_set_init(&mt->columns);
+        shash_add(&m->tables, table->schema->name, mt);
+
+        ovsdb_parser_init(&parser, node->data, "table %s", node->name);
+        columns_json = ovsdb_parser_member(&parser, "columns",
+                                           OP_ARRAY | OP_OPTIONAL);
+        select_json = ovsdb_parser_member(&parser, "select",
+                                          OP_OBJECT | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            goto error;
+        }
+
+        if (columns_json) {
+            error = ovsdb_column_set_from_json(columns_json, table,
+                                               &mt->columns);
+            if (error) {
+                goto error;
+            }
+        } else {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, &table->schema->columns) {
+                const struct ovsdb_column *column = node->data;
+                if (column->index != OVSDB_COL_UUID) {
+                    ovsdb_column_set_add(&mt->columns, column);
+                }
+            }
+        }
+
+        if (select_json) {
+            mt->select = 0;
+            ovsdb_parser_init(&parser, select_json, "table %s select",
+                              table->schema->name);
+            if (parse_bool(&parser, "initial", true)) {
+                mt->select |= OJMS_INITIAL;
+            }
+            if (parse_bool(&parser, "insert", true)) {
+                mt->select |= OJMS_INSERT;
+            }
+            if (parse_bool(&parser, "delete", true)) {
+                mt->select |= OJMS_DELETE;
+            }
+            if (parse_bool(&parser, "modify", true)) {
+                mt->select |= OJMS_MODIFY;
+            }
+            error = ovsdb_parser_finish(&parser);
+            if (error) {
+                goto error;
+            }
+        }
+    }
+
+    return ovsdb_jsonrpc_monitor_get_initial(m);
+
+error:
+    if (m) {
+        ovsdb_remove_replica(s->remote->server->db, &m->replica);
+    }
+
+    json = ovsdb_error_to_json(error);
+    ovsdb_error_destroy(error);
+    return json;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,
+                             struct json_array *params,
+                             const struct json *request_id)
+{
+    if (params->n != 1) {
+        return jsonrpc_create_error(json_string_create("invalid parameters"),
+                                    request_id);
+    } else {
+        struct ovsdb_jsonrpc_monitor *m;
+
+        m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]);
+        if (!m) {
+            return jsonrpc_create_error(json_string_create("unknown monitor"),
+                                        request_id);
+        } else {
+            ovsdb_remove_replica(s->remote->server->db, &m->replica);
+            return jsonrpc_create_reply(json_object_create(), request_id);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_monitor *m, *next;
+
+    HMAP_FOR_EACH_SAFE (m, next,
+                        struct ovsdb_jsonrpc_monitor, node, &s->monitors) {
+        ovsdb_remove_replica(s->remote->server->db, &m->replica);
+    }
+}
+
+static struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_jsonrpc_replica_class);
+    return CONTAINER_OF(replica, struct ovsdb_jsonrpc_monitor, replica);
+}
+
+struct ovsdb_jsonrpc_monitor_aux {
+    bool initial;               /* Sending initial contents of table? */
+    const struct ovsdb_jsonrpc_monitor *monitor;
+    struct json *json;          /* JSON for the whole transaction. */
+
+    /* Current table.  */
+    struct ovsdb_jsonrpc_monitor_table *mt;
+    struct json *table_json;    /* JSON for table's transaction. */
+};
+
+static bool
+ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old,
+                                const struct ovsdb_row *new,
+                                const unsigned long int *changed,
+                                void *aux_)
+{
+    struct ovsdb_jsonrpc_monitor_aux *aux = aux_;
+    const struct ovsdb_jsonrpc_monitor *m = aux->monitor;
+    struct ovsdb_table *table = new ? new->table : old->table;
+    enum ovsdb_jsonrpc_monitor_selection type;
+    struct json *old_json, *new_json;
+    struct json *row_json;
+    char uuid[UUID_LEN + 1];
+    int n_changed;
+    size_t i;
+
+    if (!aux->mt || table != aux->mt->table) {
+        aux->mt = shash_find_data(&m->tables, table->schema->name);
+        aux->table_json = NULL;
+        if (!aux->mt) {
+            /* We don't care about rows in this table at all.  Tell the caller
+             * to skip it.  */
+            return false;
+        }
+    }
+
+    type = (aux->initial ? OJMS_INITIAL
+            : !old ? OJMS_INSERT
+            : !new ? OJMS_DELETE
+            : OJMS_MODIFY);
+    if (!(aux->mt->select & type)) {
+        /* We don't care about this type of change (but do want to be called
+         * back for changes to other rows in the same table). */
+        return true;
+    }
+
+    old_json = new_json = NULL;
+    n_changed = 0;
+    for (i = 0; i < aux->mt->columns.n_columns; i++) {
+        const struct ovsdb_column *column = aux->mt->columns.columns[i];
+        unsigned int idx = column->index;
+        bool column_changed = false;
+
+        if (type == OJMS_MODIFY) {
+            column_changed = bitmap_is_set(changed, idx);
+            n_changed += column_changed;
+        }
+        if (column_changed || type == OJMS_DELETE) {
+            if (!old_json) {
+                old_json = json_object_create();
+            }
+            json_object_put(old_json, column->name,
+                            ovsdb_datum_to_json(&old->fields[idx],
+                                                &column->type));
+        }
+        if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
+            if (!new_json) {
+                new_json = json_object_create();
+            }
+            json_object_put(new_json, column->name,
+                            ovsdb_datum_to_json(&new->fields[idx],
+                                                &column->type));
+        }
+    }
+    if ((type == OJMS_MODIFY && !n_changed) || (!old_json && !new_json)) {
+        /* No reportable changes. */
+        json_destroy(old_json);
+        json_destroy(new_json);
+        return true;
+    }
+
+    /* Create JSON object for transaction overall. */
+    if (!aux->json) {
+        aux->json = json_object_create();
+    }
+
+    /* Create JSON object for transaction on this table. */
+    if (!aux->table_json) {
+        aux->table_json = json_object_create();
+        json_object_put(aux->json, aux->mt->table->schema->name,
+                        aux->table_json);
+    }
+
+    /* Create JSON object for transaction on this row. */
+    row_json = json_object_create();
+    if (old_json) {
+        json_object_put(row_json, "old", old_json);
+    }
+    if (new_json) {
+        json_object_put(row_json, "new", new_json);
+    }
+
+    /* Add JSON row to JSON table. */
+    snprintf(uuid, sizeof uuid,
+             UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+    json_object_put(aux->table_json, uuid, row_json);
+
+    return true;
+}
+
+static void
+ovsdb_jsonrpc_monitor_init_aux(struct ovsdb_jsonrpc_monitor_aux *aux,
+                               const struct ovsdb_jsonrpc_monitor *m,
+                               bool initial)
+{
+    aux->initial = initial;
+    aux->monitor = m;
+    aux->json = NULL;
+    aux->mt = NULL;
+    aux->table_json = NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_jsonrpc_monitor_commit(struct ovsdb_replica *replica,
+                             const struct ovsdb_txn *txn,
+                             bool durable OVS_UNUSED)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct ovsdb_jsonrpc_monitor_aux aux;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, false);
+    ovsdb_txn_for_each_change(txn, ovsdb_jsonrpc_monitor_change_cb, &aux);
+    if (aux.json) {
+        struct jsonrpc_msg *msg;
+        struct json *params;
+
+        params = json_array_create_2(json_clone(aux.monitor->monitor_id),
+                                     aux.json);
+        msg = jsonrpc_create_notify("update", params);
+        jsonrpc_session_send(aux.monitor->session->js, msg);
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_get_initial(const struct ovsdb_jsonrpc_monitor *m)
+{
+    struct ovsdb_jsonrpc_monitor_aux aux;
+    struct shash_node *node;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, true);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+
+        if (mt->select & OJMS_INITIAL) {
+            struct ovsdb_row *row;
+
+            HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                           &mt->table->rows) {
+                ovsdb_jsonrpc_monitor_change_cb(NULL, row, NULL, &aux);
+            }
+        }
+    }
+    return aux.json ? aux.json : json_object_create();
+}
+
+static void
+ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct shash_node *node;
+
+    json_destroy(m->monitor_id);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+        ovsdb_column_set_destroy(&mt->columns);
+        free(mt);
+    }
+    shash_destroy(&m->tables);
+    hmap_remove(&m->session->monitors, &m->node);
+    free(m);
+}
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class = {
+    ovsdb_jsonrpc_monitor_commit,
+    ovsdb_jsonrpc_monitor_destroy
+};
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644 (file)
index 0000000..6c4acd7
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+struct shash;
+
+struct ovsdb_jsonrpc_server *ovsdb_jsonrpc_server_create(struct ovsdb *);
+void ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *);
+
+void ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *,
+                                      const struct shash *);
+
+void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
+
+#endif /* ovsdb/jsonrpc-server.h */
diff --git a/ovsdb/log.c b/ovsdb/log.c
new file mode 100644 (file)
index 0000000..ccb844f
--- /dev/null
@@ -0,0 +1,379 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "log.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "sha1.h"
+#include "socket-util.h"
+#include "transaction.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_log
+#include "vlog.h"
+
+enum ovsdb_log_mode {
+    OVSDB_LOG_READ,
+    OVSDB_LOG_WRITE
+};
+
+struct ovsdb_log {
+    off_t offset;
+    char *name;
+    struct lockfile *lockfile;
+    FILE *stream;
+    struct ovsdb_error *read_error;
+    struct ovsdb_error *write_error;
+    enum ovsdb_log_mode mode;
+};
+
+/* Attempts to open 'name' with the specified 'open_mode'.  On success, stores
+ * the new log into '*filep' and returns NULL; otherwise returns NULL and
+ * stores NULL into '*filep'.
+ *
+ * Whether the file will be locked using lockfile_lock() depends on 'locking':
+ * use true to lock it, false not to lock it, or -1 to lock it only if
+ * 'open_mode' is a mode that allows writing.
+ */
+struct ovsdb_error *
+ovsdb_log_open(const char *name, enum ovsdb_log_open_mode open_mode,
+               int locking, struct ovsdb_log **filep)
+{
+    struct lockfile *lockfile;
+    struct ovsdb_error *error;
+    struct ovsdb_log *file;
+    struct stat s;
+    FILE *stream;
+    int flags;
+    int fd;
+
+    *filep = NULL;
+
+    assert(locking == -1 || locking == false || locking == true);
+    if (locking < 0) {
+        locking = open_mode != OVSDB_LOG_READ_ONLY;
+    }
+    if (locking) {
+        int retval = lockfile_lock(name, 0, &lockfile);
+        if (retval) {
+            error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
+                                   name);
+            goto error;
+        }
+    } else {
+        lockfile = NULL;
+    }
+
+    if (open_mode == OVSDB_LOG_READ_ONLY) {
+        flags = O_RDONLY;
+    } else if (open_mode == OVSDB_LOG_READ_WRITE) {
+        flags = O_RDWR;
+    } else if (open_mode == OVSDB_LOG_CREATE) {
+        flags = O_RDWR | O_CREAT | O_EXCL;
+    } else {
+        NOT_REACHED();
+    }
+    fd = open(name, flags, 0666);
+    if (fd < 0) {
+        const char *op = open_mode == OVSDB_LOG_CREATE ? "create" : "open";
+        error = ovsdb_io_error(errno, "%s: %s failed", op, name);
+        goto error_unlock;
+    }
+
+    if (!fstat(fd, &s) && s.st_size == 0) {
+        /* It's (probably) a new file so fsync() its parent directory to ensure
+         * that its directory entry is committed to disk. */
+        fsync_parent_dir(name);
+    }
+
+    stream = fdopen(fd, open_mode == OVSDB_LOG_READ_ONLY ? "rb" : "w+b");
+    if (!stream) {
+        error = ovsdb_io_error(errno, "%s: fdopen failed", name);
+        goto error_close;
+    }
+
+    file = xmalloc(sizeof *file);
+    file->name = xstrdup(name);
+    file->lockfile = lockfile;
+    file->stream = stream;
+    file->offset = 0;
+    file->read_error = NULL;
+    file->write_error = NULL;
+    file->mode = OVSDB_LOG_READ;
+    *filep = file;
+    return NULL;
+
+error_close:
+    close(fd);
+error_unlock:
+    lockfile_unlock(lockfile);
+error:
+    return error;
+}
+
+void
+ovsdb_log_close(struct ovsdb_log *file)
+{
+    if (file) {
+        free(file->name);
+        fclose(file->stream);
+        lockfile_unlock(file->lockfile);
+        ovsdb_error_destroy(file->read_error);
+        ovsdb_error_destroy(file->write_error);
+        free(file);
+    }
+}
+
+static const char magic[] = "OVSDB JSON ";
+
+static bool
+parse_header(char *header, unsigned long int *length,
+             uint8_t sha1[SHA1_DIGEST_SIZE])
+{
+    char *p;
+
+    /* 'header' must consist of a magic string... */
+    if (strncmp(header, magic, strlen(magic))) {
+        return false;
+    }
+
+    /* ...followed by a length in bytes... */
+    *length = strtoul(header + strlen(magic), &p, 10);
+    if (!*length || *length == ULONG_MAX || *p != ' ') {
+        return false;
+    }
+    p++;
+
+    /* ...followed by a SHA-1 hash... */
+    if (!sha1_from_hex(sha1, p)) {
+        return false;
+    }
+    p += SHA1_HEX_DIGEST_LEN;
+
+    /* ...and ended by a new-line. */
+    if (*p != '\n') {
+        return false;
+    }
+
+    return true;
+}
+
+struct ovsdb_log_read_cbdata {
+    char input[4096];
+    struct ovsdb_log *file;
+    int error;
+    unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length,
+           uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
+{
+    struct json_parser *parser;
+    struct sha1_ctx ctx;
+
+    sha1_init(&ctx);
+    parser = json_parser_create(JSPF_TRAILER);
+
+    while (length > 0) {
+        char input[BUFSIZ];
+        int chunk;
+
+        chunk = MIN(length, sizeof input);
+        if (fread(input, 1, chunk, file->stream) != chunk) {
+            json_parser_abort(parser);
+            return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+                                  "%s: error reading %lu bytes "
+                                  "starting at offset %lld", file->name,
+                                  length, (long long int) offset);
+        }
+        sha1_update(&ctx, input, chunk);
+        json_parser_feed(parser, input, chunk);
+        length -= chunk;
+    }
+
+    sha1_final(&ctx, sha1);
+    *jsonp = json_parser_finish(parser);
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
+{
+    uint8_t expected_sha1[SHA1_DIGEST_SIZE];
+    uint8_t actual_sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    off_t data_offset;
+    unsigned long data_length;
+    struct json *json;
+    char header[128];
+
+    *jsonp = json = NULL;
+
+    if (file->read_error) {
+        return ovsdb_error_clone(file->read_error);
+    } else if (file->mode == OVSDB_LOG_WRITE) {
+        return OVSDB_BUG("reading file in write mode");
+    }
+
+    if (!fgets(header, sizeof header, file->stream)) {
+        if (feof(file->stream)) {
+            error = NULL;
+        } else {
+            error = ovsdb_io_error(errno, "%s: read failed", file->name);
+        }
+        goto error;
+    }
+
+    if (!parse_header(header, &data_length, expected_sha1)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
+                                   "%lld in header line \"%.*s\"",
+                                   file->name, (long long int) file->offset,
+                                   (int) strcspn(header, "\n"), header);
+        goto error;
+    }
+
+    data_offset = file->offset + strlen(header);
+    error = parse_body(file, data_offset, data_length, actual_sha1, &json);
+    if (error) {
+        goto error;
+    }
+
+    if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld have SHA-1 hash "SHA1_FMT" "
+                                   "but should have hash "SHA1_FMT,
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   SHA1_ARGS(actual_sha1),
+                                   SHA1_ARGS(expected_sha1));
+        goto error;
+    }
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld are not valid JSON (%s)",
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   json->u.string);
+        goto error;
+    }
+
+    file->offset = data_offset + data_length;
+    *jsonp = json;
+    return 0;
+
+error:
+    file->read_error = ovsdb_error_clone(error);
+    json_destroy(json);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_write(struct ovsdb_log *file, struct json *json)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    char *json_string;
+    char header[128];
+    size_t length;
+
+    json_string = NULL;
+
+    if (file->write_error) {
+        return ovsdb_error_clone(file->write_error);
+    } else if (file->mode == OVSDB_LOG_READ) {
+        file->mode = OVSDB_LOG_WRITE;
+        if (fseeko(file->stream, file->offset, SEEK_SET)) {
+            error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+        if (ftruncate(fileno(file->stream), file->offset)) {
+            error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+    }
+
+    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+        error = OVSDB_BUG("bad JSON type");
+        goto error;
+    }
+
+    /* Compose content.  Add a new-line (replacing the null terminator) to make
+     * the file easier to read, even though it has no semantic value.  */
+    json_string = json_to_string(json, 0);
+    length = strlen(json_string) + 1;
+    json_string[length - 1] = '\n';
+
+    /* Compose header. */
+    sha1_bytes(json_string, length, sha1);
+    snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n",
+             magic, length, SHA1_ARGS(sha1));
+
+    /* Write. */
+    if (fwrite(header, strlen(header), 1, file->stream) != 1
+        || fwrite(json_string, length, 1, file->stream) != 1
+        || fflush(file->stream))
+    {
+        error = ovsdb_io_error(errno, "%s: write failed", file->name);
+
+        /* Remove any partially written data, ignoring errors since there is
+         * nothing further we can do. */
+        ignore(ftruncate(fileno(file->stream), file->offset));
+
+        goto error;
+    }
+
+    file->offset += strlen(header) + length;
+    free(json_string);
+    return 0;
+
+error:
+    file->write_error = ovsdb_error_clone(error);
+    free(json_string);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_commit(struct ovsdb_log *file)
+{
+    if (fsync(fileno(file->stream))) {
+        return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+    }
+    return 0;
+}
+
+/* Returns the current offset into the file backing 'log', in bytes.  This
+ * reflects the number of bytes that have been read or written in the file.  If
+ * the whole file has been read, this is the file size. */
+off_t
+ovsdb_log_get_offset(const struct ovsdb_log *log)
+{
+    return log->offset;
+}
diff --git a/ovsdb/log.h b/ovsdb/log.h
new file mode 100644 (file)
index 0000000..0593fd8
--- /dev/null
@@ -0,0 +1,46 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_LOG_H
+#define OVSDB_LOG_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_log;
+
+/* Access mode for opening an OVSDB log. */
+enum ovsdb_log_open_mode {
+    OVSDB_LOG_READ_ONLY,        /* Open existing file, read-only. */
+    OVSDB_LOG_READ_WRITE,       /* Open existing file, read/write. */
+    OVSDB_LOG_CREATE            /* Create new file, read/write. */
+};
+
+struct ovsdb_error *ovsdb_log_open(const char *name, enum ovsdb_log_open_mode,
+                                   int locking, struct ovsdb_log **)
+    WARN_UNUSED_RESULT;
+void ovsdb_log_close(struct ovsdb_log *);
+
+struct ovsdb_error *ovsdb_log_read(struct ovsdb_log *, struct json **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_write(struct ovsdb_log *, struct json *)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *)
+    WARN_UNUSED_RESULT;
+
+off_t ovsdb_log_get_offset(const struct ovsdb_log *);
+
+#endif /* ovsdb/log.h */
diff --git a/ovsdb/mutation.c b/ovsdb/mutation.c
new file mode 100644 (file)
index 0000000..9f09d59
--- /dev/null
@@ -0,0 +1,521 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "mutation.h"
+
+#include <float.h>
+#include <limits.h>
+
+#include "column.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator)
+{
+#define OVSDB_MUTATOR(ENUM, NAME)               \
+    if (!strcmp(name, NAME)) {                  \
+        *mutator = ENUM;                        \
+        return NULL;                            \
+    }
+    OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+
+    return ovsdb_syntax_error(NULL, "unknown mutator",
+                              "No mutator named %s.", name);
+}
+
+const char *
+ovsdb_mutator_to_string(enum ovsdb_mutator mutator)
+{
+    switch (mutator) {
+#define OVSDB_MUTATOR(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+    }
+
+    return NULL;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+type_mismatch(const struct ovsdb_mutation *m, const struct json *json)
+{
+    struct ovsdb_error *error;
+    char *s;
+
+    s = ovsdb_type_to_english(&m->column->type);
+    error = ovsdb_syntax_error(
+        json, NULL, "Type mismatch: \"%s\" operator may not be "
+        "applied to column %s of type %s.",
+        ovsdb_mutator_to_string(m->mutator), m->column->name, s);
+    free(s);
+
+    return error;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
+                         const struct json *json,
+                         struct ovsdb_symbol_table *symtab,
+                         struct ovsdb_mutation *m)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *mutator_name;
+    const char *column_name;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in mutation.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    m->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!m->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    ovsdb_type_clone(&m->type, &m->column->type);
+
+    mutator_name = json_string(array->elems[1]);
+    error = ovsdb_mutator_from_string(mutator_name, &m->mutator);
+    if (error) {
+        goto exit;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (m->mutator) {
+    case OVSDB_M_ADD:
+    case OVSDB_M_SUB:
+    case OVSDB_M_MUL:
+    case OVSDB_M_DIV:
+    case OVSDB_M_MOD:
+        if ((!ovsdb_type_is_scalar(&m->type) && !ovsdb_type_is_set(&m->type))
+            || (m->type.key.type != OVSDB_TYPE_INTEGER
+                && m->type.key.type != OVSDB_TYPE_REAL)
+            || (m->mutator == OVSDB_M_MOD
+                && m->type.key.type == OVSDB_TYPE_REAL)) {
+            return type_mismatch(m, json);
+        }
+        ovsdb_base_type_clear_constraints(&m->type.key);
+        m->type.n_min = m->type.n_max = 1;
+        error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                      symtab);
+        break;
+
+    case OVSDB_M_INSERT:
+    case OVSDB_M_DELETE:
+        if (!ovsdb_type_is_set(&m->type) && !ovsdb_type_is_map(&m->type)) {
+            return type_mismatch(m, json);
+        }
+        m->type.n_min = 0;
+        if (m->mutator == OVSDB_M_DELETE) {
+            m->type.n_max = UINT_MAX;
+        }
+        error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                      symtab);
+        if (error && ovsdb_type_is_map(&m->type)
+            && m->mutator == OVSDB_M_DELETE) {
+            ovsdb_error_destroy(error);
+            m->type.value.type = OVSDB_TYPE_VOID;
+            error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                          symtab);
+        }
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+
+exit:
+    if (error) {
+        ovsdb_type_destroy(&m->type);
+    }
+    return error;
+}
+
+static void
+ovsdb_mutation_free(struct ovsdb_mutation *m)
+{
+    ovsdb_datum_destroy(&m->arg, &m->type);
+    ovsdb_type_destroy(&m->type);
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_from_json(const struct ovsdb_table_schema *ts,
+                             const struct json *json,
+                             struct ovsdb_symbol_table *symtab,
+                             struct ovsdb_mutation_set *set)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    set->mutations = xmalloc(array->n * sizeof *set->mutations);
+    set->n_mutations = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_mutation_from_json(ts, array->elems[i], symtab,
+                                         &set->mutations[i]);
+        if (error) {
+            ovsdb_mutation_set_destroy(set);
+            set->mutations = NULL;
+            set->n_mutations = 0;
+            return error;
+        }
+        set->n_mutations++;
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_mutation_to_json(const struct ovsdb_mutation *m)
+{
+    return json_array_create_3(
+        json_string_create(m->column->name),
+        json_string_create(ovsdb_mutator_to_string(m->mutator)),
+        ovsdb_datum_to_json(&m->arg, &m->type));
+}
+
+struct json *
+ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *set)
+{
+    struct json **mutations;
+    size_t i;
+
+    mutations = xmalloc(set->n_mutations * sizeof *mutations);
+    for (i = 0; i < set->n_mutations; i++) {
+        mutations[i] = ovsdb_mutation_to_json(&set->mutations[i]);
+    }
+    return json_array_create(mutations, set->n_mutations);
+}
+
+void
+ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        ovsdb_mutation_free(&set->mutations[i]);
+    }
+    free(set->mutations);
+}
+\f
+enum ovsdb_mutation_scalar_error {
+    ME_OK,
+    ME_DOM,
+    ME_RANGE
+};
+
+struct ovsdb_scalar_mutation {
+    int (*mutate_integer)(int64_t *x, int64_t y);
+    int (*mutate_real)(double *x, double y);
+    enum ovsdb_mutator mutator;
+};
+
+static const struct ovsdb_scalar_mutation add_mutation;
+static const struct ovsdb_scalar_mutation sub_mutation;
+static const struct ovsdb_scalar_mutation mul_mutation;
+static const struct ovsdb_scalar_mutation div_mutation;
+static const struct ovsdb_scalar_mutation mod_mutation;
+
+static struct ovsdb_error *
+ovsdb_mutation_scalar_error(enum ovsdb_mutation_scalar_error error,
+                            enum ovsdb_mutator mutator)
+{
+    switch (error) {
+    case ME_OK:
+        return OVSDB_BUG("unexpected success");
+
+    case ME_DOM:
+        return ovsdb_error("domain error", "Division by zero.");
+
+    case ME_RANGE:
+        return ovsdb_error("range error",
+                           "Result of \"%s\" operation is out of range.",
+                           ovsdb_mutator_to_string(mutator));
+
+    default:
+        return OVSDB_BUG("unexpected error");
+    }
+}
+
+static int
+check_real_range(double x)
+{
+    return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
+}
+
+static struct ovsdb_error *
+mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
+              const union ovsdb_atom *arg,
+              const struct ovsdb_scalar_mutation *mutation)
+{
+    const struct ovsdb_base_type *base = &dst_type->key;
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    if (base->type == OVSDB_TYPE_INTEGER) {
+        int64_t y = arg->integer;
+        for (i = 0; i < dst->n; i++) {
+            enum ovsdb_mutation_scalar_error me;
+
+            me = (mutation->mutate_integer)(&dst->keys[i].integer, y);
+            if (me != ME_OK) {
+                return ovsdb_mutation_scalar_error(me, mutation->mutator);
+            }
+        }
+    } else if (base->type == OVSDB_TYPE_REAL) {
+        double y = arg->real;
+        for (i = 0; i < dst->n; i++) {
+            double *x = &dst->keys[i].real;
+            enum ovsdb_mutation_scalar_error me;
+
+            me = (mutation->mutate_real)(x, y);
+            if (me == ME_OK) {
+                me = check_real_range(*x);
+            }
+            if (me != ME_OK) {
+                return ovsdb_mutation_scalar_error(me, mutation->mutator);
+            }
+        }
+    } else {
+        NOT_REACHED();
+    }
+
+    for (i = 0; i < dst->n; i++) {
+        error = ovsdb_atom_check_constraints(&dst->keys[i], base);
+        if (error) {
+            return error;
+        }
+    }
+
+    error = ovsdb_datum_sort(dst, dst_type->key.type);
+    if (error) {
+        ovsdb_error_destroy(error);
+        return ovsdb_error("constraint violation",
+                           "Result of \"%s\" operation contains duplicates.",
+                           ovsdb_mutator_to_string(mutation->mutator));
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_mutation_check_count(struct ovsdb_datum *dst,
+                           const struct ovsdb_type *dst_type)
+{
+    if (!ovsdb_datum_conforms_to_type(dst, dst_type)) {
+        char *s = ovsdb_type_to_english(dst_type);
+        struct ovsdb_error *e = ovsdb_error(
+            "constaint violation",
+            "Attempted to store %u elements in %s.", dst->n, s);
+        free(s);
+        return e;
+    }
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_execute(struct ovsdb_row *row,
+                           const struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        const struct ovsdb_mutation *m = &set->mutations[i];
+        struct ovsdb_datum *dst = &row->fields[m->column->index];
+        const struct ovsdb_type *dst_type = &m->column->type;
+        const struct ovsdb_datum *arg = &set->mutations[i].arg;
+        const struct ovsdb_type *arg_type = &m->type;
+        struct ovsdb_error *error;
+
+        switch (m->mutator) {
+        case OVSDB_M_ADD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &add_mutation);
+            break;
+
+        case OVSDB_M_SUB:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &sub_mutation);
+            break;
+
+        case OVSDB_M_MUL:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &mul_mutation);
+            break;
+
+        case OVSDB_M_DIV:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &div_mutation);
+            break;
+
+        case OVSDB_M_MOD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &mod_mutation);
+            break;
+
+        case OVSDB_M_INSERT:
+            ovsdb_datum_union(dst, arg, dst_type, false);
+            error = ovsdb_mutation_check_count(dst, dst_type);
+            break;
+
+        case OVSDB_M_DELETE:
+            ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
+            error = ovsdb_mutation_check_count(dst, dst_type);
+            break;
+
+        default:
+            NOT_REACHED();
+        }
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+\f
+static int
+add_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = ~(*x ^ y) & INT64_MIN;
+    if ((~(*x ^ y) & ~(((*x ^ z) + y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x += y;
+        return 0;
+    }
+}
+
+static int
+sub_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = (*x ^ y) & INT64_MIN;
+    if (((*x ^ y) & (((*x ^ z) - y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x -= y;
+        return 0;
+    }
+}
+
+static int
+mul_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 30. */
+    if (*x > 0
+        ? (y > 0
+           ? *x >= INT64_MAX / y
+           : y  < INT64_MIN / *x)
+        : (y > 0
+           ? *x < INT64_MIN / y
+           : *x != 0 && y < INT64_MAX / y)) {
+        return ME_RANGE;
+    } else {
+        *x *= y;
+        return 0;
+    }
+}
+
+static int
+check_int_div(int64_t x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 32. */
+    if (!y) {
+        return ME_DOM;
+    } else if (x == INT64_MIN && y == -1) {
+        return ME_RANGE;
+    } else {
+        return 0;
+    }
+}
+
+static int
+div_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x /= y;
+    }
+    return error;
+}
+
+static int
+mod_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x %= y;
+    }
+    return error;
+}
+
+static int
+add_double(double *x, double y)
+{
+    *x += y;
+    return 0;
+}
+
+static int
+sub_double(double *x, double y)
+{
+    *x -= y;
+    return 0;
+}
+
+static int
+mul_double(double *x, double y)
+{
+    *x *= y;
+    return 0;
+}
+
+static int
+div_double(double *x, double y)
+{
+    if (y == 0) {
+        return ME_DOM;
+    } else {
+        *x /= y;
+        return 0;
+    }
+}
+
+static const struct ovsdb_scalar_mutation add_mutation = {
+    add_int, add_double, OVSDB_M_ADD
+};
+
+static const struct ovsdb_scalar_mutation sub_mutation = {
+    sub_int, sub_double, OVSDB_M_SUB
+};
+
+static const struct ovsdb_scalar_mutation mul_mutation = {
+    mul_int, mul_double, OVSDB_M_MUL
+};
+
+static const struct ovsdb_scalar_mutation div_mutation = {
+    div_int, div_double, OVSDB_M_DIV
+};
+
+static const struct ovsdb_scalar_mutation mod_mutation = {
+    mod_int, NULL, OVSDB_M_MOD
+};
diff --git a/ovsdb/mutation.h b/ovsdb/mutation.h
new file mode 100644 (file)
index 0000000..57fd965
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_MUTATION_H
+#define OVSDB_MUTATION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_MUTATORS                              \
+    OVSDB_MUTATOR(OVSDB_M_ADD, "+=")                \
+    OVSDB_MUTATOR(OVSDB_M_SUB, "-=")                \
+    OVSDB_MUTATOR(OVSDB_M_MUL, "*=")                \
+    OVSDB_MUTATOR(OVSDB_M_DIV, "/=")                \
+    OVSDB_MUTATOR(OVSDB_M_MOD, "%=")                \
+    OVSDB_MUTATOR(OVSDB_M_INSERT, "insert")         \
+    OVSDB_MUTATOR(OVSDB_M_DELETE, "delete")
+
+enum ovsdb_mutator {
+#define OVSDB_MUTATOR(ENUM, NAME) ENUM,
+    OVSDB_MUTATORS
+#undef OVSDB_MUTATOR
+};
+
+struct ovsdb_error *ovsdb_mutator_from_string(const char *,
+                                              enum ovsdb_mutator *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_mutator_to_string(enum ovsdb_mutator);
+
+struct ovsdb_mutation {
+    enum ovsdb_mutator mutator;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+    struct ovsdb_type type;
+};
+
+struct ovsdb_mutation_set {
+    struct ovsdb_mutation *mutations;
+    size_t n_mutations;
+};
+
+#define OVSDB_MUTATION_SET_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_mutation_set_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, struct ovsdb_symbol_table *,
+    struct ovsdb_mutation_set *) WARN_UNUSED_RESULT;
+struct json *ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *);
+void ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *);
+struct ovsdb_error *ovsdb_mutation_set_execute(
+    struct ovsdb_row *, const struct ovsdb_mutation_set *);
+
+#endif /* ovsdb/mutation.h */
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
new file mode 100644 (file)
index 0000000..7607157
--- /dev/null
@@ -0,0 +1,159 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-client 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-client
+.
+.SH NAME
+ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
+.
+.SH SYNOPSIS
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-dbs\fI server\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBget-schema\fI server database\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-tables\fI server database\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-columns\fI server database \fR[\fItable\fR]
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBtransact\fI server transaction\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBdump\fI server database\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI server database table\fR
+[\fIcolumn\fR[\fB,\fIcolumn\fR]...]
+[\fIselect\fR[\fB,\fIselect\fR]...]
+.br
+\fBovsdb\-client help\fR
+.IP "Output formatting options:"
+[\fB--format=\fIformat\fR]
+[\fB--no-heading\fR]
+.so lib/daemon-syn.man
+.so lib/vlog-syn.man
+.so lib/ssl-syn.man
+.so lib/ssl-bootstrap-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-client\fR program is a command-line client for
+interacting with a running \fBovsdb\-server\fR process.  For each
+command, the \fIserver\fR to connect to must be specified in one of
+the following forms:
+.RS
+.so ovsdb/remote-active.man
+.so ovsdb/remote-passive.man
+.RE
+.
+.SS "Commands"
+The following commands are implemented:
+.IP "\fBlist-dbs\fI server\fR"
+Connects to \fIserver\fR, retrieves the list of known databases, and
+prints them one per line.  These database names are the ones that may
+be used for \fIdatabase\fR in the following commands.
+.
+.IP "\fBget-schema\fI server database\fR"
+Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
+prints it in JSON format.
+.
+.IP "\fBlist-tables\fI server database\fR"
+Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
+prints a table listing the name of each table
+within the database.
+.
+.IP "\fBlist-columns\fI server database \fR[\fItable\fR]"
+Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
+prints a table listing the name and type of each
+column.  If \fItable\fR is specified, only columns in that table are
+listed; otherwise, the tables include columns in all tables.
+.
+.IP "\fBtransact\fI server transaction\fR"
+Connects to \fIserver\fR, sends it the specified \fItransaction\fR,
+which must be a JSON array containing one or more valid OVSDB
+operations, and prints the received reply on stdout.
+.
+.IP "\fBdump\fI server database\fR"
+Connects to \fIserver\fR, retrieves all of the data in \fIdatabase\fR,
+and prints it on stdout as a series of tables.
+.
+.IP "\fBmonitor\fI server database table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...] [\fIselect\fR[\fB,\fIselect\fR]...]"
+Connects to \fIserver\fR and monitors the contents of \fItable\fR in
+\fIdatabase\fR.  By default, the initial contents of \fItable\fR are
+printed, followed by each change as it occurs.  If at least one
+\fIcolumn\fR is specified, only those columns are monitored.  If at
+least one \fIselect\fR is specified, they are interpreted as follows:
+.RS
+.IP "\fBinitial\fR"
+Print the initial contents of the specified columns.
+.IP "\fBinsert\fR"
+Print newly inserted rows.
+.IP "\fBdelete\fR"
+Print deleted rows.
+.IP "\fBmodify\fR"
+Print old and new values of modified rows.
+.RE
+.IP
+If \fB--detach\fR is used with \fBmonitor\fR, then \fBovsdb\-client\fR
+detaches after it has successfully received and printed the initial
+contents of \fItable\fR.
+.SH OPTIONS
+.SS "Output Formatting Options"
+Much of the output from \fBovsdb\-client\fR is in the form of tables.
+The following options controlling output formatting:
+.
+.IP "\fB-f \fIformat\fR"
+.IQ "\fB--format=\fIformat\fR"
+Sets the type of table formatting.  The following types of
+\fIformat\fR are available:
+.RS
+.IP "\fBtable\fR (default)"
+Text-based tables with aligned columns.
+.IP "\fBhtml\fR"
+HTML tables.
+.IP "\fBcvs\fR"
+Comma-separated values as defined in RFC 4180.
+.RE
+.
+.IP "\fB-d \fIformat\fR"
+.IP "\fB--data=\fIformat\fR"
+Sets the formatting for cells within output tables.  The following
+types of \fIformat\fR are available:
+.RS
+.IP "\fBstring\fR (default)"
+The simple format described in \fBovs-vsctl\fR(8).
+.IP "\fBjson\fR"
+JSON.
+.RE
+.
+.IP "\fB--no-heading\fR"
+This option suppresses the heading row that otherwise appears in the
+first row of table output.
+.
+.IP "\fB--pretty\fR"
+By default, JSON in output is printed as compactly as possible.  This
+option causes JSON in output to be printed in a more readable
+fashion.  Members of objects and elements of arrays are printed one
+per line, with indentation.
+.IP
+This option does not affect JSON in tables, which is always printed
+compactly.
+.
+.SS "Daemon Options"
+The daemon options apply only to the \fBmonitor\fR command.  With any
+other command, they have no effect.
+.so lib/daemon.man
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
new file mode 100644 (file)
index 0000000..da7faf6
--- /dev/null
@@ -0,0 +1,1249 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "column.h"
+#include "compiler.h"
+#include "daemon.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "sort.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_client
+
+/* --format: Output formatting. */
+static enum {
+    FMT_TABLE,                  /* Textual table. */
+    FMT_HTML,                   /* HTML table. */
+    FMT_CSV                     /* Comma-separated lines. */
+} output_format;
+
+/* --no-headings: Whether table output should include headings. */
+static int output_headings = true;
+
+/* --pretty: Flags to pass to json_to_string(). */
+static int json_flags = JSSF_SORT;
+
+/* --data: Format of data in output tables. */
+static enum {
+    DF_STRING,                  /* String format. */
+    DF_JSON,                    /* JSON. */
+} data_format;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+    };
+    static struct option long_options[] = {
+        {"format", required_argument, 0, 'f'},
+        {"data", required_argument, 0, 'd'},
+        {"no-headings", no_argument, &output_headings, 0},
+        {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        STREAM_SSL_LONG_OPTIONS
+#endif
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'f':
+            if (!strcmp(optarg, "table")) {
+                output_format = FMT_TABLE;
+            } else if (!strcmp(optarg, "html")) {
+                output_format = FMT_HTML;
+            } else if (!strcmp(optarg, "csv")) {
+                output_format = FMT_CSV;
+            } else {
+                ovs_fatal(0, "unknown output format \"%s\"", optarg);
+            }
+            break;
+
+        case 'd':
+            if (!strcmp(optarg, "string")) {
+                data_format = DF_STRING;
+            } else if (!strcmp(optarg, "json")) {
+                data_format = DF_JSON;
+            } else {
+                ovs_fatal(0, "unknown data format \"%s\"", optarg);
+            }
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+#endif
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        case 0:
+            /* getopt_long() already set the value for us. */
+            break;
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database JSON-RPC client\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "\nValid commands are:\n"
+           "\n  list-dbs SERVER\n"
+           "    list databases available on SERVER\n"
+           "\n  get-schema SERVER DATABASE\n"
+           "    retrieve schema for DATABASE from SERVER\n"
+           "\n  list-tables SERVER DATABASE\n"
+           "    list tables for DATABASE on SERVER\n"
+           "\n  list-columns SERVER DATABASE [TABLE]\n"
+           "    list columns in TABLE (or all tables) in DATABASE on SERVER\n"
+           "\n  transact SERVER TRANSACTION\n"
+           "    run TRANSACTION (a JSON array of operations) on SERVER\n"
+           "    and print the results as JSON on stdout\n"
+           "\n  monitor SERVER DATABASE TABLE [COLUMN,...] [SELECT,...]\n"
+           "    monitor contents of (COLUMNs in) TABLE in DATABASE on SERVER\n"
+           "    Valid SELECTs are: initial, insert, delete, modify\n"
+           "\n  dump SERVER DATABASE\n"
+           "    dump contents of DATABASE on SERVER to stdout\n",
+           program_name, program_name);
+    stream_usage("SERVER", true, true, true);
+    printf("\nOutput formatting options:\n"
+           "  -f, --format=FORMAT         set output formatting to FORMAT\n"
+           "                              (\"table\", \"html\", or \"csv\"\n"
+           "  --no-headings               omit table heading row\n"
+           "  --pretty                    pretty-print JSON in output");
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct jsonrpc *
+open_jsonrpc(const char *server)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open_block(server, &stream);
+    if (error == EAFNOSUPPORT) {
+        struct pstream *pstream;
+
+        error = pstream_open(server, &pstream);
+        if (error) {
+            ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
+        }
+
+        VLOG_INFO("%s: waiting for connection...", server);
+        error = pstream_accept_block(pstream, &stream);
+        if (error) {
+            ovs_fatal(error, "failed to accept connection on \"%s\"", server);
+        }
+
+        pstream_close(pstream);
+    } else if (error) {
+        ovs_fatal(error, "failed to connect to \"%s\"", server);
+    }
+
+    return jsonrpc_open(stream);
+}
+
+static void
+print_json(struct json *json)
+{
+    char *string = json_to_string(json, json_flags);
+    fputs(string, stdout);
+    free(string);
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    print_json(json);
+    json_destroy(json);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+
+static struct ovsdb_schema *
+fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database)
+{
+    struct jsonrpc_msg *request, *reply;
+    struct ovsdb_schema *schema;
+    int error;
+
+    request = jsonrpc_create_request("get_schema",
+                                     json_array_create_1(
+                                         json_string_create(database)),
+                                     NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
+    jsonrpc_msg_destroy(reply);
+
+    return schema;
+}
+
+static struct ovsdb_schema *
+fetch_schema(const char *server, const char *database)
+{
+    struct ovsdb_schema *schema;
+    struct jsonrpc *rpc;
+
+    rpc = open_jsonrpc(server);
+    schema = fetch_schema_from_rpc(rpc, database);
+    jsonrpc_close(rpc);
+
+    return schema;
+}
+\f
+struct column {
+    char *heading;
+    int width;
+};
+
+struct table {
+    char **cells;
+    struct column *columns;
+    size_t n_columns, allocated_columns;
+    size_t n_rows, allocated_rows;
+    size_t current_column;
+    char *caption;
+};
+
+static void
+table_init(struct table *table)
+{
+    memset(table, 0, sizeof *table);
+}
+
+static void
+table_destroy(struct table *table)
+{
+    size_t i;
+
+    for (i = 0; i < table->n_columns; i++) {
+        free(table->columns[i].heading);
+    }
+    free(table->columns);
+
+    for (i = 0; i < table->n_columns * table->n_rows; i++) {
+        free(table->cells[i]);
+    }
+    free(table->cells);
+
+    free(table->caption);
+}
+
+static void
+table_set_caption(struct table *table, char *caption)
+{
+    free(table->caption);
+    table->caption = caption;
+}
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+    PRINTF_FORMAT(2, 3);
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+{
+    struct column *column;
+    va_list args;
+
+    assert(!table->n_rows);
+    if (table->n_columns >= table->allocated_columns) {
+        table->columns = x2nrealloc(table->columns, &table->allocated_columns,
+                                    sizeof *table->columns);
+    }
+    column = &table->columns[table->n_columns++];
+
+    va_start(args, heading);
+    column->heading = xvasprintf(heading, args);
+    column->width = strlen(column->heading);
+    va_end(args);
+}
+
+static char **
+table_cell__(const struct table *table, size_t row, size_t column)
+{
+    return &table->cells[column + row * table->n_columns];
+}
+
+static void
+table_add_row(struct table *table)
+{
+    size_t x, y;
+
+    if (table->n_rows >= table->allocated_rows) {
+        table->cells = x2nrealloc(table->cells, &table->allocated_rows,
+                                  table->n_columns * sizeof *table->cells);
+    }
+
+    y = table->n_rows++;
+    table->current_column = 0;
+    for (x = 0; x < table->n_columns; x++) {
+        *table_cell__(table, y, x) = NULL;
+    }
+}
+
+static void
+table_add_cell_nocopy(struct table *table, char *s)
+{
+    size_t x, y;
+    int length;
+
+    assert(table->n_rows > 0);
+    assert(table->current_column < table->n_columns);
+
+    x = table->current_column++;
+    y = table->n_rows - 1;
+    *table_cell__(table, y, x) = s;
+
+    length = strlen(s);
+    if (length > table->columns[x].width) {
+        table->columns[x].width = length;
+    }
+}
+
+static void
+table_add_cell(struct table *table, const char *format, ...)
+{
+    va_list args;
+
+    va_start(args, format);
+    table_add_cell_nocopy(table, xvasprintf(format, args));
+    va_end(args);
+}
+
+static void
+table_print_table_line__(struct ds *line)
+{
+    puts(ds_cstr(line));
+    ds_clear(line);
+}
+
+static void
+table_print_table__(const struct table *table)
+{
+    static int n = 0;
+    struct ds line = DS_EMPTY_INITIALIZER;
+    size_t x, y;
+
+    if (n++ > 0) {
+        putchar('\n');
+    }
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", column->width, column->heading);
+        }
+        table_print_table_line__(&line);
+
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            int i;
+
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            for (i = 0; i < column->width; i++) {
+                ds_put_char(&line, '-');
+            }
+        }
+        table_print_table_line__(&line);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            const char *cell = *table_cell__(table, y, x);
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", table->columns[x].width, cell);
+        }
+        table_print_table_line__(&line);
+    }
+
+    ds_destroy(&line);
+}
+
+static void
+table_escape_html_text__(const char *s, size_t n)
+{
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        char c = s[i];
+
+        switch (c) {
+        case '&':
+            fputs("&amp;", stdout);
+            break;
+        case '<':
+            fputs("&lt;", stdout);
+            break;
+        case '>':
+            fputs("&gt;", stdout);
+            break;
+        case '"':
+            fputs("&quot;", stdout);
+            break;
+        default:
+            putchar(c);
+            break;
+        }
+    }
+}
+
+static void
+table_print_html_cell__(const char *element, const char *content)
+{
+    const char *p;
+
+    printf("    <%s>", element);
+    for (p = content; *p; ) {
+        struct uuid uuid;
+
+        if (uuid_from_string_prefix(&uuid, p)) {
+            printf("<a href=\"#%.*s\">%.*s</a>", UUID_LEN, p, 8, p);
+            p += UUID_LEN;
+        } else {
+            table_escape_html_text__(p, 1);
+            p++;
+        }
+    }
+    printf("</%s>\n", element);
+}
+
+static void
+table_print_html__(const struct table *table)
+{
+    size_t x, y;
+
+    fputs("<table border=1>\n", stdout);
+
+    if (table->caption) {
+        table_print_html_cell__("caption", table->caption);
+    }
+
+    if (output_headings) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            table_print_html_cell__("th", column->heading);
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            const char *content = *table_cell__(table, y, x);
+
+            if (!strcmp(table->columns[x].heading, "_uuid")) {
+                fputs("    <td><a name=\"", stdout);
+                table_escape_html_text__(content, strlen(content));
+                fputs("\">", stdout);
+                table_escape_html_text__(content, 8);
+                fputs("</a></td>\n", stdout);
+            } else {
+                table_print_html_cell__("td", content);
+            }
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    fputs("</table>\n", stdout);
+}
+
+static void
+table_print_csv_cell__(const char *content)
+{
+    const char *p;
+
+    if (!strpbrk(content, "\n\",")) {
+        fputs(content, stdout);
+    } else {
+        putchar('"');
+        for (p = content; *p != '\0'; p++) {
+            switch (*p) {
+            case '"':
+                fputs("\"\"", stdout);
+                break;
+            default:
+                putchar(*p);
+                break;
+            }
+        }
+        putchar('"');
+    }
+}
+
+static void
+table_print_csv__(const struct table *table)
+{
+    static int n = 0;
+    size_t x, y;
+
+    if (n++ > 0) {
+        putchar('\n');
+    }
+
+    if (table->caption) {
+        puts(table->caption);
+    }
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(column->heading);
+        }
+        putchar('\n');
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(*table_cell__(table, y, x));
+        }
+        putchar('\n');
+    }
+}
+
+static void
+table_print(const struct table *table)
+{
+    switch (output_format) {
+    case FMT_TABLE:
+        table_print_table__(table);
+        break;
+
+    case FMT_HTML:
+        table_print_html__(table);
+        break;
+
+    case FMT_CSV:
+        table_print_csv__(table);
+        break;
+    }
+}
+\f
+static void
+do_list_dbs(int argc OVS_UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *request, *reply;
+    struct jsonrpc *rpc;
+    int error;
+    size_t i;
+
+    rpc = open_jsonrpc(argv[1]);
+    request = jsonrpc_create_request("list_dbs", json_array_create_empty(),
+                                     NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+
+    if (reply->result->type != JSON_ARRAY) {
+        ovs_fatal(0, "list_dbs response is not array");
+    }
+
+    for (i = 0; i < reply->result->u.array.n; i++) {
+        const struct json *name = reply->result->u.array.elems[i];
+
+        if (name->type != JSON_STRING) {
+            ovs_fatal(0, "list_dbs response %zu is not string", i);
+        }
+        puts(name->u.string);
+    }
+    jsonrpc_msg_destroy(reply);
+}
+
+static void
+do_get_schema(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]);
+    print_and_free_json(ovsdb_schema_to_json(schema));
+    ovsdb_schema_destroy(schema);
+}
+
+static void
+do_list_tables(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct shash_node *node;
+    struct table t;
+
+    schema = fetch_schema(argv[1], argv[2]);
+    table_init(&t);
+    table_add_column(&t, "Table");
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+
+        table_add_row(&t);
+        table_add_cell(&t, ts->name);
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_list_columns(int argc OVS_UNUSED, char *argv[])
+{
+    const char *table_name = argv[3];
+    struct ovsdb_schema *schema;
+    struct shash_node *table_node;
+    struct table t;
+
+    schema = fetch_schema(argv[1], argv[2]);
+    table_init(&t);
+    if (!table_name) {
+        table_add_column(&t, "Table");
+    }
+    table_add_column(&t, "Column");
+    table_add_column(&t, "Type");
+    SHASH_FOR_EACH (table_node, &schema->tables) {
+        struct ovsdb_table_schema *ts = table_node->data;
+
+        if (!table_name || !strcmp(table_name, ts->name)) {
+            struct shash_node *column_node;
+
+            SHASH_FOR_EACH (column_node, &ts->columns) {
+                const struct ovsdb_column *column = column_node->data;
+                struct json *type = ovsdb_type_to_json(&column->type);
+
+                table_add_row(&t);
+                if (!table_name) {
+                    table_add_cell(&t, ts->name);
+                }
+                table_add_cell(&t, column->name);
+                table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
+
+                json_destroy(type);
+            }
+        }
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_transact(int argc OVS_UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *request, *reply;
+    struct json *transaction;
+    struct jsonrpc *rpc;
+    int error;
+
+    transaction = parse_json(argv[2]);
+
+    rpc = open_jsonrpc(argv[1]);
+    request = jsonrpc_create_request("transact", transaction, NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    if (reply->error) {
+        ovs_fatal(error, "transaction returned error: %s",
+                  json_to_string(reply->error, json_flags));
+    }
+    print_json(reply->result);
+    putchar('\n');
+    jsonrpc_msg_destroy(reply);
+    jsonrpc_close(rpc);
+}
+
+static char *
+format_json(const struct json *json, const struct ovsdb_type *type)
+{
+    if (data_format == DF_JSON) {
+        return json_to_string(json, JSSF_SORT);
+    } else if (data_format == DF_STRING) {
+        struct ovsdb_datum datum;
+        struct ovsdb_error *error;
+        struct ds s;
+
+        error = ovsdb_datum_from_json(&datum, type, json, NULL);
+        if (error) {
+            return json_to_string(json, JSSF_SORT);
+        }
+
+        ds_init(&s);
+        ovsdb_datum_to_string(&datum, type, &s);
+        ovsdb_datum_destroy(&datum, type);
+        return ds_steal_cstr(&s);
+    } else {
+        NOT_REACHED();
+    }
+}
+
+static void
+monitor_print_row(struct json *row, const char *type, const char *uuid,
+                  const struct ovsdb_column_set *columns, struct table *t)
+{
+    size_t i;
+
+    if (!row) {
+        ovs_error(0, "missing %s row", type);
+        return;
+    } else if (row->type != JSON_OBJECT) {
+        ovs_error(0, "<row> is not object");
+        return;
+    }
+
+    table_add_row(t);
+    table_add_cell(t, uuid);
+    table_add_cell(t, type);
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        struct json *value = shash_find_data(json_object(row), column->name);
+        if (value) {
+            table_add_cell_nocopy(t, format_json(value, &column->type));
+        } else {
+            table_add_cell(t, "");
+        }
+    }
+}
+
+static void
+monitor_print(struct json *table_updates,
+              const struct ovsdb_table_schema *table,
+              const struct ovsdb_column_set *columns, bool initial)
+{
+    struct json *table_update;
+    struct shash_node *node;
+    struct table t;
+    size_t i;
+
+    table_init(&t);
+
+    if (table_updates->type != JSON_OBJECT) {
+        ovs_error(0, "<table-updates> is not object");
+        return;
+    }
+    table_update = shash_find_data(json_object(table_updates), table->name);
+    if (!table_update) {
+        return;
+    }
+    if (table_update->type != JSON_OBJECT) {
+        ovs_error(0, "<table-update> is not object");
+        return;
+    }
+
+    table_add_column(&t, "row");
+    table_add_column(&t, "action");
+    for (i = 0; i < columns->n_columns; i++) {
+        table_add_column(&t, "%s", columns->columns[i]->name);
+    }
+    SHASH_FOR_EACH (node, json_object(table_update)) {
+        struct json *row_update = node->data;
+        struct json *old, *new;
+
+        if (row_update->type != JSON_OBJECT) {
+            ovs_error(0, "<row-update> is not object");
+            continue;
+        }
+        old = shash_find_data(json_object(row_update), "old");
+        new = shash_find_data(json_object(row_update), "new");
+        if (initial) {
+            monitor_print_row(new, "initial", node->name, columns, &t);
+        } else if (!old) {
+            monitor_print_row(new, "insert", node->name, columns, &t);
+        } else if (!new) {
+            monitor_print_row(old, "delete", node->name, columns, &t);
+        } else {
+            monitor_print_row(old, "old", node->name, columns, &t);
+            monitor_print_row(new, "new", "", columns, &t);
+        }
+    }
+    table_print(&t);
+    table_destroy(&t);
+}
+
+static void
+do_monitor(int argc, char *argv[])
+{
+    const char *server = argv[1];
+    const char *database = argv[2];
+    const char *table_name = argv[3];
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_table_schema *table;
+    struct ovsdb_schema *schema;
+    struct jsonrpc_msg *request;
+    struct jsonrpc *rpc;
+    struct json *select, *monitor, *monitor_request, *monitor_requests,
+        *request_id;
+
+    rpc = open_jsonrpc(server);
+
+    schema = fetch_schema_from_rpc(rpc, database);
+    table = shash_find_data(&schema->tables, table_name);
+    if (!table) {
+        ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
+                  server, database, table_name);
+    }
+
+    if (argc >= 5 && *argv[4] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            const struct ovsdb_column *column;
+            column = ovsdb_table_schema_get_column(table, token);
+            if (!column) {
+                ovs_fatal(0, "%s: table \"%s\" in %s does not have a "
+                          "column named \"%s\"",
+                          server, table_name, database, token);
+            }
+            ovsdb_column_set_add(&columns, column);
+        }
+    } else {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->columns) {
+            const struct ovsdb_column *column = node->data;
+            if (column->index != OVSDB_COL_UUID) {
+                ovsdb_column_set_add(&columns, column);
+            }
+        }
+    }
+
+    if (argc >= 6 && *argv[5] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        select = json_object_create();
+        for (token = strtok_r(argv[5], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            json_object_put(select, token, json_boolean_create(true));
+        }
+    } else {
+        select = NULL;
+    }
+
+    monitor_request = json_object_create();
+    json_object_put(monitor_request,
+                    "columns", ovsdb_column_set_to_json(&columns));
+    if (select) {
+        json_object_put(monitor_request, "select", select);
+    }
+
+    monitor_requests = json_object_create();
+    json_object_put(monitor_requests, table_name, monitor_request);
+
+    monitor = json_array_create_3(json_string_create(database),
+                                  json_null_create(), monitor_requests);
+    request = jsonrpc_create_request("monitor", monitor, NULL);
+    request_id = json_clone(request->id);
+    jsonrpc_send(rpc, request);
+    for (;;) {
+        struct jsonrpc_msg *msg;
+        int error;
+
+        error = jsonrpc_recv_block(rpc, &msg);
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            ovs_fatal(error, "%s: receive failed", server);
+        }
+
+        if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+            jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
+                                                   msg->id));
+        } else if (msg->type == JSONRPC_REPLY
+                   && json_equal(msg->id, request_id)) {
+            monitor_print(msg->result, table, &columns, true);
+            fflush(stdout);
+            if (get_detach()) {
+                /* daemonize() closes the standard file descriptors.  We output
+                 * to stdout, so we need to save and restore STDOUT_FILENO. */
+                int fd = dup(STDOUT_FILENO);
+                daemonize();
+                dup2(fd, STDOUT_FILENO);
+                close(fd);
+            }
+        } else if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")) {
+            struct json *params = msg->params;
+            if (params->type == JSON_ARRAY
+                && params->u.array.n == 2
+                && params->u.array.elems[0]->type == JSON_NULL) {
+                monitor_print(params->u.array.elems[1],
+                              table, &columns, false);
+                fflush(stdout);
+            }
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+struct dump_table_aux {
+    struct ovsdb_datum **data;
+    const struct ovsdb_column **columns;
+    size_t n_columns;
+};
+
+static int
+compare_data(size_t a_y, size_t b_y, size_t x,
+             const struct dump_table_aux *aux)
+{
+    return ovsdb_datum_compare_3way(&aux->data[a_y][x],
+                                    &aux->data[b_y][x],
+                                    &aux->columns[x]->type);
+}
+
+static int
+compare_rows(size_t a_y, size_t b_y, void *aux_)
+{
+    struct dump_table_aux *aux = aux_;
+    size_t x;
+
+    /* Skip UUID columns on the first pass, since their values tend to be
+     * random and make our results less reproducible. */
+    for (x = 0; x < aux->n_columns; x++) {
+        if (aux->columns[x]->type.key.type != OVSDB_TYPE_UUID) {
+            int cmp = compare_data(a_y, b_y, x, aux);
+            if (cmp) {
+                return cmp;
+            }
+        }
+    }
+
+    /* Use UUID columns as tie-breakers. */
+    for (x = 0; x < aux->n_columns; x++) {
+        if (aux->columns[x]->type.key.type == OVSDB_TYPE_UUID) {
+            int cmp = compare_data(a_y, b_y, x, aux);
+            if (cmp) {
+                return cmp;
+            }
+        }
+    }
+
+    return 0;
+}
+
+static void
+swap_rows(size_t a_y, size_t b_y, void *aux_)
+{
+    struct dump_table_aux *aux = aux_;
+    struct ovsdb_datum *tmp = aux->data[a_y];
+    aux->data[a_y] = aux->data[b_y];
+    aux->data[b_y] = tmp;
+}
+
+static char *
+format_data(const struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    if (data_format == DF_JSON) {
+        struct json *json = ovsdb_datum_to_json(datum, type);
+        char *s = json_to_string(json, JSSF_SORT);
+        json_destroy(json);
+        return s;
+    } else if (data_format == DF_STRING) {
+        struct ds s;
+
+        ds_init(&s);
+        ovsdb_datum_to_string(datum, type, &s);
+        return ds_steal_cstr(&s);
+    } else {
+        NOT_REACHED();
+    }
+}
+
+static int
+compare_columns(const void *a_, const void *b_)
+{
+    const struct ovsdb_column *const *ap = a_;
+    const struct ovsdb_column *const *bp = b_;
+    const struct ovsdb_column *a = *ap;
+    const struct ovsdb_column *b = *bp;
+
+    return strcmp(a->name, b->name);
+}
+
+static void
+dump_table(const struct ovsdb_table_schema *ts, struct json_array *rows)
+{
+    const struct ovsdb_column **columns;
+    size_t n_columns;
+
+    struct ovsdb_datum **data;
+
+    struct dump_table_aux aux;
+    struct shash_node *node;
+    struct table t;
+    size_t x, y;
+
+    /* Sort columns by name, for reproducibility. */
+    columns = xmalloc(shash_count(&ts->columns) * sizeof *columns);
+    n_columns = 0;
+    SHASH_FOR_EACH (node, &ts->columns) {
+        struct ovsdb_column *column = node->data;
+        if (strcmp(column->name, "_version")) {
+            columns[n_columns++] = column;
+        }
+    }
+    qsort(columns, n_columns, sizeof *columns, compare_columns);
+
+    /* Extract data from table. */
+    data = xmalloc(rows->n * sizeof *data);
+    for (y = 0; y < rows->n; y++) {
+        struct shash *row;
+
+        if (rows->elems[y]->type != JSON_OBJECT) {
+            ovs_fatal(0,  "row %zu in table %s response is not a JSON object: "
+                      "%s", y, ts->name, json_to_string(rows->elems[y], 0));
+        }
+        row = json_object(rows->elems[y]);
+
+        data[y] = xmalloc(n_columns * sizeof **data);
+        for (x = 0; x < n_columns; x++) {
+            const struct json *json = shash_find_data(row, columns[x]->name);
+            if (!json) {
+                ovs_fatal(0, "row %zu in table %s response lacks %s column",
+                          y, ts->name, columns[x]->name);
+            }
+
+            check_ovsdb_error(ovsdb_datum_from_json(&data[y][x],
+                                                    &columns[x]->type,
+                                                    json, NULL));
+        }
+    }
+
+    /* Sort rows by column values, for reproducibility. */
+    aux.data = data;
+    aux.columns = columns;
+    aux.n_columns = n_columns;
+    sort(rows->n, compare_rows, swap_rows, &aux);
+
+    /* Add column headings. */
+    table_init(&t);
+    table_set_caption(&t, xasprintf("%s table", ts->name));
+    for (x = 0; x < n_columns; x++) {
+        table_add_column(&t, "%s", columns[x]->name);
+    }
+
+    /* Print rows. */
+    for (y = 0; y < rows->n; y++) {
+        table_add_row(&t);
+        for (x = 0; x < n_columns; x++) {
+            table_add_cell_nocopy(&t, format_data(&data[y][x],
+                                                  &columns[x]->type));
+        }
+    }
+    table_print(&t);
+    table_destroy(&t);
+}
+
+static void
+do_dump(int argc OVS_UNUSED, char *argv[])
+{
+    const char *server = argv[1];
+    const char *database = argv[2];
+
+    struct jsonrpc_msg *request, *reply;
+    struct ovsdb_schema *schema;
+    struct json *transaction;
+    struct jsonrpc *rpc;
+    int error;
+
+    const struct shash_node **tables;
+    size_t n_tables;
+
+    size_t i;
+
+    rpc = open_jsonrpc(server);
+
+    schema = fetch_schema_from_rpc(rpc, database);
+    tables = shash_sort(&schema->tables);
+    n_tables = shash_count(&schema->tables);
+
+    /* Construct transaction to retrieve entire database. */
+    transaction = json_array_create_1(json_string_create(database));
+    for (i = 0; i < n_tables; i++) {
+        const struct ovsdb_table_schema *ts = tables[i]->data;
+        struct json *op, *columns;
+        struct shash_node *node;
+
+        columns = json_array_create_empty();
+        SHASH_FOR_EACH (node, &ts->columns) {
+            const struct ovsdb_column *column = node->data;
+
+            if (strcmp(column->name, "_version")) {
+                json_array_add(columns, json_string_create(column->name));
+            }
+        }
+
+        op = json_object_create();
+        json_object_put_string(op, "op", "select");
+        json_object_put_string(op, "table", tables[i]->name);
+        json_object_put(op, "where", json_array_create_empty());
+        json_object_put(op, "columns", columns);
+        json_array_add(transaction, op);
+    }
+
+    /* Send request, get reply. */
+    request = jsonrpc_create_request("transact", transaction, NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+
+    /* Print database contents. */
+    if (reply->result->type != JSON_ARRAY
+        || reply->result->u.array.n != n_tables) {
+        ovs_fatal(0, "reply is not array of %zu elements: %s",
+                  n_tables, json_to_string(reply->result, 0));
+    }
+    for (i = 0; i < n_tables; i++) {
+        const struct ovsdb_table_schema *ts = tables[i]->data;
+        const struct json *op_result = reply->result->u.array.elems[i];
+        struct json *rows;
+
+        if (op_result->type != JSON_OBJECT
+            || !(rows = shash_find_data(json_object(op_result), "rows"))
+            || rows->type != JSON_ARRAY) {
+            ovs_fatal(0, "%s table reply is not an object with a \"rows\" "
+                      "member array: %s",
+                      ts->name, json_to_string(op_result, 0));
+        }
+
+        dump_table(ts, &rows->u.array);
+    }
+}
+
+static void
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "list-dbs", 1, 1, do_list_dbs },
+    { "get-schema", 2, 2, do_get_schema },
+    { "list-tables", 2, 2, do_list_tables },
+    { "list-columns", 2, 3, do_list_columns },
+    { "transact", 2, 2, do_transact },
+    { "monitor", 3, 5, do_monitor },
+    { "dump", 2, 2, do_dump },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb-doc.in b/ovsdb/ovsdb-doc.in
new file mode 100755 (executable)
index 0000000..cb21c1f
--- /dev/null
@@ -0,0 +1,329 @@
+#! @PYTHON@
+
+from datetime import date
+import getopt
+import os
+import re
+import sys
+import xml.dom.minidom
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+from OVSDB import *
+
+argv0 = sys.argv[0]
+
+def textToNroff(s):
+    def escape(match):
+        c = match.group(0)
+        if c == '\\':
+            return r'\e'
+        elif c == '"':
+            return r'\(dq'
+        elif c == "'":
+            return r'\(cq'
+        else:
+            raise Error("bad escape")
+
+    s = re.sub('([\\\\"\'])', escape, s)
+    if s.startswith('.'):
+        s = '\\' + s
+    return s
+
+def escapeNroffLiteral(s):
+    return r'\fB%s\fR' % textToNroff(s)
+
+def inlineXmlToNroff(node, font):
+    if node.nodeType == node.TEXT_NODE:
+        return textToNroff(node.data)
+    elif node.nodeType == node.ELEMENT_NODE:
+        if node.tagName == 'code' or node.tagName == 'em':
+            s = r'\fB'
+            for child in node.childNodes:
+                s += inlineXmlToNroff(child, r'\fB')
+            return s + font
+        elif node.tagName == 'ref':
+            s = r'\fB'
+            if node.hasAttribute('column'):
+                s += node.attributes['column'].nodeValue
+            elif node.hasAttribute('table'):
+                s += node.attributes['table'].nodeValue
+            elif node.hasAttribute('group'):
+                s += node.attributes['group'].nodeValue
+            else:
+                raise Error("'ref' lacks column and table attributes")
+            return s + font
+        elif node.tagName == 'var':
+            s = r'\fI'
+            for child in node.childNodes:
+                s += inlineXmlToNroff(child, r'\fI')
+            return s + font
+        else:
+            raise Error("element <%s> unknown or invalid here" % node.tagName)
+    else:
+        raise Error("unknown node %s in inline xml" % node)
+
+def blockXmlToNroff(nodes, para='.PP'):
+    s = ''
+    for node in nodes:
+        if node.nodeType == node.TEXT_NODE:
+            s += textToNroff(node.data)
+            s = s.lstrip()
+        elif node.nodeType == node.ELEMENT_NODE:
+            if node.tagName == 'ul':
+                if s != "":
+                    s += "\n"
+                s += ".RS\n"
+                for liNode in node.childNodes:
+                    if (liNode.nodeType == node.ELEMENT_NODE
+                        and liNode.tagName == 'li'):
+                        s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
+                    elif (liNode.nodeType != node.TEXT_NODE
+                          or not liNode.data.isspace()):
+                        raise Error("<ul> element may only have <li> children")
+                s += ".RE\n"
+            elif node.tagName == 'dl':
+                if s != "":
+                    s += "\n"
+                s += ".RS\n"
+                prev = "dd"
+                for liNode in node.childNodes:
+                    if (liNode.nodeType == node.ELEMENT_NODE
+                        and liNode.tagName == 'dt'):
+                        if prev == 'dd':
+                            s += '.TP\n'
+                        else:
+                            s += '.TQ\n'
+                        prev = 'dt'
+                    elif (liNode.nodeType == node.ELEMENT_NODE
+                          and liNode.tagName == 'dd'):
+                        if prev == 'dd':
+                            s += '.IP\n'
+                        prev = 'dd'
+                    elif (liNode.nodeType != node.TEXT_NODE
+                          or not liNode.data.isspace()):
+                        raise Error("<dl> element may only have <dt> and <dd> children")
+                    s += blockXmlToNroff(liNode.childNodes, ".IP")
+                s += ".RE\n"
+            elif node.tagName == 'p':
+                if s != "":
+                    if not s.endswith("\n"):
+                        s += "\n"
+                    s += para + "\n"
+                s += blockXmlToNroff(node.childNodes, para)
+            else:
+                s += inlineXmlToNroff(node, r'\fR')
+        else:
+            raise Error("unknown node %s in block xml" % node)
+    if s != "" and not s.endswith('\n'):
+        s += '\n'
+    return s
+
+def typeAndConstraintsToNroff(column):
+    type = column.type.toEnglish(escapeNroffLiteral)
+    constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
+    if constraints:
+        type += ", " + constraints
+    return type
+
+def columnToNroff(columnName, column, node):
+    type = typeAndConstraintsToNroff(column)
+    s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
+    s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
+    return s
+
+def columnGroupToNroff(table, groupXml):
+    introNodes = []
+    columnNodes = []
+    for node in groupXml.childNodes:
+        if (node.nodeType == node.ELEMENT_NODE
+            and node.tagName in ('column', 'group')):
+            columnNodes += [node]
+        else:
+            introNodes += [node]
+
+    summary = []
+    intro = blockXmlToNroff(introNodes)
+    body = ''
+    for node in columnNodes:
+        if node.tagName == 'column':
+            columnName = node.attributes['name'].nodeValue
+            column = table.columns[columnName]
+            body += columnToNroff(columnName, column, node)
+            summary += [('column', columnName, column)]
+        elif node.tagName == 'group':
+            title = node.attributes["title"].nodeValue
+            subSummary, subIntro, subBody = columnGroupToNroff(table, node)
+            summary += [('group', title, subSummary)]
+            body += '.ST "%s:"\n' % textToNroff(title)
+            body += subIntro + subBody
+        else:
+            raise Error("unknown element %s in <table>" % node.tagName)
+    return summary, intro, body
+
+def tableSummaryToNroff(summary, level=0):
+    s = ""
+    for type, name, arg in summary:
+        if type == 'column':
+            
+            s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
+                r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
+        else:
+            if s != "":
+                s += "_\n"
+            s += """.T&
+li | s
+l | l.
+%s%s
+_
+""" % (r'\ \ ' * level, name)
+            s += tableSummaryToNroff(arg, level + 1)
+    return s
+
+def tableToNroff(schema, tableXml):
+    tableName = tableXml.attributes['name'].nodeValue
+    table = schema.tables[tableName]
+
+    s = """.bp
+.SS "%s Table"
+""" % tableName
+    summary, intro, body = columnGroupToNroff(table, tableXml)
+    s += intro
+
+    s += r"""
+.sp
+.ce 1
+\fB%s\fR Table Columns:
+.TS
+center box;
+l | l.
+Column Type
+=
+""" % tableName
+    s += tableSummaryToNroff(summary)
+    s += ".TE\n"
+
+    s += body
+    return s
+
+def docsToNroff(schemaFile, xmlFile, title=None):
+    schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
+    doc = xml.dom.minidom.parse(xmlFile).documentElement
+
+    schemaDate = os.stat(schemaFile).st_mtime
+    xmlDate = os.stat(xmlFile).st_mtime
+    d = date.fromtimestamp(max(schemaDate, xmlDate))
+    
+    if title == None:
+        title = schema.name
+
+    s = r'''.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
+.\" -*- nroff -*-
+.de TQ
+.  br
+.  ns
+.  TP "\\$1"
+..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+..
+''' % (title, d.strftime("%B %Y"))
+
+    s += '.SH "%s DATABASE"\n' % schema.name
+
+    tables = ""
+    introNodes = []
+    tableNodes = []
+    summary = []
+    for dbNode in doc.childNodes:
+        if (dbNode.nodeType == dbNode.ELEMENT_NODE
+            and dbNode.tagName == "table"):
+            tableNodes += [dbNode]
+
+            name = dbNode.attributes['name'].nodeValue
+            if dbNode.hasAttribute("title"):
+                title = dbNode.attributes['title'].nodeValue
+            else:
+                title = name + " configuration."
+            summary += [(name, title)]
+        else:
+            introNodes += [dbNode]
+
+    s += blockXmlToNroff(introNodes) + "\n"
+    tableSummary = r"""
+.sp
+.ce 1
+\fB%s\fR Database Tables:
+.TS
+center box;
+l | l
+lb | l.
+Table  Purpose
+=
+""" % schema.name
+    for name, title in summary:
+        tableSummary += "%s\t%s\n" % (name, textToNroff(title))
+    tableSummary += '.TE\n'
+    s += tableSummary
+    for node in tableNodes:
+        s += tableToNroff(schema, node) + "\n"
+    return s
+
+def usage():
+    print """\
+%(argv0)s: ovsdb schema documentation generator
+Prints documentation for an OVSDB schema as an nroff-formatted manpage.
+usage: %(argv0)s [OPTIONS] SCHEMA XML
+where SCHEMA is an OVSDB schema in JSON format
+  and XML is OVSDB documentation in XML format.
+
+The following options are also available:
+  --title=TITLE               use TITLE as title instead of schema name
+  -h, --help                  display this help message
+  -V, --version               display version information\
+""" % {'argv0': argv0}
+    sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        try:
+            options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
+                                              ['title=', 'help', 'version'])
+        except getopt.GetoptError, geo:
+            sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+            sys.exit(1)
+
+        title = None
+        for key, value in options:
+            if key == '--title':
+                title = value
+            elif key in ['-h', '--help']:
+                usage()
+            elif key in ['-V', '--version']:
+                print "ovsdb-doc (Open vSwitch) @VERSION@"
+            else:
+                sys.exit(0)
+            
+        if len(args) != 2:
+            sys.stderr.write("%s: exactly 2 non-option arguments required "
+                             "(use --help for help)\n" % argv0)
+            sys.exit(1)
+        
+        # XXX we should warn about undocumented tables or columns
+        s = docsToNroff(args[0], args[1])
+        for line in s.split("\n"):
+            line = line.strip()
+            if len(line):
+                print line
+            
+    except Error, e:
+        sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+        sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/ovsdb/ovsdb-idlc.1 b/ovsdb/ovsdb-idlc.1
new file mode 100644 (file)
index 0000000..a2de7ae
--- /dev/null
@@ -0,0 +1,73 @@
+.\" -*- nroff -*-
+.TH ovsdb\-idlc 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-idlc
+.
+.SH NAME
+ovsdb\-idlc \- Open vSwitch IDL (Interface Definition Language) compiler
+.
+.SH SYNOPSIS
+\fBovsdb\-idlc \fBannotate\fI schema annotations\fR 
+.br
+\fBovsdb\-idlc \fBc\-idl\-header\fI idl\fR
+.br
+\fBovsdb\-idlc \fBc\-idl\-source\fI idl\fR
+.br
+\fBovsdb\-idlc --help\fR
+.br
+\fBovsdb\-idlc --version\fR
+.
+.SH DESCRIPTION
+The \fBovsdb\-idlc\fR program is a command-line tool for translating
+Open vSwitch database interface definition language (IDL) schemas into
+other formats.  It is used while building Open vSwitch, not at
+installation or configuration time.  Thus, it is not normally
+installed as part of Open vSwitch.
+.
+.PP
+The \fIidl\fR files used as input for most \fBovsdb\-idlc\fR commands
+have the same format as the OVSDB schemas, specified in the OVSDB
+specification, with a few additions:
+.
+.IP "\fB""\fBidlPrefix\fR"" member of <database-schema>"
+This member, which is required, specifies a string that is prefixed to
+top-level names in C bindings.  It should probably end in an
+underscore.
+.
+.IP "\fB""\fBidlHeader\fR"" member of <database-schema>"
+This member, which is required, specifies the name of the IDL header.
+It will be output on an \fB#include\fR line in the source file
+generated by the C bindings.  It should include the bracketing
+\fB""\fR or \fB<>\fR.
+.
+.SS "Commands"
+.IP "\fBannotate\fI schema annotations\fR"
+Reads \fIschema\fR, which should be a file in JSON format (ordinarily
+an OVSDB schema file), then reads and executes the Python syntax
+fragment in \fIannotations\fR.  The Python syntax fragment is passed
+the JSON object as a local variable named \fBs\fR.  It may modify this
+data in any way.  After the Python code returns, the object as
+modified is re-serialized as JSON on standard output.
+.
+.IP "\fBc\-idl\-header\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a C header file that
+defines a structure for each table defined by the schema.
+.
+.IP "\fBc\-idl\-source\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a C source file that
+implements C bindings for the database defined by the schema.
+.
+.IP "\fBdoc\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a text file that
+documents the schema.  The output may have very long lines, so it
+makes sense to pipe it through, e.g. \fBfmt \-s\fR.
+.
+.SS "Options"
+.so lib/common.man
+.
+.SH "BUGS"
+\fBovsdb\-idlc\fR is more lenient about the format of OVSDB schemas
+than other OVSDB tools.  It may successfully parse schemas that, e.g.,
+\fBovsdb\-tool\fR rejects.
+.
+.SH "SEE ALSO"
+The OVSDB specification.
diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in
new file mode 100755 (executable)
index 0000000..6a0303d
--- /dev/null
@@ -0,0 +1,601 @@
+#! @PYTHON@
+
+import getopt
+import os
+import re
+import sys
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+from OVSDB import *
+
+argv0 = sys.argv[0]
+
+class Datum:
+    def __init__(self, type, values):
+        self.type = type
+        self.values = values
+
+    @staticmethod
+    def fromJson(type_, json):
+        if not type_.value:
+            if len(json) == 2 and json[0] == "set":
+                values = []
+                for atomJson in json[1]:
+                    values += [Atom.fromJson(type_.key, atomJson)]
+            else:
+                values = [Atom.fromJson(type_.key, json)]
+        else:
+            if len(json) != 2 or json[0] != "map":
+                raise Error("%s is not valid JSON for a map" % json)
+            values = []
+            for pairJson in json[1]:
+                values += [(Atom.fromJson(type_.key, pairJson[0]),
+                            Atom.fromJson(type_.value, pairJson[1]))]
+        return Datum(type_, values)
+
+    def cInitDatum(self, var):
+        if len(self.values) == 0:
+            return ["ovsdb_datum_init_empty(%s);" % var]
+
+        s = ["%s->n = %d;" % (var, len(self.values))]
+        s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
+              % (var, len(self.values), var)]
+
+        for i in range(len(self.values)):
+            key = self.values[i]
+            if self.type.value:
+                key = key[0]
+            s += key.cInitAtom("%s->keys[%d]" % (var, i))
+        
+        if self.type.value:
+            s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
+                  % (var, len(self.values), var)]
+            for i in range(len(self.values)):
+                value = self.values[i][1]
+                s += key.cInitAtom("%s->values[%d]" % (var, i))
+        else:
+            s += ["%s->values = NULL;" % var]
+
+        if len(self.values) > 1:
+            s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
+                  % (var, self.type.key.upper())]
+
+        return s
+
+def parseSchema(filename):
+    return IdlSchema.fromJson(json.load(open(filename, "r")))
+
+def annotateSchema(schemaFile, annotationFile):
+    schemaJson = json.load(open(schemaFile, "r"))
+    execfile(annotationFile, globals(), {"s": schemaJson})
+    json.dump(schemaJson, sys.stdout)
+
+def constify(cType, const):
+    if (const
+        and cType.endswith('*') and not cType.endswith('**')
+        and (cType.startswith('struct uuid') or cType.startswith('char'))):
+        return 'const %s' % cType
+    else:
+        return cType
+
+def cMembers(prefix, columnName, column, const):
+    type = column.type
+    if type.min == 1 and type.max == 1:
+        singleton = True
+        pointer = ''
+    else:
+        singleton = False
+        if type.isOptionalPointer():
+            pointer = ''
+        else:
+            pointer = '*'
+
+    if type.value:
+        key = {'name': "key_%s" % columnName,
+               'type': constify(type.key.toCType(prefix) + pointer, const),
+               'comment': ''}
+        value = {'name': "value_%s" % columnName,
+                 'type': constify(type.value.toCType(prefix) + pointer, const),
+                 'comment': ''}
+        members = [key, value]
+    else:
+        m = {'name': columnName,
+             'type': constify(type.key.toCType(prefix) + pointer, const),
+             'comment': type.cDeclComment()}
+        members = [m]
+
+    if not singleton and not type.isOptionalPointer():
+        members.append({'name': 'n_%s' % columnName,
+                        'type': 'size_t ',
+                        'comment': ''})
+    return members
+
+def printCIDLHeader(schemaFile):
+    schema = parseSchema(schemaFile)
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#ifndef %(prefix)sIDL_HEADER
+#define %(prefix)sIDL_HEADER 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "ovsdb-idl-provider.h"
+#include "uuid.h"''' % {'prefix': prefix.upper()}
+
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+
+        print "\f"
+        print "/* %s table. */" % tableName
+        print "struct %s {" % structName
+        print "\tstruct ovsdb_idl_row header_;"
+        for columnName, column in sorted(table.columns.iteritems()):
+            print "\n\t/* %s column. */" % columnName
+            for member in cMembers(prefix, columnName, column, False):
+                print "\t%(type)s%(name)s;%(comment)s" % member
+        print "};"
+
+        # Column indexes.
+        printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
+                   for columnName in sorted(table.columns)]
+                  + ["%s_N_COLUMNS" % structName.upper()])
+
+        print
+        for columnName in table.columns:
+            print "#define %(s)s_col_%(c)s (%(s)s_columns[%(S)s_COL_%(C)s])" % {
+                's': structName,
+                'S': structName.upper(),
+                'c': columnName,
+                'C': columnName.upper()}
+
+        print "\nextern struct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (structName, structName.upper())
+
+        print '''
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *);
+const struct %(s)s *%(s)s_next(const struct %(s)s *);
+#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))
+
+void %(s)s_delete(const struct %(s)s *);
+struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
+''' % {'s': structName, 'S': structName.upper()}
+
+        for columnName, column in sorted(table.columns.iteritems()):
+            print 'void %(s)s_verify_%(c)s(const struct %(s)s *);' % {'s': structName, 'c': columnName}
+
+        print
+        for columnName, column in sorted(table.columns.iteritems()):
+
+            print 'void %(s)s_set_%(c)s(const struct %(s)s *,' % {'s': structName, 'c': columnName},
+            args = ['%(type)s%(name)s' % member for member
+                    in cMembers(prefix, columnName, column, True)]
+            print '%s);' % ', '.join(args)
+
+    # Table indexes.
+    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in sorted(schema.tables)] + ["%sN_TABLES" % prefix.upper()])
+    print
+    for tableName in schema.tables:
+        print "#define %(p)stable_%(t)s (%(p)stable_classes[%(P)sTABLE_%(T)s])" % {
+            'p': prefix,
+            'P': prefix.upper(),
+            't': tableName.lower(),
+            'T': tableName.upper()}
+    print "\nextern struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
+
+    print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+    print "\nvoid %sinit(void);" % prefix
+    print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+def printEnum(members):
+    if len(members) == 0:
+        return
+
+    print "\nenum {";
+    for member in members[:-1]:
+        print "    %s," % member
+    print "    %s" % members[-1]
+    print "};"
+
+def printCIDLSource(schemaFile):
+    schema = parseSchema(schemaFile)
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#include <config.h>
+#include %s
+#include <assert.h>
+#include <limits.h>
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+
+static bool inited;
+''' % schema.idlHeader
+
+    # Cast functions.
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+        print '''
+static struct %(s)s *
+%(s)s_cast(const struct ovsdb_idl_row *row)
+{
+    return row ? CONTAINER_OF(row, struct %(s)s, header_) : NULL;
+}\
+''' % {'s': structName}
+
+
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "\f"
+        print "/* %s table. */" % (tableName)
+
+        # Parse functions.
+        for columnName, column in sorted(table.columns.iteritems()):
+            print '''
+static void
+%(s)s_parse_%(c)s(struct ovsdb_idl_row *row_, const struct ovsdb_datum *datum)
+{
+    struct %(s)s *row = %(s)s_cast(row_);''' % {'s': structName,
+                                                'c': columnName}
+
+            type = column.type
+            if type.value:
+                keyVar = "row->key_%s" % columnName
+                valueVar = "row->value_%s" % columnName
+            else:
+                keyVar = "row->%s" % columnName
+                valueVar = None
+
+            if (type.min == 1 and type.max == 1) or type.isOptionalPointer():
+                print
+                print "    assert(inited);"
+                print "    if (datum->n >= 1) {"
+                if not type.key.refTable:
+                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key.type)
+                else:
+                    print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
+
+                if valueVar:
+                    if type.value.refTable:
+                        print "        %s = datum->values[0].%s;" % (valueVar, type.value.type)
+                    else:
+                        print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
+                print "    } else {"
+                print "        %s" % type.key.initCDefault(keyVar, type.min == 0)
+                if valueVar:
+                    print "        %s" % type.value.initCDefault(valueVar, type.min == 0)
+                print "    }"
+            else:
+                if type.max != 'unlimited':
+                    print "    size_t n = MIN(%d, datum->n);" % type.max
+                    nMax = "n"
+                else:
+                    nMax = "datum->n"
+                print "    size_t i;"
+                print
+                print "    assert(inited);"
+                print "    %s = NULL;" % keyVar
+                if valueVar:
+                    print "    %s = NULL;" % valueVar
+                print "    row->n_%s = 0;" % columnName
+                print "    for (i = 0; i < %s; i++) {" % nMax
+                refs = []
+                if type.key.refTable:
+                    print "        struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.key.refTable.lower(), prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
+                    keySrc = "keyRow"
+                    refs.append('keyRow')
+                else:
+                    keySrc = "datum->keys[i].%s" % type.key.type
+                if type.value and type.value.refTable:
+                    print "        struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.value.refTable.lower(), prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
+                    valueSrc = "valueRow"
+                    refs.append('valueRow')
+                elif valueVar:
+                    valueSrc = "datum->values[i].%s" % type.value.type
+                if refs:
+                    print "        if (%s) {" % ' && '.join(refs)
+                    indent = "            "
+                else:
+                    indent = "        "
+                print "%sif (!row->n_%s) {" % (indent, columnName)
+                print "%s    %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar)
+                if valueVar:
+                    print "%s    %s = xmalloc(%s * sizeof %s);" % (indent, valueVar, nMax, valueVar)
+                print "%s}" % indent
+                print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc)
+                if valueVar:
+                    print "%s%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc)
+                print "%srow->n_%s++;" % (indent, columnName)
+                if refs:
+                    print "        }"
+                print "    }"
+            print "}"
+
+        # Unparse functions.
+        for columnName, column in sorted(table.columns.iteritems()):
+            type = column.type
+            if (type.min != 1 or type.max != 1) and not type.isOptionalPointer():
+                print '''
+static void
+%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row_)
+{
+    struct %(s)s *row = %(s)s_cast(row_);
+
+    assert(inited);''' % {'s': structName, 'c': columnName}
+                if type.value:
+                    keyVar = "row->key_%s" % columnName
+                    valueVar = "row->value_%s" % columnName
+                else:
+                    keyVar = "row->%s" % columnName
+                    valueVar = None
+                print "    free(%s);" % keyVar
+                if valueVar:
+                    print "    free(%s);" % valueVar
+                print '}'
+            else:
+                print '''
+static void
+%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row OVS_UNUSED)
+{
+    /* Nothing to do. */
+}''' % {'s': structName, 'c': columnName}
+        # First, next functions.
+        print '''
+const struct %(s)s *
+%(s)s_first(const struct ovsdb_idl *idl)
+{
+    return %(s)s_cast(ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+
+const struct %(s)s *
+%(s)s_next(const struct %(s)s *row)
+{
+    return %(s)s_cast(ovsdb_idl_next_row(&row->header_));
+}''' % {'s': structName,
+        'p': prefix,
+        'P': prefix.upper(),
+        'T': tableName.upper()}
+
+        print '''
+void
+%(s)s_delete(const struct %(s)s *row)
+{
+    ovsdb_idl_txn_delete(&row->header_);
+}
+
+struct %(s)s *
+%(s)s_insert(struct ovsdb_idl_txn *txn)
+{
+    return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+''' % {'s': structName,
+       'p': prefix,
+       'P': prefix.upper(),
+       'T': tableName.upper()}
+
+        # Verify functions.
+        for columnName, column in sorted(table.columns.iteritems()):
+            print '''
+void
+%(s)s_verify_%(c)s(const struct %(s)s *row)
+{
+    assert(inited);
+    ovsdb_idl_txn_verify(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s]);
+}''' % {'s': structName,
+        'S': structName.upper(),
+        'c': columnName,
+        'C': columnName.upper()}
+
+        # Set functions.
+        for columnName, column in sorted(table.columns.iteritems()):
+            type = column.type
+            print '\nvoid'
+            members = cMembers(prefix, columnName, column, True)
+            keyVar = members[0]['name']
+            nVar = None
+            valueVar = None
+            if type.value:
+                valueVar = members[1]['name']
+                if len(members) > 2:
+                    nVar = members[2]['name']
+            else:
+                if len(members) > 1:
+                    nVar = members[1]['name']
+            print '%(s)s_set_%(c)s(const struct %(s)s *row, %(args)s)' % \
+                {'s': structName, 'c': columnName,
+                 'args': ', '.join(['%(type)s%(name)s' % m for m in members])}
+            print "{"
+            print "    struct ovsdb_datum datum;"
+            if type.min == 1 and type.max == 1:
+                print
+                print "    assert(inited);"
+                print "    datum.n = 1;"
+                print "    datum.keys = xmalloc(sizeof *datum.keys);"
+                print "    " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
+                if type.value:
+                    print "    datum.values = xmalloc(sizeof *datum.values);"
+                    print "    "+ type.value.copyCValue("datum.values[0].%s" % type.value.type, valueVar)
+                else:
+                    print "    datum.values = NULL;"
+            elif type.isOptionalPointer():
+                print
+                print "    assert(inited);"
+                print "    if (%s) {" % keyVar
+                print "        datum.n = 1;"
+                print "        datum.keys = xmalloc(sizeof *datum.keys);"
+                print "        " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
+                print "    } else {"
+                print "        datum.n = 0;"
+                print "        datum.keys = NULL;"
+                print "    }"
+                print "    datum.values = NULL;"
+            else:
+                print "    size_t i;"
+                print
+                print "    assert(inited);"
+                print "    datum.n = %s;" % nVar
+                print "    datum.keys = xmalloc(%s * sizeof *datum.keys);" % nVar
+                if type.value:
+                    print "    datum.values = xmalloc(%s * sizeof *datum.values);" % nVar
+                else:
+                    print "    datum.values = NULL;"
+                print "    for (i = 0; i < %s; i++) {" % nVar
+                print "        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type, "%s[i]" % keyVar)
+                if type.value:
+                    print "        " + type.value.copyCValue("datum.values[i].%s" % type.value.type, "%s[i]" % valueVar)
+                print "    }"
+            print "    ovsdb_idl_txn_write(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s], &datum);" \
+                % {'s': structName,
+                   'S': structName.upper(),
+                   'C': columnName.upper()}
+            print "}"
+
+        # Table columns.
+        print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (
+            structName, structName.upper())
+        print """
+static void\n%s_columns_init(void)
+{
+    struct ovsdb_idl_column *c;\
+""" % structName
+        for columnName, column in sorted(table.columns.iteritems()):
+            cs = "%s_col_%s" % (structName, columnName)
+            d = {'cs': cs, 'c': columnName, 's': structName}
+            print
+            print "    /* Initialize %(cs)s. */" % d
+            print "    c = &%(cs)s;" % d
+            print "    c->name = \"%(c)s\";" % d
+            print column.type.cInitType("    ", "c->type")
+            print "    c->parse = %(s)s_parse_%(c)s;" % d
+            print "    c->unparse = %(s)s_unparse_%(c)s;" % d
+        print "}"
+
+    # Table classes.
+    print "\f"
+    print "struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    {\"%s\"," % tableName
+        print "     %s_columns, ARRAY_SIZE(%s_columns)," % (
+            structName, structName)
+        print "     sizeof(struct %s)}," % structName
+    print "};"
+
+    # IDL class.
+    print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix
+    print "    \"%s\", %stable_classes, ARRAY_SIZE(%stable_classes)" % (
+        schema.name, prefix, prefix)
+    print "};"
+
+    # global init function
+    print """
+void
+%sinit(void)
+{
+    if (inited) {
+        return;
+    }
+    inited = true;
+""" % prefix
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    %s_columns_init();" % structName
+    print "}"
+
+def ovsdb_escape(string):
+    def escape(match):
+        c = match.group(0)
+        if c == '\0':
+            raise Error("strings may not contain null bytes")
+        elif c == '\\':
+            return '\\\\'
+        elif c == '\n':
+            return '\\n'
+        elif c == '\r':
+            return '\\r'
+        elif c == '\t':
+            return '\\t'
+        elif c == '\b':
+            return '\\b'
+        elif c == '\a':
+            return '\\a'
+        else:
+            return '\\x%02x' % ord(c)
+    return re.sub(r'["\\\000-\037]', escape, string)
+
+
+
+def usage():
+    print """\
+%(argv0)s: ovsdb schema compiler
+usage: %(argv0)s [OPTIONS] COMMAND ARG...
+
+The following commands are supported:
+  annotate SCHEMA ANNOTATIONS print SCHEMA combined with ANNOTATIONS
+  c-idl-header IDL            print C header file for IDL
+  c-idl-source IDL            print C source file for IDL implementation
+  nroff IDL                   print schema documentation in nroff format
+
+The following options are also available:
+  -h, --help                  display this help message
+  -V, --version               display version information\
+""" % {'argv0': argv0}
+    sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        try:
+            options, args = getopt.gnu_getopt(sys.argv[1:], 'C:hV',
+                                              ['directory',
+                                               'help',
+                                               'version'])
+        except getopt.GetoptError, geo:
+            sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+            sys.exit(1)
+            
+        for key, value in options:
+            if key in ['-h', '--help']:
+                usage()
+            elif key in ['-V', '--version']:
+                print "ovsdb-idlc (Open vSwitch) @VERSION@"
+            elif key in ['-C', '--directory']:
+                os.chdir(value)
+            else:
+                sys.exit(0)
+            
+        optKeys = [key for key, value in options]
+
+        if not args:
+            sys.stderr.write("%s: missing command argument "
+                             "(use --help for help)\n" % argv0)
+            sys.exit(1)
+
+        commands = {"annotate": (annotateSchema, 2),
+                    "c-idl-header": (printCIDLHeader, 1),
+                    "c-idl-source": (printCIDLSource, 1)}
+
+        if not args[0] in commands:
+            sys.stderr.write("%s: unknown command \"%s\" "
+                             "(use --help for help)\n" % (argv0, args[0]))
+            sys.exit(1)
+
+        func, n_args = commands[args[0]]
+        if len(args) - 1 != n_args:
+            sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
+                             "provided\n"
+                             % (argv0, args[0], n_args, len(args) - 1))
+            sys.exit(1)
+
+        func(*args[1:])
+    except Error, e:
+        sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+        sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
new file mode 100644 (file)
index 0000000..14d8894
--- /dev/null
@@ -0,0 +1,89 @@
+.\" -*- nroff -*-
+.TH ovsdb\-server 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-server
+.
+.SH NAME
+ovsdb\-server \- Open vSwitch database server
+.
+.SH SYNOPSIS
+\fBovsdb\-server\fR
+\fIdatabase\fR
+[\fB--remote=\fIremote\fR]\&...
+[\fB--run=\fIcommand\fR]
+.so lib/daemon-syn.man
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-server\fR program provides RPC interfaces to an Open
+vSwitch database (OVSDB).  It supports JSON-RPC client connections
+over active or passive TCP/IP or Unix domain sockets.
+.PP
+The name of the OVSDB file must be specified on the command line as
+\fIdatabase\fR, which must already have been created and initialized
+using, for example, \fBovsdb\-tool create\fR.
+.
+.SH OPTIONS
+.
+.IP "\fB\-\-remote=\fIremote\fR"
+Adds \fIremote\fR as a connection method used by \fBovsdb\-server\fR.
+\fIremote\fR must take one of the following forms:
+.
+.RS
+.so ovsdb/remote-passive.man
+.so ovsdb/remote-active.man
+.
+.IP "\fBdb:\fItable\fB,\fIcolumn\fR"
+Reads additional connection methods from \fIcolumn\fR in all of the
+rows in \fItable\fR within the \fBovsdb\-server\fR database.  The
+\fIcolumn\fR must have type string or set of strings.  The connection
+methods in the column must have one of the forms described above.  As
+the contents of \fIcolumn\fR changes, \fBovsdb\-server\fR also adds
+and drops connection methods accordingly.
+.RE
+.
+.IP "\fB\-\-run=\fIcommand\fR]"
+Ordinarily \fBovsdb\-server\fR runs forever, or until it is told to
+exit (see \fBRUNTIME MANAGEMENT COMMANDS\fR below).  With this option,
+\fBovsdb\-server\fR instead starts a shell subprocess running
+\fIcommand\fR.  When the subprocess terminates, \fBovsdb\-server\fR
+also exits gracefully.  If the subprocess exits normally with exit
+code 0, then \fBovsdb\-server\fR exits with exit code 0 also;
+otherwise, it exits with exit code 1.
+.IP
+This option can be useful where a database server is needed only to
+run a single command, e.g.:
+.B "ovsdb-server --remote=punix:socket --run='ovsdb-client dump unix:socket Open_vSwitch'"
+.SS "Daemon Options"
+.so lib/daemon.man
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Public Key Infrastructure Options"
+The options described below for configuring the SSL public key
+infrastructure accept a special syntax for obtaining their
+configuration from the database.  If any of these options is given
+\fBdb:\fItable\fB,\fIcolumn\fR as its argument, then the actual file
+name is read from the specified \fIcolumn\fR in \fItable\fR within the
+\fBovsdb\-server\fR database.  The \fIcolumn\fR must have type string
+or set of strings.  The first nonempty string in the table is taken as
+the file name.  (This means that ordinarily there should be at most
+one row in \fItable\fR.)
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
+.SS "Other Options"
+.so lib/common.man
+.SH "RUNTIME MANAGEMENT COMMANDS"
+\fBovs\-appctl\fR(8) can send commands to a running
+\fBovsdb\-server\fR process.  The currently supported commands are
+described below.
+.SS "OVSDB-SERVER COMMANDS"
+These commands are specific to \fBovsdb\-server\fR.
+.IP "\fBexit\fR"
+Causes \fBovsdb\-server\fR to gracefully terminate.
+.IP "\fBovsdb-server/compact\fR"
+Compacts the database in-place.  The database is also automatically
+compacted occasionally.
+.so lib/vlog-unixctl.man
+.SH "SEE ALSO"
+.
+.BR ovsdb\-tool (1).
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644 (file)
index 0000000..eae20f9
--- /dev/null
@@ -0,0 +1,446 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "column.h"
+#include "command-line.h"
+#include "daemon.h"
+#include "file.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-data.h"
+#include "ovsdb-types.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "row.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "svec.h"
+#include "table.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+#if HAVE_OPENSSL
+/* SSL configuration. */
+static char *private_key_file;
+static char *certificate_file;
+static char *ca_cert_file;
+static bool bootstrap_ca_cert;
+#endif
+
+static unixctl_cb_func ovsdb_server_exit;
+static unixctl_cb_func ovsdb_server_compact;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+                          struct shash *remotes, char **unixctl_pathp,
+                          char **run_command);
+static void usage(void) NO_RETURN;
+
+static void reconfigure_from_db(struct ovsdb_jsonrpc_server *jsonrpc,
+                                const struct ovsdb *db, struct shash *remotes);
+
+int
+main(int argc, char *argv[])
+{
+    char *unixctl_path = NULL;
+    char *run_command = NULL;
+    struct unixctl_server *unixctl;
+    struct ovsdb_jsonrpc_server *jsonrpc;
+    struct shash remotes;
+    struct ovsdb_error *error;
+    struct ovsdb_file *file;
+    struct ovsdb *db;
+    struct process *run_process;
+    char *file_name;
+    bool exiting;
+    int retval;
+
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    signal(SIGPIPE, SIG_IGN);
+    process_init();
+
+    parse_options(argc, argv, &file_name, &remotes, &unixctl_path,
+                  &run_command);
+
+    die_if_already_running();
+    daemonize_start();
+
+    error = ovsdb_file_open(file_name, false, &db, &file);
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+
+    jsonrpc = ovsdb_jsonrpc_server_create(db);
+    reconfigure_from_db(jsonrpc, db, &remotes);
+
+    retval = unixctl_server_create(unixctl_path, &unixctl);
+    if (retval) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (run_command) {
+        char *run_argv[4];
+
+        run_argv[0] = "/bin/sh";
+        run_argv[1] = "-c";
+        run_argv[2] = run_command;
+        run_argv[3] = NULL;
+
+        retval = process_start(run_argv, NULL, 0, NULL, 0, &run_process);
+        if (retval) {
+            ovs_fatal(retval, "%s: process failed to start", run_command);
+        }
+    } else {
+        run_process = NULL;
+    }
+
+    daemonize_complete();
+
+    unixctl_command_register("exit", ovsdb_server_exit, &exiting);
+    unixctl_command_register("ovsdb-server/compact", ovsdb_server_compact,
+                             file);
+
+    exiting = false;
+    while (!exiting) {
+        reconfigure_from_db(jsonrpc, db, &remotes);
+        ovsdb_jsonrpc_server_run(jsonrpc);
+        unixctl_server_run(unixctl);
+        ovsdb_trigger_run(db, time_msec());
+        if (run_process && process_exited(run_process)) {
+            exiting = true;
+        }
+
+        ovsdb_jsonrpc_server_wait(jsonrpc);
+        unixctl_server_wait(unixctl);
+        ovsdb_trigger_wait(db, time_msec());
+        if (run_process) {
+            process_wait(run_process);
+        }
+        poll_block();
+    }
+    ovsdb_jsonrpc_server_destroy(jsonrpc);
+    ovsdb_destroy(db);
+    shash_destroy(&remotes);
+    unixctl_server_destroy(unixctl);
+
+    if (run_process && process_exited(run_process)) {
+        int status = process_status(run_process);
+        if (status) {
+            ovs_fatal(0, "%s: child exited, %s",
+                      run_command, process_status_msg(status));
+        }
+    }
+
+    return 0;
+}
+
+static void
+parse_db_string_column(const struct ovsdb *db,
+                       const char *name_,
+                       const struct ovsdb_table **tablep,
+                       const struct ovsdb_column **columnp)
+{
+    char *name, *table_name, *column_name;
+    const struct ovsdb_column *column;
+    const struct ovsdb_table *table;
+    char *save_ptr = NULL;
+
+    name = xstrdup(name_);
+    strtok_r(name, ":", &save_ptr); /* "db:" */
+    table_name = strtok_r(NULL, ",", &save_ptr);
+    column_name = strtok_r(NULL, ",", &save_ptr);
+    if (!table_name || !column_name) {
+        ovs_fatal(0, "\"%s\": invalid syntax", name_);
+    }
+
+    table = ovsdb_get_table(db, table_name);
+    if (!table) {
+        ovs_fatal(0, "\"%s\": no table named %s", name_, table_name);
+    }
+
+    column = ovsdb_table_schema_get_column(table->schema, column_name);
+    if (!column) {
+        ovs_fatal(0, "\"%s\": table \"%s\" has no column \"%s\"",
+                  name_, table_name, column_name);
+    }
+    free(name);
+
+    if (column->type.key.type != OVSDB_TYPE_STRING
+        || column->type.value.type != OVSDB_TYPE_VOID) {
+        ovs_fatal(0, "\"%s\": table \"%s\" column \"%s\" is "
+                  "not string or set of strings",
+                  name_, table->schema->name, column->name);
+    }
+
+    *columnp = column;
+    *tablep = table;
+}
+
+#if HAVE_OPENSSL
+static const char *
+query_db_string(const struct ovsdb *db, const char *name)
+{
+    if (!name || strncmp(name, "db:", 3)) {
+        return name;
+    } else {
+        const struct ovsdb_column *column;
+        const struct ovsdb_table *table;
+        const struct ovsdb_row *row;
+
+        parse_db_string_column(db, name, &table, &column);
+
+        HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
+            const struct ovsdb_datum *datum;
+            size_t i;
+
+            datum = &row->fields[column->index];
+            for (i = 0; i < datum->n; i++) {
+                if (datum->keys[i].string[0]) {
+                    return datum->keys[i].string;
+                }
+            }
+        }
+        return NULL;
+    }
+}
+#endif /* HAVE_OPENSSL */
+
+static void
+query_db_remotes(const char *name, const struct ovsdb *db,
+                 struct shash *remotes)
+{
+    const struct ovsdb_column *column;
+    const struct ovsdb_table *table;
+    const struct ovsdb_row *row;
+
+    parse_db_string_column(db, name, &table, &column);
+
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
+        const struct ovsdb_datum *datum;
+        size_t i;
+
+        datum = &row->fields[column->index];
+        for (i = 0; i < datum->n; i++) {
+            shash_add_once(remotes, datum->keys[i].string, NULL);
+        }
+    }
+}
+
+/* Reconfigures ovsdb-server based on information in the database. */
+static void
+reconfigure_from_db(struct ovsdb_jsonrpc_server *jsonrpc,
+                    const struct ovsdb *db, struct shash *remotes)
+{
+    struct shash resolved_remotes;
+    struct shash_node *node;
+
+    /* Configure remotes. */
+    shash_init(&resolved_remotes);
+    SHASH_FOR_EACH (node, remotes) {
+        const char *name = node->name;
+
+        if (!strncmp(name, "db:", 3)) {
+            query_db_remotes(name, db, &resolved_remotes);
+        } else {
+            shash_add_once(&resolved_remotes, name, NULL);
+        }
+    }
+    ovsdb_jsonrpc_server_set_remotes(jsonrpc, &resolved_remotes);
+    shash_destroy(&resolved_remotes);
+
+#if HAVE_OPENSSL
+    /* Configure SSL. */
+    stream_ssl_set_private_key_file(query_db_string(db, private_key_file));
+    stream_ssl_set_certificate_file(query_db_string(db, certificate_file));
+    stream_ssl_set_ca_cert_file(query_db_string(db, ca_cert_file),
+                                bootstrap_ca_cert);
+#endif
+}
+
+static void
+ovsdb_server_exit(struct unixctl_conn *conn, const char *args OVS_UNUSED,
+                  void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+    unixctl_command_reply(conn, 200, NULL);
+}
+
+static void
+ovsdb_server_compact(struct unixctl_conn *conn, const char *args OVS_UNUSED,
+                     void *file_)
+{
+    struct ovsdb_file *file = file_;
+    struct ovsdb_error *error;
+
+    VLOG_INFO("compacting database by user request");
+    error = ovsdb_file_compact(file);
+    if (!error) {
+        unixctl_command_reply(conn, 200, NULL);
+    } else {
+        char *s = ovsdb_error_to_string(error);
+        ovsdb_error_destroy(error);
+        unixctl_command_reply(conn, 503, s);
+        free(s);
+    }
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+              struct shash *remotes, char **unixctl_pathp,
+              char **run_command)
+{
+    enum {
+        OPT_DUMMY = UCHAR_MAX + 1,
+        OPT_REMOTE,
+        OPT_UNIXCTL,
+        OPT_RUN,
+        OPT_BOOTSTRAP_CA_CERT,
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"remote",      required_argument, 0, OPT_REMOTE},
+        {"unixctl",     required_argument, 0, OPT_UNIXCTL},
+        {"run",         required_argument, 0, OPT_RUN},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+        VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        {"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);
+
+    shash_init(remotes);
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_REMOTE:
+            shash_add_once(remotes, optarg, NULL);
+            break;
+
+        case OPT_UNIXCTL:
+            *unixctl_pathp = optarg;
+            break;
+
+        case OPT_RUN:
+            *run_command = optarg;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        VLOG_OPTION_HANDLERS
+        DAEMON_OPTION_HANDLERS
+        LEAK_CHECKER_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        case 'p':
+            private_key_file = optarg;
+            break;
+
+        case 'c':
+            certificate_file = optarg;
+            break;
+
+        case 'C':
+            ca_cert_file = optarg;
+            bootstrap_ca_cert = false;
+            break;
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            ca_cert_file = optarg;
+            bootstrap_ca_cert = true;
+            break;
+#endif
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc > 1) {
+        ovs_fatal(0, "database file is only non-option argument; "
+                "use --help for usage");
+    } else if (argc < 1) {
+        ovs_fatal(0, "missing database file argument; use --help for usage");
+    }
+
+    *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database server\n"
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a database file in ovsdb format.\n",
+           program_name, program_name);
+    printf("\nJSON-RPC options (may be specified any number of times):\n"
+           "  --remote=REMOTE         connect or listen to REMOTE\n");
+    stream_usage("JSON-RPC", true, true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  --run COMMAND           run COMMAND as subprocess then exit\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    leak_checker_usage();
+    exit(EXIT_SUCCESS);
+}
diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in
new file mode 100644 (file)
index 0000000..c2c2ce3
--- /dev/null
@@ -0,0 +1,111 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-tool 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-tool
+.
+.SH NAME
+ovsdb\-tool \- Open vSwitch database management utility
+.
+.SH SYNOPSIS
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBcreate\fI db schema\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBquery\fI db transaction\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBtransact\fI db transaction\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] [\fB-m\fR | \fB--more\fR]... \fBshow\-log\fI db\fR
+.br
+\fBovsdb\-tool help\fR
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-tool\fR program is a command-line tool for managing Open
+vSwitch database (OVSDB) files.  It does not interact directly with
+running Open vSwitch database servers (instead, use
+\fBovsdb\-client\fR).
+.
+.SS "Basic Commands"
+.IP "\fBcreate\fI db schema\fR"
+Reads an OVSDB schema from the file named \fIschema\fR and creates a
+new OVSDB database file named \fIdb\fR using that schema.  The new
+database is initially empty.  This command will not overwrite an
+existing \fIdb\fR.
+.IP
+\fIschema\fR must contain an OVSDB schema in JSON format.  Refer to
+the OVSDB specification for details.
+.
+.IP "\fBcompact\fI db \fR[\fItarget\fR]"
+Reads \fIdb\fR and writes a compacted version.  If \fItarget\fR is
+specified, the compacted version is written as a new file named
+\fItarget\fR, which must not already exist.  If \fItarget\fR is
+omitted, then the compacted version of the database replaces \fIdb\fR
+in-place.
+.
+.IP "\fBconvert\fI db schema \fR[\fItarget\fR]"
+Reads \fIdb\fR, translating it into to the schema specified in
+\fIschema\fR, and writes out the new interpretation.  If \fItarget\fR
+is specified, the translated version is written as a new file named
+\fItarget\fR, which must not already exist.  If \fItarget\fR is
+omitted, then the translated version of the database replaces \fIdb\fR
+in-place.
+.IP
+This command can do simple ``upgrades'' and ``downgrades'' on a
+database's schema.  The data in \fIdb\fR must be valid when
+interpreted under \fIschema\fR, with only one exception: data in
+\fIdb\fR for tables and columns that do not exist in \fIschema\fR are
+ignored.  Columns that exist in \fIschema\fR but not in \fIdb\fR are
+set to their default values.  All of \fIschema\fR's constraints apply
+in full.
+.
+.IP "\fBquery\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the
+results.  The \fItransaction\fR must be a JSON array in the format of
+the \fBparams\fR array for the JSON-RPC \fBtransact\fR method, as
+described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened for read-only access, so this command may
+safely run concurrently with other database activity, including
+\fBovsdb-server\fR and other database writers.  The \fItransaction\fR
+may specify database modifications, but these will have no effect on
+\fIdb\fR.
+.
+.IP "\fBtransact\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, prints the results,
+and commits any changes to \fIdb\fR.  The \fItransaction\fR must be a
+JSON array in the format of the \fBparams\fR array for the JSON-RPC
+\fBtransact\fR method, as described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened and locked for read/write access, so this
+command will fail if the database is opened for writing by any other
+process, including \fBovsdb-server\fR(1).  Use \fBovsdb\-client\fR(1),
+instead, to write to a database that is served by
+\fBovsdb-server\fR(1).
+.
+.IP "\fBshow-log\fI db\fR"
+Prints a summary of the records in \fBdb\fR's log, including the time
+and date at which each database change occurred and any associated
+comment.  This may be useful for debugging.
+.PP
+To increase the verbosity of output, add \fB-m\fR (or \fB--more\fR)
+one or more times to the command line.  With one \fB-m\fR,
+\fBshow\-log\fR prints a summary of the records added, deleted, or
+modified by each transaction.  With two \fB-m\fRs, \fBshow\-log\fR
+also prints the values of the columns modified by each change to a
+record.
+.
+.SH OPTIONS
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644 (file)
index 0000000..1c9e920
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "lockfile.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "socket-util.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+/* -m, --more: Verbosity level for "show-log" command output. */
+static int show_log_verbosity;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"more", no_argument, 0, 'm'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'm':
+            show_log_verbosity++;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database management utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  create DB SCHEMA   create DB with the given SCHEMA\n"
+           "  compact DB [DST]   compact DB in-place (or to DST)\n"
+           "  convert DB SCHEMA [DST]   convert DB to SCHEMA (to DST)\n"
+           "  extract-schema DB  print DB's schema on stdout\n"
+           "  query DB TRNS      execute read-only transaction on DB\n"
+           "  transact DB TRNS   execute read/write transaction on DB\n"
+           "  show-log DB        prints information about DB's log entries\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -m, --more                  increase show-log verbosity\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+static void
+do_create(int argc OVS_UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    const char *schema_file_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct ovsdb_log *log;
+    struct json *json;
+
+    /* Read schema from file and convert to JSON. */
+    check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+    json = ovsdb_schema_to_json(schema);
+    ovsdb_schema_destroy(schema);
+
+    /* Create database file. */
+    check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_LOG_CREATE,
+                                     -1, &log));
+    check_ovsdb_error(ovsdb_log_write(log, json));
+    check_ovsdb_error(ovsdb_log_commit(log));
+    ovsdb_log_close(log);
+
+    json_destroy(json);
+}
+
+static void
+compact_or_convert(const char *src_name, const char *dst_name,
+                   const struct ovsdb_schema *new_schema,
+                   const char *comment)
+{
+    struct lockfile *src_lock;
+    struct lockfile *dst_lock;
+    bool in_place = dst_name == NULL;
+    struct ovsdb *db;
+    int retval;
+
+    /* Lock the source, if we will be replacing it. */
+    if (in_place) {
+        retval = lockfile_lock(src_name, INT_MAX, &src_lock);
+        if (retval) {
+            ovs_fatal(retval, "%s: failed to lock lockfile", src_name);
+        }
+    }
+
+    /* Get (temporary) destination and lock it. */
+    if (in_place) {
+        dst_name = xasprintf("%s.tmp", src_name);
+    }
+    retval = lockfile_lock(dst_name, INT_MAX, &dst_lock);
+    if (retval) {
+        ovs_fatal(retval, "%s: failed to lock lockfile", dst_name);
+    }
+
+    /* Save a copy. */
+    check_ovsdb_error(new_schema
+                      ? ovsdb_file_open_as_schema(src_name, new_schema, &db)
+                      : ovsdb_file_open(src_name, true, &db, NULL));
+    check_ovsdb_error(ovsdb_file_save_copy(dst_name, false, comment, db));
+    ovsdb_destroy(db);
+
+    /* Replace source. */
+    if (in_place) {
+        if (rename(dst_name, src_name)) {
+            ovs_fatal(errno, "failed to rename \"%s\" to \"%s\"",
+                      dst_name, src_name);
+        }
+        fsync_parent_dir(dst_name);
+        lockfile_unlock(src_lock);
+    }
+
+    lockfile_unlock(dst_lock);
+}
+
+static void
+do_compact(int argc OVS_UNUSED, char *argv[])
+{
+    compact_or_convert(argv[1], argv[2], NULL, "compacted by ovsdb-tool");
+}
+
+static void
+do_convert(int argc OVS_UNUSED, char *argv[])
+{
+    const char *schema_file_name = argv[2];
+    struct ovsdb_schema *new_schema;
+
+    check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &new_schema));
+    compact_or_convert(argv[1], argv[3], new_schema,
+                       "converted by ovsdb-tool");
+    ovsdb_schema_destroy(new_schema);
+}
+
+static void
+transact(bool read_only, const char *db_file_name, const char *transaction)
+{
+    struct json *request, *result;
+    struct ovsdb *db;
+
+    check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db, NULL));
+
+    request = parse_json(transaction);
+    result = ovsdb_execute(db, request, 0, NULL);
+    json_destroy(request);
+
+    print_and_free_json(result);
+    ovsdb_destroy(db);
+}
+
+static void
+do_query(int argc OVS_UNUSED, char *argv[])
+{
+    transact(true, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc OVS_UNUSED, char *argv[])
+{
+    transact(false, argv[1], argv[2]);
+}
+
+static void
+print_db_changes(struct shash *tables, struct shash *names)
+{
+    struct shash_node *n1;
+
+    SHASH_FOR_EACH (n1, tables) {
+        const char *table = n1->name;
+        struct json *rows = n1->data;
+        struct shash_node *n2;
+
+        if (n1->name[0] == '_' || rows->type != JSON_OBJECT) {
+            continue;
+        }
+
+        SHASH_FOR_EACH (n2, json_object(rows)) {
+            const char *row_uuid = n2->name;
+            struct json *columns = n2->data;
+            struct shash_node *n3;
+            char *old_name, *new_name;
+            bool free_new_name = false;
+
+            old_name = new_name = shash_find_data(names, row_uuid);
+            if (columns->type == JSON_OBJECT) {
+                struct json *new_name_json;
+
+                new_name_json = shash_find_data(json_object(columns), "name");
+                if (new_name_json) {
+                    new_name = json_to_string(new_name_json, JSSF_SORT);
+                    free_new_name = true;
+                }
+            }
+
+            printf("\ttable %s", table);
+
+            if (!old_name) {
+                if (new_name) {
+                    printf(" insert row %s:\n", new_name);
+                } else {
+                    printf(" insert row %.8s:\n", row_uuid);
+                }
+            } else {
+                printf(" row %s:\n", old_name);
+            }
+
+            if (columns->type == JSON_OBJECT) {
+                if (show_log_verbosity > 1) {
+                    SHASH_FOR_EACH (n3, json_object(columns)) {
+                        const char *column = n3->name;
+                        struct json *value = n3->data;
+                        char *value_string;
+
+                        value_string = json_to_string(value, JSSF_SORT);
+                        printf("\t\t%s=%s\n", column, value_string);
+                        free(value_string);
+                    }
+                }
+                if (!old_name
+                    || (new_name != old_name && strcmp(old_name, new_name))) {
+                    if (old_name) {
+                        shash_delete(names, shash_find(names, row_uuid));
+                        free(old_name);
+                    }
+                    shash_add(names, row_uuid, (new_name
+                                                ? xstrdup(new_name)
+                                                : xmemdup0(row_uuid, 8)));
+                }
+            } else if (columns->type == JSON_NULL) {
+                struct shash_node *node;
+
+                printf("\t\tdelete row\n");
+                node = shash_find(names, row_uuid);
+                if (node) {
+                    shash_delete(names, node);
+                }
+                free(old_name);
+            }
+
+            if (free_new_name) {
+                free(new_name);
+            }
+        }
+    }
+}
+
+static void
+do_show_log(int argc OVS_UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    struct shash names;
+    struct ovsdb_log *log;
+    unsigned int i;
+
+    check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_LOG_READ_ONLY,
+                                     -1, &log));
+    shash_init(&names);
+    for (i = 0; ; i++) {
+        struct json *json;
+
+        check_ovsdb_error(ovsdb_log_read(log, &json));
+        if (!json) {
+            break;
+        }
+
+        printf("record %u:", i);
+        if (json->type == JSON_OBJECT) {
+            struct json *date, *comment;
+
+            date = shash_find_data(json_object(json), "_date");
+            if (date && date->type == JSON_INTEGER) {
+                time_t t = json_integer(date);
+                char s[128];
+
+                strftime(s, sizeof s, "%Y-%m-%d %H:%M:%S", localtime(&t));
+                printf(" %s", s);
+            }
+
+            comment = shash_find_data(json_object(json), "_comment");
+            if (comment && comment->type == JSON_STRING) {
+                printf(" \"%s\"", json_string(comment));
+            }
+
+            if (i > 0 && show_log_verbosity > 0) {
+                putchar('\n');
+                print_db_changes(json_object(json), &names);
+            }
+        }
+        json_destroy(json);
+        putchar('\n');
+    }
+
+    /* XXX free 'names'. */
+}
+
+static void
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "create", 2, 2, do_create },
+    { "compact", 1, 2, do_compact },
+    { "convert", 2, 3, do_convert },
+    { "query", 2, 2, do_query },
+    { "transact", 2, 2, do_transact },
+    { "show-log", 1, 1, do_show_log },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644 (file)
index 0000000..4568376
--- /dev/null
@@ -0,0 +1,307 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "table.h"
+#include "transaction.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name)
+{
+    struct ovsdb_schema *schema;
+
+    schema = xzalloc(sizeof *schema);
+    schema->name = xstrdup(name);
+    shash_init(&schema->tables);
+
+    return schema;
+}
+
+struct ovsdb_schema *
+ovsdb_schema_clone(const struct ovsdb_schema *old)
+{
+    struct ovsdb_schema *new;
+    struct shash_node *node;
+
+    new = ovsdb_schema_create(old->name);
+    SHASH_FOR_EACH (node, &old->tables) {
+        const struct ovsdb_table_schema *ts = node->data;
+
+        shash_add(&new->tables, node->name, ovsdb_table_schema_clone(ts));
+    }
+    return new;
+}
+
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+
+    if (!schema) {
+        return;
+    }
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        ovsdb_table_schema_destroy(node->data);
+    }
+    shash_destroy(&schema->tables);
+    free(schema->name);
+    free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct json *json;
+
+    *schemap = NULL;
+    json = json_from_file(file_name);
+    if (json->type == JSON_STRING) {
+        error = ovsdb_error("failed to read schema",
+                           "\"%s\" could not be read as JSON (%s)",
+                           file_name, json_string(json));
+        json_destroy(json);
+        return error;
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    json_destroy(json);
+    if (error) {
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+
+    *schemap = schema;
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_schema_check_ref_table(const struct ovsdb_column *column,
+                             const struct shash *tables,
+                             const struct ovsdb_base_type *base,
+                             const char *base_name)
+{
+    if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName
+        && !shash_find(tables, base->u.uuid.refTableName)) {
+        return ovsdb_syntax_error(NULL, NULL,
+                                  "column %s %s refers to undefined table %s",
+                                  column->name, base_name,
+                                  base->u.uuid.refTableName);
+    } else {
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    const struct json *name, *tables;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+
+    *schemap = NULL;
+
+    ovsdb_parser_init(&parser, json, "database schema");
+    name = ovsdb_parser_member(&parser, "name", OP_ID);
+    tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    schema = ovsdb_schema_create(json_string(name));
+    SHASH_FOR_EACH (node, json_object(tables)) {
+        struct ovsdb_table_schema *table;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_table_schema_from_json(node->data, node->name,
+                                                 &table);
+        }
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            return error;
+        }
+
+        shash_add(&schema->tables, table->name, table);
+    }
+
+    /* Validate that all refTables refer to the names of tables that exist. */
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        struct shash_node *node2;
+
+        SHASH_FOR_EACH (node2, &table->columns) {
+            struct ovsdb_column *column = node2->data;
+
+            error = ovsdb_schema_check_ref_table(column, &schema->tables,
+                                                 &column->type.key, "key");
+            if (!error) {
+                error = ovsdb_schema_check_ref_table(column, &schema->tables,
+                                                     &column->type.value,
+                                                     "value");
+            }
+            if (error) {
+                ovsdb_schema_destroy(schema);
+                return error;
+            }
+        }
+    }
+
+    *schemap = schema;
+    return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+    struct json *json, *tables;
+    struct shash_node *node;
+
+    json = json_object_create();
+    json_object_put_string(json, "name", schema->name);
+
+    tables = json_object_create();
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        json_object_put(tables, table->name,
+                        ovsdb_table_schema_to_json(table));
+    }
+    json_object_put(json, "tables", tables);
+
+    return json;
+}
+\f
+static void
+ovsdb_set_ref_table(const struct shash *tables,
+                    struct ovsdb_base_type *base)
+{
+    if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) {
+        struct ovsdb_table *table;
+
+        table = shash_find_data(tables, base->u.uuid.refTableName);
+        base->u.uuid.refTable = table;
+    }
+}
+
+struct ovsdb *
+ovsdb_create(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+    struct ovsdb *db;
+
+    db = xmalloc(sizeof *db);
+    db->schema = schema;
+    list_init(&db->replicas);
+    list_init(&db->triggers);
+    db->run_triggers = false;
+
+    shash_init(&db->tables);
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+        shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+    }
+
+    /* Set all the refTables. */
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        struct shash_node *node2;
+
+        SHASH_FOR_EACH (node2, &table->columns) {
+            struct ovsdb_column *column = node2->data;
+
+            ovsdb_set_ref_table(&db->tables, &column->type.key);
+            ovsdb_set_ref_table(&db->tables, &column->type.value);
+        }
+    }
+
+    return db;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+    if (db) {
+        struct shash_node *node;
+
+        /* Remove all the replicas. */
+        while (!list_is_empty(&db->replicas)) {
+            struct ovsdb_replica *r
+                = CONTAINER_OF(list_pop_back(&db->replicas),
+                               struct ovsdb_replica, node);
+            ovsdb_remove_replica(db, r);
+        }
+
+        /* Delete all the tables.  This also deletes their schemas. */
+        SHASH_FOR_EACH (node, &db->tables) {
+            struct ovsdb_table *table = node->data;
+            ovsdb_table_destroy(table);
+        }
+        shash_destroy(&db->tables);
+
+        /* The schemas, but not the table that points to them, were deleted in
+         * the previous step, so we need to clear out the table.  We can't
+         * destroy the table, because ovsdb_schema_destroy() will do that. */
+        shash_clear(&db->schema->tables);
+
+        ovsdb_schema_destroy(db->schema);
+        free(db);
+    }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+    return shash_find_data(&db->tables, name);
+}
+\f
+void
+ovsdb_replica_init(struct ovsdb_replica *r,
+                   const struct ovsdb_replica_class *class)
+{
+    r->class = class;
+}
+
+void
+ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r)
+{
+    list_push_back(&db->replicas, &r->node);
+}
+
+void
+ovsdb_remove_replica(struct ovsdb *db OVS_UNUSED, struct ovsdb_replica *r)
+{
+    list_remove(&r->node);
+    (r->class->destroy)(r);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644 (file)
index 0000000..a83412d
--- /dev/null
@@ -0,0 +1,90 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_OVSDB_H
+#define OVSDB_OVSDB_H 1
+
+#include "compiler.h"
+#include "hmap.h"
+#include "list.h"
+#include "shash.h"
+
+struct json;
+struct ovsdb_log;
+struct ovsdb_txn;
+struct uuid;
+
+/* Database schema. */
+struct ovsdb_schema {
+    char *name;
+    struct shash tables;        /* Contains "struct ovsdb_table_schema *"s. */
+};
+
+struct ovsdb_schema *ovsdb_schema_create(const char *name);
+struct ovsdb_schema *ovsdb_schema_clone(const struct ovsdb_schema *);
+void ovsdb_schema_destroy(struct ovsdb_schema *);
+
+struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_schema_from_json(struct json *,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
+\f
+/* Database. */
+struct ovsdb {
+    struct ovsdb_schema *schema;
+    struct list replicas;       /* Contains "struct ovsdb_replica"s. */
+    struct shash tables;        /* Contains "struct ovsdb_table *"s. */
+
+    /* Triggers. */
+    struct list triggers;       /* Contains "struct ovsdb_trigger"s. */
+    bool run_triggers;
+};
+
+struct ovsdb *ovsdb_create(struct ovsdb_schema *);
+void ovsdb_destroy(struct ovsdb *);
+
+struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_to_json(const struct ovsdb *);
+
+struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
+
+struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+                           long long int elapsed_msec,
+                           long long int *timeout_msec);
+\f
+/* Database replication. */
+
+struct ovsdb_replica {
+    struct list node;           /* Element in "struct ovsdb" replicas list. */
+    const struct ovsdb_replica_class *class;
+};
+
+struct ovsdb_replica_class {
+    struct ovsdb_error *(*commit)(struct ovsdb_replica *,
+                                  const struct ovsdb_txn *, bool durable);
+    void (*destroy)(struct ovsdb_replica *);
+};
+
+void ovsdb_replica_init(struct ovsdb_replica *,
+                        const struct ovsdb_replica_class *);
+
+void ovsdb_add_replica(struct ovsdb *, struct ovsdb_replica *);
+void ovsdb_remove_replica(struct ovsdb *, struct ovsdb_replica *);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644 (file)
index 0000000..878ac5b
--- /dev/null
@@ -0,0 +1,99 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "query.h"
+
+#include "column.h"
+#include "condition.h"
+#include "row.h"
+#include "table.h"
+
+void
+ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd,
+            bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux)
+{
+    if (cnd->n_clauses > 0
+        && cnd->clauses[0].column->index == OVSDB_COL_UUID
+        && cnd->clauses[0].function == OVSDB_F_EQ) {
+        /* Optimize the case where the query has a clause of the form "uuid ==
+         * <some-uuid>", since we have an index on UUID. */
+        const struct ovsdb_row *row;
+
+        row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid);
+        if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) {
+            output_row(row, aux);
+        }
+    } else {
+        /* Linear scan. */
+        const struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) {
+                break;
+            }
+        }
+    }
+}
+
+static bool
+query_row_set_cb(const struct ovsdb_row *row, void *results_)
+{
+    struct ovsdb_row_set *results = results_;
+    ovsdb_row_set_add_row(results, row);
+    return true;
+}
+
+void
+ovsdb_query_row_set(struct ovsdb_table *table,
+                    const struct ovsdb_condition *condition,
+                    struct ovsdb_row_set *results)
+{
+    ovsdb_query(table, condition, query_row_set_cb, results);
+}
+
+static bool
+query_distinct_cb(const struct ovsdb_row *row, void *hash_)
+{
+    struct ovsdb_row_hash *hash = hash_;
+    ovsdb_row_hash_insert(hash, row);
+    return true;
+}
+
+void
+ovsdb_query_distinct(struct ovsdb_table *table,
+                     const struct ovsdb_condition *condition,
+                     const struct ovsdb_column_set *columns,
+                     struct ovsdb_row_set *results)
+{
+    if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) {
+        /* All the result rows are guaranteed to be distinct anyway. */
+        return ovsdb_query_row_set(table, condition, results);
+    } else {
+        /* Use hash table to drop duplicates. */
+        struct ovsdb_row_hash_node *node;
+        struct ovsdb_row_hash hash;
+
+        ovsdb_row_hash_init(&hash, columns);
+        ovsdb_query(table, condition, query_distinct_cb, &hash);
+        HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node,
+                       &hash.rows) {
+            ovsdb_row_set_add_row(results, node->row);
+        }
+        ovsdb_row_hash_destroy(&hash, false);
+    }
+}
diff --git a/ovsdb/query.h b/ovsdb/query.h
new file mode 100644 (file)
index 0000000..f5cfe2e
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_QUERY_H
+#define OVSDB_QUERY_H 1
+
+#include <stdbool.h>
+
+struct ovsdb_column_set;
+struct ovsdb_condition;
+struct ovsdb_row;
+struct ovsdb_row_set;
+struct ovsdb_table;
+struct ovsdb_txn;
+
+void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *,
+                 bool (*output_row)(const struct ovsdb_row *, void *aux),
+                 void *aux);
+void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *,
+                         struct ovsdb_row_set *);
+void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *,
+                          const struct ovsdb_column_set *,
+                          struct ovsdb_row_set *);
+
+#endif /* ovsdb/query.h */
diff --git a/ovsdb/remote-active.man b/ovsdb/remote-active.man
new file mode 100644 (file)
index 0000000..a934cf0
--- /dev/null
@@ -0,0 +1,11 @@
+.IP "\fBssl:\fIip\fB:\fIport\fR"
+The specified SSL \fIport\fR on the host at the given \fIip\fR, which
+must be expressed as an IP address (not a DNS name).  The
+\fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
+options are mandatory when this form is used.
+.
+.IP "\fBtcp:\fIip\fB:\fIport\fR"
+Connect to the given TCP \fIport\fR on \fIip\fR.
+.
+.IP "\fBunix:\fIfile\fR"
+Connect to the Unix domain server socket named \fIfile\fR.
diff --git a/ovsdb/remote-passive.man b/ovsdb/remote-passive.man
new file mode 100644 (file)
index 0000000..b313e92
--- /dev/null
@@ -0,0 +1,16 @@
+.IP "\fBpssl:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given SSL \fIport\fR for a connection.  By default,
+\fB\*(PN\fR listens for connections to any local IP address, but
+specifying \fIip\fR limits connections to those from the given
+\fIip\fR.  The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.
+.
+.IP "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given TCP \fIport\fR for a connection.  By default,
+\fB\*(PN\fR listens for connections to any local IP address, but
+\fIip\fR may be specified to listen only for connections to the given
+\fIip\fR.
+.
+.IP "\fBpunix:\fIfile\fR"
+Listen on the Unix domain server socket named \fIfile\fR for a
+connection.
diff --git a/ovsdb/row.c b/ovsdb/row.c
new file mode 100644 (file)
index 0000000..5043cbc
--- /dev/null
@@ -0,0 +1,404 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "row.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "shash.h"
+#include "sort.h"
+#include "table.h"
+
+static struct ovsdb_row *
+allocate_row(const struct ovsdb_table *table)
+{
+    size_t n_fields = shash_count(&table->schema->columns);
+    size_t row_size = (offsetof(struct ovsdb_row, fields)
+                       + sizeof(struct ovsdb_datum) * n_fields);
+    struct ovsdb_row *row = xmalloc(row_size);
+    row->table = (struct ovsdb_table *) table;
+    row->txn_row = NULL;
+    list_init(&row->src_refs);
+    list_init(&row->dst_refs);
+    row->n_refs = 0;
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_create(const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+    struct ovsdb_row *row;
+
+    row = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_init_default(&row->fields[column->index], &column->type);
+    }
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_clone(const struct ovsdb_row *old)
+{
+    const struct ovsdb_table *table = old->table;
+    const struct shash_node *node;
+    struct ovsdb_row *new;
+
+    new = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_clone(&new->fields[column->index],
+                          &old->fields[column->index],
+                          &column->type);
+    }
+    return new;
+}
+
+/* The caller is responsible for ensuring that 'row' has been removed from its
+ * table and that it is not participating in a transaction. */
+void
+ovsdb_row_destroy(struct ovsdb_row *row)
+{
+    if (row) {
+        const struct ovsdb_table *table = row->table;
+        struct ovsdb_weak_ref *weak, *next;
+        const struct shash_node *node;
+
+        LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, dst_node,
+                            &row->dst_refs) {
+            list_remove(&weak->src_node);
+            list_remove(&weak->dst_node);
+            free(weak);
+        }
+
+        LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, src_node,
+                            &row->src_refs) {
+            list_remove(&weak->src_node);
+            list_remove(&weak->dst_node);
+            free(weak);
+        }
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_datum_destroy(&row->fields[column->index], &column->type);
+        }
+        free(row);
+    }
+}
+
+uint32_t
+ovsdb_row_hash_columns(const struct ovsdb_row *row,
+                       const struct ovsdb_column_set *columns,
+                       uint32_t basis)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        basis = ovsdb_datum_hash(&row->fields[column->index], &column->type,
+                                 basis);
+    }
+
+    return basis;
+}
+
+int
+ovsdb_row_compare_columns_3way(const struct ovsdb_row *a,
+                               const struct ovsdb_row *b,
+                               const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        int cmp = ovsdb_datum_compare_3way(&a->fields[column->index],
+                                           &b->fields[column->index],
+                                           &column->type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_row_equal_columns(const struct ovsdb_row *a,
+                        const struct ovsdb_row *b,
+                        const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        if (!ovsdb_datum_equals(&a->fields[column->index],
+                                &b->fields[column->index],
+                                &column->type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void
+ovsdb_row_update_columns(struct ovsdb_row *dst,
+                         const struct ovsdb_row *src,
+                         const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        ovsdb_datum_destroy(&dst->fields[column->index], &column->type);
+        ovsdb_datum_clone(&dst->fields[column->index],
+                          &src->fields[column->index],
+                          &column->type);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json,
+                    struct ovsdb_symbol_table *symtab,
+                    struct ovsdb_column_set *included)
+{
+    struct ovsdb_table_schema *schema = row->table->schema;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+    }
+
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
+
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data,
+                                      symtab);
+        if (error) {
+            return error;
+        }
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
+        if (included) {
+            ovsdb_column_set_add(included, column);
+        }
+    }
+
+    return NULL;
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+                const struct ovsdb_column *column)
+{
+    json_object_put(object, column->name,
+                    ovsdb_datum_to_json(&row->fields[column->index],
+                                        &column->type));
+}
+
+struct json *
+ovsdb_row_to_json(const struct ovsdb_row *row,
+                  const struct ovsdb_column_set *columns)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_object_create();
+    for (i = 0; i < columns->n_columns; i++) {
+        put_json_column(json, row, columns->columns[i]);
+    }
+    return json;
+}
+\f
+void
+ovsdb_row_set_init(struct ovsdb_row_set *set)
+{
+    set->rows = NULL;
+    set->n_rows = set->allocated_rows = 0;
+}
+
+void
+ovsdb_row_set_destroy(struct ovsdb_row_set *set)
+{
+    free(set->rows);
+}
+
+void
+ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row)
+{
+    if (set->n_rows >= set->allocated_rows) {
+        set->rows = x2nrealloc(set->rows, &set->allocated_rows,
+                               sizeof *set->rows);
+    }
+    set->rows[set->n_rows++] = row;
+}
+
+struct json *
+ovsdb_row_set_to_json(const struct ovsdb_row_set *rows,
+                      const struct ovsdb_column_set *columns)
+{
+    struct json **json_rows;
+    size_t i;
+
+    json_rows = xmalloc(rows->n_rows * sizeof *json_rows);
+    for (i = 0; i < rows->n_rows; i++) {
+        json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns);
+    }
+    return json_array_create(json_rows, rows->n_rows);
+}
+
+struct ovsdb_row_set_sort_cbdata {
+    struct ovsdb_row_set *set;
+    const struct ovsdb_column_set *columns;
+};
+
+static int
+ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    return ovsdb_row_compare_columns_3way(cbdata->set->rows[a],
+                                          cbdata->set->rows[b],
+                                          cbdata->columns);
+}
+
+static void
+ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    const struct ovsdb_row *tmp = cbdata->set->rows[a];
+    cbdata->set->rows[a] = cbdata->set->rows[b];
+    cbdata->set->rows[b] = tmp;
+}
+
+void
+ovsdb_row_set_sort(struct ovsdb_row_set *set,
+                   const struct ovsdb_column_set *columns)
+{
+    if (columns && columns->n_columns && set->n_rows > 1) {
+        struct ovsdb_row_set_sort_cbdata cbdata;
+        cbdata.set = set;
+        cbdata.columns = columns;
+        sort(set->n_rows,
+             ovsdb_row_set_sort_compare_cb,
+             ovsdb_row_set_sort_swap_cb,
+             &cbdata);
+    }
+}
+\f
+void
+ovsdb_row_hash_init(struct ovsdb_row_hash *rh,
+                    const struct ovsdb_column_set *columns)
+{
+    hmap_init(&rh->rows);
+    ovsdb_column_set_clone(&rh->columns, columns);
+}
+
+void
+ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows)
+{
+    struct ovsdb_row_hash_node *node, *next;
+
+    HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node,
+                        &rh->rows) {
+        hmap_remove(&rh->rows, &node->hmap_node);
+        if (destroy_rows) {
+            ovsdb_row_destroy((struct ovsdb_row *) node->row);
+        }
+        free(node);
+    }
+    hmap_destroy(&rh->rows);
+    ovsdb_column_set_destroy(&rh->columns);
+}
+
+size_t
+ovsdb_row_hash_count(const struct ovsdb_row_hash *rh)
+{
+    return hmap_count(&rh->rows);
+}
+
+bool
+ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh,
+                        const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_contains__(rh, row, hash);
+}
+
+/* Returns true if every row in 'b' has an equal row in 'a'. */
+bool
+ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a,
+                            const struct ovsdb_row_hash *b)
+{
+    struct ovsdb_row_hash_node *node;
+
+    assert(ovsdb_column_set_equals(&a->columns, &b->columns));
+    HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) {
+        if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_insert__(rh, row, hash);
+}
+
+bool
+ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh,
+                          const struct ovsdb_row *row, size_t hash)
+{
+    struct ovsdb_row_hash_node *node;
+    HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node,
+                             hash, &rh->rows) {
+        if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row,
+                        size_t hash)
+{
+    if (!ovsdb_row_hash_contains__(rh, row, hash)) {
+        struct ovsdb_row_hash_node *node = xmalloc(sizeof *node);
+        node->row = row;
+        hmap_insert(&rh->rows, &node->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/row.h b/ovsdb/row.h
new file mode 100644 (file)
index 0000000..6c249a1
--- /dev/null
@@ -0,0 +1,168 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ROW_H
+#define OVSDB_ROW_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "column.h"
+#include "hmap.h"
+#include "list.h"
+#include "ovsdb-data.h"
+
+struct ovsdb_column_set;
+
+/* A weak reference.
+ *
+ * When a column in row A contains a weak reference to UUID of a row B this
+ * constitutes a weak reference from A (the source) to B (the destination).
+ *
+ * Rows A and B may be in the same table or different tables.
+ *
+ * Weak references from a row to itself are allowed, but no "struct
+ * ovsdb_weak_ref" structures are created for them.
+ */
+struct ovsdb_weak_ref {
+    struct list src_node;       /* In src->src_refs list. */
+    struct list dst_node;       /* In destination row's dst_refs list. */
+    struct ovsdb_row *src;      /* Source row. */
+};
+
+/* A row in a database table. */
+struct ovsdb_row {
+    struct ovsdb_table *table;     /* Table to which this belongs. */
+    struct hmap_node hmap_node;    /* Element in ovsdb_table's 'rows' hmap. */
+    struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */
+
+    /* Weak references. */
+    struct list src_refs;       /* Weak references from this row. */
+    struct list dst_refs;       /* Weak references to this row. */
+
+    /* Number of strong refs to this row from other rows, in this table or
+     * other tables, through 'uuid' columns that have a 'refTable' constraint
+     * pointing to this table and a 'refType' of "strong".  A row with nonzero
+     * 'n_refs' cannot be deleted.  Updated and checked only at transaction
+     * commit. */
+    size_t n_refs;
+
+    struct ovsdb_datum fields[];
+};
+
+struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *);
+struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *);
+void ovsdb_row_destroy(struct ovsdb_row *);
+
+uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *,
+                                const struct ovsdb_column_set *,
+                                uint32_t basis);
+bool ovsdb_row_equal_columns(const struct ovsdb_row *,
+                             const struct ovsdb_row *,
+                             const struct ovsdb_column_set *);
+int ovsdb_row_compare_columns_3way(const struct ovsdb_row *,
+                                   const struct ovsdb_row *,
+                                   const struct ovsdb_column_set *);
+void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *,
+                              const struct ovsdb_column_set *);
+
+struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *,
+                                        const struct json *,
+                                        struct ovsdb_symbol_table *,
+                                        struct ovsdb_column_set *included)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_row_to_json(const struct ovsdb_row *,
+                               const struct ovsdb_column_set *include);
+
+static inline const struct uuid *
+ovsdb_row_get_uuid(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_uuid_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline const struct uuid *
+ovsdb_row_get_version(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_version_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline uint32_t
+ovsdb_row_hash(const struct ovsdb_row *row)
+{
+    return uuid_hash(ovsdb_row_get_uuid(row));
+}
+\f
+/* An unordered collection of rows. */
+struct ovsdb_row_set {
+    const struct ovsdb_row **rows;
+    size_t n_rows, allocated_rows;
+};
+
+#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_row_set_init(struct ovsdb_row_set *);
+void ovsdb_row_set_destroy(struct ovsdb_row_set *);
+void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *);
+
+struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *,
+                                   const struct ovsdb_column_set *);
+
+void ovsdb_row_set_sort(struct ovsdb_row_set *,
+                        const struct ovsdb_column_set *);
+\f
+/* A hash table of rows.  A specified set of columns is used for hashing and
+ * comparing rows.
+ *
+ * The row hash doesn't necessarily own its rows.  They may be owned by, for
+ * example, an ovsdb_table. */
+struct ovsdb_row_hash {
+    struct hmap rows;
+    struct ovsdb_column_set columns;
+};
+
+#define OVSDB_ROW_HASH_INITIALIZER(RH) \
+    { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER }
+
+struct ovsdb_row_hash_node {
+    struct hmap_node hmap_node;
+    const struct ovsdb_row *row;
+};
+
+void ovsdb_row_hash_init(struct ovsdb_row_hash *,
+                         const struct ovsdb_column_set *);
+void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows);
+size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *,
+                             const struct ovsdb_row *);
+bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *,
+                                 const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *);
+bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *,
+                               const struct ovsdb_row *, size_t hash);
+bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *,
+                             const struct ovsdb_row *, size_t hash);
+
+#endif /* ovsdb/row.h */
diff --git a/ovsdb/simplejson/__init__.py b/ovsdb/simplejson/__init__.py
new file mode 100644 (file)
index 0000000..d5b4d39
--- /dev/null
@@ -0,0 +1,318 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+    >>> import simplejson as json
+    >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+    >>> print json.dumps("\"foo\bar")
+    "\"foo\bar"
+    >>> print json.dumps(u'\u1234')
+    "\u1234"
+    >>> print json.dumps('\\')
+    "\\"
+    >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+    {"a": 0, "b": 0, "c": 0}
+    >>> from StringIO import StringIO
+    >>> io = StringIO()
+    >>> json.dump(['streaming API'], io)
+    >>> io.getvalue()
+    '["streaming API"]'
+
+Compact encoding::
+
+    >>> import simplejson as json
+    >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+    '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+    >>> import simplejson as json
+    >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+    >>> print '\n'.join([l.rstrip() for l in  s.splitlines()])
+    {
+        "4": 5,
+        "6": 7
+    }
+
+Decoding JSON::
+
+    >>> import simplejson as json
+    >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+    >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+    True
+    >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+    True
+    >>> from StringIO import StringIO
+    >>> io = StringIO('["streaming API"]')
+    >>> json.load(io)[0] == 'streaming API'
+    True
+
+Specializing JSON object decoding::
+
+    >>> import simplejson as json
+    >>> def as_complex(dct):
+    ...     if '__complex__' in dct:
+    ...         return complex(dct['real'], dct['imag'])
+    ...     return dct
+    ...
+    >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+    ...     object_hook=as_complex)
+    (1+2j)
+    >>> import decimal
+    >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+    True
+
+Specializing JSON object encoding::
+
+    >>> import simplejson as json
+    >>> def encode_complex(obj):
+    ...     if isinstance(obj, complex):
+    ...         return [obj.real, obj.imag]
+    ...     raise TypeError(repr(o) + " is not JSON serializable")
+    ...
+    >>> json.dumps(2 + 1j, default=encode_complex)
+    '[2.0, 1.0]'
+    >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+    '[2.0, 1.0]'
+    >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+    '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+"""
+__version__ = '2.0.9'
+__all__ = [
+    'dump', 'dumps', 'load', 'loads',
+    'JSONDecoder', 'JSONEncoder',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decoder import JSONDecoder
+from encoder import JSONEncoder
+
+_default_encoder = JSONEncoder(
+    skipkeys=False,
+    ensure_ascii=True,
+    check_circular=True,
+    allow_nan=True,
+    indent=None,
+    separators=None,
+    encoding='utf-8',
+    default=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+    ``.write()``-supporting file-like object).
+
+    If ``skipkeys`` is true then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+    may be ``unicode`` instances, subject to normal Python ``str`` to
+    ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+    understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+    to cause an error.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+    in strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and object
+    members will be pretty-printed with that indent level. An indent level
+    of 0 will only insert newlines. ``None`` is the most compact representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        iterable = _default_encoder.iterencode(obj)
+    else:
+        if cls is None:
+            cls = JSONEncoder
+        iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+            check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+            separators=separators, encoding=encoding,
+            default=default, **kw).iterencode(obj)
+    # could accelerate with writelines in some versions of Python, at
+    # a debuggability cost
+    for chunk in iterable:
+        fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` to a JSON formatted ``str``.
+
+    If ``skipkeys`` is false then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the return value will be a
+    ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+    coercion rules instead of being escaped to an ASCII ``str``.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+    strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and
+    object members will be pretty-printed with that indent level. An indent
+    level of 0 will only insert newlines. ``None`` is the most compact
+    representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        return _default_encoder.encode(obj)
+    if cls is None:
+        cls = JSONEncoder
+    return cls(
+        skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+        separators=separators, encoding=encoding, default=default,
+        **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+    a JSON document) to a Python object.
+
+    If the contents of ``fp`` is encoded with an ASCII based encoding other
+    than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+    be specified. Encodings that are not ASCII based (such as UCS-2) are
+    not allowed, and should be wrapped with
+    ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+    object and passed to ``loads()``
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    return loads(fp.read(),
+        encoding=encoding, cls=cls, object_hook=object_hook,
+        parse_float=parse_float, parse_int=parse_int,
+        parse_constant=parse_constant, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+    document) to a Python object.
+
+    If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+    other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+    must be specified. Encodings that are not ASCII based (such as UCS-2)
+    are not allowed and should be decoded to ``unicode`` first.
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    ``parse_float``, if specified, will be called with the string
+    of every JSON float to be decoded. By default this is equivalent to
+    float(num_str). This can be used to use another datatype or parser
+    for JSON floats (e.g. decimal.Decimal).
+
+    ``parse_int``, if specified, will be called with the string
+    of every JSON int to be decoded. By default this is equivalent to
+    int(num_str). This can be used to use another datatype or parser
+    for JSON integers (e.g. float).
+
+    ``parse_constant``, if specified, will be called with one of the
+    following strings: -Infinity, Infinity, NaN, null, true, false.
+    This can be used to raise an exception if invalid JSON numbers
+    are encountered.
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    if (cls is None and encoding is None and object_hook is None and
+            parse_int is None and parse_float is None and
+            parse_constant is None and not kw):
+        return _default_decoder.decode(s)
+    if cls is None:
+        cls = JSONDecoder
+    if object_hook is not None:
+        kw['object_hook'] = object_hook
+    if parse_float is not None:
+        kw['parse_float'] = parse_float
+    if parse_int is not None:
+        kw['parse_int'] = parse_int
+    if parse_constant is not None:
+        kw['parse_constant'] = parse_constant
+    return cls(encoding=encoding, **kw).decode(s)
diff --git a/ovsdb/simplejson/_speedups.c b/ovsdb/simplejson/_speedups.c
new file mode 100644 (file)
index 0000000..23b5f4a
--- /dev/null
@@ -0,0 +1,2329 @@
+#include "Python.h"
+#include "structmember.h"
+#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE)
+#define Py_TYPE(ob)     (((PyObject*)(ob))->ob_type)
+#endif
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#define PyInt_FromSsize_t PyInt_FromLong
+#define PyInt_AsSsize_t PyInt_AsLong
+#endif
+#ifndef Py_IS_FINITE
+#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X))
+#endif
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((__unused__))
+#else
+#define UNUSED
+#endif
+
+#define DEFAULT_ENCODING "utf-8"
+
+#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType)
+#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType)
+#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
+#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+
+static PyTypeObject PyScannerType;
+static PyTypeObject PyEncoderType;
+
+typedef struct _PyScannerObject {
+    PyObject_HEAD
+    PyObject *encoding;
+    PyObject *strict;
+    PyObject *object_hook;
+    PyObject *parse_float;
+    PyObject *parse_int;
+    PyObject *parse_constant;
+} PyScannerObject;
+
+static PyMemberDef scanner_members[] = {
+    {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"},
+    {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"},
+    {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"},
+    {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"},
+    {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"},
+    {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"},
+    {NULL}
+};
+
+typedef struct _PyEncoderObject {
+    PyObject_HEAD
+    PyObject *markers;
+    PyObject *defaultfn;
+    PyObject *encoder;
+    PyObject *indent;
+    PyObject *key_separator;
+    PyObject *item_separator;
+    PyObject *sort_keys;
+    PyObject *skipkeys;
+    int fast_encode;
+    int allow_nan;
+} PyEncoderObject;
+
+static PyMemberDef encoder_members[] = {
+    {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"},
+    {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"},
+    {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"},
+    {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"},
+    {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"},
+    {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"},
+    {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
+    {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"},
+    {NULL}
+};
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars);
+static PyObject *
+ascii_escape_unicode(PyObject *pystr);
+static PyObject *
+ascii_escape_str(PyObject *pystr);
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr);
+void init_speedups(void);
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+scanner_dealloc(PyObject *self);
+static int
+scanner_clear(PyObject *self);
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+encoder_dealloc(PyObject *self);
+static int
+encoder_clear(PyObject *self);
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level);
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level);
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level);
+static PyObject *
+_encoded_const(PyObject *const);
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end);
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj);
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr);
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr);
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj);
+
+#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"')
+#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+#define MIN_EXPANSION 6
+#ifdef Py_UNICODE_WIDE
+#define MAX_EXPANSION (2 * MIN_EXPANSION)
+#else
+#define MAX_EXPANSION MIN_EXPANSION
+#endif
+
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr)
+{
+    /* PyObject to Py_ssize_t converter */
+    *size_ptr = PyInt_AsSsize_t(o);
+    if (*size_ptr == -1 && PyErr_Occurred());
+        return 1;
+    return 0;
+}
+
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr)
+{
+    /* Py_ssize_t to PyObject converter */
+    return PyInt_FromSsize_t(*size_ptr);
+}
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars)
+{
+    /* Escape unicode code point c to ASCII escape sequences
+    in char *output. output must have at least 12 bytes unused to
+    accommodate an escaped surrogate pair "\uXXXX\uXXXX" */
+    output[chars++] = '\\';
+    switch (c) {
+        case '\\': output[chars++] = (char)c; break;
+        case '"': output[chars++] = (char)c; break;
+        case '\b': output[chars++] = 'b'; break;
+        case '\f': output[chars++] = 'f'; break;
+        case '\n': output[chars++] = 'n'; break;
+        case '\r': output[chars++] = 'r'; break;
+        case '\t': output[chars++] = 't'; break;
+        default:
+#ifdef Py_UNICODE_WIDE
+            if (c >= 0x10000) {
+                /* UTF-16 surrogate pair */
+                Py_UNICODE v = c - 0x10000;
+                c = 0xd800 | ((v >> 10) & 0x3ff);
+                output[chars++] = 'u';
+                output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+                c = 0xdc00 | (v & 0x3ff);
+                output[chars++] = '\\';
+            }
+#endif
+            output[chars++] = 'u';
+            output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+    }
+    return chars;
+}
+
+static PyObject *
+ascii_escape_unicode(PyObject *pystr)
+{
+    /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t max_output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    Py_UNICODE *input_unicode;
+
+    input_chars = PyUnicode_GET_SIZE(pystr);
+    input_unicode = PyUnicode_AS_UNICODE(pystr);
+
+    /* One char input can be up to 6 chars output, estimate 4 of these */
+    output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    max_output_size = 2 + (input_chars * MAX_EXPANSION);
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    chars = 0;
+    output[chars++] = '"';
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = input_unicode[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        if (output_size - chars < (1 + MAX_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            Py_ssize_t new_output_size = output_size * 2;
+            /* This is an upper bound */
+            if (new_output_size > max_output_size) {
+                new_output_size = max_output_size;
+            }
+            /* Make sure that the output size changed before resizing */
+            if (new_output_size != output_size) {
+                output_size = new_output_size;
+                if (_PyString_Resize(&rval, output_size) == -1) {
+                    return NULL;
+                }
+                output = PyString_AS_STRING(rval);
+            }
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+    /* Take a PyString pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    char *input_str;
+
+    input_chars = PyString_GET_SIZE(pystr);
+    input_str = PyString_AS_STRING(pystr);
+
+    /* Fast path for a string that's already ASCII */
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (!S_CHAR(c)) {
+            /* If we have to escape something, scan the string for unicode */
+            Py_ssize_t j;
+            for (j = i; j < input_chars; j++) {
+                c = (Py_UNICODE)(unsigned char)input_str[j];
+                if (c > 0x7f) {
+                    /* We hit a non-ASCII character, bail to unicode mode */
+                    PyObject *uni;
+                    uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict");
+                    if (uni == NULL) {
+                        return NULL;
+                    }
+                    rval = ascii_escape_unicode(uni);
+                    Py_DECREF(uni);
+                    return rval;
+                }
+            }
+            break;
+        }
+    }
+
+    if (i == input_chars) {
+        /* Input is already ASCII */
+        output_size = 2 + input_chars;
+    }
+    else {
+        /* One char input can be up to 6 chars output, estimate 4 of these */
+        output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    }
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    output[0] = '"';
+
+    /* We know that everything up to i is ASCII already */
+    chars = i + 1;
+    memcpy(&output[1], input_str, i);
+
+    for (; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        /* An ASCII char can't possibly expand to a surrogate! */
+        if (output_size - chars < (1 + MIN_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            output_size *= 2;
+            if (output_size > 2 + (input_chars * MIN_EXPANSION)) {
+                output_size = 2 + (input_chars * MIN_EXPANSION);
+            }
+            if (_PyString_Resize(&rval, output_size) == -1) {
+                return NULL;
+            }
+            output = PyString_AS_STRING(rval);
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end)
+{
+    /* Use the Python function simplejson.decoder.errmsg to raise a nice
+    looking ValueError exception */
+    static PyObject *errmsg_fn = NULL;
+    PyObject *pymsg;
+    if (errmsg_fn == NULL) {
+        PyObject *decoder = PyImport_ImportModule("simplejson.decoder");
+        if (decoder == NULL)
+            return;
+        errmsg_fn = PyObject_GetAttrString(decoder, "errmsg");
+        Py_DECREF(decoder);
+        if (errmsg_fn == NULL)
+            return;
+    }
+    pymsg = PyObject_CallFunction(errmsg_fn, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end);
+    if (pymsg) {
+        PyErr_SetObject(PyExc_ValueError, pymsg);
+        Py_DECREF(pymsg);
+    }
+}
+
+static PyObject *
+join_list_unicode(PyObject *lst)
+{
+    /* return u''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyUnicode_FromUnicode(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+join_list_string(PyObject *lst)
+{
+    /* return ''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyString_FromStringAndSize(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) {
+    /* return (rval, idx) tuple, stealing reference to rval */
+    PyObject *tpl;
+    PyObject *pyidx;
+    /*
+    steal a reference to rval, returns (rval, idx)
+    */
+    if (rval == NULL) {
+        return NULL;
+    }
+    pyidx = PyInt_FromSsize_t(idx);
+    if (pyidx == NULL) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    tpl = PyTuple_New(2);
+    if (tpl == NULL) {
+        Py_DECREF(pyidx);
+        Py_DECREF(rval);
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tpl, 0, rval);
+    PyTuple_SET_ITEM(tpl, 1, pyidx);
+    return tpl;
+}
+
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyString pystr.
+    end is the index of the first character after the quote.
+    encoding is the encoding of pystr (must be an ASCII superset)
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyString (if ASCII-only) or PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyString_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    int has_unicode = 0;
+    char *buf = PyString_AS_STRING(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = (unsigned char)buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+            else if (c > 0x7f) {
+                has_unicode = 1;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            PyObject *strchunk = PyString_FromStringAndSize(&buf[end], next - end);
+            if (strchunk == NULL) {
+                goto bail;
+            }
+            if (has_unicode) {
+                chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL);
+                Py_DECREF(strchunk);
+                if (chunk == NULL) {
+                    goto bail;
+                }
+            }
+            else {
+                chunk = strchunk;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        if (c > 0x7f) {
+            has_unicode = 1;
+        }
+        if (has_unicode) {
+            chunk = PyUnicode_FromUnicode(&c, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        else {
+            char c_char = Py_CHARMASK(c);
+            chunk = PyString_FromStringAndSize(&c_char, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_string(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_CLEAR(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyUnicode pystr.
+    end is the index of the first character after the quote.
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyUnicode_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            chunk = PyUnicode_FromUnicode(&buf[end], next - end);
+            if (chunk == NULL) {
+                goto bail;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        chunk = PyUnicode_FromUnicode(&c, 1);
+        if (chunk == NULL) {
+            goto bail;
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_unicode(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_DECREF(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+PyDoc_STRVAR(pydoc_scanstring,
+    "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n"
+    "\n"
+    "Scan the string s for a JSON string. End is the index of the\n"
+    "character in s after the quote that started the JSON string.\n"
+    "Unescapes all valid JSON string escape sequences and raises ValueError\n"
+    "on attempt to decode an invalid string. If strict is False then literal\n"
+    "control characters are allowed in the string.\n"
+    "\n"
+    "Returns a tuple of the decoded string and the index of the character in s\n"
+    "after the end quote."
+);
+
+static PyObject *
+py_scanstring(PyObject* self UNUSED, PyObject *args)
+{
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t end;
+    Py_ssize_t next_end = -1;
+    char *encoding = NULL;
+    int strict = 1;
+    if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) {
+        return NULL;
+    }
+    if (encoding == NULL) {
+        encoding = DEFAULT_ENCODING;
+    }
+    if (PyString_Check(pystr)) {
+        rval = scanstring_str(pystr, end, encoding, strict, &next_end);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scanstring_unicode(pystr, end, strict, &next_end);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_end);
+}
+
+PyDoc_STRVAR(pydoc_encode_basestring_ascii,
+    "encode_basestring_ascii(basestring) -> str\n"
+    "\n"
+    "Return an ASCII-only JSON representation of a Python string"
+);
+
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr)
+{
+    /* Return an ASCII-only JSON representation of a Python string */
+    /* METH_O */
+    if (PyString_Check(pystr)) {
+        return ascii_escape_str(pystr);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        return ascii_escape_unicode(pystr);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+}
+
+static void
+scanner_dealloc(PyObject *self)
+{
+    /* Deallocate scanner object */
+    scanner_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+scanner_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_VISIT(s->encoding);
+    Py_VISIT(s->strict);
+    Py_VISIT(s->object_hook);
+    Py_VISIT(s->parse_float);
+    Py_VISIT(s->parse_int);
+    Py_VISIT(s->parse_constant);
+    return 0;
+}
+
+static int
+scanner_clear(PyObject *self)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return 0;
+}
+
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyString pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    PyObject *val = NULL;
+    char *encoding = PyString_AS_STRING(s->encoding);
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON data type */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyUnicode pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_unicode(pystr, idx + 1, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON term */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term and de-tuplefy the (rval, idx) */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term  */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON constant from PyString pystr.
+    constant is the constant string that was found
+        ("NaN", "Infinity", "-Infinity").
+    idx is the index of the first character of the constant
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the constant.
+
+    Returns the result of parse_constant
+    */
+    PyObject *cstr;
+    PyObject *rval;
+    /* constant is "NaN", "Infinity", or "-Infinity" */
+    cstr = PyString_InternFromString(constant);
+    if (cstr == NULL)
+        return NULL;
+
+    /* rval = parse_constant(constant) */
+    rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL);
+    idx += PyString_GET_SIZE(cstr);
+    Py_DECREF(cstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyString pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+
+        /* save the index of the 'e' or 'E' just in case we need to backtrack */
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyString_FromStringAndSize(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr)));
+        }
+    }
+    else {
+        /* parse as an int using a fast path if available, otherwise call user defined method */
+        if (s->parse_int != (PyObject *)&PyInt_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+        }
+        else {
+            rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10);
+        }
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyUnicode pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx < end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyUnicode_FromUnicode(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromString(numstr, NULL);
+        }
+    }
+    else {
+        /* no fast path for unicode -> int, just call */
+        rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyString pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t length = PyString_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_str(pystr, idx + 1,
+                PyString_AS_STRING(s->encoding),
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_str(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_str(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_str(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyUnicode pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t length = PyUnicode_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_unicode(pystr, idx + 1,
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_unicode(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to scan_once_{str,unicode} */
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t idx;
+    Py_ssize_t next_idx = -1;
+    static char *kwlist[] = {"string", "idx", NULL};
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx))
+        return NULL;
+
+    if (PyString_Check(pystr)) {
+        rval = scan_once_str(s, pystr, idx, &next_idx);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scan_once_unicode(s, pystr, idx, &next_idx);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                 "first argument must be a string, not %.80s",
+                 Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_idx);
+}
+
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyScannerObject *s;
+    s = (PyScannerObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->encoding = NULL;
+        s->strict = NULL;
+        s->object_hook = NULL;
+        s->parse_float = NULL;
+        s->parse_int = NULL;
+        s->parse_constant = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Initialize Scanner object */
+    PyObject *ctx;
+    static char *kwlist[] = {"context", NULL};
+    PyScannerObject *s;
+
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx))
+        return -1;
+
+    /* PyString_AS_STRING is used on encoding */
+    s->encoding = PyObject_GetAttrString(ctx, "encoding");
+    if (s->encoding == Py_None) {
+        Py_DECREF(Py_None);
+        s->encoding = PyString_InternFromString(DEFAULT_ENCODING);
+    }
+    else if (PyUnicode_Check(s->encoding)) {
+        PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL);
+        Py_DECREF(s->encoding);
+        s->encoding = tmp;
+    }
+    if (s->encoding == NULL || !PyString_Check(s->encoding))
+        goto bail;
+
+    /* All of these will fail "gracefully" so we don't need to verify them */
+    s->strict = PyObject_GetAttrString(ctx, "strict");
+    if (s->strict == NULL)
+        goto bail;
+    s->object_hook = PyObject_GetAttrString(ctx, "object_hook");
+    if (s->object_hook == NULL)
+        goto bail;
+    s->parse_float = PyObject_GetAttrString(ctx, "parse_float");
+    if (s->parse_float == NULL)
+        goto bail;
+    s->parse_int = PyObject_GetAttrString(ctx, "parse_int");
+    if (s->parse_int == NULL)
+        goto bail;
+    s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant");
+    if (s->parse_constant == NULL)
+        goto bail;
+
+    return 0;
+
+bail:
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return -1;
+}
+
+PyDoc_STRVAR(scanner_doc, "JSON scanner object");
+
+static
+PyTypeObject PyScannerType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Scanner",       /* tp_name */
+    sizeof(PyScannerObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    scanner_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    scanner_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,/* PyObject_GenericGetAttr, */                    /* tp_getattro */
+    0,/* PyObject_GenericSetAttr, */                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    scanner_doc,          /* tp_doc */
+    scanner_traverse,                    /* tp_traverse */
+    scanner_clear,                    /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    scanner_members,                    /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    scanner_init,                    /* tp_init */
+    0,/* PyType_GenericAlloc, */        /* tp_alloc */
+    scanner_new,          /* tp_new */
+    0,/* PyObject_GC_Del, */              /* tp_free */
+};
+
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyEncoderObject *s;
+    s = (PyEncoderObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->markers = NULL;
+        s->defaultfn = NULL;
+        s->encoder = NULL;
+        s->indent = NULL;
+        s->key_separator = NULL;
+        s->item_separator = NULL;
+        s->sort_keys = NULL;
+        s->skipkeys = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* initialize Encoder object */
+    static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
+
+    PyEncoderObject *s;
+    PyObject *allow_nan;
+
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOO:make_encoder", kwlist,
+        &s->markers, &s->defaultfn, &s->encoder, &s->indent, &s->key_separator, &s->item_separator, &s->sort_keys, &s->skipkeys, &allow_nan))
+        return -1;
+
+    Py_INCREF(s->markers);
+    Py_INCREF(s->defaultfn);
+    Py_INCREF(s->encoder);
+    Py_INCREF(s->indent);
+    Py_INCREF(s->key_separator);
+    Py_INCREF(s->item_separator);
+    Py_INCREF(s->sort_keys);
+    Py_INCREF(s->skipkeys);
+    s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
+    s->allow_nan = PyObject_IsTrue(allow_nan);
+    return 0;
+}
+
+static PyObject *
+encoder_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to encode_listencode_obj */
+    static char *kwlist[] = {"obj", "_current_indent_level", NULL};
+    PyObject *obj;
+    PyObject *rval;
+    Py_ssize_t indent_level;
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist,
+        &obj, _convertPyInt_AsSsize_t, &indent_level))
+        return NULL;
+    rval = PyList_New(0);
+    if (rval == NULL)
+        return NULL;
+    if (encoder_listencode_obj(s, rval, obj, indent_level)) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+_encoded_const(PyObject *obj)
+{
+    /* Return the JSON string representation of None, True, False */
+    if (obj == Py_None) {
+        static PyObject *s_null = NULL;
+        if (s_null == NULL) {
+            s_null = PyString_InternFromString("null");
+        }
+        Py_INCREF(s_null);
+        return s_null;
+    }
+    else if (obj == Py_True) {
+        static PyObject *s_true = NULL;
+        if (s_true == NULL) {
+            s_true = PyString_InternFromString("true");
+        }
+        Py_INCREF(s_true);
+        return s_true;
+    }
+    else if (obj == Py_False) {
+        static PyObject *s_false = NULL;
+        if (s_false == NULL) {
+            s_false = PyString_InternFromString("false");
+        }
+        Py_INCREF(s_false);
+        return s_false;
+    }
+    else {
+        PyErr_SetString(PyExc_ValueError, "not a const");
+        return NULL;
+    }
+}
+
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a PyFloat */
+    double i = PyFloat_AS_DOUBLE(obj);
+    if (!Py_IS_FINITE(i)) {
+        if (!s->allow_nan) {
+            PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
+            return NULL;
+        }
+        if (i > 0) {
+            return PyString_FromString("Infinity");
+        }
+        else if (i < 0) {
+            return PyString_FromString("-Infinity");
+        }
+        else {
+            return PyString_FromString("NaN");
+        }
+    }
+    /* Use a better float format here? */
+    return PyObject_Repr(obj);
+}
+
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a string */
+    if (s->fast_encode)
+        return py_encode_basestring_ascii(NULL, obj);
+    else
+        return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL);
+}
+
+static int
+_steal_list_append(PyObject *lst, PyObject *stolen)
+{
+    /* Append stolen and then decrement its reference count */
+    int rval = PyList_Append(lst, stolen);
+    Py_DECREF(stolen);
+    return rval;
+}
+
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level)
+{
+    /* Encode Python object obj to a JSON term, rval is a PyList */
+    PyObject *newobj;
+    int rv;
+
+    if (obj == Py_None || obj == Py_True || obj == Py_False) {
+        PyObject *cstr = _encoded_const(obj);
+        if (cstr == NULL)
+            return -1;
+        return _steal_list_append(rval, cstr);
+    }
+    else if (PyString_Check(obj) || PyUnicode_Check(obj))
+    {
+        PyObject *encoded = encoder_encode_string(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyInt_Check(obj) || PyLong_Check(obj)) {
+        PyObject *encoded = PyObject_Str(obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyFloat_Check(obj)) {
+        PyObject *encoded = encoder_encode_float(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyList_Check(obj) || PyTuple_Check(obj)) {
+        return encoder_listencode_list(s, rval, obj, indent_level);
+    }
+    else if (PyDict_Check(obj)) {
+        return encoder_listencode_dict(s, rval, obj, indent_level);
+    }
+    else {
+        PyObject *ident = NULL;
+        if (s->markers != Py_None) {
+            int has_key;
+            ident = PyLong_FromVoidPtr(obj);
+            if (ident == NULL)
+                return -1;
+            has_key = PyDict_Contains(s->markers, ident);
+            if (has_key) {
+                if (has_key != -1)
+                    PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+                Py_DECREF(ident);
+                return -1;
+            }
+            if (PyDict_SetItem(s->markers, ident, obj)) {
+                Py_DECREF(ident);
+                return -1;
+            }
+        }
+        newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL);
+        if (newobj == NULL) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+        Py_DECREF(newobj);
+        if (rv) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        if (ident != NULL) {
+            if (PyDict_DelItem(s->markers, ident)) {
+                Py_XDECREF(ident);
+                return -1;
+            }
+            Py_XDECREF(ident);
+        }
+        return rv;
+    }
+}
+
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level)
+{
+    /* Encode Python dict dct a JSON term, rval is a PyList */
+    static PyObject *open_dict = NULL;
+    static PyObject *close_dict = NULL;
+    static PyObject *empty_dict = NULL;
+    PyObject *kstr = NULL;
+    PyObject *ident = NULL;
+    PyObject *key, *value;
+    Py_ssize_t pos;
+    int skipkeys;
+    Py_ssize_t idx;
+
+    if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) {
+        open_dict = PyString_InternFromString("{");
+        close_dict = PyString_InternFromString("}");
+        empty_dict = PyString_InternFromString("{}");
+        if (open_dict == NULL || close_dict == NULL || empty_dict == NULL)
+            return -1;
+    }
+    if (PyDict_Size(dct) == 0)
+        return PyList_Append(rval, empty_dict);
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(dct);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, dct)) {
+            goto bail;
+        }
+    }
+
+    if (PyList_Append(rval, open_dict))
+        goto bail;
+
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+
+    /* TODO: C speedup not implemented for sort_keys */
+
+    pos = 0;
+    skipkeys = PyObject_IsTrue(s->skipkeys);
+    idx = 0;
+    while (PyDict_Next(dct, &pos, &key, &value)) {
+        PyObject *encoded;
+
+        if (PyString_Check(key) || PyUnicode_Check(key)) {
+            Py_INCREF(key);
+            kstr = key;
+        }
+        else if (PyFloat_Check(key)) {
+            kstr = encoder_encode_float(s, key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (PyInt_Check(key) || PyLong_Check(key)) {
+            kstr = PyObject_Str(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (key == Py_True || key == Py_False || key == Py_None) {
+            kstr = _encoded_const(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (skipkeys) {
+            continue;
+        }
+        else {
+            /* TODO: include repr of key */
+            PyErr_SetString(PyExc_ValueError, "keys must be a string");
+            goto bail;
+        }
+
+        if (idx) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+
+        encoded = encoder_encode_string(s, kstr);
+        Py_CLEAR(kstr);
+        if (encoded == NULL)
+            goto bail;
+        if (PyList_Append(rval, encoded)) {
+            Py_DECREF(encoded);
+            goto bail;
+        }
+        Py_DECREF(encoded);
+        if (PyList_Append(rval, s->key_separator))
+            goto bail;
+        if (encoder_listencode_obj(s, rval, value, indent_level))
+            goto bail;
+        idx += 1;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_dict))
+        goto bail;
+    return 0;
+
+bail:
+    Py_XDECREF(kstr);
+    Py_XDECREF(ident);
+    return -1;
+}
+
+
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level)
+{
+    /* Encode Python list seq to a JSON term, rval is a PyList */
+    static PyObject *open_array = NULL;
+    static PyObject *close_array = NULL;
+    static PyObject *empty_array = NULL;
+    PyObject *ident = NULL;
+    PyObject *s_fast = NULL;
+    Py_ssize_t num_items;
+    PyObject **seq_items;
+    Py_ssize_t i;
+
+    if (open_array == NULL || close_array == NULL || empty_array == NULL) {
+        open_array = PyString_InternFromString("[");
+        close_array = PyString_InternFromString("]");
+        empty_array = PyString_InternFromString("[]");
+        if (open_array == NULL || close_array == NULL || empty_array == NULL)
+            return -1;
+    }
+    ident = NULL;
+    s_fast = PySequence_Fast(seq, "_iterencode_list needs a sequence");
+    if (s_fast == NULL)
+        return -1;
+    num_items = PySequence_Fast_GET_SIZE(s_fast);
+    if (num_items == 0) {
+        Py_DECREF(s_fast);
+        return PyList_Append(rval, empty_array);
+    }
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(seq);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, seq)) {
+            goto bail;
+        }
+    }
+
+    seq_items = PySequence_Fast_ITEMS(s_fast);
+    if (PyList_Append(rval, open_array))
+        goto bail;
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+    for (i = 0; i < num_items; i++) {
+        PyObject *obj = seq_items[i];
+        if (i) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+        if (encoder_listencode_obj(s, rval, obj, indent_level))
+            goto bail;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_array))
+        goto bail;
+    Py_DECREF(s_fast);
+    return 0;
+
+bail:
+    Py_XDECREF(ident);
+    Py_DECREF(s_fast);
+    return -1;
+}
+
+static void
+encoder_dealloc(PyObject *self)
+{
+    /* Deallocate Encoder */
+    encoder_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+encoder_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_VISIT(s->markers);
+    Py_VISIT(s->defaultfn);
+    Py_VISIT(s->encoder);
+    Py_VISIT(s->indent);
+    Py_VISIT(s->key_separator);
+    Py_VISIT(s->item_separator);
+    Py_VISIT(s->sort_keys);
+    Py_VISIT(s->skipkeys);
+    return 0;
+}
+
+static int
+encoder_clear(PyObject *self)
+{
+    /* Deallocate Encoder */
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_CLEAR(s->markers);
+    Py_CLEAR(s->defaultfn);
+    Py_CLEAR(s->encoder);
+    Py_CLEAR(s->indent);
+    Py_CLEAR(s->key_separator);
+    Py_CLEAR(s->item_separator);
+    Py_CLEAR(s->sort_keys);
+    Py_CLEAR(s->skipkeys);
+    return 0;
+}
+
+PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable");
+
+static
+PyTypeObject PyEncoderType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Encoder",       /* tp_name */
+    sizeof(PyEncoderObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    encoder_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    encoder_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,                    /* tp_getattro */
+    0,                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    encoder_doc,          /* tp_doc */
+    encoder_traverse,     /* tp_traverse */
+    encoder_clear,        /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    encoder_members,      /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    encoder_init,         /* tp_init */
+    0,                    /* tp_alloc */
+    encoder_new,          /* tp_new */
+    0,                    /* tp_free */
+};
+
+static PyMethodDef speedups_methods[] = {
+    {"encode_basestring_ascii",
+        (PyCFunction)py_encode_basestring_ascii,
+        METH_O,
+        pydoc_encode_basestring_ascii},
+    {"scanstring",
+        (PyCFunction)py_scanstring,
+        METH_VARARGS,
+        pydoc_scanstring},
+    {NULL, NULL, 0, NULL}
+};
+
+PyDoc_STRVAR(module_doc,
+"simplejson speedups\n");
+
+void
+init_speedups(void)
+{
+    PyObject *m;
+    PyScannerType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyScannerType) < 0)
+        return;
+    PyEncoderType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyEncoderType) < 0)
+        return;
+    m = Py_InitModule3("_speedups", speedups_methods, module_doc);
+    Py_INCREF((PyObject*)&PyScannerType);
+    PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType);
+    Py_INCREF((PyObject*)&PyEncoderType);
+    PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType);
+}
diff --git a/ovsdb/simplejson/decoder.py b/ovsdb/simplejson/decoder.py
new file mode 100644 (file)
index 0000000..b769ea4
--- /dev/null
@@ -0,0 +1,354 @@
+"""Implementation of JSONDecoder
+"""
+import re
+import sys
+import struct
+
+from simplejson.scanner import make_scanner
+try:
+    from simplejson._speedups import scanstring as c_scanstring
+except ImportError:
+    c_scanstring = None
+
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+    if sys.byteorder != 'big':
+        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+    nan, inf = struct.unpack('dd', _BYTES)
+    return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+
+def linecol(doc, pos):
+    lineno = doc.count('\n', 0, pos) + 1
+    if lineno == 1:
+        colno = pos
+    else:
+        colno = pos - doc.rindex('\n', 0, pos)
+    return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+    # Note that this function is called from _speedups
+    lineno, colno = linecol(doc, pos)
+    if end is None:
+        #fmt = '{0}: line {1} column {2} (char {3})'
+        #return fmt.format(msg, lineno, colno, pos)
+        fmt = '%s: line %d column %d (char %d)'
+        return fmt % (msg, lineno, colno, pos)
+    endlineno, endcolno = linecol(doc, end)
+    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
+    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
+    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+_CONSTANTS = {
+    '-Infinity': NegInf,
+    'Infinity': PosInf,
+    'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+    '"': u'"', '\\': u'\\', '/': u'/',
+    'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+    """Scan the string s for a JSON string. End is the index of the
+    character in s after the quote that started the JSON string.
+    Unescapes all valid JSON string escape sequences and raises ValueError
+    on attempt to decode an invalid string. If strict is False then literal
+    control characters are allowed in the string.
+    
+    Returns a tuple of the decoded string and the index of the character in s
+    after the end quote."""
+    if encoding is None:
+        encoding = DEFAULT_ENCODING
+    chunks = []
+    _append = chunks.append
+    begin = end - 1
+    while 1:
+        chunk = _m(s, end)
+        if chunk is None:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        end = chunk.end()
+        content, terminator = chunk.groups()
+        # Content is contains zero or more unescaped string characters
+        if content:
+            if not isinstance(content, unicode):
+                content = unicode(content, encoding)
+            _append(content)
+        # Terminator is the end of string, a literal control character,
+        # or a backslash denoting that an escape sequence follows
+        if terminator == '"':
+            break
+        elif terminator != '\\':
+            if strict:
+                msg = "Invalid control character %r at" % (terminator,)
+                #msg = "Invalid control character {0!r} at".format(terminator)
+                raise ValueError(errmsg(msg, s, end))
+            else:
+                _append(terminator)
+                continue
+        try:
+            esc = s[end]
+        except IndexError:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        # If not a unicode escape sequence, must be in the lookup table
+        if esc != 'u':
+            try:
+                char = _b[esc]
+            except KeyError:
+                msg = "Invalid \\escape: " + repr(esc)
+                raise ValueError(errmsg(msg, s, end))
+            end += 1
+        else:
+            # Unicode escape sequence
+            esc = s[end + 1:end + 5]
+            next_end = end + 5
+            if len(esc) != 4:
+                msg = "Invalid \\uXXXX escape"
+                raise ValueError(errmsg(msg, s, end))
+            uni = int(esc, 16)
+            # Check for surrogate pair on UCS-4 systems
+            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
+                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
+                if not s[end + 5:end + 7] == '\\u':
+                    raise ValueError(errmsg(msg, s, end))
+                esc2 = s[end + 7:end + 11]
+                if len(esc2) != 4:
+                    raise ValueError(errmsg(msg, s, end))
+                uni2 = int(esc2, 16)
+                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
+                next_end += 6
+            char = unichr(uni)
+            end = next_end
+        # Append the unescaped character
+        _append(char)
+    return u''.join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    pairs = {}
+    # Use a slice to prevent IndexError from being raised, the following
+    # check will raise a more specific ValueError if the string is empty
+    nextchar = s[end:end + 1]
+    # Normally we expect nextchar == '"'
+    if nextchar != '"':
+        if nextchar in _ws:
+            end = _w(s, end).end()
+            nextchar = s[end:end + 1]
+        # Trivial empty object
+        if nextchar == '}':
+            return pairs, end + 1
+        elif nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end))
+    end += 1
+    while True:
+        key, end = scanstring(s, end, encoding, strict)
+
+        # To skip some function call overhead we optimize the fast paths where
+        # the JSON key separator is ": " or just ":".
+        if s[end:end + 1] != ':':
+            end = _w(s, end).end()
+            if s[end:end + 1] != ':':
+                raise ValueError(errmsg("Expecting : delimiter", s, end))
+
+        end += 1
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        pairs[key] = value
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end = _w(s, end + 1).end()
+                nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+        end += 1
+
+        if nextchar == '}':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end += 1
+                nextchar = s[end]
+                if nextchar in _ws:
+                    end = _w(s, end + 1).end()
+                    nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+
+        end += 1
+        if nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end - 1))
+
+    if object_hook is not None:
+        pairs = object_hook(pairs)
+    return pairs, end
+
+def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    values = []
+    nextchar = s[end:end + 1]
+    if nextchar in _ws:
+        end = _w(s, end + 1).end()
+        nextchar = s[end:end + 1]
+    # Look-ahead for trivial empty array
+    if nextchar == ']':
+        return values, end + 1
+    _append = values.append
+    while True:
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        _append(value)
+        nextchar = s[end:end + 1]
+        if nextchar in _ws:
+            end = _w(s, end + 1).end()
+            nextchar = s[end:end + 1]
+        end += 1
+        if nextchar == ']':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end))
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+    return values, end
+
+class JSONDecoder(object):
+    """Simple JSON <http://json.org> decoder
+
+    Performs the following translations in decoding by default:
+
+    +---------------+-------------------+
+    | JSON          | Python            |
+    +===============+===================+
+    | object        | dict              |
+    +---------------+-------------------+
+    | array         | list              |
+    +---------------+-------------------+
+    | string        | unicode           |
+    +---------------+-------------------+
+    | number (int)  | int, long         |
+    +---------------+-------------------+
+    | number (real) | float             |
+    +---------------+-------------------+
+    | true          | True              |
+    +---------------+-------------------+
+    | false         | False             |
+    +---------------+-------------------+
+    | null          | None              |
+    +---------------+-------------------+
+
+    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+    their corresponding ``float`` values, which is outside the JSON spec.
+
+    """
+
+    def __init__(self, encoding=None, object_hook=None, parse_float=None,
+            parse_int=None, parse_constant=None, strict=True):
+        """``encoding`` determines the encoding used to interpret any ``str``
+        objects decoded by this instance (utf-8 by default).  It has no
+        effect when decoding ``unicode`` objects.
+
+        Note that currently only encodings that are a superset of ASCII work,
+        strings of other encodings should be passed in as ``unicode``.
+
+        ``object_hook``, if specified, will be called with the result
+        of every JSON object decoded and its return value will be used in
+        place of the given ``dict``.  This can be used to provide custom
+        deserializations (e.g. to support JSON-RPC class hinting).
+
+        ``parse_float``, if specified, will be called with the string
+        of every JSON float to be decoded. By default this is equivalent to
+        float(num_str). This can be used to use another datatype or parser
+        for JSON floats (e.g. decimal.Decimal).
+
+        ``parse_int``, if specified, will be called with the string
+        of every JSON int to be decoded. By default this is equivalent to
+        int(num_str). This can be used to use another datatype or parser
+        for JSON integers (e.g. float).
+
+        ``parse_constant``, if specified, will be called with one of the
+        following strings: -Infinity, Infinity, NaN.
+        This can be used to raise an exception if invalid JSON numbers
+        are encountered.
+
+        """
+        self.encoding = encoding
+        self.object_hook = object_hook
+        self.parse_float = parse_float or float
+        self.parse_int = parse_int or int
+        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+        self.strict = strict
+        self.parse_object = JSONObject
+        self.parse_array = JSONArray
+        self.parse_string = scanstring
+        self.scan_once = make_scanner(self)
+
+    def decode(self, s, _w=WHITESPACE.match):
+        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+        instance containing a JSON document)
+
+        """
+        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+        end = _w(s, end).end()
+        if end != len(s):
+            raise ValueError(errmsg("Extra data", s, end, len(s)))
+        return obj
+
+    def raw_decode(self, s, idx=0):
+        """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+        with a JSON document) and return a 2-tuple of the Python
+        representation and the index in ``s`` where the document ended.
+
+        This can be used to decode a JSON document from a string that may
+        have extraneous data at the end.
+
+        """
+        try:
+            obj, end = self.scan_once(s, idx)
+        except StopIteration:
+            raise ValueError("No JSON object could be decoded")
+        return obj, end
diff --git a/ovsdb/simplejson/encoder.py b/ovsdb/simplejson/encoder.py
new file mode 100644 (file)
index 0000000..cf58290
--- /dev/null
@@ -0,0 +1,440 @@
+"""Implementation of JSONEncoder
+"""
+import re
+
+try:
+    from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+    c_encode_basestring_ascii = None
+try:
+    from simplejson._speedups import make_encoder as c_make_encoder
+except ImportError:
+    c_make_encoder = None
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+    '\\': '\\\\',
+    '"': '\\"',
+    '\b': '\\b',
+    '\f': '\\f',
+    '\n': '\\n',
+    '\r': '\\r',
+    '\t': '\\t',
+}
+for i in range(0x20):
+    #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def encode_basestring(s):
+    """Return a JSON representation of a Python string
+
+    """
+    def replace(match):
+        return ESCAPE_DCT[match.group(0)]
+    return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+    """Return an ASCII-only JSON representation of a Python string
+
+    """
+    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+        s = s.decode('utf-8')
+    def replace(match):
+        s = match.group(0)
+        try:
+            return ESCAPE_DCT[s]
+        except KeyError:
+            n = ord(s)
+            if n < 0x10000:
+                #return '\\u{0:04x}'.format(n)
+                return '\\u%04x' % (n,)
+            else:
+                # surrogate pair
+                n -= 0x10000
+                s1 = 0xd800 | ((n >> 10) & 0x3ff)
+                s2 = 0xdc00 | (n & 0x3ff)
+                #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+                return '\\u%04x\\u%04x' % (s1, s2)
+    return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+
+class JSONEncoder(object):
+    """Extensible JSON <http://json.org> encoder for Python data structures.
+
+    Supports the following objects and types by default:
+
+    +-------------------+---------------+
+    | Python            | JSON          |
+    +===================+===============+
+    | dict              | object        |
+    +-------------------+---------------+
+    | list, tuple       | array         |
+    +-------------------+---------------+
+    | str, unicode      | string        |
+    +-------------------+---------------+
+    | int, long, float  | number        |
+    +-------------------+---------------+
+    | True              | true          |
+    +-------------------+---------------+
+    | False             | false         |
+    +-------------------+---------------+
+    | None              | null          |
+    +-------------------+---------------+
+
+    To extend this to recognize other objects, subclass and implement a
+    ``.default()`` method with another method that returns a serializable
+    object for ``o`` if possible, otherwise it should call the superclass
+    implementation (to raise ``TypeError``).
+
+    """
+    item_separator = ', '
+    key_separator = ': '
+    def __init__(self, skipkeys=False, ensure_ascii=True,
+            check_circular=True, allow_nan=True, sort_keys=False,
+            indent=None, separators=None, encoding='utf-8', default=None):
+        """Constructor for JSONEncoder, with sensible defaults.
+
+        If skipkeys is false, then it is a TypeError to attempt
+        encoding of keys that are not str, int, long, float or None.  If
+        skipkeys is True, such items are simply skipped.
+
+        If ensure_ascii is true, the output is guaranteed to be str
+        objects with all incoming unicode characters escaped.  If
+        ensure_ascii is false, the output will be unicode object.
+
+        If check_circular is true, then lists, dicts, and custom encoded
+        objects will be checked for circular references during encoding to
+        prevent an infinite recursion (which would cause an OverflowError).
+        Otherwise, no such check takes place.
+
+        If allow_nan is true, then NaN, Infinity, and -Infinity will be
+        encoded as such.  This behavior is not JSON specification compliant,
+        but is consistent with most JavaScript based encoders and decoders.
+        Otherwise, it will be a ValueError to encode such floats.
+
+        If sort_keys is true, then the output of dictionaries will be
+        sorted by key; this is useful for regression tests to ensure
+        that JSON serializations can be compared on a day-to-day basis.
+
+        If indent is a non-negative integer, then JSON array
+        elements and object members will be pretty-printed with that
+        indent level.  An indent level of 0 will only insert newlines.
+        None is the most compact representation.
+
+        If specified, separators should be a (item_separator, key_separator)
+        tuple.  The default is (', ', ': ').  To get the most compact JSON
+        representation you should specify (',', ':') to eliminate whitespace.
+
+        If specified, default is a function that gets called for objects
+        that can't otherwise be serialized.  It should return a JSON encodable
+        version of the object or raise a ``TypeError``.
+
+        If encoding is not None, then all input strings will be
+        transformed into unicode using that encoding prior to JSON-encoding.
+        The default is UTF-8.
+
+        """
+
+        self.skipkeys = skipkeys
+        self.ensure_ascii = ensure_ascii
+        self.check_circular = check_circular
+        self.allow_nan = allow_nan
+        self.sort_keys = sort_keys
+        self.indent = indent
+        if separators is not None:
+            self.item_separator, self.key_separator = separators
+        if default is not None:
+            self.default = default
+        self.encoding = encoding
+
+    def default(self, o):
+        """Implement this method in a subclass such that it returns
+        a serializable object for ``o``, or calls the base implementation
+        (to raise a ``TypeError``).
+
+        For example, to support arbitrary iterators, you could
+        implement default like this::
+
+            def default(self, o):
+                try:
+                    iterable = iter(o)
+                except TypeError:
+                    pass
+                else:
+                    return list(iterable)
+                return JSONEncoder.default(self, o)
+
+        """
+        raise TypeError(repr(o) + " is not JSON serializable")
+
+    def encode(self, o):
+        """Return a JSON string representation of a Python data structure.
+
+        >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+        '{"foo": ["bar", "baz"]}'
+
+        """
+        # This is for extremely simple cases and benchmarks.
+        if isinstance(o, basestring):
+            if isinstance(o, str):
+                _encoding = self.encoding
+                if (_encoding is not None
+                        and not (_encoding == 'utf-8')):
+                    o = o.decode(_encoding)
+            if self.ensure_ascii:
+                return encode_basestring_ascii(o)
+            else:
+                return encode_basestring(o)
+        # This doesn't pass the iterator directly to ''.join() because the
+        # exceptions aren't as detailed.  The list call should be roughly
+        # equivalent to the PySequence_Fast that ''.join() would do.
+        chunks = self.iterencode(o, _one_shot=True)
+        if not isinstance(chunks, (list, tuple)):
+            chunks = list(chunks)
+        return ''.join(chunks)
+
+    def iterencode(self, o, _one_shot=False):
+        """Encode the given object and yield each string
+        representation as available.
+
+        For example::
+
+            for chunk in JSONEncoder().iterencode(bigobject):
+                mysocket.write(chunk)
+
+        """
+        if self.check_circular:
+            markers = {}
+        else:
+            markers = None
+        if self.ensure_ascii:
+            _encoder = encode_basestring_ascii
+        else:
+            _encoder = encode_basestring
+        if self.encoding != 'utf-8':
+            def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+                if isinstance(o, str):
+                    o = o.decode(_encoding)
+                return _orig_encoder(o)
+
+        def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
+            # Check for specials.  Note that this type of test is processor- and/or
+            # platform-specific, so do tests which don't depend on the internals.
+
+            if o != o:
+                text = 'NaN'
+            elif o == _inf:
+                text = 'Infinity'
+            elif o == _neginf:
+                text = '-Infinity'
+            else:
+                return _repr(o)
+
+            if not allow_nan:
+                raise ValueError(
+                    "Out of range float values are not JSON compliant: " +
+                    repr(o))
+
+            return text
+
+
+        if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+            _iterencode = c_make_encoder(
+                markers, self.default, _encoder, self.indent,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, self.allow_nan)
+        else:
+            _iterencode = _make_iterencode(
+                markers, self.default, _encoder, self.indent, floatstr,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, _one_shot)
+        return _iterencode(o, 0)
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+        ## HACK: hand-optimized bytecode; turn globals into locals
+        False=False,
+        True=True,
+        ValueError=ValueError,
+        basestring=basestring,
+        dict=dict,
+        float=float,
+        id=id,
+        int=int,
+        isinstance=isinstance,
+        list=list,
+        long=long,
+        str=str,
+        tuple=tuple,
+    ):
+
+    def _iterencode_list(lst, _current_indent_level):
+        if not lst:
+            yield '[]'
+            return
+        if markers is not None:
+            markerid = id(lst)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = lst
+        buf = '['
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        else:
+            newline_indent = None
+            separator = _item_separator
+        first = True
+        for value in lst:
+            if first:
+                first = False
+            else:
+                buf = separator
+            if isinstance(value, basestring):
+                yield buf + _encoder(value)
+            elif value is None:
+                yield buf + 'null'
+            elif value is True:
+                yield buf + 'true'
+            elif value is False:
+                yield buf + 'false'
+            elif isinstance(value, (int, long)):
+                yield buf + str(value)
+            elif isinstance(value, float):
+                yield buf + _floatstr(value)
+            else:
+                yield buf
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield ']'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode_dict(dct, _current_indent_level):
+        if not dct:
+            yield '{}'
+            return
+        if markers is not None:
+            markerid = id(dct)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = dct
+        yield '{'
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            item_separator = _item_separator + newline_indent
+            yield newline_indent
+        else:
+            newline_indent = None
+            item_separator = _item_separator
+        first = True
+        if _sort_keys:
+            items = dct.items()
+            items.sort(key=lambda kv: kv[0])
+        else:
+            items = dct.iteritems()
+        for key, value in items:
+            if isinstance(key, basestring):
+                pass
+            # JavaScript is weakly typed for these, so it makes sense to
+            # also allow them.  Many encoders seem to do something like this.
+            elif isinstance(key, float):
+                key = _floatstr(key)
+            elif key is True:
+                key = 'true'
+            elif key is False:
+                key = 'false'
+            elif key is None:
+                key = 'null'
+            elif isinstance(key, (int, long)):
+                key = str(key)
+            elif _skipkeys:
+                continue
+            else:
+                raise TypeError("key " + repr(key) + " is not a string")
+            if first:
+                first = False
+            else:
+                yield item_separator
+            yield _encoder(key)
+            yield _key_separator
+            if isinstance(value, basestring):
+                yield _encoder(value)
+            elif value is None:
+                yield 'null'
+            elif value is True:
+                yield 'true'
+            elif value is False:
+                yield 'false'
+            elif isinstance(value, (int, long)):
+                yield str(value)
+            elif isinstance(value, float):
+                yield _floatstr(value)
+            else:
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield '}'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode(o, _current_indent_level):
+        if isinstance(o, basestring):
+            yield _encoder(o)
+        elif o is None:
+            yield 'null'
+        elif o is True:
+            yield 'true'
+        elif o is False:
+            yield 'false'
+        elif isinstance(o, (int, long)):
+            yield str(o)
+        elif isinstance(o, float):
+            yield _floatstr(o)
+        elif isinstance(o, (list, tuple)):
+            for chunk in _iterencode_list(o, _current_indent_level):
+                yield chunk
+        elif isinstance(o, dict):
+            for chunk in _iterencode_dict(o, _current_indent_level):
+                yield chunk
+        else:
+            if markers is not None:
+                markerid = id(o)
+                if markerid in markers:
+                    raise ValueError("Circular reference detected")
+                markers[markerid] = o
+            o = _default(o)
+            for chunk in _iterencode(o, _current_indent_level):
+                yield chunk
+            if markers is not None:
+                del markers[markerid]
+
+    return _iterencode
diff --git a/ovsdb/simplejson/scanner.py b/ovsdb/simplejson/scanner.py
new file mode 100644 (file)
index 0000000..adbc6ec
--- /dev/null
@@ -0,0 +1,65 @@
+"""JSON token scanner
+"""
+import re
+try:
+    from simplejson._speedups import make_scanner as c_make_scanner
+except ImportError:
+    c_make_scanner = None
+
+__all__ = ['make_scanner']
+
+NUMBER_RE = re.compile(
+    r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+    (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+def py_make_scanner(context):
+    parse_object = context.parse_object
+    parse_array = context.parse_array
+    parse_string = context.parse_string
+    match_number = NUMBER_RE.match
+    encoding = context.encoding
+    strict = context.strict
+    parse_float = context.parse_float
+    parse_int = context.parse_int
+    parse_constant = context.parse_constant
+    object_hook = context.object_hook
+
+    def _scan_once(string, idx):
+        try:
+            nextchar = string[idx]
+        except IndexError:
+            raise StopIteration
+
+        if nextchar == '"':
+            return parse_string(string, idx + 1, encoding, strict)
+        elif nextchar == '{':
+            return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+        elif nextchar == '[':
+            return parse_array((string, idx + 1), _scan_once)
+        elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+            return None, idx + 4
+        elif nextchar == 't' and string[idx:idx + 4] == 'true':
+            return True, idx + 4
+        elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+            return False, idx + 5
+
+        m = match_number(string, idx)
+        if m is not None:
+            integer, frac, exp = m.groups()
+            if frac or exp:
+                res = parse_float(integer + (frac or '') + (exp or ''))
+            else:
+                res = parse_int(integer)
+            return res, m.end()
+        elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+            return parse_constant('NaN'), idx + 3
+        elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+            return parse_constant('Infinity'), idx + 8
+        elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+            return parse_constant('-Infinity'), idx + 9
+        else:
+            raise StopIteration
+
+    return _scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/ovsdb/simplejson/tests/__init__.py b/ovsdb/simplejson/tests/__init__.py
new file mode 100644 (file)
index 0000000..17c9796
--- /dev/null
@@ -0,0 +1,23 @@
+import unittest
+import doctest
+
+def additional_tests():
+    import simplejson
+    import simplejson.encoder
+    import simplejson.decoder
+    suite = unittest.TestSuite()
+    for mod in (simplejson, simplejson.encoder, simplejson.decoder):
+        suite.addTest(doctest.DocTestSuite(mod))
+    suite.addTest(doctest.DocFileSuite('../../index.rst'))
+    return suite
+
+def main():
+    suite = additional_tests()
+    runner = unittest.TextTestRunner()
+    runner.run(suite)
+
+if __name__ == '__main__':
+    import os
+    import sys
+    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+    main()
diff --git a/ovsdb/simplejson/tests/test_check_circular.py b/ovsdb/simplejson/tests/test_check_circular.py
new file mode 100644 (file)
index 0000000..af6463d
--- /dev/null
@@ -0,0 +1,30 @@
+from unittest import TestCase
+import simplejson as json
+
+def default_iterable(obj):
+    return list(obj)
+
+class TestCheckCircular(TestCase):
+    def test_circular_dict(self):
+        dct = {}
+        dct['a'] = dct
+        self.assertRaises(ValueError, json.dumps, dct)
+
+    def test_circular_list(self):
+        lst = []
+        lst.append(lst)
+        self.assertRaises(ValueError, json.dumps, lst)
+
+    def test_circular_composite(self):
+        dct2 = {}
+        dct2['a'] = []
+        dct2['a'].append(dct2)
+        self.assertRaises(ValueError, json.dumps, dct2)
+
+    def test_circular_default(self):
+        json.dumps([set()], default=default_iterable)
+        self.assertRaises(TypeError, json.dumps, [set()])
+
+    def test_circular_off_default(self):
+        json.dumps([set()], default=default_iterable, check_circular=False)
+        self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
diff --git a/ovsdb/simplejson/tests/test_decode.py b/ovsdb/simplejson/tests/test_decode.py
new file mode 100644 (file)
index 0000000..1cd701d
--- /dev/null
@@ -0,0 +1,22 @@
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDecode(TestCase):
+    def test_decimal(self):
+        rval = json.loads('1.1', parse_float=decimal.Decimal)
+        self.assert_(isinstance(rval, decimal.Decimal))
+        self.assertEquals(rval, decimal.Decimal('1.1'))
+
+    def test_float(self):
+        rval = json.loads('1', parse_int=float)
+        self.assert_(isinstance(rval, float))
+        self.assertEquals(rval, 1.0)
+
+    def test_decoder_optimizations(self):
+        # Several optimizations were made that skip over calls to
+        # the whitespace regex, so this test is designed to try and
+        # exercise the uncommon cases. The array cases are already covered.
+        rval = json.loads('{   "key"    :    "value"    ,  "k":"v"    }')
+        self.assertEquals(rval, {"key":"value", "k":"v"})
diff --git a/ovsdb/simplejson/tests/test_default.py b/ovsdb/simplejson/tests/test_default.py
new file mode 100644 (file)
index 0000000..139e42b
--- /dev/null
@@ -0,0 +1,9 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDefault(TestCase):
+    def test_default(self):
+        self.assertEquals(
+            json.dumps(type, default=repr),
+            json.dumps(repr(type)))
diff --git a/ovsdb/simplejson/tests/test_dump.py b/ovsdb/simplejson/tests/test_dump.py
new file mode 100644 (file)
index 0000000..4de37cf
--- /dev/null
@@ -0,0 +1,21 @@
+from unittest import TestCase
+from cStringIO import StringIO
+
+import simplejson as json
+
+class TestDump(TestCase):
+    def test_dump(self):
+        sio = StringIO()
+        json.dump({}, sio)
+        self.assertEquals(sio.getvalue(), '{}')
+
+    def test_dumps(self):
+        self.assertEquals(json.dumps({}), '{}')
+
+    def test_encode_truefalse(self):
+        self.assertEquals(json.dumps(
+                 {True: False, False: True}, sort_keys=True),
+                 '{"false": true, "true": false}')
+        self.assertEquals(json.dumps(
+                {2: 3.0, 4.0: 5L, False: 1, 6L: True, "7": 0}, sort_keys=True),
+                '{"false": 1, "2": 3.0, "4.0": 5, "6": true, "7": 0}')
diff --git a/ovsdb/simplejson/tests/test_encode_basestring_ascii.py b/ovsdb/simplejson/tests/test_encode_basestring_ascii.py
new file mode 100644 (file)
index 0000000..7128495
--- /dev/null
@@ -0,0 +1,38 @@
+from unittest import TestCase
+
+import simplejson.encoder
+
+CASES = [
+    (u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+    (u'controls', '"controls"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
+    (u' s p a c e d ', '" s p a c e d "'),
+    (u'\U0001d120', '"\\ud834\\udd20"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+]
+
+class TestEncodeBaseStringAscii(TestCase):
+    def test_py_encode_basestring_ascii(self):
+        self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
+
+    def test_c_encode_basestring_ascii(self):
+        if not simplejson.encoder.c_encode_basestring_ascii:
+            return
+        self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
+
+    def _test_encode_basestring_ascii(self, encode_basestring_ascii):
+        fname = encode_basestring_ascii.__name__
+        for input_string, expect in CASES:
+            result = encode_basestring_ascii(input_string)
+            self.assertEquals(result, expect,
+                '%r != %r for %s(%r)' % (result, expect, fname, input_string))
diff --git a/ovsdb/simplejson/tests/test_fail.py b/ovsdb/simplejson/tests/test_fail.py
new file mode 100644 (file)
index 0000000..002eea0
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# Fri Dec 30 18:57:26 2005
+JSONDOCS = [
+    # http://json.org/JSON_checker/test/fail1.json
+    '"A JSON payload should be an object or array, not a string."',
+    # http://json.org/JSON_checker/test/fail2.json
+    '["Unclosed array"',
+    # http://json.org/JSON_checker/test/fail3.json
+    '{unquoted_key: "keys must be quoted}',
+    # http://json.org/JSON_checker/test/fail4.json
+    '["extra comma",]',
+    # http://json.org/JSON_checker/test/fail5.json
+    '["double extra comma",,]',
+    # http://json.org/JSON_checker/test/fail6.json
+    '[   , "<-- missing value"]',
+    # http://json.org/JSON_checker/test/fail7.json
+    '["Comma after the close"],',
+    # http://json.org/JSON_checker/test/fail8.json
+    '["Extra close"]]',
+    # http://json.org/JSON_checker/test/fail9.json
+    '{"Extra comma": true,}',
+    # http://json.org/JSON_checker/test/fail10.json
+    '{"Extra value after close": true} "misplaced quoted value"',
+    # http://json.org/JSON_checker/test/fail11.json
+    '{"Illegal expression": 1 + 2}',
+    # http://json.org/JSON_checker/test/fail12.json
+    '{"Illegal invocation": alert()}',
+    # http://json.org/JSON_checker/test/fail13.json
+    '{"Numbers cannot have leading zeroes": 013}',
+    # http://json.org/JSON_checker/test/fail14.json
+    '{"Numbers cannot be hex": 0x14}',
+    # http://json.org/JSON_checker/test/fail15.json
+    '["Illegal backslash escape: \\x15"]',
+    # http://json.org/JSON_checker/test/fail16.json
+    '["Illegal backslash escape: \\\'"]',
+    # http://json.org/JSON_checker/test/fail17.json
+    '["Illegal backslash escape: \\017"]',
+    # http://json.org/JSON_checker/test/fail18.json
+    '[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
+    # http://json.org/JSON_checker/test/fail19.json
+    '{"Missing colon" null}',
+    # http://json.org/JSON_checker/test/fail20.json
+    '{"Double colon":: null}',
+    # http://json.org/JSON_checker/test/fail21.json
+    '{"Comma instead of colon", null}',
+    # http://json.org/JSON_checker/test/fail22.json
+    '["Colon instead of comma": false]',
+    # http://json.org/JSON_checker/test/fail23.json
+    '["Bad value", truth]',
+    # http://json.org/JSON_checker/test/fail24.json
+    "['single quote']",
+    # http://code.google.com/p/simplejson/issues/detail?id=3
+    u'["A\u001FZ control characters in string"]',
+]
+
+SKIPS = {
+    1: "why not have a string payload?",
+    18: "spec doesn't specify any nesting limitations",
+}
+
+class TestFail(TestCase):
+    def test_failures(self):
+        for idx, doc in enumerate(JSONDOCS):
+            idx = idx + 1
+            if idx in SKIPS:
+                json.loads(doc)
+                continue
+            try:
+                json.loads(doc)
+            except ValueError:
+                pass
+            else:
+                self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
diff --git a/ovsdb/simplejson/tests/test_float.py b/ovsdb/simplejson/tests/test_float.py
new file mode 100644 (file)
index 0000000..1a2b98a
--- /dev/null
@@ -0,0 +1,15 @@
+import math
+from unittest import TestCase
+
+import simplejson as json
+
+class TestFloat(TestCase):
+    def test_floats(self):
+        for num in [1617161771.7650001, math.pi, math.pi**100, math.pi**-100, 3.1]:
+            self.assertEquals(float(json.dumps(num)), num)
+            self.assertEquals(json.loads(json.dumps(num)), num)
+
+    def test_ints(self):
+        for num in [1, 1L, 1<<32, 1<<64]:
+            self.assertEquals(json.dumps(num), str(num))
+            self.assertEquals(int(json.dumps(num)), num)
diff --git a/ovsdb/simplejson/tests/test_indent.py b/ovsdb/simplejson/tests/test_indent.py
new file mode 100644 (file)
index 0000000..66e19b9
--- /dev/null
@@ -0,0 +1,41 @@
+from unittest import TestCase
+
+import simplejson as json
+import textwrap
+
+class TestIndent(TestCase):
+    def test_indent(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ],
+          [
+            "whoops"
+          ],
+          [],
+          "d-shtaeou",
+          "d-nthiouh",
+          "i-vhbjkhnth",
+          {
+            "nifty": 87
+          },
+          {
+            "field": "yes",
+            "morefield": false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_pass1.py b/ovsdb/simplejson/tests/test_pass1.py
new file mode 100644 (file)
index 0000000..c3d6302
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass1.json
+JSON = r'''
+[
+    "JSON Test Pattern pass1",
+    {"object with 1 member":["array with 1 element"]},
+    {},
+    [],
+    -42,
+    true,
+    false,
+    null,
+    {
+        "integer": 1234567890,
+        "real": -9876.543210,
+        "e": 0.123456789e-12,
+        "E": 1.234567890E+34,
+        "":  23456789012E666,
+        "zero": 0,
+        "one": 1,
+        "space": " ",
+        "quote": "\"",
+        "backslash": "\\",
+        "controls": "\b\f\n\r\t",
+        "slash": "/ & \/",
+        "alpha": "abcdefghijklmnopqrstuvwyz",
+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+        "digit": "0123456789",
+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+        "true": true,
+        "false": false,
+        "null": null,
+        "array":[  ],
+        "object":{  },
+        "address": "50 St. James Street",
+        "url": "http://www.JSON.org/",
+        "comment": "// /* <!-- --",
+        "# -- --> */": " ",
+        " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5        ,          6           ,7        ],
+        "compact": [1,2,3,4,5,6,7],
+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+    },
+    0.5 ,98.6
+,
+99.44
+,
+
+1066
+
+
+,"rosebud"]
+'''
+
+class TestPass1(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
+        try:
+            json.dumps(res, allow_nan=False)
+        except ValueError:
+            pass
+        else:
+            self.fail("23456789012E666 should be out of range")
diff --git a/ovsdb/simplejson/tests/test_pass2.py b/ovsdb/simplejson/tests/test_pass2.py
new file mode 100644 (file)
index 0000000..de4ee00
--- /dev/null
@@ -0,0 +1,14 @@
+from unittest import TestCase
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass2.json
+JSON = r'''
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
+'''
+
+class TestPass2(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_pass3.py b/ovsdb/simplejson/tests/test_pass3.py
new file mode 100644 (file)
index 0000000..f591aba
--- /dev/null
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass3.json
+JSON = r'''
+{
+    "JSON Test Pattern pass3": {
+        "The outermost value": "must be an object or array.",
+        "In this test": "It is an object."
+    }
+}
+'''
+
+class TestPass3(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_recursion.py b/ovsdb/simplejson/tests/test_recursion.py
new file mode 100644 (file)
index 0000000..97422a6
--- /dev/null
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class JSONTestObject:
+    pass
+
+
+class RecursiveJSONEncoder(json.JSONEncoder):
+    recurse = False
+    def default(self, o):
+        if o is JSONTestObject:
+            if self.recurse:
+                return [JSONTestObject]
+            else:
+                return 'JSONTestObject'
+        return json.JSONEncoder.default(o)
+
+
+class TestRecursion(TestCase):
+    def test_listrecursion(self):
+        x = []
+        x.append(x)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on list recursion")
+        x = []
+        y = [x]
+        x.append(y)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on alternating list recursion")
+        y = []
+        x = [y, y]
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_dictrecursion(self):
+        x = {}
+        x["test"] = x
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on dict recursion")
+        x = {}
+        y = {"a": x, "b": x}
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_defaultrecursion(self):
+        enc = RecursiveJSONEncoder()
+        self.assertEquals(enc.encode(JSONTestObject), '"JSONTestObject"')
+        enc.recurse = True
+        try:
+            enc.encode(JSONTestObject)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on default recursion")
diff --git a/ovsdb/simplejson/tests/test_scanstring.py b/ovsdb/simplejson/tests/test_scanstring.py
new file mode 100644 (file)
index 0000000..b08dec7
--- /dev/null
@@ -0,0 +1,111 @@
+import sys
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+import simplejson.decoder
+
+class TestScanString(TestCase):
+    def test_py_scanstring(self):
+        self._test_scanstring(simplejson.decoder.py_scanstring)
+
+    def test_c_scanstring(self):
+        if not simplejson.decoder.c_scanstring:
+            return
+        self._test_scanstring(simplejson.decoder.c_scanstring)
+
+    def _test_scanstring(self, scanstring):
+        self.assertEquals(
+            scanstring('"z\\ud834\\udd20x"', 1, None, True),
+            (u'z\U0001d120x', 16))
+
+        if sys.maxunicode == 65535:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 6))
+        else:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 5))
+
+        self.assertEquals(
+            scanstring('"\\u007b"', 1, None, True),
+            (u'{', 8))
+
+        self.assertEquals(
+            scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
+            (u'A JSON payload should be an object or array, not a string.', 60))
+
+        self.assertEquals(
+            scanstring('["Unclosed array"', 2, None, True),
+            (u'Unclosed array', 17))
+
+        self.assertEquals(
+            scanstring('["extra comma",]', 2, None, True),
+            (u'extra comma', 14))
+
+        self.assertEquals(
+            scanstring('["double extra comma",,]', 2, None, True),
+            (u'double extra comma', 21))
+
+        self.assertEquals(
+            scanstring('["Comma after the close"],', 2, None, True),
+            (u'Comma after the close', 24))
+
+        self.assertEquals(
+            scanstring('["Extra close"]]', 2, None, True),
+            (u'Extra close', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra comma": true,}', 2, None, True),
+            (u'Extra comma', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
+            (u'Extra value after close', 26))
+
+        self.assertEquals(
+            scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
+            (u'Illegal expression', 21))
+
+        self.assertEquals(
+            scanstring('{"Illegal invocation": alert()}', 2, None, True),
+            (u'Illegal invocation', 21))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
+            (u'Numbers cannot have leading zeroes', 37))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
+            (u'Numbers cannot be hex', 24))
+
+        self.assertEquals(
+            scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
+            (u'Too deep', 30))
+
+        self.assertEquals(
+            scanstring('{"Missing colon" null}', 2, None, True),
+            (u'Missing colon', 16))
+
+        self.assertEquals(
+            scanstring('{"Double colon":: null}', 2, None, True),
+            (u'Double colon', 15))
+
+        self.assertEquals(
+            scanstring('{"Comma instead of colon", null}', 2, None, True),
+            (u'Comma instead of colon', 25))
+
+        self.assertEquals(
+            scanstring('["Colon instead of comma": false]', 2, None, True),
+            (u'Colon instead of comma', 25))
+
+        self.assertEquals(
+            scanstring('["Bad value", truth]', 2, None, True),
+            (u'Bad value', 12))
+
+    def test_issue3623(self):
+        self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
+                          "xxx")
+        self.assertRaises(UnicodeDecodeError,
+                          json.encoder.encode_basestring_ascii, "xx\xff")
diff --git a/ovsdb/simplejson/tests/test_separators.py b/ovsdb/simplejson/tests/test_separators.py
new file mode 100644 (file)
index 0000000..8fa0dac
--- /dev/null
@@ -0,0 +1,42 @@
+import textwrap
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestSeparators(TestCase):
+    def test_separators(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ] ,
+          [
+            "whoops"
+          ] ,
+          [] ,
+          "d-shtaeou" ,
+          "d-nthiouh" ,
+          "i-vhbjkhnth" ,
+          {
+            "nifty" : 87
+          } ,
+          {
+            "field" : "yes" ,
+            "morefield" : false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(' ,', ' : '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_unicode.py b/ovsdb/simplejson/tests/test_unicode.py
new file mode 100644 (file)
index 0000000..6f4384a
--- /dev/null
@@ -0,0 +1,64 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestUnicode(TestCase):
+    def test_encoding1(self):
+        encoder = json.JSONEncoder(encoding='utf-8')
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = encoder.encode(u)
+        js = encoder.encode(s)
+        self.assertEquals(ju, js)
+
+    def test_encoding2(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = json.dumps(u, encoding='utf-8')
+        js = json.dumps(s, encoding='utf-8')
+        self.assertEquals(ju, js)
+
+    def test_encoding3(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u)
+        self.assertEquals(j, '"\\u03b1\\u03a9"')
+
+    def test_encoding4(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u])
+        self.assertEquals(j, '["\\u03b1\\u03a9"]')
+
+    def test_encoding5(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u, ensure_ascii=False)
+        self.assertEquals(j, u'"%s"' % (u,))
+
+    def test_encoding6(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u], ensure_ascii=False)
+        self.assertEquals(j, u'["%s"]' % (u,))
+
+    def test_big_unicode_encode(self):
+        u = u'\U0001d120'
+        self.assertEquals(json.dumps(u), '"\\ud834\\udd20"')
+        self.assertEquals(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
+
+    def test_big_unicode_decode(self):
+        u = u'z\U0001d120x'
+        self.assertEquals(json.loads('"' + u + '"'), u)
+        self.assertEquals(json.loads('"z\\ud834\\udd20x"'), u)
+
+    def test_unicode_decode(self):
+        for i in range(0, 0xd7ff):
+            u = unichr(i)
+            s = '"\\u%04x"' % (i,)
+            self.assertEquals(json.loads(s), u)
+
+    def test_default_encoding(self):
+        self.assertEquals(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
+            {'a': u'\xe9'})
+
+    def test_unicode_preservation(self):
+        self.assertEquals(type(json.loads(u'""')), unicode)
+        self.assertEquals(type(json.loads(u'"a"')), unicode)
+        self.assertEquals(type(json.loads(u'["a"]')[0]), unicode)
\ No newline at end of file
diff --git a/ovsdb/simplejson/tool.py b/ovsdb/simplejson/tool.py
new file mode 100644 (file)
index 0000000..9044331
--- /dev/null
@@ -0,0 +1,37 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+
+"""
+import sys
+import simplejson
+
+def main():
+    if len(sys.argv) == 1:
+        infile = sys.stdin
+        outfile = sys.stdout
+    elif len(sys.argv) == 2:
+        infile = open(sys.argv[1], 'rb')
+        outfile = sys.stdout
+    elif len(sys.argv) == 3:
+        infile = open(sys.argv[1], 'rb')
+        outfile = open(sys.argv[2], 'wb')
+    else:
+        raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+    try:
+        obj = simplejson.load(infile)
+    except ValueError, e:
+        raise SystemExit(e)
+    simplejson.dump(obj, outfile, sort_keys=True, indent=4)
+    outfile.write('\n')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/ovsdb/table.c b/ovsdb/table.c
new file mode 100644 (file)
index 0000000..6a4e7ae
--- /dev/null
@@ -0,0 +1,251 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "table.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "column.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "row.h"
+
+static void
+add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
+{
+    assert(!shash_find(&ts->columns, column->name));
+    column->index = shash_count(&ts->columns);
+    shash_add(&ts->columns, column->name, column);
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_create(const char *name, bool mutable,
+                          unsigned int max_rows)
+{
+    struct ovsdb_column *uuid, *version;
+    struct ovsdb_table_schema *ts;
+
+    ts = xzalloc(sizeof *ts);
+    ts->name = xstrdup(name);
+    ts->mutable = mutable;
+    shash_init(&ts->columns);
+    ts->max_rows = max_rows;
+
+    uuid = ovsdb_column_create("_uuid", false, true, &ovsdb_type_uuid);
+    add_column(ts, uuid);
+    assert(uuid->index == OVSDB_COL_UUID);
+
+    version = ovsdb_column_create("_version", false, false, &ovsdb_type_uuid);
+    add_column(ts, version);
+    assert(version->index == OVSDB_COL_VERSION);
+
+    return ts;
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_clone(const struct ovsdb_table_schema *old)
+{
+    struct ovsdb_table_schema *new;
+    struct shash_node *node;
+
+    new = ovsdb_table_schema_create(old->name, old->mutable, old->max_rows);
+    SHASH_FOR_EACH (node, &old->columns) {
+        const struct ovsdb_column *column = node->data;
+
+        if (column->name[0] == '_') {
+            /* Added automatically by ovsdb_table_schema_create(). */
+            continue;
+        }
+
+        add_column(new, ovsdb_column_clone(column));
+    }
+    return new;
+}
+
+void
+ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        ovsdb_column_destroy(node->data);
+    }
+    shash_destroy(&ts->columns);
+    free(ts->name);
+    free(ts);
+}
+
+struct ovsdb_error *
+ovsdb_table_schema_from_json(const struct json *json, const char *name,
+                             struct ovsdb_table_schema **tsp)
+{
+    struct ovsdb_table_schema *ts;
+    const struct json *columns, *mutable, *max_rows;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+    long long int n_max_rows;
+
+    *tsp = NULL;
+
+    ovsdb_parser_init(&parser, json, "table schema for table %s", name);
+    columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                  OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    max_rows = ovsdb_parser_member(&parser, "maxRows",
+                                   OP_INTEGER | OP_OPTIONAL);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    if (max_rows) {
+        if (json_integer(max_rows) <= 0) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "maxRows must be at least 1");
+        }
+        n_max_rows = max_rows->u.integer;
+    } else {
+        n_max_rows = UINT_MAX;
+    }
+
+    if (shash_is_empty(json_object(columns))) {
+        return ovsdb_syntax_error(json, NULL,
+                                  "table must have at least one column");
+    }
+
+    ts = ovsdb_table_schema_create(name,
+                                   mutable ? json_boolean(mutable) : true,
+                                   MIN(n_max_rows, UINT_MAX));
+    SHASH_FOR_EACH (node, json_object(columns)) {
+        struct ovsdb_column *column;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_column_from_json(node->data, node->name, &column);
+        }
+        if (error) {
+            ovsdb_table_schema_destroy(ts);
+            return error;
+        }
+
+        add_column(ts, column);
+    }
+    *tsp = ts;
+    return 0;
+}
+
+struct json *
+ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
+{
+    struct json *json, *columns;
+    struct shash_node *node;
+
+    json = json_object_create();
+    if (!ts->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+
+    columns = json_object_create();
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        const struct ovsdb_column *column = node->data;
+        if (node->name[0] != '_') {
+            json_object_put(columns, column->name,
+                            ovsdb_column_to_json(column));
+        }
+    }
+    json_object_put(json, "columns", columns);
+    if (ts->max_rows != UINT_MAX) {
+        json_object_put(json, "maxRows", json_integer_create(ts->max_rows));
+    }
+
+    return json;
+}
+
+const struct ovsdb_column *
+ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts,
+                              const char *name)
+{
+    return shash_find_data(&ts->columns, name);
+}
+\f
+struct ovsdb_table *
+ovsdb_table_create(struct ovsdb_table_schema *ts)
+{
+    struct ovsdb_table *table;
+
+    table = xmalloc(sizeof *table);
+    table->schema = ts;
+    table->txn_table = NULL;
+    hmap_init(&table->rows);
+
+    return table;
+}
+
+void
+ovsdb_table_destroy(struct ovsdb_table *table)
+{
+    if (table) {
+        struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            ovsdb_row_destroy(row);
+        }
+        hmap_destroy(&table->rows);
+
+        ovsdb_table_schema_destroy(table->schema);
+        free(table);
+    }
+}
+
+const struct ovsdb_row *
+ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, uuid_hash(uuid),
+                             &table->rows) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) {
+            return row;
+        }
+    }
+
+    return NULL;
+}
+
+/* This is probably not the function you want.  Use ovsdb_txn_row_modify()
+ * instead. */
+bool
+ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row)
+{
+    const struct uuid *uuid = ovsdb_row_get_uuid(row);
+    if (!ovsdb_table_get_row(table, uuid)) {
+        hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid));
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/table.h b/ovsdb/table.h
new file mode 100644 (file)
index 0000000..4d3b9ee
--- /dev/null
@@ -0,0 +1,66 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TABLE_H
+#define OVSDB_TABLE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "hmap.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Schema for a database table. */
+struct ovsdb_table_schema {
+    char *name;
+    bool mutable;
+    struct shash columns;       /* Contains "struct ovsdb_column *"s. */
+    unsigned int max_rows;      /* Maximum number of rows. */
+};
+
+struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
+                                                     bool mutable,
+                                                     unsigned int max_rows);
+struct ovsdb_table_schema *ovsdb_table_schema_clone(
+    const struct ovsdb_table_schema *);
+void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
+
+struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *,
+                                                 const char *name,
+                                                 struct ovsdb_table_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *);
+
+const struct ovsdb_column *ovsdb_table_schema_get_column(
+    const struct ovsdb_table_schema *, const char *name);
+\f
+/* Database table. */
+
+struct ovsdb_table {
+    struct ovsdb_table_schema *schema;
+    struct ovsdb_txn_table *txn_table; /* Only if table is in a transaction. */
+    struct hmap rows;           /* Contains "struct ovsdb_row"s. */
+};
+
+struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *);
+void ovsdb_table_destroy(struct ovsdb_table *);
+
+const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *,
+                                            const struct uuid *);
+bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *);
+
+#endif /* ovsdb/table.h */
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
new file mode 100644 (file)
index 0000000..218fbce
--- /dev/null
@@ -0,0 +1,747 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "transaction.h"
+
+#include <assert.h>
+
+#include "bitmap.h"
+#include "dynamic-string.h"
+#include "hash.h"
+#include "hmap.h"
+#include "json.h"
+#include "list.h"
+#include "ovsdb-error.h"
+#include "ovsdb.h"
+#include "row.h"
+#include "table.h"
+#include "uuid.h"
+
+struct ovsdb_txn {
+    struct ovsdb *db;
+    struct list txn_tables;     /* Contains "struct ovsdb_txn_table"s. */
+    struct ds comment;
+};
+
+/* A table modified by a transaction. */
+struct ovsdb_txn_table {
+    struct list node;           /* Element in ovsdb_txn's txn_tables list. */
+    struct ovsdb_table *table;
+    struct hmap txn_rows;       /* Contains "struct ovsdb_txn_row"s. */
+
+    /* Used by for_each_txn_row(). */
+    unsigned int serial;        /* Serial number of in-progress iteration. */
+    unsigned int n_processed;   /* Number of rows processed. */
+};
+
+/* A row modified by the transaction:
+ *
+ *      - A row added by a transaction will have null 'old' and non-null 'new'.
+ *
+ *      - A row deleted by a transaction will have non-null 'old' and null
+ *        'new'.
+ *
+ *      - A row modified by a transaction will have non-null 'old' and 'new'.
+ *
+ *      - 'old' and 'new' both null is invalid.  It would indicate that a row
+ *        was added then deleted within a single transaction, but we instead
+ *        handle that case by deleting the txn_row entirely.
+ */
+struct ovsdb_txn_row {
+    struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */
+    struct ovsdb_row *old;      /* The old row. */
+    struct ovsdb_row *new;      /* The new row. */
+    size_t n_refs;              /* Number of remaining references. */
+
+    /* Used by for_each_txn_row(). */
+    unsigned int serial;        /* Serial number of in-progress commit. */
+
+    unsigned long changed[];    /* Bits set to 1 for columns that changed. */
+};
+
+static void ovsdb_txn_row_prefree(struct ovsdb_txn_row *);
+static struct ovsdb_error * WARN_UNUSED_RESULT
+for_each_txn_row(struct ovsdb_txn *txn,
+                      struct ovsdb_error *(*)(struct ovsdb_txn *,
+                                              struct ovsdb_txn_row *));
+
+/* Used by for_each_txn_row() to track tables and rows that have been
+ * processed.  */
+static unsigned int serial;
+
+struct ovsdb_txn *
+ovsdb_txn_create(struct ovsdb *db)
+{
+    struct ovsdb_txn *txn = xmalloc(sizeof *txn);
+    txn->db = db;
+    list_init(&txn->txn_tables);
+    ds_init(&txn->comment);
+    return txn;
+}
+
+static void
+ovsdb_txn_free(struct ovsdb_txn *txn)
+{
+    assert(list_is_empty(&txn->txn_tables));
+    ds_destroy(&txn->comment);
+    free(txn);
+}
+
+static struct ovsdb_error *
+ovsdb_txn_row_abort(struct ovsdb_txn *txn OVS_UNUSED,
+                    struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_row *old = txn_row->old;
+    struct ovsdb_row *new = txn_row->new;
+
+    ovsdb_txn_row_prefree(txn_row);
+    if (!old) {
+        hmap_remove(&new->table->rows, &new->hmap_node);
+    } else if (!new) {
+        hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old));
+    } else {
+        hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node);
+    }
+    ovsdb_row_destroy(new);
+    free(txn_row);
+
+    return NULL;
+}
+
+void
+ovsdb_txn_abort(struct ovsdb_txn *txn)
+{
+    ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_abort));
+    ovsdb_txn_free(txn);
+}
+
+static struct ovsdb_txn_row *
+find_txn_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_txn_row *txn_row;
+
+    if (!table->txn_table) {
+        return NULL;
+    }
+
+    HMAP_FOR_EACH_WITH_HASH (txn_row, struct ovsdb_txn_row, hmap_node,
+                             uuid_hash(uuid), &table->txn_table->txn_rows) {
+        const struct ovsdb_row *row;
+
+        row = txn_row->old ? txn_row->old : txn_row->new;
+        if (uuid_equals(uuid, ovsdb_row_get_uuid(row))) {
+            return txn_row;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_adjust_atom_refs(struct ovsdb_txn *txn,
+                           const struct ovsdb_base_type *base,
+                           const union ovsdb_atom *atoms, unsigned int n,
+                           int delta)
+{
+    const struct ovsdb_table *table;
+    unsigned int i;
+
+    if (!ovsdb_base_type_is_strong_ref(base)) {
+        return NULL;
+    }
+
+    table = base->u.uuid.refTable;
+    for (i = 0; i < n; i++) {
+        const struct uuid *uuid = &atoms[i].uuid;
+        struct ovsdb_txn_row *txn_row = find_txn_row(table, uuid);
+        if (!txn_row) {
+            const struct ovsdb_row *row = ovsdb_table_get_row(table, uuid);
+            if (row) {
+                txn_row = ovsdb_txn_row_modify(txn, row)->txn_row;
+            } else {
+                return ovsdb_error("referential integrity violation",
+                                   "reference to nonexistent row "
+                                   UUID_FMT, UUID_ARGS(uuid));
+            }
+        }
+        txn_row->n_refs += delta;
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_adjust_row_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r,
+                          const struct ovsdb_column *column, int delta)
+{
+    const struct ovsdb_datum *field = &r->fields[column->index];
+    struct ovsdb_error *error;
+
+    error = ovsdb_txn_adjust_atom_refs(txn, &column->type.key,
+                                       field->keys, field->n, delta);
+    if (!error) {
+        error = ovsdb_txn_adjust_atom_refs(txn, &column->type.value,
+                                           field->values, field->n, delta);
+    }
+    return error;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+update_row_ref_count(struct ovsdb_txn *txn, struct ovsdb_txn_row *r)
+{
+    struct ovsdb_table *table = r->old ? r->old->table : r->new->table;
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        struct ovsdb_error *error;
+
+        if (r->old) {
+            error = ovsdb_txn_adjust_row_refs(txn, r->old, column, -1);
+            if (error) {
+                ovsdb_error_destroy(error);
+                return OVSDB_BUG("error decreasing refcount");
+            }
+        }
+        if (r->new) {
+            error = ovsdb_txn_adjust_row_refs(txn, r->new, column, 1);
+            if (error) {
+                return error;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+check_ref_count(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *r)
+{
+    if (r->new || !r->n_refs) {
+        return NULL;
+    } else {
+        return ovsdb_error("referential integrity violation",
+                           "cannot delete %s row "UUID_FMT" because "
+                           "of %zu remaining reference(s)",
+                           r->old->table->schema->name,
+                           UUID_ARGS(ovsdb_row_get_uuid(r->old)),
+                           r->n_refs);
+    }
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+update_ref_counts(struct ovsdb_txn *txn)
+{
+    struct ovsdb_error *error;
+
+    error = for_each_txn_row(txn, update_row_ref_count);
+    if (error) {
+        return error;
+    }
+
+    return for_each_txn_row(txn, check_ref_count);
+}
+
+static struct ovsdb_error *
+ovsdb_txn_row_commit(struct ovsdb_txn *txn OVS_UNUSED,
+                     struct ovsdb_txn_row *txn_row)
+{
+    ovsdb_txn_row_prefree(txn_row);
+    if (txn_row->new) {
+        txn_row->new->n_refs = txn_row->n_refs;
+    }
+    ovsdb_row_destroy(txn_row->old);
+    free(txn_row);
+
+    return NULL;
+}
+
+static void
+add_weak_ref(struct ovsdb_txn *txn,
+             const struct ovsdb_row *src_, const struct ovsdb_row *dst_)
+{
+    struct ovsdb_row *src = (struct ovsdb_row *) src_;
+    struct ovsdb_row *dst = (struct ovsdb_row *) dst_;
+    struct ovsdb_weak_ref *weak;
+
+    if (src == dst) {
+        return;
+    }
+
+    dst = ovsdb_txn_row_modify(txn, dst);
+
+    if (!list_is_empty(&dst->dst_refs)) {
+        /* Omit duplicates. */
+        weak = CONTAINER_OF(list_back(&dst->dst_refs),
+                            struct ovsdb_weak_ref, dst_node);
+        if (weak->src == src) {
+            return;
+        }
+    }
+
+    weak = xmalloc(sizeof *weak);
+    weak->src = src;
+    list_push_back(&dst->dst_refs, &weak->dst_node);
+    list_push_back(&src->src_refs, &weak->src_node);
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+assess_weak_refs(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_table *table;
+    struct shash_node *node;
+
+    if (txn_row->old) {
+        /* Mark rows that have weak references to 'txn_row' as modified, so
+         * that their weak references will get reassessed. */
+        struct ovsdb_weak_ref *weak, *next;
+
+        LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, dst_node,
+                            &txn_row->old->dst_refs) {
+            if (!weak->src->txn_row) {
+                ovsdb_txn_row_modify(txn, weak->src);
+            }
+        }
+    }
+
+    if (!txn_row->new) {
+        /* We don't have to do anything about references that originate at
+         * 'txn_row', because ovsdb_row_destroy() will remove those weak
+         * references. */
+        return NULL;
+    }
+
+    table = txn_row->new->table;
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        struct ovsdb_datum *datum = &txn_row->new->fields[column->index];
+        unsigned int orig_n, i;
+        bool zero = false;
+
+        orig_n = datum->n;
+
+        if (ovsdb_base_type_is_weak_ref(&column->type.key)) {
+            for (i = 0; i < datum->n; ) {
+                const struct ovsdb_row *row;
+
+                row = ovsdb_table_get_row(column->type.key.u.uuid.refTable,
+                                          &datum->keys[i].uuid);
+                if (row) {
+                    add_weak_ref(txn, txn_row->new, row);
+                    i++;
+                } else {
+                    if (uuid_is_zero(&datum->keys[i].uuid)) {
+                        zero = true;
+                    }
+                    ovsdb_datum_remove_unsafe(datum, i, &column->type);
+                }
+            }
+        }
+
+        if (ovsdb_base_type_is_weak_ref(&column->type.value)) {
+            for (i = 0; i < datum->n; ) {
+                const struct ovsdb_row *row;
+
+                row = ovsdb_table_get_row(column->type.value.u.uuid.refTable,
+                                          &datum->values[i].uuid);
+                if (row) {
+                    add_weak_ref(txn, txn_row->new, row);
+                    i++;
+                } else {
+                    if (uuid_is_zero(&datum->values[i].uuid)) {
+                        zero = true;
+                    }
+                    ovsdb_datum_remove_unsafe(datum, i, &column->type);
+                }
+            }
+        }
+
+        if (datum->n != orig_n) {
+            bitmap_set1(txn_row->changed, column->index);
+            ovsdb_datum_sort_assert(datum, column->type.key.type);
+            if (datum->n < column->type.n_min) {
+                const struct uuid *row_uuid = ovsdb_row_get_uuid(txn_row->new);
+                if (zero && !txn_row->old) {
+                    return ovsdb_error(
+                        "constraint violation",
+                        "Weak reference column \"%s\" in \"%s\" row "UUID_FMT
+                        " (inserted within this transaction) contained "
+                        "all-zeros UUID (probably as the default value for "
+                        "this column) but deleting this value caused a "
+                        "constraint volation because this column is not "
+                        "allowed to be empty.", column->name,
+                        table->schema->name, UUID_ARGS(row_uuid));
+                } else {
+                    return ovsdb_error(
+                        "constraint violation",
+                        "Deletion of %u weak reference(s) to deleted (or "
+                        "never-existing) rows from column \"%s\" in \"%s\" "
+                        "row "UUID_FMT" %scaused this column to become empty, "
+                        "but constraints on this column disallow an "
+                        "empty column.",
+                        orig_n - datum->n, column->name, table->schema->name,
+                        UUID_ARGS(row_uuid),
+                        (txn_row->old
+                         ? ""
+                         : "(inserted within this transaction) "));
+                }
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+determine_changes(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_table *table;
+
+    table = (txn_row->old ? txn_row->old : txn_row->new)->table;
+    if (txn_row->old && txn_row->new) {
+        struct shash_node *node;
+        bool changed = false;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_type *type = &column->type;
+            unsigned int idx = column->index;
+
+            if (!ovsdb_datum_equals(&txn_row->old->fields[idx],
+                                    &txn_row->new->fields[idx],
+                                    type)) {
+                bitmap_set1(txn_row->changed, idx);
+                changed = true;
+            }
+        }
+
+        if (!changed) {
+            /* Nothing actually changed in this row, so drop it. */
+            ovsdb_txn_row_abort(txn, txn_row);
+        }
+    } else {
+        bitmap_set_multiple(txn_row->changed, 0,
+                            shash_count(&table->schema->columns), 1);
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+check_max_rows(struct ovsdb_txn *txn)
+{
+    struct ovsdb_txn_table *t;
+
+    LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+        size_t n_rows = hmap_count(&t->table->rows);
+        unsigned int max_rows = t->table->schema->max_rows;
+
+        if (n_rows > max_rows) {
+            return ovsdb_error("constraint violation",
+                               "transaction causes \"%s\" table to contain "
+                               "%zu rows, greater than the schema-defined "
+                               "limit of %u row(s)",
+                               t->table->schema->name, n_rows, max_rows);
+        }
+    }
+
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_replica *replica;
+    struct ovsdb_error *error;
+
+    /* Figure out what actually changed, and abort early if the transaction
+     * was really a no-op. */
+    error = for_each_txn_row(txn, determine_changes);
+    if (error) {
+        ovsdb_error_destroy(error);
+        return OVSDB_BUG("can't happen");
+    }
+    if (list_is_empty(&txn->txn_tables)) {
+        ovsdb_txn_abort(txn);
+        return NULL;
+    }
+
+    /* Check maximum rows table constraints. */
+    error = check_max_rows(txn);
+    if (error) {
+        ovsdb_txn_abort(txn);
+        return error;
+    }
+
+    /* Update reference counts and check referential integrity. */
+    error = update_ref_counts(txn);
+    if (error) {
+        ovsdb_txn_abort(txn);
+        return error;
+    }
+
+    /* Check reference counts and remove bad reference for "weak" referential
+     * integrity. */
+    error = for_each_txn_row(txn, assess_weak_refs);
+    if (error) {
+        ovsdb_txn_abort(txn);
+        return error;
+    }
+
+    /* Send the commit to each replica. */
+    LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) {
+        error = (replica->class->commit)(replica, txn, durable);
+        if (error) {
+            /* We don't support two-phase commit so only the first replica is
+             * allowed to report an error. */
+            assert(&replica->node == txn->db->replicas.next);
+
+            ovsdb_txn_abort(txn);
+            return error;
+        }
+    }
+
+    /* Finalize commit. */
+    txn->db->run_triggers = true;
+    ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_commit));
+    ovsdb_txn_free(txn);
+
+    return NULL;
+}
+
+void
+ovsdb_txn_for_each_change(const struct ovsdb_txn *txn,
+                          ovsdb_txn_row_cb_func *cb, void *aux)
+{
+    struct ovsdb_txn_table *t;
+    struct ovsdb_txn_row *r;
+
+    LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+        HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+            if (!cb(r->old, r->new, r->changed, aux)) {
+                break;
+            }
+        }
+   }
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_create_txn_table(struct ovsdb_txn *txn, struct ovsdb_table *table)
+{
+    if (!table->txn_table) {
+        struct ovsdb_txn_table *txn_table;
+
+        table->txn_table = txn_table = xmalloc(sizeof *table->txn_table);
+        txn_table->table = table;
+        hmap_init(&txn_table->txn_rows);
+        txn_table->serial = serial - 1;
+        list_push_back(&txn->txn_tables, &txn_table->node);
+    }
+    return table->txn_table;
+}
+
+static struct ovsdb_txn_row *
+ovsdb_txn_row_create(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                     const struct ovsdb_row *old_, struct ovsdb_row *new)
+{
+    struct ovsdb_row *old = (struct ovsdb_row *) old_;
+    size_t n_columns = shash_count(&table->schema->columns);
+    struct ovsdb_txn_table *txn_table;
+    struct ovsdb_txn_row *txn_row;
+
+    txn_row = xzalloc(offsetof(struct ovsdb_txn_row, changed)
+                      + bitmap_n_bytes(n_columns));
+    txn_row->old = (struct ovsdb_row *) old;
+    txn_row->new = new;
+    txn_row->n_refs = old ? old->n_refs : 0;
+    txn_row->serial = serial - 1;
+
+    if (old) {
+        old->txn_row = txn_row;
+    }
+    if (new) {
+        new->txn_row = txn_row;
+    }
+
+    txn_table = ovsdb_txn_create_txn_table(txn, table);
+    hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node,
+                ovsdb_row_hash(old ? old : new));
+
+    return txn_row;
+}
+
+struct ovsdb_row *
+ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
+{
+    struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_;
+
+    if (ro_row->txn_row) {
+        assert(ro_row == ro_row->txn_row->new);
+        return ro_row;
+    } else {
+        struct ovsdb_table *table = ro_row->table;
+        struct ovsdb_row *rw_row;
+
+        rw_row = ovsdb_row_clone(ro_row);
+        rw_row->n_refs = ro_row->n_refs;
+        uuid_generate(ovsdb_row_get_version_rw(rw_row));
+        ovsdb_txn_row_create(txn, table, ro_row, rw_row);
+        hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
+
+        return rw_row;
+    }
+}
+
+void
+ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row)
+{
+    uint32_t hash = ovsdb_row_hash(row);
+    struct ovsdb_table *table = row->table;
+
+    uuid_generate(ovsdb_row_get_version_rw(row));
+
+    ovsdb_txn_row_create(txn, table, NULL, row);
+    hmap_insert(&table->rows, &row->hmap_node, hash);
+}
+
+/* 'row' must be assumed destroyed upon return; the caller must not reference
+ * it again. */
+void
+ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_)
+{
+    struct ovsdb_row *row = (struct ovsdb_row *) row_;
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_row *txn_row = row->txn_row;
+
+    hmap_remove(&table->rows, &row->hmap_node);
+
+    if (!txn_row) {
+        ovsdb_txn_row_create(txn, table, row, NULL);
+    } else {
+        assert(txn_row->new == row);
+        if (txn_row->old) {
+            txn_row->new = NULL;
+        } else {
+            hmap_remove(&table->txn_table->txn_rows, &txn_row->hmap_node);
+            free(txn_row);
+        }
+        ovsdb_row_destroy(row);
+    }
+}
+
+void
+ovsdb_txn_add_comment(struct ovsdb_txn *txn, const char *s)
+{
+    if (txn->comment.length) {
+        ds_put_char(&txn->comment, '\n');
+    }
+    ds_put_cstr(&txn->comment, s);
+}
+
+const char *
+ovsdb_txn_get_comment(const struct ovsdb_txn *txn)
+{
+    return txn->comment.length ? ds_cstr_ro(&txn->comment) : NULL;
+}
+\f
+static void
+ovsdb_txn_row_prefree(struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new;
+    struct ovsdb_txn_table *txn_table = row->table->txn_table;
+
+    txn_table->n_processed--;
+    hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+
+    if (txn_row->old) {
+        txn_row->old->txn_row = NULL;
+    }
+    if (txn_row->new) {
+        txn_row->new->txn_row = NULL;
+    }
+}
+
+static void
+ovsdb_txn_table_destroy(struct ovsdb_txn_table *txn_table)
+{
+    assert(hmap_is_empty(&txn_table->txn_rows));
+    txn_table->table->txn_table = NULL;
+    hmap_destroy(&txn_table->txn_rows);
+    list_remove(&txn_table->node);
+    free(txn_table);
+}
+
+/* Calls 'cb' for every txn_row within 'txn'.  If 'cb' returns nonnull, this
+ * aborts the iteration and for_each_txn_row() passes the error up.  Otherwise,
+ * returns a null pointer after iteration is complete.
+ *
+ * 'cb' may insert new txn_rows and new txn_tables into 'txn'.  It may delete
+ * the txn_row that it is passed in, or txn_rows in txn_tables other than the
+ * one passed to 'cb'.  It may *not* delete txn_rows other than the one passed
+ * in within the same txn_table.  It may *not* delete any txn_tables.  As long
+ * as these rules are followed, 'cb' will be called exactly once for each
+ * txn_row in 'txn', even those added by 'cb'.
+ */
+static struct ovsdb_error * WARN_UNUSED_RESULT
+for_each_txn_row(struct ovsdb_txn *txn,
+                 struct ovsdb_error *(*cb)(struct ovsdb_txn *,
+                                           struct ovsdb_txn_row *))
+{
+    bool any_work;
+
+    serial++;
+
+    do {
+        struct ovsdb_txn_table *t, *next_txn_table;
+
+        any_work = false;
+        LIST_FOR_EACH_SAFE (t, next_txn_table, struct ovsdb_txn_table, node,
+                            &txn->txn_tables) {
+            if (t->serial != serial) {
+                t->serial = serial;
+                t->n_processed = 0;
+            }
+
+            while (t->n_processed < hmap_count(&t->txn_rows)) {
+                struct ovsdb_txn_row *r, *next_txn_row;
+
+                HMAP_FOR_EACH_SAFE (r, next_txn_row,
+                                    struct ovsdb_txn_row, hmap_node,
+                                    &t->txn_rows) {
+                    if (r->serial != serial) {
+                        struct ovsdb_error *error;
+
+                        r->serial = serial;
+                        t->n_processed++;
+                        any_work = true;
+
+                        error = cb(txn, r);
+                        if (error) {
+                            return error;
+                        }
+                    }
+                }
+            }
+            if (hmap_is_empty(&t->txn_rows)) {
+                /* Table is empty.  Drop it. */
+                ovsdb_txn_table_destroy(t);
+            }
+        }
+    } while (any_work);
+
+    return NULL;
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644 (file)
index 0000000..414b358
--- /dev/null
@@ -0,0 +1,46 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRANSACTION_H
+#define OVSDB_TRANSACTION_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable);
+
+struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *,
+                                       const struct ovsdb_row *);
+void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *);
+void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *);
+
+typedef bool ovsdb_txn_row_cb_func(const struct ovsdb_row *old,
+                                   const struct ovsdb_row *new,
+                                   const unsigned long int *changed,
+                                   void *aux);
+void ovsdb_txn_for_each_change(const struct ovsdb_txn *,
+                               ovsdb_txn_row_cb_func *, void *aux);
+
+void ovsdb_txn_add_comment(struct ovsdb_txn *, const char *);
+const char *ovsdb_txn_get_comment(const struct ovsdb_txn *);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644 (file)
index 0000000..1ecfdca
--- /dev/null
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "trigger.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "poll-loop.h"
+
+static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *,
+                              long long int now);
+static void ovsdb_trigger_complete(struct ovsdb_trigger *);
+
+void
+ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger,
+                   struct json *request, struct list *completion,
+                   long long int now)
+{
+    list_push_back(&db->triggers, &trigger->node);
+    trigger->completion = completion;
+    trigger->request = request;
+    trigger->result = NULL;
+    trigger->created = now;
+    trigger->timeout_msec = LLONG_MAX;
+    ovsdb_trigger_try(db, trigger, now);
+}
+
+void
+ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
+{
+    list_remove(&trigger->node);
+    json_destroy(trigger->request);
+    json_destroy(trigger->result);
+}
+
+bool
+ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)
+{
+    return trigger->result != NULL;
+}
+
+struct json *
+ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)
+{
+    struct json *result = trigger->result;
+    trigger->result = NULL;
+    return result;
+}
+
+void
+ovsdb_trigger_run(struct ovsdb *db, long long int now)
+{
+    struct ovsdb_trigger *t, *next;
+    bool run_triggers;
+
+    run_triggers = db->run_triggers;
+    db->run_triggers = false;
+    LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) {
+        if (run_triggers || now - t->created >= t->timeout_msec) {
+            ovsdb_trigger_try(db, t, now);
+        }
+    }
+}
+
+void
+ovsdb_trigger_wait(struct ovsdb *db, long long int now)
+{
+    if (db->run_triggers) {
+        poll_immediate_wake();
+    } else {
+        long long int deadline = LLONG_MAX;
+        struct ovsdb_trigger *t;
+
+        LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) {
+            if (t->created < LLONG_MAX - t->timeout_msec) {
+                long long int t_deadline = t->created + t->timeout_msec;
+                if (deadline > t_deadline) {
+                    deadline = t_deadline;
+                    if (now >= deadline) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (deadline < LLONG_MAX) {
+            poll_timer_wait(MIN(deadline - now, INT_MAX));
+        }
+    }
+}
+
+static bool
+ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now)
+{
+    t->result = ovsdb_execute(db, t->request, now - t->created,
+                              &t->timeout_msec);
+    if (t->result) {
+        ovsdb_trigger_complete(t);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void
+ovsdb_trigger_complete(struct ovsdb_trigger *t)
+{
+    assert(t->result != NULL);
+    list_remove(&t->node);
+    list_push_back(t->completion, &t->node);
+}
diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h
new file mode 100644 (file)
index 0000000..521b150
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRIGGER_H
+#define OVSDB_TRIGGER_H 1
+
+#include "list.h"
+
+struct ovsdb;
+
+struct ovsdb_trigger {
+    struct list node;           /* !result: in struct ovsdb "triggers" list;
+                                 * result: in completion list. */
+    struct list *completion;    /* Completion list. */
+    struct json *request;       /* Database request. */
+    struct json *result;        /* Result (null if none yet). */
+    long long int created;      /* Time created. */
+    long long int timeout_msec; /* Max wait duration. */
+};
+
+void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *,
+                        struct json *request, struct list *completion,
+                        long long int now);
+void ovsdb_trigger_destroy(struct ovsdb_trigger *);
+
+bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
+struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
+
+void ovsdb_trigger_run(struct ovsdb *, long long int now);
+void ovsdb_trigger_wait(struct ovsdb *, long long int now);
+
+#endif /* ovsdb/trigger.h */
diff --git a/secchan/automake.mk b/secchan/automake.mk
deleted file mode 100644 (file)
index d6bf1b0..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2009 Nicira Networks, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved.  This file is offered as-is,
-# without warranty of any kind.
-
-bin_PROGRAMS += secchan/secchan
-man_MANS += secchan/secchan.8
-
-secchan_secchan_SOURCES = secchan/main.c
-secchan_secchan_LDADD = \
-       secchan/libsecchan.a \
-       lib/libopenvswitch.a \
-       $(FAULT_LIBS) \
-       $(SSL_LIBS)
-
-noinst_LIBRARIES += secchan/libsecchan.a
-secchan_libsecchan_a_SOURCES = \
-       secchan/discovery.c \
-       secchan/discovery.h \
-       secchan/executer.c \
-       secchan/executer.h \
-       secchan/fail-open.c \
-       secchan/fail-open.h \
-       secchan/in-band.c \
-       secchan/in-band.h \
-       secchan/netflow.c \
-       secchan/netflow.h \
-       secchan/ofproto.c \
-       secchan/ofproto.h \
-       secchan/pktbuf.c \
-       secchan/pktbuf.h \
-       secchan/pinsched.c \
-       secchan/pinsched.h \
-       secchan/status.c \
-       secchan/status.h
-
-EXTRA_DIST += secchan/secchan.8.in
-DISTCLEANFILES += secchan/secchan.8
-
-include secchan/commands/automake.mk
diff --git a/secchan/commands/automake.mk b/secchan/commands/automake.mk
deleted file mode 100644 (file)
index cbe44d8..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-commandsdir = ${pkgdatadir}/commands
-dist_commands_SCRIPTS = \
-       secchan/commands/reboot
diff --git a/secchan/commands/reboot b/secchan/commands/reboot
deleted file mode 100755 (executable)
index 42fd10c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-ovs-kill --force --signal=USR1 ovs-switchui.pid
-reboot
diff --git a/secchan/executer.c b/secchan/executer.c
deleted file mode 100644 (file)
index 6b8c8e5..0000000
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "executer.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <unistd.h>
-#include "dirs.h"
-#include "dynamic-string.h"
-#include "fatal-signal.h"
-#include "openflow/nicira-ext.h"
-#include "ofpbuf.h"
-#include "openflow/openflow.h"
-#include "poll-loop.h"
-#include "rconn.h"
-#include "socket-util.h"
-#include "util.h"
-#include "vconn.h"
-
-#define THIS_MODULE VLM_executer
-#include "vlog.h"
-
-#define MAX_CHILDREN 8
-
-struct child {
-    /* Information about child process. */
-    char *name;                 /* argv[0] passed to child. */
-    pid_t pid;                  /* Child's process ID. */
-
-    /* For sending a reply to the controller when the child dies. */
-    struct rconn *rconn;
-    uint32_t xid;               /* Transaction ID used by controller. */
-
-    /* We read up to MAX_OUTPUT bytes of output and send them back to the
-     * controller when the child dies. */
-#define MAX_OUTPUT 4096
-    int output_fd;              /* FD from which to read child's output. */
-    uint8_t *output;            /* Output data. */
-    size_t output_size;         /* Number of bytes of output data so far. */
-};
-
-struct executer {
-    /* Settings. */
-    char *command_acl;          /* Command white/blacklist, as shell globs. */
-    char *command_dir;          /* Directory that contains commands. */
-
-    /* Children. */
-    struct child children[MAX_CHILDREN];
-    size_t n_children;
-};
-
-/* File descriptors for waking up when a child dies. */
-static int signal_fds[2] = {-1, -1};
-
-static void send_child_status(struct rconn *, uint32_t xid, uint32_t status,
-                              const void *data, size_t size);
-static void send_child_message(struct rconn *, uint32_t xid, uint32_t status,
-                               const char *message);
-
-/* Returns true if 'cmd' is allowed by 'acl', which is a command-separated
- * access control list in the format described for --command-acl in
- * secchan(8). */
-static bool
-executer_is_permitted(const char *acl_, const char *cmd)
-{
-    char *acl, *save_ptr, *pattern;
-    bool allowed, denied;
-
-    /* Verify that 'cmd' consists only of alphanumerics plus _ or -. */
-    if (cmd[strspn(cmd, "abcdefghijklmnopqrstuvwxyz"
-                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")] != '\0') {
-        VLOG_WARN("rejecting command name \"%s\" that contain forbidden "
-                  "characters", cmd);
-        return false;
-    }
-
-    /* Check 'cmd' against 'acl'. */
-    acl = xstrdup(acl_);
-    save_ptr = acl;
-    allowed = denied = false;
-    while ((pattern = strsep(&save_ptr, ",")) != NULL && !denied) {
-        if (pattern[0] != '!' && !fnmatch(pattern, cmd, 0)) {
-            allowed = true;
-        } else if (pattern[0] == '!' && !fnmatch(pattern + 1, cmd, 0)) {
-            denied = true;
-        }
-    }
-    free(acl);
-
-    /* Check the command white/blacklisted state. */
-    if (allowed && !denied) {
-        VLOG_INFO("permitting command execution: \"%s\" is whitelisted", cmd);
-    } else if (allowed && denied) {
-        VLOG_WARN("denying command execution: \"%s\" is both blacklisted "
-                  "and whitelisted", cmd);
-    } else if (!allowed) {
-        VLOG_WARN("denying command execution: \"%s\" is not whitelisted", cmd);
-    } else if (denied) {
-        VLOG_WARN("denying command execution: \"%s\" is blacklisted", cmd);
-    }
-    return allowed && !denied;
-}
-
-int
-executer_handle_request(struct executer *e, struct rconn *rconn,
-                        struct nicira_header *request)
-{
-    char **argv;
-    char *args;
-    char *exec_file = NULL;
-    int max_fds;
-    struct stat s;
-    size_t args_size;
-    size_t argc;
-    size_t i;
-    pid_t pid;
-    int output_fds[2];
-
-    /* Verify limit on children not exceeded.
-     * XXX should probably kill children when the connection drops? */
-    if (e->n_children >= MAX_CHILDREN) {
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "too many child processes");
-        return 0;
-    }
-
-    /* Copy argument buffer, adding a null terminator at the end.  Now every
-     * argument is null-terminated, instead of being merely null-delimited. */
-    args_size = ntohs(request->header.length) - sizeof *request;
-    args = xmemdup0((const void *) (request + 1), args_size);
-
-    /* Count arguments. */
-    argc = 0;
-    for (i = 0; i <= args_size; i++) {
-        argc += args[i] == '\0';
-    }
-
-    /* Set argv[*] to point to each argument. */
-    argv = xmalloc((argc + 1) * sizeof *argv);
-    argv[0] = args;
-    for (i = 1; i < argc; i++) {
-        argv[i] = strchr(argv[i - 1], '\0') + 1;
-    }
-    argv[argc] = NULL;
-
-    /* Check permissions. */
-    if (!executer_is_permitted(e->command_acl, argv[0])) {
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-
-    /* Find the executable. */
-    exec_file = xasprintf("%s/%s", e->command_dir, argv[0]);
-    if (stat(exec_file, &s)) {
-        VLOG_WARN("failed to stat \"%s\": %s", exec_file, strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-    if (!S_ISREG(s.st_mode)) {
-        VLOG_WARN("\"%s\" is not a regular file", exec_file);
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-    argv[0] = exec_file;
-
-    /* Arrange to capture output. */
-    if (pipe(output_fds)) {
-        VLOG_WARN("pipe failed: %s", strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "internal error (pipe)");
-        goto done;
-    }
-
-    pid = fork();
-    if (!pid) {
-        /* Running in child.
-         * XXX should run in new process group so that we can signal all
-         * subprocesses at once?  Would also want to catch fatal signals and
-         * kill them at the same time though. */
-        fatal_signal_fork();
-        dup2(get_null_fd(), 0);
-        dup2(output_fds[1], 1);
-        dup2(get_null_fd(), 2);
-        max_fds = get_max_fds();
-        for (i = 3; i < max_fds; i++) {
-            close(i);
-        }
-        if (chdir(e->command_dir)) {
-            printf("could not change directory to \"%s\": %s",
-                   e->command_dir, strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        execv(argv[0], argv);
-        printf("failed to start \"%s\": %s\n", argv[0], strerror(errno));
-        exit(EXIT_FAILURE);
-    } else if (pid > 0) {
-        /* Running in parent. */
-        struct child *child;
-
-        VLOG_INFO("started \"%s\" subprocess", argv[0]);
-        send_child_status(rconn, request->header.xid, NXT_STATUS_STARTED,
-                          NULL, 0);
-        child = &e->children[e->n_children++];
-        child->name = xstrdup(argv[0]);
-        child->pid = pid;
-        child->rconn = rconn;
-        child->xid = request->header.xid;
-        child->output_fd = output_fds[0];
-        child->output = xmalloc(MAX_OUTPUT);
-        child->output_size = 0;
-        set_nonblocking(output_fds[0]);
-        close(output_fds[1]);
-    } else {
-        VLOG_WARN("fork failed: %s", strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "internal error (fork)");
-        close(output_fds[0]);
-        close(output_fds[1]);
-    }
-
-done:
-    free(exec_file);
-    free(args);
-    free(argv);
-    return 0;
-}
-
-static void
-send_child_status(struct rconn *rconn, uint32_t xid, uint32_t status,
-                  const void *data, size_t size)
-{
-    if (rconn) {
-        struct nx_command_reply *r;
-        struct ofpbuf *buffer;
-
-        r = make_openflow_xid(sizeof *r, OFPT_VENDOR, xid, &buffer);
-        r->nxh.vendor = htonl(NX_VENDOR_ID);
-        r->nxh.subtype = htonl(NXT_COMMAND_REPLY);
-        r->status = htonl(status);
-        ofpbuf_put(buffer, data, size);
-        update_openflow_length(buffer);
-        if (rconn_send(rconn, buffer, NULL)) {
-            ofpbuf_delete(buffer);
-        }
-    }
-}
-
-static void
-send_child_message(struct rconn *rconn, uint32_t xid, uint32_t status,
-                   const char *message)
-{
-    send_child_status(rconn, xid, status, message, strlen(message));
-}
-
-/* 'child' died with 'status' as its return code.  Deal with it. */
-static void
-child_terminated(struct child *child, int status)
-{
-    struct ds ds;
-    uint32_t ofp_status;
-
-    /* Log how it terminated. */
-    ds_init(&ds);
-    if (WIFEXITED(status)) {
-        ds_put_format(&ds, "normally with status %d", WEXITSTATUS(status));
-    } else if (WIFSIGNALED(status)) {
-        const char *name = NULL;
-#ifdef HAVE_STRSIGNAL
-        name = strsignal(WTERMSIG(status));
-#endif
-        ds_put_format(&ds, "by signal %d", WTERMSIG(status));
-        if (name) {
-            ds_put_format(&ds, " (%s)", name);
-        }
-    }
-    if (WCOREDUMP(status)) {
-        ds_put_cstr(&ds, " (core dumped)");
-    }
-    VLOG_INFO("child process \"%s\" with pid %ld terminated %s",
-              child->name, (long int) child->pid, ds_cstr(&ds));
-    ds_destroy(&ds);
-
-    /* Send a status message back to the controller that requested the
-     * command. */
-    if (WIFEXITED(status)) {
-        ofp_status = WEXITSTATUS(status) | NXT_STATUS_EXITED;
-    } else if (WIFSIGNALED(status)) {
-        ofp_status = WTERMSIG(status) | NXT_STATUS_SIGNALED;
-    } else {
-        ofp_status = NXT_STATUS_UNKNOWN;
-    }
-    if (WCOREDUMP(status)) {
-        ofp_status |= NXT_STATUS_COREDUMP;
-    }
-    send_child_status(child->rconn, child->xid, ofp_status,
-                      child->output, child->output_size);
-}
-
-/* Read output from 'child' and append it to its output buffer. */
-static void
-poll_child(struct child *child)
-{
-    ssize_t n;
-
-    if (child->output_fd < 0) {
-        return;
-    }
-
-    do {
-        n = read(child->output_fd, child->output + child->output_size,
-                 MAX_OUTPUT - child->output_size);
-    } while (n < 0 && errno == EINTR);
-    if (n > 0) {
-        child->output_size += n;
-        if (child->output_size < MAX_OUTPUT) {
-            return;
-        }
-    } else if (n < 0 && errno == EAGAIN) {
-        return;
-    }
-    close(child->output_fd);
-    child->output_fd = -1;
-}
-
-void
-executer_run(struct executer *e)
-{
-    char buffer[MAX_CHILDREN];
-    size_t i;
-
-    if (!e->n_children) {
-        return;
-    }
-
-    /* Read output from children. */
-    for (i = 0; i < e->n_children; i++) {
-        struct child *child = &e->children[i];
-        poll_child(child);
-    }
-
-    /* If SIGCHLD was received, reap dead children. */
-    if (read(signal_fds[0], buffer, sizeof buffer) <= 0) {
-        return;
-    }
-    for (;;) {
-        int status;
-        pid_t pid;
-
-        /* Get dead child in 'pid' and its return code in 'status'. */
-        pid = waitpid(WAIT_ANY, &status, WNOHANG);
-        if (pid < 0 && errno == EINTR) {
-            continue;
-        } else if (pid <= 0) {
-            return;
-        }
-
-        /* Find child with given 'pid' and drop it from the list. */
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-            if (child->pid == pid) {
-                poll_child(child);
-                child_terminated(child, status);
-                free(child->name);
-                free(child->output);
-                *child = e->children[--e->n_children];
-                goto found;
-            }
-        }
-        VLOG_WARN("child with unknown pid %ld terminated", (long int) pid);
-    found:;
-    }
-
-}
-
-void
-executer_wait(struct executer *e)
-{
-    if (e->n_children) {
-        size_t i;
-
-        /* Wake up on SIGCHLD. */
-        poll_fd_wait(signal_fds[0], POLLIN);
-
-        /* Wake up when we get output from a child. */
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-            if (child->output_fd >= 0) {
-                poll_fd_wait(child->output_fd, POLLIN);
-            }
-        }
-    }
-}
-
-void
-executer_rconn_closing(struct executer *e, struct rconn *rconn)
-{
-    size_t i;
-
-    /* If any of our children was connected to 'r', then disconnect it so we
-     * don't try to reference a dead connection when the process terminates
-     * later.
-     * XXX kill the children started by 'r'? */
-    for (i = 0; i < e->n_children; i++) {
-        if (e->children[i].rconn == rconn) {
-            e->children[i].rconn = NULL;
-        }
-    }
-}
-
-static void
-sigchld_handler(int signr UNUSED)
-{
-    write(signal_fds[1], "", 1);
-}
-
-int
-executer_create(const char *command_acl, const char *command_dir,
-                struct executer **executerp)
-{
-    struct executer *e;
-    struct sigaction sa;
-
-    *executerp = NULL;
-    if (signal_fds[0] == -1) {
-        /* Make sure we can get a fd for /dev/null. */
-        int null_fd = get_null_fd();
-        if (null_fd < 0) {
-            return -null_fd;
-        }
-
-        /* Create pipe for notifying us that SIGCHLD was invoked. */
-        if (pipe(signal_fds)) {
-            VLOG_ERR("pipe failed: %s", strerror(errno));
-            return errno;
-        }
-        set_nonblocking(signal_fds[0]);
-        set_nonblocking(signal_fds[1]);
-    }
-
-    /* Set up signal handler. */
-    memset(&sa, 0, sizeof sa);
-    sa.sa_handler = sigchld_handler;
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
-    if (sigaction(SIGCHLD, &sa, NULL)) {
-        VLOG_ERR("sigaction(SIGCHLD) failed: %s", strerror(errno));
-        return errno;
-    }
-
-    e = xcalloc(1, sizeof *e);
-    e->command_acl = xstrdup(command_acl);
-    e->command_dir = (command_dir
-                      ? xstrdup(command_dir)
-                      : xasprintf("%s/commands", ovs_pkgdatadir));
-    e->n_children = 0;
-    *executerp = e;
-    return 0;
-}
-
-void
-executer_destroy(struct executer *e)
-{
-    if (e) {
-        size_t i;
-
-        free(e->command_acl);
-        free(e->command_dir);
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-
-            free(child->name);
-            kill(child->pid, SIGHUP);
-            /* We don't own child->rconn. */
-            free(child->output);
-            free(child);
-        }
-        free(e);
-    }
-}
-
-void
-executer_set_acl(struct executer *e, const char *acl, const char *dir)
-{
-    free(e->command_acl);
-    e->command_acl = xstrdup(acl);
-    free(e->command_dir);
-    e->command_dir = xstrdup(dir);
-}
diff --git a/secchan/executer.h b/secchan/executer.h
deleted file mode 100644 (file)
index fbed85b..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef EXECUTER_H
-#define EXECUTER_H 1
-
-struct executer;
-struct nicira_header;
-struct rconn;
-
-int executer_create(const char *acl, const char *dir, struct executer **);
-void executer_set_acl(struct executer *, const char *acl, const char *dir);
-void executer_destroy(struct executer *);
-void executer_run(struct executer *);
-void executer_wait(struct executer *);
-void executer_rconn_closing(struct executer *, struct rconn *);
-int executer_handle_request(struct executer *, struct rconn *,
-                            struct nicira_header *);
-
-#endif /* executer.h */
index 1112503..c987014 100644 (file)
@@ -1,10 +1,28 @@
 /Makefile
 /Makefile.in
+/atconfig
+/atlocal
+/idltest.c
+/idltest.h
+/idltest.ovsidl
+/test-aes128
 /test-classifier
+/test-csum
 /test-dhcp-client
+/test-dir_name
 /test-flows
 /test-hash
 /test-hmap
+/test-json
+/test-jsonrpc
 /test-list
+/test-lockfile
+/test-ovsdb
+/test-reconnect
+/test-timeval
+/test-sha1
 /test-stp
 /test-type-props
+/test-uuid
+/test-vconn
+/testsuite
diff --git a/tests/aes128.at b/tests/aes128.at
new file mode 100644 (file)
index 0000000..4818f5c
--- /dev/null
@@ -0,0 +1,146 @@
+AT_BANNER([AES-128 unit tests])
+
+m4_define([AES128_CHECK], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([aes128])
+   AT_CHECK([test-aes128 $2 $3], [0], [$4
+], [])
+   AT_CLEANUP])
+
+AES128_CHECK(
+  [wikipedia test vector 1],
+  [00010203050607080a0b0c0d0f101112],
+  [506812a45f08c889b97f5980038b8359],
+  [d8f532538289ef7d06b506a4fd5be9c9])
+
+AES128_CHECK(
+  [wikipedia test vector 2],
+  [95A8EE8E89979B9EFDCBC6EB9797528D],
+  [4ec137a426dabf8aa0beb8bc0c2b89d6],
+  [d9b65d1232ba0199cdbd487b2a1fd646])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 0],
+  [10a58869d74be5a374cf867cfb473859],
+  [00000000000000000000000000000000],
+  [6d251e6944b051e04eaa6fb4dbf78465])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 1],
+  [caea65cdbb75e9169ecd22ebe6e54675],
+  [00000000000000000000000000000000],
+  [6e29201190152df4ee058139def610bb])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 2],
+  [a2e2fa9baf7d20822ca9f0542f764a41],
+  [00000000000000000000000000000000],
+  [c3b44b95d9d2f25670eee9a0de099fa3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 3],
+  [b6364ac4e1de1e285eaf144a2415f7a0],
+  [00000000000000000000000000000000],
+  [5d9b05578fc944b3cf1ccf0e746cd581])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 4],
+  [64cf9c7abc50b888af65f49d521944b2],
+  [00000000000000000000000000000000],
+  [f7efc89d5dba578104016ce5ad659c05])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 5],
+  [47d6742eefcc0465dc96355e851b64d9],
+  [00000000000000000000000000000000],
+  [0306194f666d183624aa230a8b264ae7])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 6],
+  [3eb39790678c56bee34bbcdeccf6cdb5],
+  [00000000000000000000000000000000],
+  [858075d536d79ccee571f7d7204b1f67])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 7],
+  [64110a924f0743d500ccadae72c13427],
+  [00000000000000000000000000000000],
+  [35870c6a57e9e92314bcb8087cde72ce])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 8],
+  [18d8126516f8a12ab1a36d9f04d68e51],
+  [00000000000000000000000000000000],
+  [6c68e9be5ec41e22c825b7c7affb4363])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 9],
+  [f530357968578480b398a3c251cd1093],
+  [00000000000000000000000000000000],
+  [f5df39990fc688f1b07224cc03e86cea])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 10],
+  [da84367f325d42d601b4326964802e8e],
+  [00000000000000000000000000000000],
+  [bba071bcb470f8f6586e5d3add18bc66])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 11],
+  [e37b1c6aa2846f6fdb413f238b089f23],
+  [00000000000000000000000000000000],
+  [43c9f7e62f5d288bb27aa40ef8fe1ea8])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 12],
+  [6c002b682483e0cabcc731c253be5674],
+  [00000000000000000000000000000000],
+  [3580d19cff44f1014a7c966a69059de5])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 13],
+  [143ae8ed6555aba96110ab58893a8ae1],
+  [00000000000000000000000000000000],
+  [806da864dd29d48deafbe764f8202aef])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 14],
+  [b69418a85332240dc82492353956ae0c],
+  [00000000000000000000000000000000],
+  [a303d940ded8f0baff6f75414cac5243])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 15],
+  [71b5c08a1993e1362e4d0ce9b22b78d5],
+  [00000000000000000000000000000000],
+  [c2dabd117f8a3ecabfbb11d12194d9d0])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 16],
+  [e234cdca2606b81f29408d5f6da21206],
+  [00000000000000000000000000000000],
+  [fff60a4740086b3b9c56195b98d91a7b])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 17],
+  [13237c49074a3da078dc1d828bb78c6f],
+  [00000000000000000000000000000000],
+  [8146a08e2357f0caa30ca8c94d1a0544])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 18],
+  [3071a2a48fe6cbd04f1a129098e308f8],
+  [00000000000000000000000000000000],
+  [4b98e06d356deb07ebb824e5713f7be3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 19],
+  [90f42ec0f68385f2ffc5dfc03a654dce],
+  [00000000000000000000000000000000],
+  [7a20a53d460fc9ce0423a7a0764c6cf2])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 20],
+  [febd9a24d8b65c1c787d50a4ed3619a9],
+  [00000000000000000000000000000000],
+  [f4a70d8af877f9b02b4c40df57d45b17])
diff --git a/tests/atlocal.in b/tests/atlocal.in
new file mode 100644 (file)
index 0000000..8ac4f67
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- shell-script -*-
+HAVE_OPENSSL='@HAVE_OPENSSL@'
+HAVE_PYTHON='@HAVE_PYTHON@'
+PERL='@PERL@'
+PYTHON='@PYTHON@'
index 3a417c7..ebf2a01 100644 (file)
-TESTS += tests/test-classifier
+EXTRA_DIST += \
+       $(TESTSUITE_AT) \
+       $(TESTSUITE) \
+       tests/atlocal.in \
+       $(srcdir)/package.m4 \
+       $(srcdir)/tests/testsuite
+TESTSUITE_AT = \
+       tests/testsuite.at \
+       tests/ovsdb-macros.at \
+       tests/library.at \
+       tests/check-structs.at \
+       tests/daemon.at \
+       tests/vconn.at \
+       tests/dir_name.at \
+       tests/aes128.at \
+       tests/uuid.at \
+       tests/json.at \
+       tests/jsonrpc.at \
+       tests/timeval.at \
+       tests/lockfile.at \
+       tests/reconnect.at \
+       tests/ovsdb.at \
+       tests/ovsdb-log.at \
+       tests/ovsdb-types.at \
+       tests/ovsdb-data.at \
+       tests/ovsdb-column.at \
+       tests/ovsdb-table.at \
+       tests/ovsdb-row.at \
+       tests/ovsdb-schema.at \
+       tests/ovsdb-condition.at \
+       tests/ovsdb-mutation.at \
+       tests/ovsdb-query.at \
+       tests/ovsdb-transaction.at \
+       tests/ovsdb-execution.at \
+       tests/ovsdb-trigger.at \
+       tests/ovsdb-tool.at \
+       tests/ovsdb-server.at \
+       tests/ovsdb-monitor.at \
+       tests/ovsdb-idl.at \
+       tests/stp.at \
+       tests/ovs-vsctl.at \
+       tests/interface-reconfigure.at
+TESTSUITE = $(srcdir)/tests/testsuite
+DISTCLEANFILES += tests/atconfig tests/atlocal $(TESTSUITE)
+
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:tests
+
+check-local: tests/atconfig tests/atlocal $(TESTSUITE)
+       $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH) $(TESTSUITEFLAGS)
+\f
+# lcov support
+
+lcov_wrappers = \
+       tests/lcov/ovs-appctl \
+       tests/lcov/ovs-vsctl \
+       tests/lcov/ovsdb-client \
+       tests/lcov/ovsdb-server \
+       tests/lcov/ovsdb-tool \
+       tests/lcov/test-aes128 \
+       tests/lcov/test-classifier \
+       tests/lcov/test-csum \
+       tests/lcov/test-dhcp-client \
+       tests/lcov/test-dir_name \
+       tests/lcov/test-flows \
+       tests/lcov/test-hash \
+       tests/lcov/test-hmap \
+       tests/lcov/test-json \
+       tests/lcov/test-jsonrpc \
+       tests/lcov/test-list \
+       tests/lcov/test-lockfile \
+       tests/lcov/test-ovsdb \
+       tests/lcov/test-reconnect \
+       tests/lcov/test-sha1 \
+       tests/lcov/test-stp \
+       tests/lcov/test-timeval \
+       tests/lcov/test-type-props \
+       tests/lcov/test-uuid \
+       tests/lcov/test-vconn
+
+$(lcov_wrappers): tests/lcov-wrapper.in
+       @test -d tests/lcov || mkdir tests/lcov
+       sed -e 's,[@]abs_top_builddir[@],$(abs_top_builddir),' \
+           -e 's,[@]wrap_program[@],$@,' \
+               $(top_srcdir)/tests/lcov-wrapper.in > $@.tmp
+       chmod +x $@.tmp
+       mv $@.tmp $@
+CLEANFILES += $(lcov_wrappers)
+EXTRA_DIST += tests/lcov-wrapper.in
+
+LCOV = lcov -b $(abs_top_builddir) -d $(abs_top_builddir) -q
+check-lcov: all tests/atconfig tests/atlocal $(TESTSUITE) $(lcov_wrappers)
+       rm -fr tests/coverage.html tests/coverage.info
+       $(LCOV) -c -i -o - > tests/coverage.info
+       $(SHELL) '$(TESTSUITE)' -C tests CHECK_LCOV=true AUTOTEST_PATH='tests/lcov:$(AUTOTEST_PATH)' $(TESTSUITEFLAGS); \
+               rc=$$?; \
+               echo "Producing coverage.html..."; \
+               cd tests && genhtml -q -o coverage.html coverage.info; \
+               exit $$rc
+\f
+# valgrind support
+
+valgrind_wrappers = \
+       tests/valgrind/ovs-appctl \
+       tests/valgrind/ovs-vsctl \
+       tests/valgrind/ovsdb-client \
+       tests/valgrind/ovsdb-server \
+       tests/valgrind/ovsdb-tool \
+       tests/valgrind/test-aes128 \
+       tests/valgrind/test-classifier \
+       tests/valgrind/test-csum \
+       tests/valgrind/test-dhcp-client \
+       tests/valgrind/test-dir_name \
+       tests/valgrind/test-flows \
+       tests/valgrind/test-hash \
+       tests/valgrind/test-hmap \
+       tests/valgrind/test-json \
+       tests/valgrind/test-jsonrpc \
+       tests/valgrind/test-list \
+       tests/valgrind/test-lockfile \
+       tests/valgrind/test-ovsdb \
+       tests/valgrind/test-reconnect \
+       tests/valgrind/test-sha1 \
+       tests/valgrind/test-stp \
+       tests/valgrind/test-timeval \
+       tests/valgrind/test-type-props \
+       tests/valgrind/test-uuid \
+       tests/valgrind/test-vconn
+
+$(valgrind_wrappers): tests/valgrind-wrapper.in
+       @test -d tests/valgrind || mkdir tests/valgrind
+       sed -e 's,[@]wrap_program[@],$@,' \
+               $(top_srcdir)/tests/valgrind-wrapper.in > $@.tmp
+       chmod +x $@.tmp
+       mv $@.tmp $@
+CLEANFILES += $(valgrind_wrappers)
+EXTRA_DIST += tests/valgrind-wrapper.in
+
+VALGRIND = valgrind --log-file=valgrind.%p --leak-check=full \
+       --suppressions=$(abs_top_srcdir)/tests/openssl.supp --num-callers=20
+EXTRA_DIST += tests/openssl.supp
+check-valgrind: all tests/atconfig tests/atlocal $(TESTSUITE) $(valgrind_wrappers)
+       $(SHELL) '$(TESTSUITE)' -C tests CHECK_VALGRIND=true VALGRIND='$(VALGRIND)' AUTOTEST_PATH='tests/valgrind:$(AUTOTEST_PATH)' -d $(TESTSUITEFLAGS)
+       @echo
+       @echo '----------------------------------------------------------------------'
+       @echo 'Valgrind output can be found in tests/testsuite.dir/*/valgrind.*'
+       @echo '----------------------------------------------------------------------'
+\f
+clean-local:
+       test ! -f '$(TESTSUITE)' || $(SHELL) '$(TESTSUITE)' -C tests --clean
+
+AUTOM4TE = autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+       $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+       mv $@.tmp $@
+
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+       :;{ \
+         echo '# Signature of the current package.' && \
+         echo 'm4_define([AT_PACKAGE_NAME],      [$(PACKAGE_NAME)])' && \
+         echo 'm4_define([AT_PACKAGE_TARNAME],   [$(PACKAGE_TARNAME)])' && \
+         echo 'm4_define([AT_PACKAGE_VERSION],   [$(PACKAGE_VERSION)])' && \
+         echo 'm4_define([AT_PACKAGE_STRING],    [$(PACKAGE_STRING)])' && \
+         echo 'm4_define([AT_PACKAGE_BUGREPORT], [$(PACKAGE_BUGREPORT)])'; \
+       } >'$(srcdir)/package.m4'
+
+noinst_PROGRAMS += tests/test-aes128
+tests_test_aes128_SOURCES = tests/test-aes128.c
+tests_test_aes128_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-classifier
 tests_test_classifier_SOURCES = tests/test-classifier.c
 tests_test_classifier_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-csum
 noinst_PROGRAMS += tests/test-csum
 tests_test_csum_SOURCES = tests/test-csum.c
 tests_test_csum_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-flows.sh
+noinst_PROGRAMS += tests/test-dir_name
+tests_test_dir_name_SOURCES = tests/test-dir_name.c
+tests_test_dir_name_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-flows
 tests_test_flows_SOURCES = tests/test-flows.c
 tests_test_flows_LDADD = lib/libopenvswitch.a
-dist_check_SCRIPTS = tests/test-flows.sh tests/flowgen.pl
+dist_check_SCRIPTS = tests/flowgen.pl
 
-TESTS += tests/test-hash
 noinst_PROGRAMS += tests/test-hash
 tests_test_hash_SOURCES = tests/test-hash.c
 tests_test_hash_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-hmap
 noinst_PROGRAMS += tests/test-hmap
 tests_test_hmap_SOURCES = tests/test-hmap.c
 tests_test_hmap_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-list
+noinst_PROGRAMS += tests/test-json
+tests_test_json_SOURCES = tests/test-json.c
+tests_test_json_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-jsonrpc
+tests_test_jsonrpc_SOURCES = tests/test-jsonrpc.c
+tests_test_jsonrpc_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 noinst_PROGRAMS += tests/test-list
 tests_test_list_SOURCES = tests/test-list.c
 tests_test_list_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-sha1
+noinst_PROGRAMS += tests/test-lockfile
+tests_test_lockfile_SOURCES = tests/test-lockfile.c
+tests_test_lockfile_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-ovsdb
+tests_test_ovsdb_SOURCES = \
+       tests/test-ovsdb.c \
+       tests/idltest.c \
+       tests/idltest.h
+EXTRA_DIST += tests/uuidfilt.pl tests/ovsdb-monitor-sort.pl
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+
+# idltest schema and IDL
+OVSIDL_BUILT +=        tests/idltest.c tests/idltest.h tests/idltest.ovsidl
+IDLTEST_IDL_FILES = tests/idltest.ovsschema tests/idltest.ann
+EXTRA_DIST += $(IDLTEST_IDL_FILES)
+tests/idltest.ovsidl: $(IDLTEST_IDL_FILES)
+       $(OVSDB_IDLC) -C $(srcdir) annotate $(IDLTEST_IDL_FILES) > $@.tmp
+       mv $@.tmp $@
+
+tests/idltest.c: tests/idltest.h
+
+noinst_PROGRAMS += tests/test-reconnect
+tests_test_reconnect_SOURCES = tests/test-reconnect.c
+tests_test_reconnect_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-sha1
 tests_test_sha1_SOURCES = tests/test-sha1.c
 tests_test_sha1_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-type-props
+noinst_PROGRAMS += tests/test-timeval
+tests_test_timeval_SOURCES = tests/test-timeval.c
+tests_test_timeval_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-strtok_r
+tests_test_strtok_r_SOURCES = tests/test-strtok_r.c
+
 noinst_PROGRAMS += tests/test-type-props
 tests_test_type_props_SOURCES = tests/test-type-props.c
 
 noinst_PROGRAMS += tests/test-dhcp-client
 tests_test_dhcp_client_SOURCES = tests/test-dhcp-client.c
-tests_test_dhcp_client_LDADD = lib/libopenvswitch.a $(FAULT_LIBS)
+tests_test_dhcp_client_LDADD = lib/libopenvswitch.a
 
-TESTS += tests/test-stp.sh
-EXTRA_DIST += tests/test-stp.sh
 noinst_PROGRAMS += tests/test-stp
-
 tests_test_stp_SOURCES = tests/test-stp.c
 tests_test_stp_LDADD = lib/libopenvswitch.a
-stp_files = \
-       tests/test-stp-ieee802.1d-1998 \
-       tests/test-stp-ieee802.1d-2004-fig17.4 \
-       tests/test-stp-ieee802.1d-2004-fig17.6 \
-       tests/test-stp-ieee802.1d-2004-fig17.7 \
-       tests/test-stp-iol-op-1.1 \
-       tests/test-stp-iol-op-1.4 \
-       tests/test-stp-iol-op-3.1 \
-       tests/test-stp-iol-op-3.3 \
-       tests/test-stp-iol-io-1.1 \
-       tests/test-stp-iol-io-1.2 \
-       tests/test-stp-iol-io-1.4 \
-       tests/test-stp-iol-io-1.5
-TESTS_ENVIRONMENT += stp_files='$(stp_files)'
-
-EXTRA_DIST += $(stp_files)
+
+noinst_PROGRAMS += tests/test-uuid
+tests_test_uuid_SOURCES = tests/test-uuid.c
+tests_test_uuid_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-vconn
+tests_test_vconn_SOURCES = tests/test-vconn.c
+tests_test_vconn_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+EXTRA_DIST += \
+       tests/testpki-cacert.pem \
+       tests/testpki-cert.pem \
+       tests/testpki-cert2.pem \
+       tests/testpki-privkey.pem \
+       tests/testpki-privkey2.pem \
+       tests/testpki-req.pem \
+       tests/testpki-req2.pem
diff --git a/tests/check-structs.at b/tests/check-structs.at
new file mode 100644 (file)
index 0000000..52e92ec
--- /dev/null
@@ -0,0 +1,41 @@
+AT_BANNER([struct alignment checker unit tests])
+
+m4_define([check_structs], [$top_srcdir/build-aux/check-structs])
+m4_define([RUN_STRUCT_CHECKER], 
+  [AT_SKIP_IF([test $HAVE_PYTHON = no])
+   AT_DATA([test.h], [$1
+])
+   AT_CHECK_UNQUOTED([$PYTHON check_structs test.h], [$2], [$3], [$4])])
+
+AT_SETUP([check struct tail padding])
+RUN_STRUCT_CHECKER(
+[struct xyz {
+    uint16_t x;
+};], 
+  [1], [], 
+  [test.h:3: warning: struct xyz needs 2 bytes of tail padding
+])
+AT_CLEANUP
+
+AT_SETUP([check struct internal alignment])
+RUN_STRUCT_CHECKER(
+[struct xyzzy {
+    uint16_t x;
+    uint32_t y;
+};], 
+  [1], [], 
+  [test.h:3: warning: struct xyzzy member y is 2 bytes short of 4-byte alignment
+])
+AT_CLEANUP
+
+AT_SETUP([check struct declared size])
+RUN_STRUCT_CHECKER(
+[struct wibble {
+    uint64_t z;
+};
+OFP_ASSERT(sizeof(struct wibble) == 12);
+], 
+  [1], [], 
+  [test.h:4: warning: struct wibble is 8 bytes long but declared as 12
+])
+AT_CLEANUP
diff --git a/tests/daemon.at b/tests/daemon.at
new file mode 100644 (file)
index 0000000..d2b0180
--- /dev/null
@@ -0,0 +1,169 @@
+AT_BANNER([daemon unit tests])
+
+AT_SETUP([daemon])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([pid])
+AT_CAPTURE_FILE([expected])
+# Start the daemon and wait for the pidfile to get created
+# and that its contents are the correct pid.
+AT_CHECK([ovsdb-server --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db& echo $! > expected], [0], [ignore], [ignore])
+OVS_WAIT_UNTIL([test -s pid], [kill `cat expected`])
+AT_CHECK(
+  [pid=`cat pid` && expected=`cat expected` && test "$pid" = "$expected"],
+  [0], [], [], [kill `cat expected`])
+AT_CHECK([kill -0 `cat pid`], [0], [], [], [kill `cat expected`])
+# Kill the daemon and make sure that the pidfile gets deleted.
+kill `cat expected`
+OVS_WAIT_WHILE([kill -0 `cat expected`])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --monitor])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([pid])
+AT_CAPTURE_FILE([parent])
+AT_CAPTURE_FILE([parentpid])
+AT_CAPTURE_FILE([newpid])
+# Start the daemon and wait for the pidfile to get created.
+AT_CHECK([ovsdb-server --monitor --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db& echo $! > parent], [0], [ignore], [ignore])
+OVS_WAIT_UNTIL([test -s pid], [kill `cat parent`])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+AT_CHECK([kill -0 `cat pid`], [0], [], [], [kill `cat parent`])
+AT_CHECK([ps -o ppid= -p `cat pid` > parentpid],
+  [0], [], [], [kill `cat parent`])
+AT_CHECK(
+  [parentpid=`cat parentpid` && 
+   parent=`cat parent` && 
+   test $parentpid = $parent],
+  [0], [], [], [kill `cat parent`])
+# Kill the daemon process, making it look like a segfault,
+# and wait for a new child process to get spawned.
+AT_CHECK([cp pid oldpid], [0], [], [], [kill `cat parent`])
+AT_CHECK([kill -SEGV `cat pid`], [0], [], [ignore], [kill `cat parent`])
+OVS_WAIT_WHILE([kill -0 `cat oldpid`], [kill `cat parent`])
+OVS_WAIT_UNTIL([test -s pid && test `cat pid` != `cat oldpid`],
+  [kill `cat parent`])
+AT_CHECK([cp pid newpid], [0], [], [], [kill `cat parent`])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+AT_CHECK([ps -o ppid= -p `cat pid` > parentpid],
+  [0], [], [], [kill `cat parent`])
+AT_CHECK(
+  [parentpid=`cat parentpid` && 
+   parent=`cat parent` && 
+   test $parentpid = $parent],
+  [0], [], [], [kill `cat parent`])
+# Kill the daemon process with SIGTERM, and wait for the daemon
+# and the monitor processes to go away and the pidfile to get deleted.
+AT_CHECK([kill `cat pid`], [0], [], [ignore], [kill `cat parent`])
+OVS_WAIT_WHILE([kill -0 `cat parent` || kill -0 `cat newpid` || test -e pid],
+  [kill `cat parent`])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+# Start the daemon and make sure that the pidfile exists immediately.
+# We don't wait for the pidfile to get created because the daemon is
+# supposed to do so before the parent exits.
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+# Kill the daemon and make sure that the pidfile gets deleted.
+cp pid saved-pid
+kill `cat pid`
+OVS_WAIT_WHILE([kill -0 `cat saved-pid`])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+m4_define([CHECK], 
+  [AT_CHECK([$1], [$2], [$3], [$4], [kill `cat daemon monitor`])])
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([daemon])
+AT_CAPTURE_FILE([olddaemon])
+AT_CAPTURE_FILE([newdaemon])
+AT_CAPTURE_FILE([monitor])
+AT_CAPTURE_FILE([newmonitor])
+AT_CAPTURE_FILE([init])
+# Start the daemon and make sure that the pidfile exists immediately.
+# We don't wait for the pidfile to get created because the daemon is
+# supposed to do so before the parent exits.
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/daemon --monitor --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK([test -s daemon])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is a running process,
+# and that the parent process of that process is init.
+CHECK([kill -0 `cat daemon`])
+CHECK([ps -o ppid= -p `cat daemon` > monitor])
+CHECK([kill -0 `cat monitor`])
+CHECK([ps -o ppid= -p `cat monitor` > init])
+CHECK([test `cat init` = 1])
+# Kill the daemon process, making it look like a segfault,
+# and wait for a new daemon process to get spawned.
+CHECK([cp daemon olddaemon])
+CHECK([kill -SEGV `cat daemon`], [0], [ignore], [ignore])
+OVS_WAIT_WHILE([kill -0 `cat olddaemon`], [kill `cat olddaemon daemon`])
+OVS_WAIT_UNTIL([test -s daemon && test `cat daemon` != `cat olddaemon`],
+  [kill `cat olddaemon daemon`])
+CHECK([cp daemon newdaemon])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+CHECK([kill -0 `cat daemon`])
+CHECK([diff olddaemon newdaemon], [1], [ignore])
+CHECK([ps -o ppid= -p `cat daemon` > newmonitor])
+CHECK([diff monitor newmonitor])
+CHECK([kill -0 `cat newmonitor`])
+CHECK([ps -o ppid= -p `cat newmonitor` > init])
+CHECK([test `cat init` = 1])
+# Kill the daemon process with SIGTERM, and wait for the daemon
+# and the monitor processes to go away and the pidfile to get deleted.
+CHECK([kill `cat daemon`], [0], [], [ignore])
+OVS_WAIT_WHILE(
+  [kill -0 `cat monitor` || kill -0 `cat newdaemon` || test -e daemon],
+  [kill `cat monitor newdaemon`])
+m4_undefine([CHECK])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach startup errors])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/nonexistent/unixctl db], [1], [], [stderr])
+AT_CHECK([grep 'ovsdb-server: could not initialize control socket' stderr],
+  [0], [ignore], [])
+AT_CHECK([test ! -s pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor startup errors])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --monitor --unixctl=$PWD/nonexistent/unixctl db], [1], [], [stderr])
+AT_CHECK([grep 'ovsdb-server: could not initialize control socket' stderr],
+  [0], [ignore], [])
+AT_CHECK([test ! -s pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach closes standard fds])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([(yes; echo $? > status) | ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/unixctl db], [0], [], [stderr])
+AT_CHECK([kill `cat pid`])
+AT_CHECK([test -s status])
+AT_CHECK([kill -l `cat status`], [0], [PIPE
+])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor closes standard fds])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([(yes; echo $? > status) | ovsdb-server --detach --monitor --pidfile=$PWD/pid --unixctl=$PWD/unixctl db], [0], [], [stderr])
+AT_CHECK([kill `cat pid`])
+AT_CHECK([test -s status])
+AT_CHECK([kill -l `cat status`], [0], [PIPE
+])
+AT_CLEANUP
diff --git a/tests/dir_name.at b/tests/dir_name.at
new file mode 100644 (file)
index 0000000..062e422
--- /dev/null
@@ -0,0 +1,25 @@
+AT_BANNER([test dir_name function])
+
+m4_define([CHECK_DIR_NAME],
+  [AT_SETUP([dir_name("$1") returns "$2"])
+   AT_KEYWORDS([dir_name])
+   AT_CHECK([test-dir_name "AS_ESCAPE($1)"], [0], [$2
+])
+   AT_CLEANUP])
+
+# These are the test cases given in POSIX for dirname().
+CHECK_DIR_NAME([/usr/lib], [/usr])
+CHECK_DIR_NAME([/usr/], [/])
+CHECK_DIR_NAME([usr], [.])
+CHECK_DIR_NAME([/], [/])
+CHECK_DIR_NAME([.], [.])
+CHECK_DIR_NAME([..], [.])
+CHECK_DIR_NAME([//], [//])      # / is also allowed
+CHECK_DIR_NAME([//foo], [//])   # / is also allowed
+CHECK_DIR_NAME([], [.])
+
+# Additional test cases.
+CHECK_DIR_NAME([dir/file], [dir])
+CHECK_DIR_NAME([dir/file/], [dir])
+CHECK_DIR_NAME([dir/file//], [dir])
+CHECK_DIR_NAME([///foo], [/])
index 45b9728..bbb6f11 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/perl
 
-# Copyright (c) 2009 Nicira Networks.
+# Copyright (c) 2009, 2010 Nicira Networks.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -60,6 +60,7 @@ sub output {
     $flow{DL_SRC} = "00:02:e3:0f:80:a4";
     $flow{DL_DST} = "00:1a:92:40:ac:05";
     $flow{NW_PROTO} = 0;
+    $flow{NW_TOS} = 0;
     $flow{NW_SRC} = '0.0.0.0';
     $flow{NW_DST} = '0.0.0.0';
     $flow{TP_SRC} = 0;
@@ -78,6 +79,7 @@ sub output {
         $flow{DL_TYPE} = 0x0800; # ETH_TYPE_IP
         $flow{NW_SRC} = '10.0.2.15';
         $flow{NW_DST} = '192.168.1.20';
+        $flow{NW_TOS} = 44;
         if ($attrs{TP_PROTO} eq 'other') {
             $flow{NW_PROTO} = 42;
         } elsif ($attrs{TP_PROTO} eq 'TCP' ||
@@ -124,7 +126,7 @@ sub output {
         if ($attrs{DL_TYPE} eq 'ip') {
             my $ip = pack('CCnnnCCnNN',
                           (4 << 4) | 5,    # version, hdrlen
-                          0,               # type of service
+                          $flow{NW_TOS},   # type of service
                           0,               # total length (filled in later)
                           65432,           # id
                           0,               # frag offset
@@ -203,9 +205,11 @@ sub output {
                      1);        # in_port
     print FLOWS pack_ethaddr($flow{DL_SRC});
     print FLOWS pack_ethaddr($flow{DL_DST});
-    print FLOWS pack('nnCxNNnn',
+    print FLOWS pack('nCxnCCxxNNnn',
                      $flow{DL_VLAN},
+                     0,          # DL_VLAN_PCP
                      $flow{DL_TYPE},
+                     $flow{NW_TOS},
                      $flow{NW_PROTO},
                      inet_aton($flow{NW_SRC}),
                      inet_aton($flow{NW_DST}),
diff --git a/tests/idltest.ann b/tests/idltest.ann
new file mode 100644 (file)
index 0000000..66e8637
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates idltest.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "idltest_"
+s["idlHeader"] = "\"tests/idltest.h\""
diff --git a/tests/idltest.ovsschema b/tests/idltest.ovsschema
new file mode 100644 (file)
index 0000000..545242b
--- /dev/null
@@ -0,0 +1,109 @@
+{
+  "name": "idltest", 
+  "tables": {
+    "link1": {
+      "columns": {
+        "i": {
+          "type": "integer"
+        }, 
+        "k": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            }
+          }
+        }, 
+        "ka": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            },
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "l2": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link2"
+            },
+            "min": 0
+          }
+        }
+      }
+    }, 
+    "link2": {
+      "columns": {
+        "i": {
+          "type": "integer"
+        }, 
+        "l1": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            },
+            "min": 0
+          }
+        }
+      }
+    }, 
+    "simple": {
+      "columns": {
+        "b": {
+          "type": "boolean"
+        }, 
+        "ba": {
+          "type": {
+            "key": "boolean", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "i": {
+          "type": "integer"
+        }, 
+        "ia": {
+          "type": {
+            "key": "integer", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "r": {
+          "type": "real"
+        }, 
+        "ra": {
+          "type": {
+            "key": "real", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "s": {
+          "type": "string"
+        }, 
+        "sa": {
+          "type": {
+            "key": "string", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "u": {
+          "type": "uuid"
+        }, 
+        "ua": {
+          "type": {
+            "key": "uuid", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/interface-reconfigure.at b/tests/interface-reconfigure.at
new file mode 100644 (file)
index 0000000..feed280
--- /dev/null
@@ -0,0 +1,932 @@
+dnl IFR_SETUP
+dnl
+dnl Creates a directory tree for use with "interface-reconfigure --root-dir".
+m4_define([IFR_SETUP], 
+  [[for script in \
+        interface-reconfigure \
+        InterfaceReconfigure.py \
+        InterfaceReconfigureBridge.py \
+        InterfaceReconfigureVswitch.py
+    do
+        cp $top_srcdir/xenserver/opt_xensource_libexec_$script $script
+    done
+
+    mkdir -p etc
+    cat > etc/xensource-inventory <<EOF
+PRODUCT_BRAND='XenServer'
+PRODUCT_NAME='xenenterprise'
+PRODUCT_VERSION='5.5.0'
+BUILD_NUMBER='24648p'
+KERNEL_VERSION='2.6.18-128.1.6.el5.xs5.5.0.505.1024xen'
+XEN_VERSION='3.3.1'
+INSTALLATION_DATE='2010-02-10 16:45:28.748345'
+PRIMARY_DISK='/dev/disk/by-id/scsi-360022190add7fc001241a14e0ee3e85c'
+BACKUP_PARTITION='/dev/disk/by-id/scsi-360022190add7fc001241a14e0ee3e85c-part2'
+INSTALLATION_UUID='852ee692-71b4-439e-abfb-0eba72dc85f0'
+CONTROL_DOMAIN_UUID='44e6b66e-3074-4a3c-bbcd-756d845a3b56'
+DEFAULT_SR_PHYSDEVS='/dev/sda3'
+DOM0_MEM='752'
+MANAGEMENT_INTERFACE='xenbr2'
+EOF
+
+    mkdir -p etc/xensource
+    echo vswitch > etc/xensource/network.conf
+
+    for utility in \
+        sbin/ethtool \
+        sbin/ifconfig \
+        sbin/ifdown \
+        sbin/ifup \
+        sbin/ip \
+        sbin/update-issue \
+        sbin/vconfig \
+        usr/bin/ovs-vsctl \
+        usr/sbin/brctl
+    do
+        mkdir -p `dirname $utility`
+        cat > $utility <<'EOF'
+#! /bin/sh
+echo ${0} ${*} >&2
+EOF
+        chmod +x $utility
+    done
+
+    mkdir -p etc/sysconfig/network-scripts
+    configure_netdev () {
+        mkdir -p sys/class/net/${1}
+        echo ${2} > sys/class/net/${1}/address
+        echo ${3} > sys/class/net/${1}/tx_queue_len
+
+        : >> etc/sysconfig/network-scripts/ifcfg-${1}
+    }
+
+    configure_netdev lo   00:00:00:00:00:00 0
+    configure_netdev eth0 00:22:19:22:4b:af 1000
+    configure_netdev eth1 00:22:19:22:4b:b1 1000
+    configure_netdev eth2 00:15:17:a0:29:80 1000
+    configure_netdev eth3 00:15:17:a0:29:81 1000
+    configure_netdev eth4 00:1b:21:29:ce:51 1000
+
+    mkdir -p var/xapi
+    cat > var/xapi/network.dbcache <<'EOF'
+<?xml version="1.0" ?>
+<xenserver-network-configuration>
+       <pif ref="OpaqueRef:e0955887-571f-17fc-a971-61c1ec7d81b6">
+               <VLAN_slave_of/>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:86d81bcf-0d25-90b2-cb11-af2007bd586e
+               </bond_slave_of>
+               <uuid>
+                       bd62a141-091f-3909-e334-0334f67ff3be
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:22:19:22:4b:af
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth0
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       False
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:83e4a934-aeb5-e6f0-a743-d1c7ef7364c5
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:eea8da94-a5e6-18fc-34a7-5e9b5a235806">
+               <VLAN_slave_of/>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       df8d35c2-cc3a-a623-7065-d987a29feb75
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:1b:21:29:ce:51
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth4
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       False
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:bf51b4d3-7bdc-ea55-ba21-539b150b0531
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:2956e6c8-487e-981c-85ff-c84796418768">
+               <VLAN_slave_of/>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:86d81bcf-0d25-90b2-cb11-af2007bd586e
+               </bond_slave_of>
+               <uuid>
+                       2f87fc95-5ab4-571a-2487-3f4ac1985663
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:22:19:22:4b:b1
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth1
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       False
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:92b41bf6-aa21-45d3-1c86-c87a5fa98f7d
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:d2d1e51e-4da9-3163-8f57-bb683429335e">
+               <VLAN_slave_of/>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       d2dfdab3-daf4-afea-f055-a25a0d24d714
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:4c0eb823-4d96-da1d-e75f-411b85badb0c
+               </VLAN_master_of>
+               <VLAN>
+                       4
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       fe:ff:ff:ff:ff:ff
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       bond0
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       True
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:a63afad0-fb4c-b4a4-3696-cbb3d88afc47
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:2bc0fab5-523a-4125-609d-212391f5f6fc">
+               <VLAN_slave_of>
+                       <master>
+                               OpaqueRef:e623e1d6-cd02-be8d-820d-49d65c710297
+                       </master>
+               </VLAN_slave_of>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       f4ba396e-a993-a592-5fbc-a1d566afb59e
+               </uuid>
+               <IP>
+                       10.0.0.188
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       255.0.0.0
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:15:17:a0:29:81
+               </MAC>
+               <ip_configuration_mode>
+                       Static
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth3
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       True
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:d9189da2-d00b-61ba-8a6d-ac42cc868e32
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:205d1186-2cd1-d5e6-45e4-ea1698ea6e15">
+               <VLAN_slave_of/>
+               <management>
+                       True
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       646ca9a1-36ad-e2f9-3ecc-1e5622c201c2
+               </uuid>
+               <IP>
+                       172.18.3.188
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       255.255.0.0
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:15:17:a0:29:80
+               </MAC>
+               <ip_configuration_mode>
+                       DHCP
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth2
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       True
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:6e7c6e81-6b5e-b91f-e1f9-9e028567bdfe
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:8e3e37e6-ebb9-087e-0201-f6a56bf554c3">
+               <VLAN_slave_of/>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       3941edd2-865b-8dd8-61f0-199f5e1fa652
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:e623e1d6-cd02-be8d-820d-49d65c710297
+               </VLAN_master_of>
+               <VLAN>
+                       123
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       fe:ff:ff:ff:ff:ff
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       eth3
+               </device>
+               <bond_master_of/>
+               <currently_attached>
+                       True
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:240fb5f8-addc-6ea3-f921-2a42b42acd17
+               </network>
+       </pif>
+       <pif ref="OpaqueRef:69c904bb-8da9-3424-485b-8b47c2d3ef11">
+               <VLAN_slave_of>
+                       <master>
+                               OpaqueRef:4c0eb823-4d96-da1d-e75f-411b85badb0c
+                       </master>
+               </VLAN_slave_of>
+               <management>
+                       False
+               </management>
+               <bond_slave_of>
+                       OpaqueRef:NULL
+               </bond_slave_of>
+               <uuid>
+                       6c0327a9-afa3-fc19-6798-a1bfe20095ed
+               </uuid>
+               <IP>
+                       
+               </IP>
+               <VLAN_master_of>
+                       OpaqueRef:NULL
+               </VLAN_master_of>
+               <VLAN>
+                       -1
+               </VLAN>
+               <netmask>
+                       
+               </netmask>
+               <other_config/>
+               <MAC>
+                       00:22:19:22:4b:af
+               </MAC>
+               <ip_configuration_mode>
+                       None
+               </ip_configuration_mode>
+               <DNS>
+                       
+               </DNS>
+               <device>
+                       bond0
+               </device>
+               <bond_master_of>
+                       <slave>
+                               OpaqueRef:86d81bcf-0d25-90b2-cb11-af2007bd586e
+                       </slave>
+               </bond_master_of>
+               <currently_attached>
+                       True
+               </currently_attached>
+               <gateway>
+                       
+               </gateway>
+               <network>
+                       OpaqueRef:ec1e5037-60ea-97e5-54b8-39bdb43c071a
+               </network>
+       </pif>
+       <bond ref="OpaqueRef:86d81bcf-0d25-90b2-cb11-af2007bd586e">
+               <master>
+                       OpaqueRef:69c904bb-8da9-3424-485b-8b47c2d3ef11
+               </master>
+               <uuid>
+                       dad825f1-6d81-386e-849c-5589281e53e1
+               </uuid>
+               <slaves>
+                       <slave>
+                               OpaqueRef:e0955887-571f-17fc-a971-61c1ec7d81b6
+                       </slave>
+                       <slave>
+                               OpaqueRef:2956e6c8-487e-981c-85ff-c84796418768
+                       </slave>
+               </slaves>
+       </bond>
+       <vlan ref="OpaqueRef:4c0eb823-4d96-da1d-e75f-411b85badb0c">
+               <tagged_PIF>
+                       OpaqueRef:69c904bb-8da9-3424-485b-8b47c2d3ef11
+               </tagged_PIF>
+               <uuid>
+                       841814da-d0d2-9da4-0b2e-b6143480bbfb
+               </uuid>
+               <untagged_PIF>
+                       OpaqueRef:d2d1e51e-4da9-3163-8f57-bb683429335e
+               </untagged_PIF>
+       </vlan>
+       <vlan ref="OpaqueRef:e623e1d6-cd02-be8d-820d-49d65c710297">
+               <tagged_PIF>
+                       OpaqueRef:2bc0fab5-523a-4125-609d-212391f5f6fc
+               </tagged_PIF>
+               <uuid>
+                       399279a2-5ccd-5368-9af3-8622a1f1ac82
+               </uuid>
+               <untagged_PIF>
+                       OpaqueRef:8e3e37e6-ebb9-087e-0201-f6a56bf554c3
+               </untagged_PIF>
+       </vlan>
+       <network ref="OpaqueRef:a63afad0-fb4c-b4a4-3696-cbb3d88afc47">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:d2d1e51e-4da9-3163-8f57-bb683429335e
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xapi2
+               </bridge>
+               <other_config/>
+               <uuid>
+                       99be2da4-6c33-6f8e-49ea-3bc592fe3c85
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:d9189da2-d00b-61ba-8a6d-ac42cc868e32">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:2bc0fab5-523a-4125-609d-212391f5f6fc
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xenbr3
+               </bridge>
+               <other_config/>
+               <uuid>
+                       2902ae1b-8013-897a-b697-0b200ea3aaa5
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:ec1e5037-60ea-97e5-54b8-39bdb43c071a">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:69c904bb-8da9-3424-485b-8b47c2d3ef11
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xapi1
+               </bridge>
+               <other_config/>
+               <uuid>
+                       45cbbb43-113d-a712-3231-c6463f253cef
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:92b41bf6-aa21-45d3-1c86-c87a5fa98f7d">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:2956e6c8-487e-981c-85ff-c84796418768
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xenbr1
+               </bridge>
+               <other_config/>
+               <uuid>
+                       99f8771a-645a-26a3-e06c-30a401f1d009
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:6e7c6e81-6b5e-b91f-e1f9-9e028567bdfe">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:205d1186-2cd1-d5e6-45e4-ea1698ea6e15
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xenbr2
+               </bridge>
+               <other_config/>
+               <uuid>
+                       d08c8749-0c8f-9e8d-ce25-fd364661ee99
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:83e4a934-aeb5-e6f0-a743-d1c7ef7364c5">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:e0955887-571f-17fc-a971-61c1ec7d81b6
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xenbr0
+               </bridge>
+               <other_config/>
+               <uuid>
+                       c9eecb03-560d-61de-b6a8-56dfc766f67e
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:bf51b4d3-7bdc-ea55-ba21-539b150b0531">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:eea8da94-a5e6-18fc-34a7-5e9b5a235806
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xenbr4
+               </bridge>
+               <other_config/>
+               <uuid>
+                       d2c14c89-29cc-51d4-7664-633eff02b2ad
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:0b7354a4-8f4b-aa08-2f16-a22c117e4211">
+               <PIFs/>
+               <bridge>
+                       xapi0
+               </bridge>
+               <other_config/>
+               <uuid>
+                       dc0f0632-c2aa-1b78-2fea-0d3a23c51740
+               </uuid>
+       </network>
+       <network ref="OpaqueRef:240fb5f8-addc-6ea3-f921-2a42b42acd17">
+               <PIFs>
+                       <PIF>
+                               OpaqueRef:8e3e37e6-ebb9-087e-0201-f6a56bf554c3
+                       </PIF>
+               </PIFs>
+               <bridge>
+                       xapi3
+               </bridge>
+               <other_config/>
+               <uuid>
+                       db7bdc03-074d-42ae-fc73-9b06de1d57f6
+               </uuid>
+       </network>
+</xenserver-network-configuration>
+EOF
+]])
+
+m4_define([IFR_RUN], [./interface-reconfigure --root-prefix=$PWD --no-syslog])
+
+m4_define([IFR_FILTER], [sed -n -e "s,$PWD,,g" -e 's/ -- /\
+    /g' -e '/^Running command:/!p' stderr])
+\f
+AT_BANNER([interface-reconfigure])
+
+AT_SETUP([non-VLAN, non-bond])
+AT_KEYWORDS([interface-reconfigure])
+IFR_SETUP
+
+AT_CHECK([IFR_RUN --force xenbr2 up], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xenbr2 up
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_up: xenbr2
+Writing network configuration for xenbr2
+Configuring xenbr2 using DHCP configuration
+configure_datapath: bridge      - xenbr2
+configure_datapath: physical    - [u'eth2']
+configure_datapath: extra ports - []
+configure_datapath: extra bonds - []
+Applying changes to /etc/sysconfig/network-scripts/route-xenbr2 configuration
+Applying changes to /etc/sysconfig/network configuration
+Applying changes to /etc/sysconfig/network-scripts/ifcfg-xenbr2 configuration
+/sbin/ifconfig eth2 up mtu 1500
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port eth2
+    --may-exist add-br xenbr2
+    --may-exist add-port xenbr2 eth2
+    br-set-external-id xenbr2 network-uuids d08c8749-0c8f-9e8d-ce25-fd364661ee99
+    set Interface xenbr2 MAC="00:15:17:a0:29:80"
+/sbin/ifup xenbr2
+/sbin/update-issue
+Committing changes to /etc/sysconfig/network-scripts/route-xenbr2 configuration
+Committing changes to /etc/sysconfig/network configuration
+Committing changes to /etc/sysconfig/network-scripts/ifcfg-xenbr2 configuration
+]])
+
+AT_CHECK([cat etc/sysconfig/network-scripts/ifcfg-xenbr2], [0],
+  [# DO NOT EDIT: This file (ifcfg-xenbr2) was autogenerated by interface-reconfigure
+XEMANAGED=yes
+DEVICE=xenbr2
+ONBOOT=no
+TYPE=Ethernet
+BOOTPROTO=dhcp
+PERSISTENT_DHCLIENT=yes
+MTU=1500
+])
+
+# Simulate interface-reconfigure creating xenbr2, so that we can tell
+# interface-reconfigure to take it back down.
+AT_CHECK([configure_netdev xenbr2 00:15:17:a0:29:80 0])
+
+AT_CHECK([IFR_RUN --force xenbr2 down], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xenbr2 down
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_down: xenbr2
+/sbin/ifdown xenbr2
+deconfigure ipdev xenbr2 on xenbr2
+deconfigure_bridge: bridge           - xenbr2
+action_down: bring down physical devices - [u'eth2']
+/sbin/ifconfig eth2 down
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port xenbr2
+    --if-exists del-br xenbr2
+]])
+
+AT_CLEANUP
+\f
+AT_SETUP([VLAN, non-bond])
+AT_KEYWORDS([interface-reconfigure])
+IFR_SETUP
+
+AT_CHECK([IFR_RUN --force xapi3 up], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi3 up
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_up: xapi3
+Writing network configuration for xapi3
+Configuring xapi3 using None configuration
+configure_datapath: bridge      - xenbr3
+configure_datapath: physical    - [u'eth3']
+configure_datapath: extra ports - []
+configure_datapath: extra bonds - []
+Applying changes to /etc/sysconfig/network-scripts/route-xapi3 configuration
+Applying changes to /etc/sysconfig/network-scripts/ifcfg-xapi3 configuration
+/sbin/ifconfig eth3 up mtu 1500
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port eth3
+    --may-exist add-br xenbr3
+    --may-exist add-port xenbr3 eth3
+    br-set-external-id xenbr3 network-uuids 2902ae1b-8013-897a-b697-0b200ea3aaa5;db7bdc03-074d-42ae-fc73-9b06de1d57f6
+    set Interface xenbr3 MAC="00:15:17:a0:29:81"
+    --if-exists del-br xapi3
+    --may-exist add-br xapi3 xenbr3 123
+    br-set-external-id xapi3 network-uuids 2902ae1b-8013-897a-b697-0b200ea3aaa5;db7bdc03-074d-42ae-fc73-9b06de1d57f6
+    set Interface xapi3 MAC="00:15:17:a0:29:81"
+/sbin/ifup xapi3
+/sbin/update-issue
+Committing changes to /etc/sysconfig/network-scripts/route-xapi3 configuration
+Committing changes to /etc/sysconfig/network-scripts/ifcfg-xapi3 configuration
+]])
+
+AT_CHECK([cat etc/sysconfig/network-scripts/ifcfg-xapi3], [0],
+  [# DO NOT EDIT: This file (ifcfg-xapi3) was autogenerated by interface-reconfigure
+XEMANAGED=yes
+DEVICE=xapi3
+ONBOOT=no
+TYPE=Ethernet
+BOOTPROTO=none
+MTU=1500
+])
+
+# Simulate interface-reconfigure creating xapi3, so that we can tell
+# interface-reconfigure to take it back down.
+AT_CHECK([configure_netdev xapi3 00:23:20:AC:AF:02 0])
+
+AT_CHECK([IFR_RUN --force xapi3 down], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi3 down
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_down: xapi3
+/sbin/ifdown xapi3
+deconfigure ipdev xapi3 on xenbr3
+deconfigure_bridge: bridge           - xapi3
+action_down: no more masters, bring down slave xenbr3
+deconfigure_bridge: bridge           - xenbr3
+action_down: bring down physical devices - [u'eth3']
+/sbin/ifconfig eth3 down
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port xapi3
+    --if-exists del-br xapi3
+    --if-exists del-br xenbr3
+]])
+
+AT_CLEANUP
+\f
+AT_SETUP([Bond, non-VLAN])
+AT_KEYWORDS([interface-reconfigure])
+IFR_SETUP
+
+# Pretend that bond0 exists, even though it would really be created by
+# a "create-bond" call in an ovs-vsctl invocation within
+# interface-reconfigure, because otherwise interface-reconfigure will
+# die with "failed to apply changes: netdev: up: device bond0 does not
+# exist" after it thinks it created bond0.
+AT_CHECK([configure_netdev bond0 00:23:20:e6:39:75 0])
+
+AT_CHECK([IFR_RUN --force xapi1 up], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi1 up
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_up: xapi1
+Writing network configuration for xapi1
+Configuring xapi1 using None configuration
+configure_datapath: leaving bond bond0 up
+configure_datapath: leaving bond bond0 up
+configure_datapath: bridge      - xapi1
+configure_datapath: physical    - [u'eth0', u'eth1']
+configure_datapath: extra ports - []
+configure_datapath: extra bonds - []
+netdev: down: device xenbr0 does not exist, ignoring
+netdev: down: device xenbr1 does not exist, ignoring
+Applying changes to /etc/sysconfig/network-scripts/route-xapi1 configuration
+Applying changes to /etc/sysconfig/network-scripts/ifcfg-xapi1 configuration
+/sbin/ifconfig eth0 up mtu 1500
+/sbin/ifconfig eth1 up mtu 1500
+/usr/bin/ovs-vsctl --timeout=20
+    --if-exists del-br xenbr0
+    --if-exists del-br xenbr1
+    --with-iface --if-exists del-port eth0
+    --with-iface --if-exists del-port eth1
+    --may-exist add-br xapi1
+    --with-iface --if-exists del-port bond0
+    --fake-iface add-bond xapi1 bond0 eth0 eth1
+    set Port bond0 MAC="00:22:19:22:4b:af" bond_downdelay=200 other-config:"bond-miimon"=100 other-config:"bond-use_carrier"=1 other-config:"bond-mode"="balance-slb" bond_updelay=31000
+    br-set-external-id xapi1 network-uuids 99be2da4-6c33-6f8e-49ea-3bc592fe3c85;45cbbb43-113d-a712-3231-c6463f253cef
+    set Interface xapi1 MAC="00:22:19:22:4b:af"
+/sbin/ifup xapi1
+action_up: bring up bond0
+/sbin/ifconfig bond0 up
+/sbin/update-issue
+Committing changes to /etc/sysconfig/network-scripts/route-xapi1 configuration
+Committing changes to /etc/sysconfig/network-scripts/ifcfg-xapi1 configuration
+]])
+
+AT_CHECK([cat etc/sysconfig/network-scripts/ifcfg-xapi1], [0],
+  [# DO NOT EDIT: This file (ifcfg-xapi1) was autogenerated by interface-reconfigure
+XEMANAGED=yes
+DEVICE=xapi1
+ONBOOT=no
+TYPE=Ethernet
+BOOTPROTO=none
+MTU=1500
+])
+
+# Simulate interface-reconfigure creating xapi1, so that we can tell
+# interface-reconfigure to take it back down.
+AT_CHECK([configure_netdev xapi1 00:22:19:22:4B:AF 0])
+
+AT_CHECK([IFR_RUN --force xapi1 down], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi1 down
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_down: xapi1
+/sbin/ifdown xapi1
+deconfigure ipdev xapi1 on xapi1
+deconfigure_bridge: bridge           - xapi1
+action_down: bring down physical devices - [u'eth0', u'eth1']
+/sbin/ifconfig eth0 down
+/sbin/ifconfig eth1 down
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port xapi1
+    --if-exists del-br xapi1
+]])
+
+AT_CLEANUP
+\f
+AT_SETUP([VLAN on bond])
+AT_KEYWORDS([interface-reconfigure])
+IFR_SETUP
+
+# Pretend that bond0 exists, even though it would really be created by
+# a "create-bond" call in an ovs-vsctl invocation within
+# interface-reconfigure, because otherwise interface-reconfigure will
+# die with "failed to apply changes: netdev: up: device bond0 does not
+# exist" after it thinks it created bond0.
+AT_CHECK([configure_netdev bond0 00:23:20:e6:39:75 0])
+
+AT_CHECK([IFR_RUN --force xapi2 up], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi2 up
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_up: xapi2
+Writing network configuration for xapi2
+Configuring xapi2 using None configuration
+configure_datapath: leaving bond bond0 up
+configure_datapath: leaving bond bond0 up
+configure_datapath: bridge      - xapi1
+configure_datapath: physical    - [u'eth0', u'eth1']
+configure_datapath: extra ports - []
+configure_datapath: extra bonds - []
+netdev: down: device xenbr0 does not exist, ignoring
+netdev: down: device xenbr1 does not exist, ignoring
+Applying changes to /etc/sysconfig/network-scripts/route-xapi2 configuration
+Applying changes to /etc/sysconfig/network-scripts/ifcfg-xapi2 configuration
+/sbin/ifconfig eth0 up mtu 1500
+/sbin/ifconfig eth1 up mtu 1500
+/usr/bin/ovs-vsctl --timeout=20
+    --if-exists del-br xenbr0
+    --if-exists del-br xenbr1
+    --with-iface --if-exists del-port eth0
+    --with-iface --if-exists del-port eth1
+    --may-exist add-br xapi1
+    --with-iface --if-exists del-port bond0
+    --fake-iface add-bond xapi1 bond0 eth0 eth1
+    set Port bond0 MAC="00:22:19:22:4b:af" bond_downdelay=200 other-config:"bond-miimon"=100 other-config:"bond-use_carrier"=1 other-config:"bond-mode"="balance-slb" bond_updelay=31000
+    br-set-external-id xapi1 network-uuids 99be2da4-6c33-6f8e-49ea-3bc592fe3c85;45cbbb43-113d-a712-3231-c6463f253cef
+    set Interface xapi1 MAC="00:22:19:22:4b:af"
+    --if-exists del-br xapi2
+    --may-exist add-br xapi2 xapi1 4
+    br-set-external-id xapi2 network-uuids 99be2da4-6c33-6f8e-49ea-3bc592fe3c85;45cbbb43-113d-a712-3231-c6463f253cef
+    set Interface xapi2 MAC="00:22:19:22:4b:af"
+/sbin/ifup xapi2
+action_up: bring up bond0
+/sbin/ifconfig bond0 up
+/sbin/update-issue
+Committing changes to /etc/sysconfig/network-scripts/route-xapi2 configuration
+Committing changes to /etc/sysconfig/network-scripts/ifcfg-xapi2 configuration
+]])
+
+AT_CHECK([cat etc/sysconfig/network-scripts/ifcfg-xapi2], [0],
+  [# DO NOT EDIT: This file (ifcfg-xapi2) was autogenerated by interface-reconfigure
+XEMANAGED=yes
+DEVICE=xapi2
+ONBOOT=no
+TYPE=Ethernet
+BOOTPROTO=none
+MTU=1500
+])
+
+# Simulate interface-reconfigure creating xapi2, so that we can tell
+# interface-reconfigure to take it back down.
+AT_CHECK([configure_netdev xapi2 00:23:20:A4:71:C2 0])
+
+AT_CHECK([IFR_RUN --force xapi2 down], [0], [], [stderr])
+AT_CHECK([IFR_FILTER], [0], [[Force interface xapi2 down
+Loading xapi database cache from /var/xapi/network.dbcache
+Configured for Vswitch datapath
+action_down: xapi2
+/sbin/ifdown xapi2
+deconfigure ipdev xapi2 on xapi1
+deconfigure_bridge: bridge           - xapi2
+action_down: no more masters, bring down slave xapi1
+deconfigure_bridge: bridge           - xapi1
+action_down: bring down physical devices - [u'eth0', u'eth1']
+/sbin/ifconfig eth0 down
+/sbin/ifconfig eth1 down
+/usr/bin/ovs-vsctl --timeout=20
+    --with-iface --if-exists del-port xapi2
+    --if-exists del-br xapi2
+    --if-exists del-br xapi1
+]])
+
+AT_CLEANUP
diff --git a/tests/json.at b/tests/json.at
new file mode 100644 (file)
index 0000000..0449e03
--- /dev/null
@@ -0,0 +1,312 @@
+m4_define([JSON_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json positive])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   AT_CAPTURE_FILE([input])
+   AT_CHECK([test-json $4 input], [0], [stdout], [])
+   AT_CHECK([cat stdout], [0], [$3
+])
+   AT_CLEANUP])
+
+m4_define([JSON_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json negative])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   AT_CAPTURE_FILE([input])
+   AT_CHECK([test-json $4 input], [1], [stdout], [])
+   AT_CHECK([[sed 's/^error: [^:]*:/error:/' < stdout]], [0], [$3
+])
+   AT_CLEANUP])
+
+AT_BANNER([JSON -- arrays])
+
+JSON_CHECK_POSITIVE([empty array], [[ [   ] ]], [[[]]])
+JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
+JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
+JSON_CHECK_POSITIVE([many-element array],
+                    [[ [ 1, 2, 3, 4, 5 ] ]],
+                    [[[1,2,3,4,5]]])
+JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
+                    [error: syntax error expecting '@:>@' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed], 
+                    [[[1,2,]]], [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([doubled comma not allowed], 
+                    [[[1,,2]]], [error: syntax error expecting value])
+
+AT_BANNER([JSON -- strings])
+
+JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
+JSON_CHECK_POSITIVE([1-character strings], 
+                    [[[ "a", "b", "c" ]]],
+                    [[["a","b","c"]]])
+JSON_CHECK_POSITIVE([escape sequences], 
+  [[[ " \" \\ \/ \b \f \n \r \t" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([Unicode escape sequences], 
+  [[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([surrogate pairs],
+  [[["\ud834\udd1e"]]],
+  [[["𝄞"]]])
+JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([end of line in quoted string],
+                    [[["xxx
+"]]],
+                    [error: U+000A must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([formfeed in quoted string],
+                    [[["xxx\f"]]],
+                    [error: U+000C must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([bad escape in quoted string],
+                    [[["\x12"]]],
+                    [error: bad escape \x])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits (1)],
+                    [[["\u1x"]]],
+                    [error: quoted string ends within \u escape])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits (2)],
+                    [[["\u1xyz"]]],
+                    [error: malformed \u escape])
+JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
+                    [[["\ud834xxx"]]],
+                    [error: malformed escaped surrogate pair])
+JSON_CHECK_NEGATIVE([surrogatess must paired properly],
+                    [[["\ud834\u1234"]]],
+                    [error: second half of escaped surrogate pair is not trailing surrogate])
+JSON_CHECK_NEGATIVE([null bytes not allowed], 
+                    [[["\u0000"]]], 
+                    [error: null bytes not supported in quoted strings])
+
+AT_SETUP([end of input in quoted string])
+AT_KEYWORDS([json negative])
+AT_CHECK([printf '\"xxx' | test-json -], [1],
+  [error: line 0, column 4, byte 4: unexpected end of input in quoted string
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- objects])
+
+JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
+JSON_CHECK_POSITIVE([simple object],
+                    [[{"b": 2, "a": 1, "c": 3}]],
+                    [[{"a":1,"b":2,"c":3}]])
+JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]], 
+                    [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
+                    [error: syntax error parsing object expecting ':'])
+JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
+                    [error: syntax error expecting '}' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+                    [[{"b": 2, "a": 1, "c": 3, }]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+                    [[{"b": 2, "a": 1,, "c": 3}]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([names must be strings],
+                    [[{1: 2}]],
+                    [[error: syntax error parsing object expecting string]])
+
+AT_BANNER([JSON -- literal names])
+
+JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
+JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
+JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
+JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]], 
+                    [error: invalid keyword 'nullify'])
+JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]],
+                    [error: invalid keyword 'nubs'])
+JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]], 
+                    [error: invalid keyword 'xxx'])
+
+AT_BANNER([JSON -- numbers])
+
+JSON_CHECK_POSITIVE(
+  [integers expressed as reals],
+  [[[1.0000000000,
+     2.00000000000000000000000000000000000,
+     2e5,
+     2.1234e4,
+     2.1230e3,
+     0e-10000,
+     0e10000]]],
+  [[[1,2,200000,21234,2123,0,0]]])
+JSON_CHECK_POSITIVE(
+  [large integers], 
+  [[[9223372036854775807, -9223372036854775808]]],
+  [[[9223372036854775807,-9223372036854775808]]])
+JSON_CHECK_POSITIVE(
+  [large integers expressed as reals], 
+  [[[9223372036854775807.0, -9223372036854775808.0,
+     92233720.36854775807e11, -9.223372036854775808e18]]],
+  [[[9223372036854775807,-9223372036854775808,9223372036854775807,-9223372036854775808]]])
+# It seems likely that the following test will fail on some system that
+# rounds slightly differently in arithmetic or in printf, but I'd like
+# to keep it this way until we run into such a system.
+JSON_CHECK_POSITIVE(
+  [large integers that overflow to reals], 
+  [[[9223372036854775807000, -92233720368547758080000]]],
+  [[[9.22337203685478e+21,-9.22337203685478e+22]]])
+
+JSON_CHECK_POSITIVE(
+  [negative zero],
+  [[[-0, -0.0, 1e-9999, -1e-9999]]],
+  [[[0,0,0,0]]])
+
+JSON_CHECK_POSITIVE(
+  [reals], 
+  [[[0.0, 1.0, 2.0, 3.0, 3.5, 81.250]]],
+  [[[0,1,2,3,3.5,81.25]]])
+JSON_CHECK_POSITIVE(
+  [scientific notation],
+  [[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
+  [[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
+# It seems likely that the following test will fail on some system that
+# rounds slightly differently in arithmetic or in printf, but I'd like
+# to keep it this way until we run into such a system.
+JSON_CHECK_POSITIVE(
+  [+/- DBL_MAX],
+  [[[1.7976931348623157e+308, -1.7976931348623157e+308]]],
+  [[[1.79769313486232e+308,-1.79769313486232e+308]]])
+
+JSON_CHECK_POSITIVE(
+  [negative reals], 
+  [[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
+  [[[0,-1,-2,-3,-3.5,-8.125]]])
+JSON_CHECK_POSITIVE(
+  [negative scientific notation],
+  [[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
+  [[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [1e-9999 underflows to 0],
+  [[[1e-9999]]],
+  [[[0]]])
+JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE(
+  [leading zeros not allowed],
+  [[[0123]]],
+  [error: leading zeros not allowed])
+JSON_CHECK_NEGATIVE(
+  [1e9999 is too big],
+  [[[1e9999]]],
+  [error: number outside valid range])
+JSON_CHECK_NEGATIVE(
+  [exponent bigger than INT_MAX],
+  [[[1e9999999999999999999]]],
+  [error: exponent outside valid range])
+JSON_CHECK_NEGATIVE(
+  [decimal point must be followed by digit],
+  [[[1.]]],
+  [error: decimal point must be followed by digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (1)],
+  [[[1e]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (2)],
+  [[[1e+]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (3)],
+  [[[1e-]]],
+  [error: exponent must contain at least one digit])
+
+AT_BANNER([JSON -- RFC 4627 examples])
+
+JSON_CHECK_POSITIVE([RFC 4267 object example],
+[[{
+   "Image": {
+       "Width":  800,
+       "Height": 600,
+       "Title":  "View from 15th Floor",
+       "Thumbnail": {
+           "Url":    "http://www.example.com/image/481989943",
+           "Height": 125,
+           "Width":  "100"
+       },
+       "IDs": [116, 943, 234, 38793]
+     }
+}]],
+[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
+
+JSON_CHECK_POSITIVE([RFC 4267 array example],
+[[[
+   {
+      "precision": "zip",
+      "Latitude":  37.7668,
+      "Longitude": -122.3959,
+      "Address":   "",
+      "City":      "SAN FRANCISCO",
+      "State":     "CA",
+      "Zip":       "94107",
+      "Country":   "US"
+   },
+   {
+      "precision": "zip",
+      "Latitude":  37.371991,
+      "Longitude": -122.026020,
+      "Address":   "",
+      "City":      "SUNNYVALE",
+      "State":     "CA",
+      "Zip":       "94085",
+      "Country":   "US"
+   }
+]]],
+[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
+
+AT_BANNER([JSON -- pathological cases])
+
+JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
+                    [error: trailing garbage at end of input])
+JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
+                    [[[\f]]], [error: invalid character U+000c])
+JSON_CHECK_NEGATIVE([';' is not a valid token],
+                    [;], [error: invalid character ';'])
+JSON_CHECK_NEGATIVE([arrays nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [@<:@])dnl
+                     m4_for([i], [0], [1002], [1], [@:>@])],
+                    [error: input exceeds maximum nesting depth 1000])
+JSON_CHECK_NEGATIVE([objects nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [{"x":])dnl
+                     m4_for([i], [0], [1002], [1], [}])],
+                    [error: input exceeds maximum nesting depth 1000])
+
+AT_SETUP([input may not be empty])
+AT_KEYWORDS([json negative])
+AT_CHECK([test-json /dev/null], [1], [error: line 0, column 0, byte 0: empty input stream
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- multiple inputs])
+
+JSON_CHECK_POSITIVE([multiple adjacent objects], [[{}{}{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple space-separated objects], [[{}  {}  {}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects on separate lines], [[{}
+{}
+{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects and arrays], [[{}[]{}[]]], [[{}
+[]
+{}
+[]]],
+  [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage between multiple objects], [[{}x{}]], [[{}
+error: invalid keyword 'x'
+{}]], [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage after multiple objects], [[{}{}x]], [[{}
+{}
+error: invalid keyword 'x']], [--multiple])
diff --git a/tests/jsonrpc.at b/tests/jsonrpc.at
new file mode 100644 (file)
index 0000000..83410f9
--- /dev/null
@@ -0,0 +1,45 @@
+AT_BANNER([JSON-RPC])
+
+AT_SETUP([JSON-RPC request and successful reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket echo '[{"a": "b", "x": null}]']], [0],
+  [[{"error":null,"id":0,"result":[{"a":"b","x":null}]}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC request and error reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket bad-request '[]']], [0],
+  [[{"error":{"error":"unknown method"},"id":0,"result":null}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC notification])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+# When a daemon dies it deletes its pidfile, so make a copy.
+AT_CHECK([cp pid pid2])
+AT_CHECK([kill -0 `cat pid2`])
+AT_CHECK([[test-jsonrpc notify unix:socket shutdown '[]']], [0], [], 
+  [ignore], [kill `cat pid2`])
+AT_CHECK(
+  [pid=`cat pid2`
+   # First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   echo failure; exit 1], [0], [success
+], [ignore])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
diff --git a/tests/lcov-wrapper.in b/tests/lcov-wrapper.in
new file mode 100755 (executable)
index 0000000..1981cda
--- /dev/null
@@ -0,0 +1,58 @@
+#! /bin/sh
+
+abs_top_builddir='@abs_top_builddir@'
+wrap_program=`basename '@wrap_program@'`
+
+# Strip the first directory from $PATH that contains $wrap_program,
+# so that below we run the real $wrap_program, not ourselves.
+not_found=true
+new_path=
+first=true
+save_IFS=$IFS
+IFS=:
+for dir in $PATH; do
+    IFS=$save_IFS
+    if $not_found && test -x "$dir/$wrap_program"; then
+        not_found=false
+    else
+        if $first; then
+            first=false
+            new_path=$dir
+        else
+            new_path=$new_path:$dir
+        fi
+    fi
+done
+IFS=$save_IFS
+if $not_found; then
+    echo "$0: error: cannot find $wrap_program in \$PATH" >&2
+    exit 1
+fi
+PATH=$new_path
+export PATH
+
+# XXX Probably want some kind of synchronization here to deal with
+# programs running in parallel.
+LCOV="lcov -b $abs_top_builddir -d $abs_top_builddir -q"
+$LCOV -z
+
+# Run the subprocess and propagate signals to it.
+for signal in 1 2 3 5 15; do
+    trap "kill -$signal \$!     # Propagate signal
+          trap - $signal        # Reset signal to default
+          wait                  # Wait for child to die
+          kill -$signal $$      # Kill ourselves with same signal
+          exit 1                # Exit in case 'kill' failed" $signal
+done
+$wrap_program 0<&0 "$@" &       # 0<&0 prevents shell from closing stdin
+exec 0</dev/null                # Don't hold stdin open unnecessarily
+wait $!; rc=$?
+
+# Run lcov, but only if some .gcda files were produced, since lcov
+# complains otherwise.
+for file in `find "$abs_top_builddir" -name '*.gcda'`; do
+    $LCOV -c -o - >> "$abs_top_builddir/tests/coverage.info"
+    break
+done
+
+exit $rc
diff --git a/tests/library.at b/tests/library.at
new file mode 100644 (file)
index 0000000..0e408f0
--- /dev/null
@@ -0,0 +1,40 @@
+AT_BANNER([library unit tests])
+
+AT_SETUP([test flow extractor])
+AT_CHECK([$PERL `which flowgen.pl` >/dev/null 3>flows 4>pcap])
+AT_CHECK([test-flows <flows 3<pcap], [0], [checked 247 packets, 0 errors
+])
+AT_CLEANUP
+
+AT_SETUP([test TCP/IP checksumming])
+AT_CHECK([test-csum], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test flow classifier])
+AT_KEYWORDS([slow])
+AT_CHECK([test-classifier], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test hash functions])
+AT_CHECK([test-hash], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test hash map])
+AT_CHECK([test-hmap], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test linked lists])
+AT_CHECK([test-list], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test SHA-1])
+AT_CHECK([test-sha1], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test type properties])
+AT_CHECK([test-type-props], [0], [ignore])
+AT_CLEANUP
+
+AT_SETUP([test strtok_r bug fix])
+AT_CHECK([test-strtok_r], [0], [ignore])
+AT_CLEANUP
diff --git a/tests/lockfile.at b/tests/lockfile.at
new file mode 100644 (file)
index 0000000..9cc95a8
--- /dev/null
@@ -0,0 +1,20 @@
+AT_BANNER([lockfile unit tests])
+
+m4_define([CHECK_LOCKFILE],
+  [AT_SETUP([m4_translit([$1], [_], [ ])])
+   AT_KEYWORDS([lockfile])
+   AT_CHECK([test-lockfile $1], [0], [$1: success (m4_if(
+     [$2], [1], [$2 child], [$2 children]))
+])
+   AT_CLEANUP])
+
+CHECK_LOCKFILE([lock_and_unlock], [0])
+CHECK_LOCKFILE([lock_and_unlock_twice], [0])
+CHECK_LOCKFILE([lock_blocks_same_process], [0])
+CHECK_LOCKFILE([lock_blocks_same_process_twice], [0])
+CHECK_LOCKFILE([lock_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_twice_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_and_unlock_allows_other_process], [1])
+CHECK_LOCKFILE([lock_timeout_gets_the_lock], [1])
+CHECK_LOCKFILE([lock_timeout_runs_out], [1])
+CHECK_LOCKFILE([lock_multiple], [0])
diff --git a/tests/openssl.supp b/tests/openssl.supp
new file mode 100644 (file)
index 0000000..eb461f8
--- /dev/null
@@ -0,0 +1,13 @@
+# suppress OpenSSL errors from valgrind
+
+{
+   BN_mod_inverse
+   Memcheck:Cond
+   fun:BN_mod_inverse
+}
+
+{
+   BN_div
+   Memcheck:Cond
+   fun:BN_div
+}
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
new file mode 100644 (file)
index 0000000..2ea9e40
--- /dev/null
@@ -0,0 +1,638 @@
+dnl OVS_VSCTL_SETUP
+dnl
+dnl Creates an empty database in the current directory and then starts
+dnl an ovsdb-server on it for ovs-vsctl to connect to.
+m4_define([OVS_VSCTL_SETUP],
+  [OVSDB_INIT([db])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db >/dev/null 2>&1], [0], [ignore], [ignore])])
+
+dnl OVS_VSCTL_CLEANUP
+dnl
+dnl Kills off the database server.
+m4_define([OVS_VSCTL_CLEANUP], [OVSDB_SERVER_SHUTDOWN])
+
+dnl RUN_OVS_VSCTL(COMMAND, ...)
+dnl
+dnl Executes each ovs-vsctl COMMAND.
+m4_define([RUN_OVS_VSCTL],
+  [m4_foreach([command], [$@], [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket -- command
+])])
+m4_define([RUN_OVS_VSCTL_ONELINE],
+  [m4_foreach([command], [$@], [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket --oneline -- command
+])])
+
+dnl RUN_OVS_VSCTL_TOGETHER(COMMAND, ...)
+dnl
+dnl Executes each ovs-vsctl COMMAND in a single run of ovs-vsctl.
+m4_define([RUN_OVS_VSCTL_TOGETHER],
+  [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket --oneline dnl
+m4_foreach([command], [$@], [ -- command])])
+
+dnl CHECK_BRIDGES([BRIDGE, PARENT, VLAN], ...)
+dnl
+dnl Verifies that "ovs-vsctl list-br" prints the specified list of bridges,
+dnl which must be in alphabetical order.  Also checks that each BRIDGE has the
+dnl specified PARENT and is on the given VLAN.
+m4_define([_CHECK_BRIDGE],
+  [AT_CHECK([RUN_OVS_VSCTL([br-to-parent $1])], [0], [$2
+], [], [OVS_VSCTL_CLEANUP])
+
+   # Check br-to-vlan, without --oneline.
+   AT_CHECK([RUN_OVS_VSCTL([br-to-vlan $1])], [0], [$3
+], [], [OVS_VSCTL_CLEANUP])
+   # Check br-to-vlan, with --oneline.
+   # (This particular test is interesting with --oneline because it returns
+   # an integer instead of a string and that can cause type mismatches inside
+   # python if not done carefully.)
+   AT_CHECK([RUN_OVS_VSCTL_ONELINE([br-to-vlan $1])], [0], [$3
+], [], [OVS_VSCTL_CLEANUP])
+
+   # Check multiple queries in a single run.
+   AT_CHECK([RUN_OVS_VSCTL_TOGETHER([br-to-parent $1], [br-to-vlan $1])], [0],
+[$2
+$3
+], [], [OVS_VSCTL_CLEANUP])])
+m4_define([CHECK_BRIDGES],
+  [dnl Check that the bridges appear on list-br, without --oneline.
+   AT_CHECK(
+     [RUN_OVS_VSCTL([list-br])],
+     [0],
+     [m4_foreach([brinfo], [$@], [m4_car(brinfo)
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
+
+   dnl Check that the bridges appear on list-br, with --oneline.
+   AT_CHECK(
+     [RUN_OVS_VSCTL_ONELINE([list-br])],
+     [0],
+     [m4_join([\n], m4_foreach([brinfo], [$@], [m4_car(brinfo),]))
+],
+     [],
+     [OVS_VSCTL_CLEANUP])
+
+   dnl Check that each bridge exists according to br-exists and that
+   dnl a bridge that should not exist does not.
+   m4_foreach([brinfo], [$@], 
+              [AT_CHECK([RUN_OVS_VSCTL([br-exists m4_car(brinfo)])], [0], [],
+                        [], [OVS_VSCTL_CLEANUP])])
+   AT_CHECK([RUN_OVS_VSCTL([br-exists nonexistent])], [2], [], [],
+            [OVS_VSCTL_CLEANUP])
+
+   dnl Check that each bridge has the expected parent and VLAN.
+   m4_map([_CHECK_BRIDGE], [$@])])
+
+dnl CHECK_PORTS(BRIDGE, PORT[, PORT...])
+dnl
+dnl Verifies that "ovs-vsctl list-ports BRIDGE" prints the specified
+dnl list of ports, which must be in alphabetical order.  Also checks
+dnl that "ovs-vsctl port-to-br" reports that each port is
+dnl in BRIDGE.
+m4_define([CHECK_PORTS],
+  [dnl Check ports without --oneline.
+   AT_CHECK(
+     [RUN_OVS_VSCTL([list-ports $1])],
+     [0],
+     [m4_foreach([port], m4_cdr($@), [port
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
+
+   dnl Check ports with --oneline.
+   AT_CHECK(
+     [RUN_OVS_VSCTL_ONELINE([list-ports $1])],
+     [0],
+     [m4_join([\n], m4_shift($@))
+],
+     [],
+     [OVS_VSCTL_CLEANUP])
+   AT_CHECK([RUN_OVS_VSCTL([port-to-br $1])], [1], [],
+            [ovs-vsctl: no port named $1
+],
+            [OVS_VSCTL_CLEANUP])
+   m4_foreach(
+     [port], m4_cdr($@), 
+     [AT_CHECK([RUN_OVS_VSCTL([[port-to-br] port])], [0], [$1
+], [], [OVS_VSCTL_CLEANUP])])])
+
+dnl CHECK_IFACES(BRIDGE, IFACE[, IFACE...])
+dnl
+dnl Verifies that "ovs-vsctl list-ifaces BRIDGE" prints the specified
+dnl list of ifaces, which must be in alphabetical order.  Also checks
+dnl that "ovs-vsctl iface-to-br" reports that each interface is
+dnl in BRIDGE.
+m4_define([CHECK_IFACES],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL([list-ifaces $1])],
+     [0],
+     [m4_foreach([iface], m4_cdr($@), [iface
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
+   AT_CHECK([RUN_OVS_VSCTL([iface-to-br $1])], [1], [],
+            [ovs-vsctl: no interface named $1
+],
+            [OVS_VSCTL_CLEANUP])
+   m4_foreach(
+     [iface], m4_cdr($@), 
+     [AT_CHECK([RUN_OVS_VSCTL([[iface-to-br] iface])], [0], [$1
+],
+               [], [OVS_VSCTL_CLEANUP])])])
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ovs-vsctl unit tests -- real bridges])
+
+AT_SETUP([add-br a])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a])], [0], [], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-br a])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a])], [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add-br a])], [1], [],
+  [ovs-vsctl: cannot create a bridge named a because a bridge named a already exists
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-br b])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b])], [0], [], [],
+         [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-br a b 9])], [1], [],
+  [ovs-vsctl: "--may-exist add-br a b 9" but a is not a VLAN bridge
+],
+  [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0], [b, b, 0])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+CHECK_PORTS([b])
+CHECK_IFACES([b])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-br b, del-br a])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b], [del-br a])], [0], [], [],
+         [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([b, b, 0])
+CHECK_PORTS([b])
+CHECK_IFACES([b])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-port a a1, add-port a a2])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL(
+   [add-br a],
+   [--if-exists del-br b],
+   [add-port a a1],
+   [add-port a a2])], [0], [], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a], [a1], [a2])
+CHECK_IFACES([a], [a1], [a2])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-port a a1, add-port a a1])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL(
+   [add-br a], 
+   [add-port a a1])], [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add-port a a1])], [1], [],
+  [ovs-vsctl: cannot create a port named a1 because a port named a1 already exists on bridge a
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-br a])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+   [add-br a], 
+   [add-br b], 
+   [add-port a a1],
+   [add-port b b1],
+   [--if-exists del-port b b2],
+   [del-br a])], [0], [
+
+
+
+
+
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([b, b, 0])
+CHECK_PORTS([b], [b1])
+CHECK_IFACES([b], [b1])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL(
+   [add-br a], 
+   [add-bond a bond0 a1 a2 a3])], [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-bond a bond0 a3 a1 a2])], [0], [], [],
+  [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-bond a bond0 a2 a1])], [1], [], 
+  [ovs-vsctl: "--may-exist add-bond a bond0 a2 a1" but bond0 actually has interface(s) a1, a2, a3
+],
+  [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a], [bond0])
+CHECK_IFACES([a], [a1], [a2], [a3])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-port a a1])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL(
+  [add-br a], 
+  [add-br b], 
+  [add-port a a1],
+  [--may-exist add-port b b1],
+  [del-port a a1])], [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-port b b1])], [0], [], [],
+  [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-port a b1])], [1], [], 
+  [ovs-vsctl: "--may-exist add-port a b1" but b1 is actually attached to bridge b
+],
+  [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0], [b, b, 0])
+CHECK_PORTS([a])
+CHECK_IFACES([a])
+CHECK_PORTS([b], [b1])
+CHECK_IFACES([b], [b1])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3, del-port bond0])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [add-br a], 
+  [add-bond a bond0 a1 a2 a3],
+  [del-port bond0])], [0], [
+
+
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([external IDs])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [add-br a], 
+  [add-port a a1],
+  [add-bond a bond0 a2 a3],
+  [br-set-external-id a key0 value0],
+  [set port a1 external-ids:key1=value1],
+  [set interface a2 external-ids:key2=value2],
+  [set interface a2 external-ids:key3=value3],
+  [set interface a3 external-ids:key4=value4],
+  [br-get-external-id a],
+  [br-get-external-id a key0],
+  [br-get-external-id a key1],
+  [br-set-external-id a key0 othervalue],
+  [br-get-external-id a],
+  [br-set-external-id a key0],
+  [br-get-external-id a],
+  [get port a1 external-ids],
+  [get interface a2 external-ids],
+  [get interface a3 external-ids])], [0], [
+
+
+
+
+
+
+
+key0=value0
+value0
+
+
+key0=othervalue
+
+
+{"key1"="value1"}
+{"key2"="value2", "key3"="value3"}
+{"key4"="value4"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [br-get-external-id a],
+  [get port a1 external-ids],
+  [get interface a2 external-ids],
+  [get interface a3 external-ids])], [0],
+[
+{"key1"="value1"}
+{"key2"="value2", "key3"="value3"}
+{"key4"="value4"}
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a], [a1], [bond0])
+CHECK_IFACES([a], [a1], [a2], [a3])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ovs-vsctl unit tests -- fake bridges])
+
+m4_define([OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL(
+        [add-br xenbr0],
+        [--may-exist add-br xenbr0],
+        [add-port xenbr0 eth0],
+        [--may-exist add-port xenbr0 eth0],
+        [add-br xapi1 xenbr0 9],
+        [--may-exist add-br xapi1 xenbr0 9],
+        [add-port xapi1 eth0.9])],
+     [0], [], [], [OVS_VSCTL_CLEANUP])])
+
+AT_SETUP([simple fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-br xapi1])], [1], [],
+  [ovs-vsctl: "--may-exist add-br xapi1" but xapi1 is a VLAN bridge for VLAN 9
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-br xapi1 xxx 9])], [1], [],
+  [ovs-vsctl: "--may-exist add-br xapi1 xxx 9" but xapi1 has the wrong parent xenbr0
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--may-exist add-br xapi1 xenbr0 10])], [1], [],
+  [ovs-vsctl: "--may-exist add-br xapi1 xenbr0 10" but xapi1 is a VLAN bridge for the wrong VLAN 9
+], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([xapi1, xenbr0, 9], [xenbr0, xenbr0, 0])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+CHECK_PORTS([xapi1], [eth0.9])
+CHECK_IFACES([xapi1], [eth0.9])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([simple fake bridge + del-br fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])], [0], [], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([xenbr0, xenbr0, 0])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([simple fake bridge + del-br real bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([del-br xenbr0])], [0], [], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([simple fake bridge + external IDs])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [br-set-external-id xenbr0 key0 value0],
+  [br-set-external-id xapi1 key1 value1],
+  [br-get-external-id xenbr0],
+  [br-get-external-id xenbr0 key0],
+  [br-get-external-id xapi1],
+  [br-get-external-id xapi1 key1])], [0], [
+
+key0=value0
+value0
+key1=value1
+value1
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([xapi1, xenbr0, 9], [xenbr0, xenbr0, 0])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+CHECK_PORTS([xapi1], [eth0.9])
+CHECK_IFACES([xapi1], [eth0.9])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+m4_define([OVS_VSCTL_SETUP_BOND_FAKE_CONF],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL(
+        [add-br xapi1],
+        [add-bond xapi1 bond0 eth0 eth1],
+        [add-br xapi2 xapi1 11],
+        [add-port xapi2 bond0.11])],
+     [0], [], [], [OVS_VSCTL_CLEANUP])])
+
+AT_SETUP([fake bridge on bond])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
+CHECK_BRIDGES([xapi1, xapi1, 0], [xapi2, xapi1, 11])
+CHECK_PORTS([xapi1], [bond0])
+CHECK_IFACES([xapi1], [eth0], [eth1])
+CHECK_PORTS([xapi2], [bond0.11])
+CHECK_IFACES([xapi2], [bond0.11])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([fake bridge on bond + del-br fake bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL_ONELINE([del-br xapi2])], [0], [
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([xapi1, xapi1, 0])
+CHECK_PORTS([xapi1], [bond0])
+CHECK_IFACES([xapi1], [eth0], [eth1])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([fake bridge on bond + del-br real bridge])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])])
+CHECK_BRIDGES
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ovs-vsctl unit tests -- database commands])
+
+AT_SETUP([database commands -- positive checks])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout out1
+AT_CHECK([RUN_OVS_VSCTL([list b])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout out2
+AT_CHECK([perl $srcdir/uuidfilt.pl out1 out2], [0], 
+  [[<0>
+_uuid               : <0>
+controller          : []
+datapath_id         : []
+datapath_type       : ""
+external_ids        : {}
+flood_vlans         : []
+mirrors             : []
+name                : "br0"
+netflow             : []
+other_config        : {}
+ports               : []
+sflow               : []
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK(
+  [RUN_OVS_VSCTL(
+    [set bridge br0 \
+      'other_config:datapath_id="0123456789ab"' \
+      'other_config:hwaddr="00:11:22:33:44:55"' \
+      'external-ids={"uuids"="9c45f225-a7cf-439d-976d-83db6271fda1"}' -- \
+     add bridge br0 external_ids '"roles"="local; remote; cloud"'])], 
+  [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL_ONELINE([get bridge br0 other_config external-ids])], 
+  [0], [{datapath_id="0123456789ab", hwaddr="00:11:22:33:44:55"}\n{roles="local; remote; cloud", uuids="9c45f225-a7cf-439d-976d-83db6271fda1"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 other_config:hwaddr -- --if-exists get bridge br0 other-config:nonexistent])], 
+  [0], ["00:11:22:33:44:55"
+
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config hwaddr 'datapath_id=""' -- get br br0 other_config])], 
+  [0], [{datapath_id="0123456789ab"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config 'datapath_id="0123456789ab"' -- get br br0 other_config])], 
+  [0], [{}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([clear br br0 external-ids -- get br br0 external_ids])], 
+  [0], [{}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([destroy b br0])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b])], 
+  [0], [], [], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([database commands -- negative checks])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add-br br1])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set-controller br1 tcp:127.0.0.1])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([create n targets='"1.2.3.4:567"'])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout netflow-uuid
+AT_CHECK([RUN_OVS_VSCTL([list n `cat netflow-uuid`])],
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([perl $srcdir/uuidfilt.pl netflow-uuid stdout], [0], 
+  [[<0>
+_uuid               : <0>
+active_timeout      : 0
+add_id_to_interface : false
+engine_id           : []
+engine_type         : []
+targets             : ["1.2.3.4:567"]
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([RUN_OVS_VSCTL([list interx x])], 
+  [1], [], [ovs-vsctl: unknown table "interx"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b x])], 
+  [1], [], [ovs-vsctl: no row "x" in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b br])], 
+  [1], [], [ovs-vsctl: multiple rows in Bridge match "br"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 d])], 
+  [1], [], [ovs-vsctl: Bridge contains more than one column whose name matches "d"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 x])], 
+  [1], [], [ovs-vsctl: Bridge does not contain a column whose name matches "x"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 :y=z])], 
+  [1], [], [ovs-vsctl: :y=z: missing column name
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:y=z])], 
+  [1], [], [ovs-vsctl: datapath_id:y=z: value not accepted here
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id::])], 
+  [1], [], [ovs-vsctl: datapath_id::: trailing garbage ":" in argument
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:x])], 
+  [1], [], [ovs-vsctl: cannot specify key to get for non-map column datapath_id
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 external_ids:x])], 
+  [1], [], [ovs-vsctl: no key "x" in Bridge record "br0" column external_ids
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=-1])], 
+  [1], [], [ovs-vsctl: constraint violation: -1 is not in the valid range 0 to 4095 (inclusive)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])], 
+  [1], [], [ovs-vsctl: constraint violation: 4096 is not in the valid range 0 to 4095 (inclusive)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])], 
+  [1], [], [[ovs-vsctl: constraint violation: xyz is not one of the allowed values ([in-band, out-of-band])
+]], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])], 
+  [1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add b br1 datapath_id x y])], 
+  [1], [], [ovs-vsctl: "add" operation would put 2 values in column datapath_id of table Bridge but the maximum number is 1
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove n `cat netflow-uuid` targets '"1.2.3.4:567"'])], 
+  [1], [], [ovs-vsctl: "remove" operation would put 0 values in column targets of table NetFlow but the minimun number is 1
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([clear n `cat netflow-uuid` targets])], 
+  [1], [], [ovs-vsctl: "clear" operation cannot be applied to column targets of table NetFlow, which is not allowed to be empty
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([destroy b br2])], 
+  [1], [], [ovs-vsctl: no row "br2" in table Bridge
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+dnl This test really shows a bug -- "create" followed by "list" in
+dnl the same execution shows the wrong UUID on the "list" command.
+dnl The bug is documented in ovs-vsctl.8.
+AT_SETUP([created row UUID is wrong in same execution])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([create Bridge name=br0 -- list b])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], 
+  [[<0>
+_uuid               : <1>
+controller          : []
+datapath_id         : []
+datapath_type       : ""
+external_ids        : {}
+flood_vlans         : []
+mirrors             : []
+name                : "br0"
+netflow             : []
+other_config        : {}
+ports               : []
+sflow               : []
+]], [ignore], [test ! -e pid || kill `cat pid`])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at
new file mode 100644 (file)
index 0000000..7dd55e4
--- /dev/null
@@ -0,0 +1,13 @@
+AT_BANNER([OVSDB -- columns])
+
+OVSDB_CHECK_POSITIVE([ordinary column],
+  [[parse-column mycol '{"type": "integer"}']],
+  [[{"type":"integer"}]])
+
+OVSDB_CHECK_POSITIVE([immutable column],
+  [[parse-column mycol '{"type": "real", "mutable": false}']],
+  [[{"mutable":false,"type":"real"}]])
+
+OVSDB_CHECK_POSITIVE([ephemeral column],
+  [[parse-column mycol '{"type": "uuid", "ephemeral": true}']],
+  [[{"ephemeral":true,"type":"uuid"}]])
diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at
new file mode 100644 (file)
index 0000000..80961cc
--- /dev/null
@@ -0,0 +1,558 @@
+AT_BANNER([OVSDB -- conditions])
+
+OVSDB_CHECK_POSITIVE([null condition],
+  [[parse-conditions \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([conditions on scalars],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "==", 0]]' \
+    '[["i", "!=", 1]]' \
+    '[["i", "<", 2]]' \
+    '[["i", "<=", 3]]' \
+    '[["i", ">", 4]]' \
+    '[["i", ">=", 5]]' \
+    '[["i", "includes", 6]]' \
+    '[["i", "excludes", 7]]' \
+    '[["r", "==", 0.5]]' \
+    '[["r", "!=", 1.5]]' \
+    '[["r", "<", 2.5]]' \
+    '[["r", "<=", 3.5]]' \
+    '[["r", ">", 4.5]]' \
+    '[["r", ">=", 5.5]]' \
+    '[["r", "includes", 6.5]]' \
+    '[["r", "excludes", 7.5]]' \
+    '[["b", "==", true]]' \
+    '[["b", "!=", false]]' \
+    '[["b", "includes", false]]' \
+    '[["b", "excludes", true]]' \
+    '[["s", "==", "a"]]' \
+    '[["s", "!=", "b"]]' \
+    '[["s", "includes", "c"]]' \
+    '[["s", "excludes", "d"]]' \
+    '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [[[["i","==",0]]
+[["i","!=",1]]
+[["i","<",2]]
+[["i","<=",3]]
+[["i",">",4]]
+[["i",">=",5]]
+[["i","includes",6]]
+[["i","excludes",7]]
+[["r","==",0.5]]
+[["r","!=",1.5]]
+[["r","<",2.5]]
+[["r","<=",3.5]]
+[["r",">",4.5]]
+[["r",">=",5.5]]
+[["r","includes",6.5]]
+[["r","excludes",7.5]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",true]]
+[["s","==","a"]]
+[["s","!=","b"]]
+[["s","includes","c"]]
+[["s","excludes","d"]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]]
+[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]
+[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]],
+  [condition])
+
+AT_SETUP([disallowed conditions on scalars])
+AT_KEYWORDS([ovsdb negative condition])
+AT_CHECK([[test-ovsdb parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["b", ">", true]]' \
+    '[["b", ">=", false]]' \
+    '[["b", "<", false]]' \
+    '[["b", "<=", false]]' \
+    '[["s", ">", "a"]]' \
+    '[["s", ">=", "b"]]' \
+    '[["s", "<", "c"]]' \
+    '[["s", "<=", "d"]]' \
+    '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [1], [],
+  [[test-ovsdb: syntax "["b",">",true]": syntax error: Type mismatch: ">" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b",">=",false]": syntax error: Type mismatch: ">=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<",false]": syntax error: Type mismatch: "<" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<=",false]": syntax error: Type mismatch: "<=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s",">","a"]": syntax error: Type mismatch: ">" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s",">=","b"]": syntax error: Type mismatch: ">=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<","c"]": syntax error: Type mismatch: "<" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<=","d"]": syntax error: Type mismatch: "<=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": syntax error: Type mismatch: ">" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u",">=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: ">=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([conditions on sets],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "==", ["set", []]]]' \
+    '[["i", "!=", ["set", [1]]]]' \
+    '[["i", "includes", ["set", [1, 2]]]]' \
+    '[["i", "excludes", ["set", [1, 2, 3]]]]' \
+    '[["r", "==", ["set", []]]]' \
+    '[["r", "!=", ["set", [1.5]]]]' \
+    '[["r", "includes", ["set", [1.5, 2.5]]]]' \
+    '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \
+    '[["b", "==", ["set", [true]]]]' \
+    '[["b", "!=", ["set", [false]]]]' \
+    '[["b", "includes", ["set", [false]]]]' \
+    '[["b", "excludes", ["set", [true, false]]]]' \
+    '[["s", "==", ["set", ["a"]]]]' \
+    '[["s", "!=", ["set", ["a", "b"]]]]' \
+    '[["s", "includes", ["set", ["c"]]]]' \
+    '[["s", "excludes", ["set", ["c", "d"]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+    '[["u", "includes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \
+    '[["u", "excludes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],
+                ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \
+]],
+  [[[["i","==",["set",[]]]]
+[["i","!=",1]]
+[["i","includes",["set",[1,2]]]]
+[["i","excludes",["set",[1,2,3]]]]
+[["r","==",["set",[]]]]
+[["r","!=",1.5]]
+[["r","includes",["set",[1.5,2.5]]]]
+[["r","excludes",["set",[1.5,2.5,3.5]]]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",["set",[false,true]]]]
+[["s","==","a"]]
+[["s","!=",["set",["a","b"]]]]
+[["s","includes","c"]]
+[["s","excludes",["set",["c","d"]]]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [condition])
+
+OVSDB_CHECK_POSITIVE([condition sorting],
+  [[parse-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[["i", "excludes", 7],
+      ["i", "!=", 8],
+      ["i", "==", 1],
+      ["i", "includes", 2],
+      ["i", "<=", 3],
+      ["i", "<", 4],
+      ["i", ">", 6],
+      ["i", ">=", 5],
+      ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']],
+  [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]])
+
+OVSDB_CHECK_POSITIVE([evaluating null condition],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: TTT])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on integers],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "<", 1]],
+      [["i", "<=", 1]],
+      [["i", "==", 1]],
+      [["i", "!=", 1]],
+      [["i", ">=", 1]],
+      [["i", ">", 1]],
+      [["i", "includes", 1]],
+      [["i", "excludes", 1]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on reals],
+  [[evaluate-conditions \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "<", 5.0]],
+      [["r", "<=", 5.0]],
+      [["r", "==", 5.0]],
+      [["r", "!=", 5.0]],
+      [["r", ">=", 5.0]],
+      [["r", ">", 5.0]],
+      [["r", "includes", 5.0]],
+      [["r", "excludes", 5.0]]]' \
+    '[{"r": 0},
+      {"r": 5.0},
+      {"r": 5.1}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on booleans],
+  [[evaluate-conditions \
+    '{"columns": {"b": {"type": "boolean"}}}' \
+    '[[["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", true]],
+      [["b", "excludes", true]],
+      [["b", "==", false]],
+      [["b", "!=", false]],
+      [["b", "includes", false]],
+      [["b", "excludes", false]]]' \
+    '[{"b": true},
+      {"b": false}']]],
+  [condition  0: T-
+condition  1: -T
+condition  2: T-
+condition  3: -T
+condition  4: -T
+condition  5: T-
+condition  6: -T
+condition  7: T-], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on strings],
+  [[evaluate-conditions \
+    '{"columns": {"s": {"type": "string"}}}' \
+    '[[["s", "==", ""]],
+      [["s", "!=", ""]],
+      [["s", "includes", ""]],
+      [["s", "excludes", ""]],
+      [["s", "==", "foo"]],
+      [["s", "!=", "foo"]],
+      [["s", "includes", "foo"]],
+      [["s", "excludes", "foo"]]]' \
+    '[{"s": ""},
+      {"s": "foo"},
+      {"s": "xxx"}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs],
+  [[evaluate-conditions \
+    '{"columns": {"u": {"type": "uuid"}}}' \
+    '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \
+    '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]},
+      {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]},
+      {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on sets],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]']],
+  [dnl
+condition  0: T---- ----- -----
+condition  1: -T--- ----- -----
+condition  2: ---T- ----- -----
+condition  3: ----- T---- -----
+condition  4: ----- --T-- -----
+condition  5: ----- ----T -----
+condition  6: ----- ----- -T---
+condition  7: ----- ----- ---T-
+condition  8: -TTTT TTTTT TTTTT
+condition  9: T-TTT TTTTT TTTTT
+condition 10: TTT-T TTTTT TTTTT
+condition 11: TTTTT -TTTT TTTTT
+condition 12: TTTTT TT-TT TTTTT
+condition 13: TTTTT TTTT- TTTTT
+condition 14: TTTTT TTTTT T-TTT
+condition 15: TTTTT TTTTT TTT-T
+condition 16: TTTTT TTTTT TTTTT
+condition 17: -T--- TT--T T--T-
+condition 18: ---T- T---- -T-TT
+condition 19: ----- T---- ---T-
+condition 20: ----- --T-T -TTTT
+condition 21: ----- ----T ---T-
+condition 22: ----- ----- -T-TT
+condition 23: ----- ----- ---T-
+condition 24: TTTTT TTTTT TTTTT
+condition 25: T-TTT --TT- -TT-T
+condition 26: TTT-T -TTTT T-T--
+condition 27: T-T-T --TT- --T--
+condition 28: TTTTT TT-T- T----
+condition 29: T-TTT ---T- -----
+condition 30: TTT-T -T-T- T----
+condition 31: T-T-T ---T- -----], [condition])
diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
new file mode 100644 (file)
index 0000000..4bfd909
--- /dev/null
@@ -0,0 +1,680 @@
+AT_BANNER([OVSDB -- atoms without constraints])
+
+OVSDB_CHECK_POSITIVE([integer atom from JSON], 
+  [[parse-atoms '["integer"]' \
+    '[0]' \
+    '[-1]' \
+    '[1e3]' \
+    '[9223372036854775807]' \
+    '[-9223372036854775808]' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([integer atom from string], 
+  [[parse-atom-strings -- '["integer"]' \
+    '0' \
+    '-1' \
+    '+1000' \
+    '9223372036854775807' \
+    '-9223372036854775808' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([real atom from JSON], 
+  [[parse-atoms '["real"]' \
+    '[0]' \
+    '[0.0]' \
+    '[-0.0]' \
+    '[-1.25]' \
+    '[1e3]' \
+    '[1e37]' \
+    '[0.00390625]' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([real atom from string], 
+  [[parse-atom-strings -- '["real"]' \
+    '0' \
+    '0.0' \
+    '-0.0' \
+    '-1.25' \
+    '1e3' \
+    '1e37' \
+    '0.00390625' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([boolean atom from JSON],
+  [[parse-atoms '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([boolean atom from string],
+  [[parse-atom-strings '["boolean"]' 'true' 'false' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string atom from JSON],
+  [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([string atom from string],
+  [[parse-atom-strings '["string"]' \
+    'unquoted' \
+    '"quoted-string"' \
+    '"needs quotes"' \
+    '""' \
+    '"true"' \
+    '"\"\\\/\b\f\n\r\t"']],
+  [unquoted
+quoted-string
+"needs quotes"
+""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([uuid atom from JSON],
+  [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']],
+  [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom from string],
+  [[parse-atom-strings '["uuid"]' '550e8400-e29b-41d4-a716-446655440000']],
+  [550e8400-e29b-41d4-a716-446655440000])
+
+OVSDB_CHECK_POSITIVE([integer atom sorting],
+  [[sort-atoms '["integer"]' '[55,0,-1,2,1]']],
+  [[[-1,0,1,2,55]]])
+
+OVSDB_CHECK_POSITIVE([real atom sorting],
+  [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']],
+  [[[-1e+99,0,0,1.23,1.25]]])
+
+OVSDB_CHECK_POSITIVE([boolean atom sorting],
+  [[sort-atoms '["boolean"]' '[true,false,true,false,false]']],
+  [[[false,false,false,true,true]]])
+
+OVSDB_CHECK_POSITIVE([string atom sorting],
+  [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']],
+  [[["\b","abc","abd","xxx"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom sorting],
+  [[sort-atoms '["uuid"]' '[
+    ["uuid", "00000000-0000-0000-0000-000000000001"],
+    ["uuid", "00000000-1000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-1000-0000-000000000000"],
+    ["uuid", "00010000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000100"],
+    ["uuid", "00000000-0000-0000-0000-000100000000"],
+    ["uuid", "00000000-0000-0010-0000-000000000000"],
+    ["uuid", "00000100-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0001-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000001000000"],
+    ["uuid", "01000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000001000"],
+    ["uuid", "00000000-0000-0000-0000-000010000000"],
+    ["uuid", "00000000-0000-0000-0000-010000000000"],
+    ["uuid", "00000000-0000-0100-0000-000000000000"],
+    ["uuid", "10000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000010"],
+    ["uuid", "00000000-0100-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0100-000000000000"],
+    ["uuid", "00000000-0000-0000-0001-000000000000"],
+    ["uuid", "00000010-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0010-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000010000"],
+    ["uuid", "00000000-0000-0000-1000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-100000000000"],
+    ["uuid", "00000000-0000-0000-0000-001000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000100000"],
+    ["uuid", "00000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0010-0000-0000-000000000000"],
+    ["uuid", "00100000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0001-0000-0000-000000000000"],
+    ["uuid", "00000001-0000-0000-0000-000000000000"],
+    ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
+  [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
+
+OVSDB_CHECK_POSITIVE([real not acceptable integer JSON atom],
+  [[parse-atoms '["integer"]' '[0.5]' ]],
+  [syntax "0.5": syntax error: expected integer])
+
+dnl <C0> is not allowed anywhere in a UTF-8 string.
+dnl <ED A0 80> is a surrogate and not allowed in UTF-8.
+OVSDB_CHECK_POSITIVE([no invalid UTF-8 sequences in strings],
+  [parse-atoms '[["string"]]' \
+     '@<:@"m4_esyscmd([printf "\xc0"])"@:>@' \
+     '@<:@"m4_esyscmd([printf "\xed\xa0\x80"])"@:>@' \
+],
+  [constraint violation: "m4_esyscmd([printf "\xc0"])" is not a valid UTF-8 string: invalid UTF-8 sequence 0xc0
+constraint violation: "m4_esyscmd([printf "\xed\xa0\x80"])" is not a valid UTF-8 string: invalid UTF-8 sequence 0xed 0xa0])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer string atom],
+  [[parse-atom-strings '["integer"]' '0.5' ]],
+  ["0.5" is not a valid integer])
+
+OVSDB_CHECK_POSITIVE([string "true" not acceptable boolean JSON atom],
+  [[parse-atoms '["boolean"]' '["true"]' ]],
+  [syntax ""true"": syntax error: expected boolean])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean string atom],
+  [[parse-atom-strings '["boolean"]' '"true"' ]],
+  [""true"" is not a valid boolean (use "true" or "false")])
+
+OVSDB_CHECK_POSITIVE([integer not acceptable string JSON atom],
+  [[parse-atoms '["string"]' '[1]']],
+  [syntax "1": syntax error: expected string])
+
+OVSDB_CHECK_POSITIVE([uuid atom must be expressed as JSON array],
+  [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
+  [[syntax ""550e8400-e29b-41d4-a716-446655440000"": syntax error: expected ["uuid", <string>]]])
+
+OVSDB_CHECK_NEGATIVE([empty string atom must be quoted],
+  [[parse-atom-strings '["string"]' '']],
+  [An empty string is not valid as input; use "" to represent the empty string])
+
+OVSDB_CHECK_NEGATIVE([quotes must be balanced],
+  [parse-atom-strings '[["string"]]' '"asdf'],
+  ["asdf: missing quote at end of quoted string])
+
+OVSDB_CHECK_NEGATIVE([uuids must be valid],
+  [parse-atom-strings '[["uuid"]]' '1234-5678'],
+  ["1234-5678" is not a valid UUID])
+\f
+AT_BANNER([OVSDB -- atoms with enum constraints])
+
+OVSDB_CHECK_POSITIVE([integer atom enum], 
+  [[parse-atoms '[{"type": "integer", "enum": ["set", [1, 6, 8, 10]]}]' \
+    '[0]' \
+    '[1]' \
+    '[2]' \
+    '[3]' \
+    '[6]' \
+    '[7]' \
+    '[8]' \
+    '[9]' \
+    '[10]' \
+    '[11]']], 
+  [[constraint violation: 0 is not one of the allowed values ([1, 6, 8, 10])
+1
+constraint violation: 2 is not one of the allowed values ([1, 6, 8, 10])
+constraint violation: 3 is not one of the allowed values ([1, 6, 8, 10])
+6
+constraint violation: 7 is not one of the allowed values ([1, 6, 8, 10])
+8
+constraint violation: 9 is not one of the allowed values ([1, 6, 8, 10])
+10
+constraint violation: 11 is not one of the allowed values ([1, 6, 8, 10])]])
+
+OVSDB_CHECK_POSITIVE([real atom enum], 
+  [[parse-atoms '[{"type": "real", "enum": ["set", [-1.5, 1.5]]}]' \
+    '[-2]' \
+    '[-1]' \
+    '[-1.5]' \
+    '[0]' \
+    '[1]' \
+    '[1.5]' \
+    '[2]']], 
+  [[constraint violation: -2 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: -1 is not one of the allowed values ([-1.5, 1.5])
+-1.5
+constraint violation: 0 is not one of the allowed values ([-1.5, 1.5])
+constraint violation: 1 is not one of the allowed values ([-1.5, 1.5])
+1.5
+constraint violation: 2 is not one of the allowed values ([-1.5, 1.5])]])
+
+OVSDB_CHECK_POSITIVE([boolean atom enum], 
+  [[parse-atoms '[{"type": "boolean", "enum": false}]' \
+    '[false]' \
+    '[true]']], 
+  [[false
+constraint violation: true is not one of the allowed values ([false])]])
+
+OVSDB_CHECK_POSITIVE([string atom enum], 
+  [[parse-atoms '[{"type": "string", "enum": ["set", ["abc", "def"]]}]' \
+    '[""]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["def"]' \
+    '["defg"]' \
+    '["DEF"]']], 
+  [[constraint violation: "" is not one of the allowed values ([abc, def])
+constraint violation: ab is not one of the allowed values ([abc, def])
+"abc"
+"def"
+constraint violation: defg is not one of the allowed values ([abc, def])
+constraint violation: DEF is not one of the allowed values ([abc, def])]])
+
+OVSDB_CHECK_POSITIVE([uuid atom enum], 
+  [[parse-atoms '[{"type": "uuid", "enum": ["set", [["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"], ["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]]]}]' \
+    '["uuid", "6d53a6dd-2da7-4924-9927-97f613812382"]' \
+    '["uuid", "52cbc842-137a-4db5-804f-9f34106a0ba3"]' \
+    '["uuid", "dab2a6b2-6094-4f43-a7ef-4c0f0608f176"]']], 
+  [[["uuid","6d53a6dd-2da7-4924-9927-97f613812382"]
+["uuid","52cbc842-137a-4db5-804f-9f34106a0ba3"]
+constraint violation: dab2a6b2-6094-4f43-a7ef-4c0f0608f176 is not one of the allowed values ([52cbc842-137a-4db5-804f-9f34106a0ba3, 6d53a6dd-2da7-4924-9927-97f613812382])]])
+\f
+AT_BANNER([OVSDB -- atoms with other constraints])
+
+OVSDB_CHECK_POSITIVE([integers >= 5], 
+  [[parse-atoms '[{"type": "integer", "minInteger": 5}]' \
+    '[0]' \
+    '[4]' \
+    '[5]' \
+    '[6]' \
+    '[12345]']], 
+  [constraint violation: 0 is less than minimum allowed value 5
+constraint violation: 4 is less than minimum allowed value 5
+5
+6
+12345])
+
+OVSDB_CHECK_POSITIVE([integers <= -1], 
+  [[parse-atoms '[{"type": "integer", "maxInteger": -1}]' \
+    '[0]' \
+    '[-1]' \
+    '[-2]' \
+    '[-123]']], 
+  [constraint violation: 0 is greater than maximum allowed value -1
+-1
+-2
+-123])
+
+OVSDB_CHECK_POSITIVE([integers in range -10 to 10], 
+  [[parse-atoms '[{"type": "integer", "minInteger": -10, "maxInteger": 10}]' \
+    '[-20]' \
+    '[-11]' \
+    '[-10]' \
+    '[-9]' \
+    '[1]' \
+    '[9]' \
+    '[10]' \
+    '[11]' \
+    '[123576]']], 
+  [constraint violation: -20 is not in the valid range -10 to 10 (inclusive)
+constraint violation: -11 is not in the valid range -10 to 10 (inclusive)
+-10
+-9
+1
+9
+10
+constraint violation: 11 is not in the valid range -10 to 10 (inclusive)
+constraint violation: 123576 is not in the valid range -10 to 10 (inclusive)])
+
+OVSDB_CHECK_POSITIVE([reals >= 5], 
+  [[parse-atoms '[{"type": "real", "minReal": 5}]' \
+    '[0]' \
+    '[4]' \
+    '[5]' \
+    '[6]' \
+    '[12345]']], 
+  [constraint violation: 0 is less than minimum allowed value 5
+constraint violation: 4 is less than minimum allowed value 5
+5
+6
+12345])
+
+OVSDB_CHECK_POSITIVE([reals <= -1], 
+  [[parse-atoms '[{"type": "real", "maxReal": -1}]' \
+    '[0]' \
+    '[-1]' \
+    '[-2]' \
+    '[-123]']], 
+  [constraint violation: 0 is greater than maximum allowed value -1
+-1
+-2
+-123])
+
+OVSDB_CHECK_POSITIVE([reals in range -10 to 10], 
+  [[parse-atoms '[{"type": "real", "minReal": -10, "maxReal": 10}]' \
+    '[-20]' \
+    '[-11]' \
+    '[-10]' \
+    '[-9]' \
+    '[1]' \
+    '[9]' \
+    '[10]' \
+    '[11]' \
+    '[123576]']], 
+  [constraint violation: -20 is not in the valid range -10 to 10 (inclusive)
+constraint violation: -11 is not in the valid range -10 to 10 (inclusive)
+-10
+-9
+1
+9
+10
+constraint violation: 11 is not in the valid range -10 to 10 (inclusive)
+constraint violation: 123576 is not in the valid range -10 to 10 (inclusive)])
+
+OVSDB_CHECK_POSITIVE([strings at least 2 characters long],
+  [[parse-atoms '{"type": "string", "minLength": 2}' \
+    '[""]' \
+    '["a"]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["\ud834\udd1e"]']],
+  [[constraint violation: "" length 0 is less than minimum allowed length 2
+constraint violation: "a" length 1 is less than minimum allowed length 2
+"ab"
+"abc"
+constraint violation: "𝄞" length 1 is less than minimum allowed length 2]])
+
+OVSDB_CHECK_POSITIVE([strings no more than 2 characters long],
+  [[parse-atoms '{"type": "string", "maxLength": 2}' \
+    '[""]' \
+    '["a"]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["\ud834\udd1e"]']],
+  [[""
+"a"
+"ab"
+constraint violation: "abc" length 3 is greater than maximum allowed length 2
+"𝄞"]])
+
+AT_BANNER([OSVDB -- simple data])
+
+OVSDB_CHECK_POSITIVE([integer JSON datum],
+  [[parse-data '["integer"]' '[0]' '["set",[1]]' '[-1]']],
+  [0
+1
+-1])
+
+OVSDB_CHECK_POSITIVE([integer string datum],
+  [[parse-data-strings -- '["integer"]' '0' '1' '-1' '+1']],
+  [0
+1
+-1
+1])
+
+OVSDB_CHECK_POSITIVE([real JSON datum], 
+  [[parse-data '["real"]' '[0]' '["set",[1.0]]' '[-1.25]']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([real string datum], 
+  [[parse-data-strings -- '["real"]' '0' '1.0' '-1.25']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([boolean JSON datum],
+  [[parse-data '["boolean"]' '["set", [true]]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([boolean string datum],
+  [[parse-data-strings '["boolean"]' 'true' 'false' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string JSON datum],
+  [[parse-data '["string"]' '["set",[""]]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([string string datum],
+  [[parse-data-strings '["string"]' '"x"' '""' '"true"' '"\"\\\/\b\f\n\r\t"']],
+  [x
+""
+"true"
+"\"\\/\b\f\n\r\t"])
+\f
+AT_BANNER([OVSDB -- set data])
+
+OVSDB_CHECK_POSITIVE([JSON optional boolean],
+  [[parse-data '{"key": "boolean", "min": 0}' \
+    '[true]' \
+    '["set", [false]]' \
+    '["set", []]']], 
+  [[true
+false
+["set",[]]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([string optional boolean],
+  [[parse-data-strings '{"key": "boolean", "min": 0}' \
+    'true' \
+    'false' \
+    '[]']], 
+  [[true
+false
+[]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([JSON set of 0 or more integers],
+  [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '["set", [0]]' \
+    '[1]' \
+    '["set", [0, 1]]' \
+    '["set", [0, 1, 2]]' \
+    '["set", [0, 1, 2, 3, 4, 5]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']],
+  [[0
+1
+["set",[0,1]]
+["set",[0,1,2]]
+["set",[0,1,2,3,4,5]]
+["set",[0,1,2,3,4,5,6,7,8]]
+["set",[0,1,2,3,4,5,6,7,8,9,10]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 0 or more integers],
+  [[parse-data-strings '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '0' \
+    '0,1' \
+    '0, 1, 2' \
+    '[0, 1,2, 3, 4, 5]' \
+    '0, 1,2, 3,4, 5, 6, 7, 8' \
+    '[0, 1, 2, 3, 4,5, 6,7, 8, 9, 10]']],
+  [[[0]
+[0, 1]
+[0, 1, 2]
+[0, 1, 2, 3, 4, 5]
+[0, 1, 2, 3, 4, 5, 6, 7, 8]
+[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]])
+
+OVSDB_CHECK_POSITIVE([JSON set of 1 to 3 uuids],
+  [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \
+    '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \
+    '["uuid", "b5078be0-7664-4299-b836-8bcc03ef941f"]' \
+    '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"],
+              ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"],
+              ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']],
+  [[["uuid","550e8400-e29b-41d4-a716-446655440000"]
+["uuid","b5078be0-7664-4299-b836-8bcc03ef941f"]
+["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 1 to 3 uuids],
+  [[parse-data-strings '{"key": "uuid", "min": 1, "max": 3}' \
+    '[550e8400-e29b-41d4-a716-446655440000]' \
+    '[c5051240-30ff-43ed-b4b9-93cf3f050813,
+      90558331-09af-4d2f-a572-509cad2e9088,
+      550e8400-e29b-41d4-a716-446655440000]']],
+  [[[550e8400-e29b-41d4-a716-446655440000]
+[550e8400-e29b-41d4-a716-446655440000, 90558331-09af-4d2f-a572-509cad2e9088, c5051240-30ff-43ed-b4b9-93cf3f050813]]])
+
+OVSDB_CHECK_POSITIVE([JSON set of 0 to 3 strings],
+  [[parse-data '{"key": "string", "min": 0, "max": 3}' \
+    '["set", []]' \
+    '["a longer string"]' \
+    '["set", ["a relatively long string"]]' \
+    '["set", ["short string", "a relatively long string"]]' \
+    '["set", ["zzz", "short string", "a relatively long string"]]']],
+  [[["set",[]]
+"a longer string"
+"a relatively long string"
+["set",["a relatively long string","short string"]]
+["set",["a relatively long string","short string","zzz"]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 0 to 3 strings],
+  [[parse-data-strings '{"key": "string", "min": 0, "max": 3}' \
+    '[]' \
+    '"a relatively long string"' \
+    '["short string", "a relatively long string"]' \
+    '"zzz","short string","a relatively long string"']],
+  [[[]
+["a relatively long string"]
+["a relatively long string", "short string"]
+["a relatively long string", "short string", zzz]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in JSON set],
+  [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in string set],
+  [[parse-data-strings '{"key": "boolean", "max": 5}' 'true, true']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in JSON set],
+  [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in string set],
+  [[parse-data-strings '{"key": "integer", "max": 5}' '[1, 2, 3, 1]']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in JSON set],
+  [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in string set],
+  [[parse-data-strings '{"key": "real", "max": 5}' '0.0, -0.0']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in JSON set],
+  [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in string set],
+  [[parse-data-strings '{"key": "string", "max": 5}' 'asdf, ASDF, "asdf"']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in JSON set],
+  [[parse-data '{"key": "uuid", "max": 5}' \
+    '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"],
+              ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"],
+              ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in string set],
+  [[parse-data-strings '{"key": "uuid", "max": 5}' \
+    '7ef21525-0088-4a28-a418-5518413e43ea,
+     355ad037-f1da-40aa-b47c-ff9c7e8c6a38,
+     7ef21525-0088-4a28-a418-5518413e43ea']],
+  [set contains duplicate value])
+\f
+AT_BANNER([OVSDB -- map data])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean"}' \
+    '["map", [[1, true]]]']],
+  [[["map",[[1,true]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 integer to boolean],
+  [[parse-data-strings '{"key": "integer", "value": "boolean"}' \
+    '1=true']],
+  [[1=true]])
+
+OVSDB_CHECK_POSITIVE([JSON map of at least 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '["map", [[1, true]]]' \
+    '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \
+    '["map", [[3, false], [0, true], [4, false]]]']],
+  [[["map",[[1,true]]]
+["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]]
+["map",[[0,true],[3,false],[4,false]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of at least 1 integer to boolean],
+  [[parse-data-strings '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '1=true' \
+    '0=true 1=false 2=true, 3=true 4=true,' \
+    '3=false,0=true ,4=false']],
+  [[{1=true}
+{0=true, 1=false, 2=true, 3=true, 4=true}
+{0=true, 3=false, 4=false}]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 boolean to integer],
+ [[parse-data '{"key": "boolean", "value": "integer"}' \
+   '["map", [[true, 1]]]']],
+ [[["map",[[true,1]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 boolean to integer],
+ [[parse-data-strings '{"key": "boolean", "value": "integer"}' \
+   'true=1']],
+ [[true=1]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 uuid to real],
+  [[parse-data '{"key": "uuid", "value": "real", "min": 1, "max": 5}' \
+    '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0],
+             [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0],
+             [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0],
+             [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0],
+             [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']],
+  [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 uuid to real],
+  [[parse-data-strings '{"key": "uuid", "value": "real", "min": 1, "max": 5}' \
+    'cad8542b-6ee1-486b-971b-7dcbf6e14979=1.0,
+     6b94b968-2702-4f64-9457-314a34d69b8c=2.0,
+     d2c4a168-24de-47eb-a8a3-c1abfc814979=3.0,
+     25bfa475-d072-4f60-8be1-00f48643e9cb=4.0,
+     1c92b8ca-d5e4-4628-a85d-1dc2d099a99a=5.0']],
+  [[{1c92b8ca-d5e4-4628-a85d-1dc2d099a99a=5, 25bfa475-d072-4f60-8be1-00f48643e9cb=4, 6b94b968-2702-4f64-9457-314a34d69b8c=2, cad8542b-6ee1-486b-971b-7dcbf6e14979=1, d2c4a168-24de-47eb-a8a3-c1abfc814979=3}]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 10 string to string],
+  [[parse-data '{"key": "string", "value": "string", "min": 1, "max": 10}' \
+    '["map", [["2 gills", "1 chopin"],
+             ["2 chopins", "1 pint"],
+             ["2 pints", "1 quart"],
+             ["2 quarts", "1 pottle"],
+             ["2 pottles", "1 gallon"],
+             ["2 gallons", "1 peck"],
+             ["2 pecks", "1 demibushel"],
+             ["2 demibushel", "1 firkin"],
+             ["2 firkins", "1 kilderkin"],
+             ["2 kilderkins", "1 barrel"]]]']],
+   [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 10 string to string],
+  [[parse-data-strings '{"key": "string", "value": "string", "min": 1, "max": 10}' \
+    '{"2 gills"="1 chopin",
+      "2 chopins"= "1 pint",
+      "2 pints"= "1 quart",
+      "2 quarts"= "1 pottle",
+      "2 pottles"= "1 gallon",
+      "2 gallons"= "1 peck",
+      "2 pecks"= "1 demibushel",
+      "2 demibushel"= "1 firkin",
+      "2 firkins"= "1 kilderkin",
+      "2 kilderkins"= "1 barrel"}']],
+   [[{"2 chopins"="1 pint", "2 demibushel"="1 firkin", "2 firkins"="1 kilderkin", "2 gallons"="1 peck", "2 gills"="1 chopin", "2 kilderkins"="1 barrel", "2 pecks"="1 demibushel", "2 pints"="1 quart", "2 pottles"="1 gallon", "2 quarts"="1 pottle"}]])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in JSON map],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \
+    '["map", [[1, true], [2, false], [1, false]]]']],
+  [ovsdb error: map contains duplicate key])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in string map],
+  [[parse-data-strings '{"key": "integer", "value": "boolean", "max": 5}' \
+    '1=true 2=false 1=false']],
+  [map contains duplicate key])
diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at
new file mode 100644 (file)
index 0000000..bbdcbb5
--- /dev/null
@@ -0,0 +1,729 @@
+AT_BANNER([OVSDB -- execution])
+
+m4_define([ORDINAL_SCHEMA],
+  [[{"name": "ordinals",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}}}}}]])
+
+m4_define([CONSTRAINT_SCHEMA],
+  [[{"name": "constraints",
+     "tables": {
+       "a": {
+         "columns": {
+           "a": {"type": "integer"},
+           "a2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+                            "min": 0, "max": "unlimited"}},
+           "a2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+                            "min": 0, "max": "unlimited"}}}},
+       "b": {
+         "columns": {
+           "b": {"type": "integer"},
+           "b2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+                            "min": 0, "max": "unlimited"}},
+           "b2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+                            "min": 0, "max": "unlimited"}}}},
+       "constrained": {
+         "columns": {
+           "positive": {"type": {"key": {"type": "integer",
+                                         "minInteger": 1}}}},
+         "maxRows": 1}}}]])
+
+m4_define([WEAK_SCHEMA],
+  [[{"name": "weak",
+     "tables": {
+       "a": {
+         "columns": {
+           "a": {"type": "integer"},
+           "a2a": {"type": {"key": {"type": "uuid",
+                                    "refTable": "a",
+                                    "refType": "weak"},
+                            "min": 0, "max": "unlimited"}},
+           "a2a1": {"type": {"key": {"type": "uuid",
+                                     "refTable": "a",
+                                     "refType": "weak"}}},
+           "a2b": {"type": {"key": {"type": "uuid",
+                                    "refTable": "b",
+                                    "refType": "weak"}}}}},
+       "b": {
+         "columns": {
+           "b": {"type": "integer"},
+           "b2a": {"type": {"key": {"type": "uuid",
+                                    "refTable": "a",
+                                    "refType": "weak"},
+                            "min": 0, "max": "unlimited"}}}}}}]])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Runs "test-ovsdb execute" with the given SCHEMA and each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings).
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution positive $5])
+   AT_CHECK([test-ovsdb execute '$2' m4_foreach([txn], [$3], [ 'txn'])],
+     [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$4])
+   AT_CLEANUP])
+
+m4_define([EXECUTION_EXAMPLES], [
+dnl At one point the "commit" code ignored new rows with all-default values,
+dnl so this checks for that problem.
+OVSDB_CHECK_EXECUTION([insert default row, query table],
+  [ORDINAL_SCHEMA], 
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {}}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table],
+  [ORDINAL_SCHEMA], 
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by value],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "one"]]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "second"]]]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "row": {"name": "nought"}}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, mutate rows],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "mutate",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "mutations": [["number", "+=", 2]]}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1},{"_uuid":["uuid","<0>"],"_version":["uuid","<3>"],"name":"zero","number":2}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete rows by value],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[["ordinals",
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "nought"]]}]]],
+   [[["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":0}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete all],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": []},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": false}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit durably],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": true}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert and update constraints],
+  [CONSTRAINT_SCHEMA],
+  [[[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {}}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {"positive": -1}}]]],
+   [[["constraints",
+      {"op": "update",
+       "table": "constrained",
+       "where": [],
+       "row": {"positive": -2}}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {"positive": 1}}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {"positive": 2}}]]]],
+  [[[{"details":"0 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"details":"-1 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"details":"-2 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]},{"details":"transaction causes \"constrained\" table to contain 2 rows, greater than the schema-defined limit of 1 row(s)","error":"constraint violation"}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- simple],
+  [CONSTRAINT_SCHEMA],
+  [[[["constraints",
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 1},
+       "uuid-name": "brow"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2b": ["set", [["named-uuid", "brow"]]]}},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 1,
+               "a2b": ["set", [["named-uuid", "brow"]]]}},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 2,
+               "a2b": ["set", [["named-uuid", "brow"]]]}}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 1]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 2]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]
+[{"count":1},{"details":"cannot delete b row <0> because of 3 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 2 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- mutual references],
+  [CONSTRAINT_SCHEMA],
+  [[[["constraints",
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2b": ["set", [["named-uuid", "row2"]]],
+               "a2a": ["set", [["named-uuid", "row1"]]]},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 1,
+               "b2b": ["set", [["named-uuid", "row2"]]],
+               "b2a": ["set", [["named-uuid", "row1"]]]},
+       "uuid-name": "row2"}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "a",
+       "row": {"a2b": ["set", [["uuid", "b516b960-5b19-4fc2-bb82-fe1cbd6d0241"]]]}}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]],
+   dnl Try the deletions again to make sure that the refcounts got rolled back.
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]],
+   [[["constraints",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]},
+      {"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+[{"uuid":["uuid","<2>"]},{"details":"reference to nonexistent row <3>","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete a row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete b row <1> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete a row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete b row <1> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"count":1}]
+]])
+
+OVSDB_CHECK_EXECUTION([weak references],
+  [WEAK_SCHEMA],
+  [[[["weak",
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2a": ["set", [["named-uuid", "row1"],
+                               ["named-uuid", "row2"],
+                               ["uuid", "0e767b36-6822-4044-8307-d58467e04669"]]],
+               "a2a1": ["named-uuid", "row1"],
+               "a2b": ["named-uuid", "row3"]},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 1,
+               "a2a": ["set", [["named-uuid", "row1"],
+                               ["named-uuid", "row2"]]],
+               "a2a1": ["named-uuid", "row2"],
+               "a2b": ["named-uuid", "row3"]},
+       "uuid-name": "row2"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 2,
+               "a2a": ["set", [["named-uuid", "row1"],
+                               ["named-uuid", "row2"]]],
+               "a2a1": ["named-uuid", "row2"],
+               "a2b": ["named-uuid", "row4"]}},
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 2,
+               "b2a": ["named-uuid", "row1"]},
+       "uuid-name": "row3"},
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 3,
+               "b2a": ["named-uuid", "row2"]},
+       "uuid-name": "row4"}]]],
+   dnl Check that the nonexistent row UUID we added to row a0 was deleted,
+   dnl and that other rows were inserted as requested.
+   [[["weak",
+      {"op": "select",
+       "table": "a",
+       "where": [],
+       "columns": ["_uuid", "a2a", "a2a1", "a2b"],
+       "sort": ["a"]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "b",
+       "where": [],
+       "columns": ["_uuid", "b", "b2a"],
+       "sort": ["b"]}]]],
+   dnl Try to insert invalid all-zeros weak reference (the default) into
+   dnl "a2b", which requires exactly one value.
+   [[["weak",
+      {"op": "insert",
+       "table": "a",
+       "row": {}}]]],
+   dnl Try to delete row from "b" that is referred to by weak references
+   dnl from "a" table "a2b" column that requires exactly one value.
+   [[["weak",
+      {"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 3]]}]]],
+   dnl Try to delete row from "a" that is referred to by weak references
+   dnl from "a" table "a2a1" column that requires exactly one value.
+   [[["weak",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 1]]}]]],
+   dnl Delete the row that had the reference that caused the previous
+   dnl deletion to fail, then check that other rows are unchanged.
+   [[["weak",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 2]]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "a",
+       "where": [],
+       "columns": ["_uuid", "a2a", "a2a1", "a2b"],
+       "sort": ["a"]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "b",
+       "where": [],
+       "columns": ["_uuid", "b", "b2a"],
+       "sort": ["b"]}]]],
+   dnl Delete row a0 then check that references to it were removed.
+   [[["weak",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "a",
+       "where": [],
+       "columns": ["_uuid", "a2a", "a2a1", "a2b"],
+       "sort": ["a"]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "b",
+       "where": [],
+       "columns": ["_uuid", "b", "b2a"],
+       "sort": ["b"]}]]],
+   dnl Delete row a1 then check that references to it were removed.
+   [[["weak",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 1]]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "a",
+       "where": [],
+       "columns": ["_uuid", "a2a", "a2a1", "a2b"],
+       "sort": ["a"]}]]],
+   [[["weak",
+      {"op": "select",
+       "table": "b",
+       "where": [],
+       "columns": ["_uuid", "b", "b2a"],
+       "sort": ["b"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]},{"uuid":["uuid","<4>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<0>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<1>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<2>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<4>"]}]}]
+[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["uuid","<0>"]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}]
+[{"uuid":["uuid","<5>"]},{"details":"Weak reference column \"a2b\" in \"a\" row <5> (inserted within this transaction) contained all-zeros UUID (probably as the default value for this column) but deleting this value caused a constraint volation because this column is not allowed to be empty.","error":"constraint violation"}]
+[{"count":1},{"details":"Deletion of 1 weak reference(s) to deleted (or never-existing) rows from column \"a2b\" in \"a\" row <2> caused this column to become empty, but constraints on this column disallow an empty column.","error":"constraint violation"}]
+[{"count":1},{"details":"Deletion of 1 weak reference(s) to deleted (or never-existing) rows from column \"a2a1\" in \"a\" row <2> caused this column to become empty, but constraints on this column disallow an empty column.","error":"constraint violation"}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<0>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<0>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<1>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]}]}]
+[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["uuid","<0>"]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"a2a":["uuid","<1>"],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]}]}]
+[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["set",[]]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}]
+[{"count":1}]
+[{"rows":[]}]
+[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["set",[]]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["set",[]]}]}]
+]])])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
new file mode 100644 (file)
index 0000000..9b90b03
--- /dev/null
@@ -0,0 +1,347 @@
+AT_BANNER([OVSDB -- interface description language (IDL)])
+
+# OVSDB_CHECK_IDL(TITLE, [PRE-IDL-TXN], TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with a schema derived from idltest.ovsidl, runs
+# each PRE-IDL-TXN (if any), starts an ovsdb-server on that database,
+# and runs "test-ovsdb idl" passing each of the TRANSACTIONS along.
+#
+# Checks that the overall output is OUTPUT.  Before comparison, the
+# output is sorted (using "sort") and UUIDs in the output are replaced
+# by markers of the form <N> where N is a number.  The first unique
+# UUID is replaced by <0>, the next by <1>, and so on.  If a given
+# UUID appears more than once it is always replaced by the same
+# marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_IDL], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server idl positive $5])
+   AT_CHECK([ovsdb-tool create db $abs_srcdir/idltest.ovsschema],
+                  [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server '-vPATTERN:console:ovsdb-server|%c|%m' --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_if([$2], [], [],
+     [AT_CHECK([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore], [kill `cat pid`])])
+   AT_CHECK([test-ovsdb '-vPATTERN:console:test-ovsdb|%c|%m' -vjsonrpc -t10 idl unix:socket $3], 
+            [0], [stdout], [ignore], [kill `cat pid`])
+   AT_CHECK([sort stdout | perl $srcdir/uuidfilt.pl], [0], [$4], [],
+            [kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, no ops],
+  [],
+  [],
+  [000: empty
+001: done
+])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, various ops],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"r": 123.5}}]' \
+    '["idltest",
+      {"op": "insert",
+       "table": "simple",
+       "row": {"i": -1,
+               "r": 125,
+               "b": false,
+               "s": "",
+               "ia": ["set", [1]],
+               "ra": ["set", [1.5]],
+               "ba": ["set", [false]],
+               "sa": ["set", []], 
+               "ua": ["set", []]}}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "simple",
+       "where": [["i", "<", 1]],
+       "row": {"s": "newstring"}}]' \
+    '["idltest",
+      {"op": "delete",
+       "table": "simple",
+       "where": [["i", "==", 0]]}]' \
+    'reconnect']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 r=0 b=false s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+003: {"error":null,"result":[{"count":2}]}
+004: i=0 r=0 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+005: {"error":null,"result":[{"count":2}]}
+006: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+006: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<6>"]}]}
+008: i=-1 r=125 b=false s= u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+008: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+008: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+009: {"error":null,"result":[{"count":2}]}
+010: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+010: i=0 r=123.5 b=true s=newstring u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+010: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+011: {"error":null,"result":[{"count":1}]}
+012: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+012: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+013: reconnect
+014: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+014: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+015: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, initially populated],
+  [['["idltest",
+      {"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['["idltest",
+      {"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: {"error":null,"result":[{"count":2}]}
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, writing via IDL],
+  [['["idltest",
+      {"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['set 0 b 1, set 1 r 3.5' \
+    'insert 2, delete 1']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: commit, status=success
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=3.5 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: commit, status=success
+004: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<6>
+005: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, increment operation],
+  [['["idltest",
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['set 0 r 2.0, increment simple i']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+001: commit, status=success, increment=1
+002: i=1 r=2 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+003: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, consistent ops],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "k": ["named-uuid", "self"]},
+       "uuid-name": "self"}]' \
+    '["idltest",
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "k": ["named-uuid", "row2"]},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "k": ["named-uuid", "row1"]},
+       "uuid-name": "row2"}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "link1",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k=0 ka=[] l2= uuid=<0>
+003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
+004: i=0 k=0 ka=[] l2= uuid=<0>
+004: i=1 k=2 ka=[] l2= uuid=<1>
+004: i=2 k=1 ka=[] l2= uuid=<2>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k=0 ka=[] l2= uuid=<0>
+006: i=1 k=1 ka=[] l2= uuid=<1>
+006: i=2 k=1 ka=[] l2= uuid=<2>
+007: {"error":null,"result":[{"count":3}]}
+008: i=0 k=0 ka=[] l2= uuid=<0>
+008: i=1 k=0 ka=[] l2= uuid=<1>
+008: i=2 k=0 ka=[] l2= uuid=<2>
+009: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
+    '+["idltest",
+      {"op": "insert",
+       "table": "link1",
+       "uuid-name": "one",
+       "row": {"i": 1, "k": ["named-uuid", "one"]}},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "k": ["named-uuid", "one"]}}]' \
+     '["idltest",
+      {"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
+     '+["idltest",
+      {"op": "delete",
+       "table": "link1",
+       "where": [["_uuid", "==", ["uuid", "#1#"]]]}]' \
+     '+["idltest",
+      {"op": "delete",
+       "table": "link1",
+       "where": [["_uuid", "==", ["uuid", "#2#"]]]}]' \
+     '["idltest",
+      {"op": "delete",
+       "table": "link1",
+       "where": []}]' \
+]],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"details":"reference to nonexistent row <1>","error":"referential integrity violation"}]}
+002: {"error":null,"result":[{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+003: i=1 k=1 ka=[] l2= uuid=<2>
+003: i=2 k=1 ka=[] l2= uuid=<3>
+004: {"error":null,"result":[{"count":2},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+005: {"error":null,"result":[{"count":1},{"details":"cannot delete link1 row <2> because of 1 remaining reference(s)","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":1}]}
+007: i=1 k=1 ka=[] l2= uuid=<2>
+008: {"error":null,"result":[{"count":1}]}
+009: empty
+010: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, sets],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i0"]]]},
+       "uuid-name": "i0"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i1"]]]},
+       "uuid-name": "i1"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i2"]]]},
+       "uuid-name": "i2"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 3, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i3"]]]},
+       "uuid-name": "i3"}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "#1#"], ["uuid", "#2#"], ["uuid", "#3#"]]]}}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]' \
+    '+["idltest",
+      {"op": "delete",
+       "table": "link1",
+       "where": []}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+002: i=0 k=0 ka=[0] l2= uuid=<0>
+002: i=1 k=0 ka=[1] l2= uuid=<1>
+002: i=2 k=0 ka=[2] l2= uuid=<2>
+002: i=3 k=0 ka=[3] l2= uuid=<3>
+003: {"error":null,"result":[{"count":4}]}
+004: i=0 k=0 ka=[0 1 2 3] l2= uuid=<0>
+004: i=1 k=0 ka=[0 1 2 3] l2= uuid=<1>
+004: i=2 k=0 ka=[0 1 2 3] l2= uuid=<2>
+004: i=3 k=0 ka=[0 1 2 3] l2= uuid=<3>
+005: {"error":null,"result":[{"count":4},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":4}]}
+007: empty
+008: done
+]])
+
+OVSDB_CHECK_IDL([external-linking idl, consistent ops],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "link2",
+       "row": {"i": 0},
+       "uuid-name": "row0"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "k": ["named-uuid", "row1"], "l2": ["set", [["named-uuid", "row0"]]]},
+       "uuid-name": "row1"}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 l1= uuid=<0>
+002: i=1 k=1 ka=[] l2=0 uuid=<1>
+003: done
+]])
diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at
new file mode 100644 (file)
index 0000000..507ef8e
--- /dev/null
@@ -0,0 +1,282 @@
+AT_BANNER([OVSDB -- logging])
+
+AT_SETUP([create empty, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([log])
+AT_CHECK(
+  [test-ovsdb log-io file create], [0], 
+  [file: open successful
+], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read], [0], 
+  [file: open successful
+file: read: end of log
+], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([check that create fails if file exists])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[1]']], [0], 
+  [[file: open successful
+file: write:[1] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read], [0], 
+  [[file: open successful
+file: read: [1]
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb -vlockfile:console:emer log-io file create read], [1], 
+  [], [test-ovsdb: I/O error: create: file failed (File exists)
+])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread, append])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read read read 'write:["append"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: write:["append"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["append"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, reread one, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read 'write:["more data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: write:["more data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: ["more data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read read read read 'write:[3]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+file: write:[3] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: [3]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, corrupt some data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '\[3]' file]], [0], [1
+])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442
+file: write:["longer data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, truncate file, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '^2$' file]], [0], [1
+])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file)
+file: write:["longer data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write bad JSON, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file create 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
+AT_CHECK(
+  [[test-ovsdb log-io file read/write read read read read 'write:["replacement data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (line 1, column 0, byte 5: syntax error at beginning of input)
+file: write:["replacement data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file read-only read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["replacement data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
diff --git a/tests/ovsdb-macros.at b/tests/ovsdb-macros.at
new file mode 100644 (file)
index 0000000..c1aa619
--- /dev/null
@@ -0,0 +1,14 @@
+dnl OVSDB_INIT([$1])
+dnl
+dnl Creates an empty database named $1.
+m4_define([OVSDB_INIT],
+  [AT_CHECK(
+     [ovsdb-tool create $1 $abs_top_srcdir/vswitchd/vswitch.ovsschema],
+     [0], [stdout], [ignore])
+   AT_CHECK(
+     [[ovsdb-tool transact $1 \
+        '["Open_vSwitch",
+          {"op": "insert",
+           "table": "Open_vSwitch",
+           "row": {}}]']],
+     [0], [ignore], [ignore])])
diff --git a/tests/ovsdb-monitor-sort.pl b/tests/ovsdb-monitor-sort.pl
new file mode 100755 (executable)
index 0000000..12034f7
--- /dev/null
@@ -0,0 +1,49 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+# Breaks lines read from <STDIN> into groups using blank lines as
+# group separators, then sorts lines within the groups for
+# reproducibility.
+
+sub compare_lines {
+    my ($a, $b) = @_;
+
+    my $u = '[0-9a-fA-F]';
+    my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+    if ($a =~ /^$uuid_re/) {
+        if ($b =~ /^$uuid_re/) {
+            return substr($a, 36) cmp substr($b, 36);
+        } else {
+            return 1;
+        }
+    } elsif ($b =~ /^$uuid_re/) {
+        return -1;
+    } else {
+        return $a cmp $b;
+    }
+}
+
+sub output_group {
+    my (@group) = @_;
+    print "$_\n" foreach sort { compare_lines($a, $b) } @group;
+}
+
+my @group = ();
+while (<STDIN>) {
+    chomp;
+    if ($_ eq '') {
+        output_group(@group);
+        @group = ();
+        print "\n";
+    } else {
+        if (/^,/ && @group) {
+            $group[$#group] .= "\n" . $_;
+        } else {
+            push(@group, $_);
+        }
+    }
+}
+
+output_group(@group) if @group;
diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at
new file mode 100644 (file)
index 0000000..0f29a05
--- /dev/null
@@ -0,0 +1,216 @@
+AT_BANNER([OVSDB -- ovsdb-server monitors])
+
+# OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], DB, TABLE,
+#                     TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_MONITOR], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server monitor positive $8])
+   AT_DATA([schema], [$2
+])
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3],
+     [AT_CHECK([ovsdb-tool transact db 'txn'], [0], [ignore], [ignore])])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   AT_CHECK([ovsdb-client --detach --pidfile=$PWD/client-pid -d json monitor --format=csv unix:socket $4 $5 > output], 
+            [0], [ignore], [ignore], [kill `cat server-pid`])
+   m4_foreach([txn], [$6],
+     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0],
+                     [ignore], [ignore], [kill `cat server-pid client-pid`])])
+   AT_CHECK([ovsdb-client transact unix:socket '[["$4"]]'], [0],
+            [ignore], [ignore], [kill `cat server-pid client-pid`])
+   AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore])
+   OVS_WAIT_UNTIL([test ! -e server-pid && test ! -e client-pid])
+   AT_CHECK([perl $srcdir/ovsdb-monitor-sort.pl < output | perl $srcdir/uuidfilt.pl], [0], [$7], [ignore])
+   AT_CLEANUP])
+
+OVSDB_CHECK_MONITOR([monitor insert into empty table],
+  [ORDINAL_SCHEMA],
+  [],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,insert,"""zero""",0,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert into populated table],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<2>,insert,"""zero""",0,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor delete],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 10]]}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<0>,delete,"""ten""",10,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor row update],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"name": "five plus five"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<0>,old,"""ten""",,"[""uuid"",""<1>""]"
+,new,"""five plus five""",10,"[""uuid"",""<2>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor no-op row updates],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"number": 10, "name": "ten"}}]]],
+   [[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<2>,insert,"""nine""",9,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert-and-update transaction],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<2>,insert,"""three squared""",9,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert-update-and-delete transaction],
+  [ORDINAL_SCHEMA],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals], [ordinals],
+  [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 7, "name": "seven"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+
+row,action,name,number,_version
+<2>,insert,"""seven""",7,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor weak reference change],
+  [WEAK_SCHEMA],
+  [[[["weak",
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2a1": ["named-uuid", "a0"],
+               "a2b": ["named-uuid", "b2"]},
+       "uuid-name": "a0"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 1,
+               "a2a": ["named-uuid", "a0"],
+               "a2a1": ["named-uuid", "a1"],
+               "a2b": ["named-uuid", "b2"]},
+       "uuid-name": "a1"},
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 2},
+       "uuid-name": "b2"}]]]],
+  [weak], [a],
+  [[[["weak",
+      {"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]]],
+  [[row,action,a,a2a,a2b,a2a1,_version
+<0>,initial,0,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<0>""]","[""uuid"",""<2>""]"
+<3>,initial,1,"[""uuid"",""<0>""]","[""uuid"",""<1>""]","[""uuid"",""<3>""]","[""uuid"",""<4>""]"
+
+row,action,a,a2a,a2b,a2a1,_version
+<0>,delete,0,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<0>""]","[""uuid"",""<2>""]"
+<3>,old,,"[""uuid"",""<0>""]",,,
+,new,1,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<3>""]","[""uuid"",""<5>""]"
+]])
+
diff --git a/tests/ovsdb-mutation.at b/tests/ovsdb-mutation.at
new file mode 100644 (file)
index 0000000..4308dd3
--- /dev/null
@@ -0,0 +1,776 @@
+AT_BANNER([OVSDB -- mutations])
+
+OVSDB_CHECK_POSITIVE([null mutation],
+  [[parse-mutations \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([mutations on scalars],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "+=", 0]]' \
+    '[["i", "-=", 1]]' \
+    '[["i", "*=", 2]]' \
+    '[["i", "/=", 3]]' \
+    '[["i", "%=", 4]]' \
+    '[["r", "+=", 0.5]]' \
+    '[["r", "-=", 1.5]]' \
+    '[["r", "*=", 2.5]]' \
+    '[["r", "/=", 3.5]]']],
+  [[[["i","+=",0]]
+[["i","-=",1]]
+[["i","*=",2]]
+[["i","/=",3]]
+[["i","%=",4]]
+[["r","+=",0.5]]
+[["r","-=",1.5]]
+[["r","*=",2.5]]
+[["r","/=",3.5]]]],
+  [mutation])
+
+AT_SETUP([disallowed mutations on scalars])
+AT_KEYWORDS([ovsdb negative mutation])
+AT_CHECK([[test-ovsdb parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "xxx", 1]]' \
+    '[["i", "insert", 1]]' \
+    '[["i", "delete", 2]]' \
+    '[["r", "%=", 0.5]]' \
+    '[["r", "insert", 1.5]]' \
+    '[["r", "delete", 2.5]]' \
+    '[["b", "+=", true]]' \
+    '[["b", "-=", false]]' \
+    '[["b", "*=", true]]' \
+    '[["b", "/=", false]]' \
+    '[["b", "%=", true]]' \
+    '[["b", "insert", false]]' \
+    '[["b", "delete", true]]' \
+    '[["s", "+=", "a"]]' \
+    '[["s", "-=", "b"]]' \
+    '[["s", "*=", "c"]]' \
+    '[["s", "/=", "d"]]' \
+    '[["s", "%=", "e"]]' \
+    '[["s", "insert", "f"]]' \
+    '[["s", "delete", "g"]]' \
+    '[["u", "+=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "-=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "*=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "/=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "insert", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "delete", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]']],
+  [1], [],
+  [[test-ovsdb: unknown mutator: No mutator named xxx.
+test-ovsdb: syntax "["i","insert",1]": syntax error: Type mismatch: "insert" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["i","delete",2]": syntax error: Type mismatch: "delete" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["r","%=",0.5]": syntax error: Type mismatch: "%=" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","insert",1.5]": syntax error: Type mismatch: "insert" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","delete",2.5]": syntax error: Type mismatch: "delete" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["b","+=",true]": syntax error: Type mismatch: "+=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","-=",false]": syntax error: Type mismatch: "-=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","*=",true]": syntax error: Type mismatch: "*=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","/=",false]": syntax error: Type mismatch: "/=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","%=",true]": syntax error: Type mismatch: "%=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","insert",false]": syntax error: Type mismatch: "insert" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","delete",true]": syntax error: Type mismatch: "delete" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s","+=","a"]": syntax error: Type mismatch: "+=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","-=","b"]": syntax error: Type mismatch: "-=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","*=","c"]": syntax error: Type mismatch: "*=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","/=","d"]": syntax error: Type mismatch: "/=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","%=","e"]": syntax error: Type mismatch: "%=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","insert","f"]": syntax error: Type mismatch: "insert" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","delete","g"]": syntax error: Type mismatch: "delete" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u","+=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "+=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","-=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "-=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","*=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "*=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","/=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "/=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","insert",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "insert" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","delete",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "delete" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([mutations on sets],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "+=", 1]]' \
+    '[["i", "-=", 2]]' \
+    '[["i", "*=", 3]]' \
+    '[["i", "/=", 4]]' \
+    '[["i", "%=", 5]]' \
+    '[["i", "insert", ["set", [1, 2]]]]' \
+    '[["i", "delete", ["set", [1, 2, 3]]]]' \
+    '[["r", "+=", 1]]' \
+    '[["r", "-=", 2]]' \
+    '[["r", "*=", 3]]' \
+    '[["r", "/=", 4]]' \
+    '[["r", "insert", ["set", [1, 2]]]]' \
+    '[["r", "delete", ["set", [1, 2, 3]]]]' \
+    '[["b", "insert", ["set", [true]]]]' \
+    '[["b", "delete", ["set", [false]]]]' \
+    '[["s", "insert", ["set", ["a"]]]]' \
+    '[["s", "delete", ["set", ["a", "b"]]]]' \
+    '[["u", "insert", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "delete", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+]],
+  [[[["i","+=",1]]
+[["i","-=",2]]
+[["i","*=",3]]
+[["i","/=",4]]
+[["i","%=",5]]
+[["i","insert",["set",[1,2]]]]
+[["i","delete",["set",[1,2,3]]]]
+[["r","+=",1]]
+[["r","-=",2]]
+[["r","*=",3]]
+[["r","/=",4]]
+[["r","insert",["set",[1,2]]]]
+[["r","delete",["set",[1,2,3]]]]
+[["b","insert",true]]
+[["b","delete",false]]
+[["s","insert","a"]]
+[["s","delete",["set",["a","b"]]]]
+[["u","insert",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","delete",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [mutation])
+
+OVSDB_CHECK_POSITIVE([executing null mutation],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: no change
+row 1: no change
+row 2: no change
+])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integers],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: {"i":1}
+row 1: {"i":2}
+row 2: {"i":3}
+
+mutation  1:
+row 0: {"i":-2}
+row 1: {"i":-1}
+row 2: {"i":0}
+
+mutation  2:
+row 0: no change
+row 1: {"i":3}
+row 2: {"i":6}
+
+mutation  3:
+row 0: no change
+row 1: {"i":0}
+row 2: {"i":0}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([integer overflow detection],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 9223372036854775807]],
+      [["i", "+=", -9223372036854775808]],
+      [["i", "-=", -9223372036854775808]],
+      [["i", "-=", 9223372036854775807]],
+      [["i", "*=", 3037000500]],
+      [["i", "/=", -1]],
+      [["i", "/=", 0]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": -1},
+      {"i": 9223372036854775807},
+      {"i": -9223372036854775808},
+      {"i": 3037000500},
+      {"i": -3037000500}']]],
+  [mutation  0:
+row 0: {"i":9223372036854775807}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"i":9223372036854775806}
+row 3: range error: Result of "+=" operation is out of range.
+row 4: {"i":-1}
+row 5: range error: Result of "+=" operation is out of range.
+row 6: {"i":9223372033817775307}
+
+mutation  1:
+row 0: {"i":-9223372036854775808}
+row 1: {"i":-9223372036854775807}
+row 2: range error: Result of "+=" operation is out of range.
+row 3: {"i":-1}
+row 4: range error: Result of "+=" operation is out of range.
+row 5: {"i":-9223372033817775308}
+row 6: range error: Result of "+=" operation is out of range.
+
+mutation  2:
+row 0: range error: Result of "-=" operation is out of range.
+row 1: range error: Result of "-=" operation is out of range.
+row 2: {"i":9223372036854775807}
+row 3: range error: Result of "-=" operation is out of range.
+row 4: {"i":0}
+row 5: range error: Result of "-=" operation is out of range.
+row 6: {"i":9223372033817775308}
+
+mutation  3:
+row 0: {"i":-9223372036854775807}
+row 1: {"i":-9223372036854775806}
+row 2: {"i":-9223372036854775808}
+row 3: {"i":0}
+row 4: range error: Result of "-=" operation is out of range.
+row 5: {"i":-9223372033817775307}
+row 6: range error: Result of "-=" operation is out of range.
+
+mutation  4:
+row 0: no change
+row 1: {"i":3037000500}
+row 2: {"i":-3037000500}
+row 3: range error: Result of "*=" operation is out of range.
+row 4: range error: Result of "*=" operation is out of range.
+row 5: range error: Result of "*=" operation is out of range.
+row 6: range error: Result of "*=" operation is out of range.
+
+mutation  5:
+row 0: no change
+row 1: {"i":-1}
+row 2: {"i":1}
+row 3: {"i":-9223372036854775807}
+row 4: range error: Result of "/=" operation is out of range.
+row 5: {"i":-3037000500}
+row 6: {"i":3037000500}
+
+mutation  6:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+row 3: domain error: Division by zero.
+row 4: domain error: Division by zero.
+row 5: domain error: Division by zero.
+row 6: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integers with constraints],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": {"type": "integer",
+                                         "minInteger": 0,
+                                         "maxInteger": 2}}}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: {"i":1}
+row 1: {"i":2}
+row 2: constraint violation: 3 is not in the valid range 0 to 2 (inclusive)
+
+mutation  1:
+row 0: constraint violation: -2 is not in the valid range 0 to 2 (inclusive)
+row 1: constraint violation: -1 is not in the valid range 0 to 2 (inclusive)
+row 2: {"i":0}
+
+mutation  2:
+row 0: no change
+row 1: constraint violation: 3 is not in the valid range 0 to 2 (inclusive)
+row 2: constraint violation: 6 is not in the valid range 0 to 2 (inclusive)
+
+mutation  3:
+row 0: no change
+row 1: {"i":0}
+row 2: {"i":0}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on reals],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]]]' \
+    '[{"r": 0},
+      {"r": -2.5},
+      {"r": 1.25}']]],
+  [mutation  0:
+row 0: {"r":0.5}
+row 1: {"r":-2}
+row 2: {"r":1.75}
+
+mutation  1:
+row 0: {"r":-1.5}
+row 1: {"r":-4}
+row 2: {"r":-0.25}
+
+mutation  2:
+row 0: no change
+row 1: {"r":-6.25}
+row 2: {"r":3.125}
+
+mutation  3:
+row 0: no change
+row 1: {"r":-0.625}
+row 2: {"r":0.3125}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([real overflow detection],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 1.7976931348623157e+308]],
+      [["r", "-=", 1.7976931348623157e+308]],
+      [["r", "*=", 2]],
+      [["r", "/=", 4]],
+      [["r", "/=", 0.5]],
+      [["r", "/=", 0]]]' \
+    '[{"r": 0},
+      {"r": 1.7976931348623157e+308},
+      {"r": -1.7976931348623157e+308}']]],
+  [mutation  0:
+row 0: {"r":1.79769313486232e+308}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"r":0}
+
+mutation  1:
+row 0: {"r":-1.79769313486232e+308}
+row 1: {"r":0}
+row 2: range error: Result of "-=" operation is out of range.
+
+mutation  2:
+row 0: no change
+row 1: range error: Result of "*=" operation is out of range.
+row 2: range error: Result of "*=" operation is out of range.
+
+mutation  3:
+row 0: no change
+row 1: {"r":4.49423283715579e+307}
+row 2: {"r":-4.49423283715579e+307}
+
+mutation  4:
+row 0: no change
+row 1: range error: Result of "/=" operation is out of range.
+row 2: range error: Result of "/=" operation is out of range.
+
+mutation  5:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on reals with constraints],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": {"key": {"type": "real",
+                                         "minReal": -2.5,
+                                         "maxReal": 1.75}}}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]]]' \
+    '[{"r": 0},
+      {"r": -2.5},
+      {"r": 1.25}']]],
+  [mutation  0:
+row 0: {"r":0.5}
+row 1: {"r":-2}
+row 2: {"r":1.75}
+
+mutation  1:
+row 0: {"r":-1.5}
+row 1: constraint violation: -4 is not in the valid range -2.5 to 1.75 (inclusive)
+row 2: {"r":-0.25}
+
+mutation  2:
+row 0: no change
+row 1: constraint violation: -6.25 is not in the valid range -2.5 to 1.75 (inclusive)
+row 2: constraint violation: 3.125 is not in the valid range -2.5 to 1.75 (inclusive)
+
+mutation  3:
+row 0: no change
+row 1: {"r":-0.625}
+row 2: {"r":0.3125}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer sets],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": {"type": "integer", 
+                                         "maxInteger": 5},
+                                 "min": 0, 
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]],
+      [["i", "insert", ["set", [1]]]],
+      [["i", "insert", ["set", [2, 3]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [0, 1, 2]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"i":1}
+row 2: {"i":["set",[1,2]]}
+row 3: {"i":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"i":-2}
+row 2: {"i":["set",[-2,-1]]}
+row 3: {"i":["set",[-2,-1,0]]}
+
+mutation  2:
+row 0: no change
+row 1: no change
+row 2: {"i":["set",[0,3]]}
+row 3: constraint violation: 6 is greater than maximum allowed value 5
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: constraint violation: Result of "/=" operation contains duplicates.
+row 3: constraint violation: Result of "/=" operation contains duplicates.
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: constraint violation: Result of "%=" operation contains duplicates.
+
+mutation  5:
+row 0: {"i":1}
+row 1: {"i":["set",[0,1]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"i":["set",[2,3]]}
+row 1: {"i":["set",[0,2,3]]}
+row 2: {"i":["set",[0,1,2,3]]}
+row 3: {"i":["set",[0,1,2,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+row 3: {"i":["set",[0,2]]}
+
+mutation  8:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"i":["set",[0,1]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on real sets],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": {"key": {"type": "real",
+                                         "maxReal": 6},
+                                 "min": 0, "max": "unlimited"}}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]],
+      [["r", "*=", 0]],
+      [["r", "insert", 1.5]],
+      [["r", "insert", 3]],
+      [["r", "delete", ["set", [1.5, 3.5]]]],
+      [["r", "delete", ["set", [0.5, 1.5, 2.5]]]]]' \
+    '[{"r": ["set", []]},
+      {"r": 0.5},
+      {"r": ["set", [0.5, 1.5]]},
+      {"r": ["set", [0.5, 1.5, 2.5]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"r":1}
+row 2: {"r":["set",[1,2]]}
+row 3: {"r":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"r":-1}
+row 2: {"r":["set",[-1,0]]}
+row 3: {"r":["set",[-1,0,1]]}
+
+mutation  2:
+row 0: no change
+row 1: {"r":1.25}
+row 2: {"r":["set",[1.25,3.75]]}
+row 3: constraint violation: 6.25 is greater than maximum allowed value 6
+
+mutation  3:
+row 0: no change
+row 1: {"r":0.125}
+row 2: {"r":["set",[0.125,0.375]]}
+row 3: {"r":["set",[0.125,0.375,0.625]]}
+
+mutation  4:
+row 0: no change
+row 1: {"r":0}
+row 2: constraint violation: Result of "*=" operation contains duplicates.
+row 3: constraint violation: Result of "*=" operation contains duplicates.
+
+mutation  5:
+row 0: {"r":1.5}
+row 1: {"r":["set",[0.5,1.5]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"r":3}
+row 1: {"r":["set",[0.5,3]]}
+row 2: {"r":["set",[0.5,1.5,3]]}
+row 3: {"r":["set",[0.5,1.5,2.5,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"r":0.5}
+row 3: {"r":["set",[0.5,2.5]]}
+
+mutation  8:
+row 0: no change
+row 1: {"r":["set",[]]}
+row 2: {"r":["set",[]]}
+row 3: {"r":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on boolean sets],
+  [[execute-mutations \
+    '{"columns": {"b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}}}' \
+    '[[["b", "insert", ["set", [false]]]],
+      [["b", "insert", ["set", [true]]]],
+      [["b", "insert", ["set", [false, true]]]],
+      [["b", "delete", ["set", [false]]]],
+      [["b", "delete", ["set", [true]]]],
+      [["b", "delete", ["set", [true, false]]]]]' \
+    '[{"b": ["set", []]},
+      {"b": ["set", [false]]},
+      {"b": ["set", [true]]},
+      {"b": ["set", [false, true]]}']]],
+  [[mutation  0:
+row 0: {"b":false}
+row 1: no change
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  1:
+row 0: {"b":true}
+row 1: {"b":["set",[false,true]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"b":["set",[false,true]]}
+row 1: {"b":["set",[false,true]]}
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: no change
+row 3: {"b":true}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"b":["set",[]]}
+row 3: {"b":false}
+
+mutation  5:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: {"b":["set",[]]}
+row 3: {"b":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on string sets],
+  [[execute-mutations \
+    '{"columns": {"s": {"type": {"key": "string", "min": 0, "max": "unlimited"}}}}' \
+    '[[["s", "insert", ["set", ["a"]]]],
+      [["s", "insert", ["set", ["b"]]]],
+      [["s", "insert", ["set", ["c", "d"]]]],
+      [["s", "delete", ["set", ["a"]]]],
+      [["s", "delete", ["set", ["b"]]]],
+      [["s", "delete", ["set", ["c", "d"]]]]]' \
+    '[{"s": ["set", []]},
+      {"s": ["set", ["a"]]},
+      {"s": ["set", ["a", "b"]]},
+      {"s": ["set", ["a", "b", "c", "d"]]}']]],
+  [[mutation  0:
+row 0: {"s":"a"}
+row 1: no change
+row 2: no change
+row 3: no change
+
+mutation  1:
+row 0: {"s":"b"}
+row 1: {"s":["set",["a","b"]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"s":["set",["c","d"]]}
+row 1: {"s":["set",["a","c","d"]]}
+row 2: {"s":["set",["a","b","c","d"]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"s":["set",[]]}
+row 2: {"s":"b"}
+row 3: {"s":["set",["b","c","d"]]}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"s":"a"}
+row 3: {"s":["set",["a","c","d"]]}
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"s":["set",["a","b"]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on uuid sets],
+  [[execute-mutations \
+    '{"columns": {"u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[[["u", "insert", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "insert", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "insert", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]],
+      [["u", "delete", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "delete", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "delete", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]]]' \
+    '[{"u": ["set", []]},
+      {"u": ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]},
+      {"u": ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]},
+      {"u": ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]}']]],
+  [[mutation  0:
+row 0: {"u":["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]}
+row 1: no change
+row 2: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+
+mutation  1:
+row 0: {"u":["uuid","a60fe7ff-317b-4568-9106-892b37445313"]}
+row 1: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: no change
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+
+mutation  2:
+row 0: {"u":["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"]}
+row 1: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"u":["set",[]]}
+row 2: no change
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"u":["set",[]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"u":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer maps],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": "integer", "value": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "insert", ["map", [[1, 2]]]]],
+      [["i", "insert", ["map", [[2, 4], [3, 5]]]]],
+      [["i", "delete", ["map", [[1, 2]]]]],
+      [["i", "delete", ["map", [[2, 3]]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[1, 2]]]},
+      {"i": ["map", [[1, 3], [2, 3]]]},
+      {"i": ["map", [[3, 5]]]}']]],
+  [[mutation  0:
+row 0: {"i":["map",[[1,2]]]}
+row 1: no change
+row 2: no change
+row 3: {"i":["map",[[1,2],[3,5]]]}
+
+mutation  1:
+row 0: {"i":["map",[[2,4],[3,5]]]}
+row 1: {"i":["map",[[1,2],[2,4],[3,5]]]}
+row 2: {"i":["map",[[1,3],[2,3],[3,5]]]}
+row 3: {"i":["map",[[2,4],[3,5]]]}
+
+mutation  2:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: no change
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: {"i":["map",[[2,3]]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: {"i":["map",[]]}
+]], [mutation])
diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at
new file mode 100644 (file)
index 0000000..2c2b648
--- /dev/null
@@ -0,0 +1,543 @@
+AT_BANNER([OVSDB -- queries])
+
+OVSDB_CHECK_POSITIVE([queries on scalars],
+  [[query \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']],
+  [dnl
+query  0: 11111
+query  1: 1----
+query  2: 1-111
+query  3: 11---
+query  4: 1111-
+query  5: ---11
+query  6: ----1
+query  7: ---1-
+query  8: 11-11
+query  9: 1----
+query 10: 1-111
+query 11: 11---
+query 12: 1111-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: 11111
+query 17: 1-1-1
+query 18: -1-1-
+query 19: -1-1-
+query 20: -1-1-
+query 21: 1----
+query 22: 1-111
+query 23: --1--
+query 24: 111-1
+query 25: 1----
+query 26: 1-111
+query 27: --1--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([queries on sets],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]' \
+    '[[],
+      [["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([queries on maps (1)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([queries on maps (2)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 11111 11111
+query  1: 1---- ----- -----
+query  2: -1--- ----- -----
+query  3: ---1- ----- -----
+query  4: ----- 1---- -----
+query  5: ----- --1-- -----
+query  6: ----- ----1 -----
+query  7: ----- ----- -1---
+query  8: ----- ----- ---1-
+query  9: -1111 11111 11111
+query 10: 1-111 11111 11111
+query 11: 111-1 11111 11111
+query 12: 11111 -1111 11111
+query 13: 11111 11-11 11111
+query 14: 11111 1111- 11111
+query 15: 11111 11111 1-111
+query 16: 11111 11111 111-1
+query 17: 11111 11111 11111
+query 18: -1--- 11--1 1--1-
+query 19: ---1- 1---- -1-11
+query 20: ----- 1---- ---1-
+query 21: ----- --1-1 -1111
+query 22: ----- ----1 ---1-
+query 23: ----- ----- -1-11
+query 24: ----- ----- ---1-
+query 25: 11111 11111 11111
+query 26: 1-111 --11- -11-1
+query 27: 111-1 -1111 1-1--
+query 28: 1-1-1 --11- --1--
+query 29: 11111 11-1- 1----
+query 30: 1-111 ---1- -----
+query 31: 111-1 -1-1- 1----
+query 32: 1-1-1 ---1- -----], [query])
+
+OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["_uuid"]']],
+  [dnl
+query  0: abcde
+query  1: a----
+query  2: a-cde
+query  3: ab---
+query  4: abcd-
+query  5: ---de
+query  6: ----e
+query  7: ---d-
+query  8: ab-de
+query  9: a----
+query 10: a-cde
+query 11: ab---
+query 12: abcd-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: abcde
+query 17: a-c-e
+query 18: -b-d-
+query 19: -b-d-
+query 20: -b-d-
+query 21: a----
+query 22: a-cde
+query 23: --c--
+query 24: abc-e
+query 25: a----
+query 26: a-cde
+query 27: --c--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["b"]']],
+  [dnl
+query  0: ababa
+query  1: a-a-a
+query  2: ababa
+query  3: ababa
+query  4: ababa
+query  5: ababa
+query  6: a-a-a
+query  7: -b-b-
+query  8: ababa
+query  9: a-a-a
+query 10: ababa
+query 11: ababa
+query 12: ababa
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: ababa
+query 17: a-a-a
+query 18: -b-b-
+query 19: -b-b-
+query 20: -b-b-
+query 21: a-a-a
+query 22: ababa
+query 23: a-a-a
+query 24: ababa
+query 25: a-a-a
+query 26: ababa
+query 27: a-a-a],
+  [query])
+
+OVSDB_CHECK_NEGATIVE([parse colunn set containing bad name],
+  [[query-distinct \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[{"i": 0}]' \
+    '[[]]' \
+    '["i", "bad"]']],
+  [bad is not a valid column name])
diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at
new file mode 100644 (file)
index 0000000..34a102c
--- /dev/null
@@ -0,0 +1,273 @@
+AT_BANNER([OVSDB -- rows])
+
+m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]])
+
+OVSDB_CHECK_POSITIVE([row with one string column],
+  [[parse-rows \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '{"name": "value"}' \
+    '{"name": ""}' \
+    '{"name": "longer string with spaces"}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"name":"value"}
+name
+{RESERVED_COLUMNS,"name":""}
+name
+{RESERVED_COLUMNS,"name":"longer string with spaces"}
+name
+{RESERVED_COLUMNS,"name":""}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with one integer column],
+  [[parse-rows \
+    '{"columns": {"count": {"type": "integer"}}}' \
+    '{"count": 1}' \
+    '{"count": -1}' \
+    '{"count": 2e10}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"count":1}
+count
+{RESERVED_COLUMNS,"count":-1}
+count
+{RESERVED_COLUMNS,"count":20000000000}
+count
+{RESERVED_COLUMNS,"count":0}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with one real column],
+  [[parse-rows \
+    '{"columns": {"cost": {"type": "real"}}}' \
+    '{"cost": 1.0}' \
+    '{"cost": -2.0}' \
+    '{"cost": 123000}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"cost":1}
+cost
+{RESERVED_COLUMNS,"cost":-2}
+cost
+{RESERVED_COLUMNS,"cost":123000}
+cost
+{RESERVED_COLUMNS,"cost":0}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with one boolean column],
+  [[parse-rows \
+    '{"columns": {"feasible": {"type": "boolean"}}}' \
+    '{"feasible": true}' \
+    '{"feasible": false}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"feasible":true}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with one uuid column],
+  [[parse-rows \
+    '{"columns": {"ref": {"type": "uuid"}}}' \
+    '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \
+    '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["myset":0]}
+<none>])
+
+OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row with several columns],
+  [[parse-rows \
+    '{"columns":
+        {"vswitch": {"type": "uuid"},
+         "name": {"type": "string"},
+         "datapath_id": {"type": {"key": "string", "min": 0}},
+         "hwaddr": {"type": "string"},
+         "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "controller": {"type": {"key": "uuid", "min": 0}},
+         "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"],
+      "name": "br0",
+      "datapath_id": "000ae4256bb0",
+      "hwaddr": "00:0a:e4:25:6b:b0"}' \
+    '{}']],
+ [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":"000ae4256bb0","hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]}
+datapath_id, hwaddr, name, vswitch
+{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [])
+
+OVSDB_CHECK_POSITIVE([row hashing (scalars)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+     '["null", {}]' \
+     '["i1", {"i": 1}]' \
+     '["i2", {"i": 2}]' \
+     '["i4", {"i": 4}]' \
+     '["i8", {"i": 8}]' \
+     '["i16", {"i": 16}]' \
+     '["i32", {"i": 32}]' \
+     '["i64", {"i": 64}]' \
+     '["i128", {"i": 128}]' \
+     '["i256", {"i": 256}]' \
+     '["null2", {"r": -0}]' \
+     '["r123", {"r": 123}]' \
+     '["r0.0625", {"r": 0.0625}]' \
+     '["r0.125", {"r": 0.125}]' \
+     '["r0.25", {"r": 0.25}]' \
+     '["r0.5", {"r": 0.5}]' \
+     '["r1", {"r": 1}]' \
+     '["r2", {"r": 2}]' \
+     '["r4", {"r": 4}]' \
+     '["r8", {"r": 8}]' \
+     '["r16", {"r": 16}]' \
+     '["r32", {"r": 32}]' \
+     '["null3", {"b": false}]' \
+     '["b1", {"b": true}]' \
+     '["null4", {"s": ""}]' \
+     '["s0", {"s": "a"}]' \
+     '["s1", {"s": "b"}]' \
+     '["s2", {"s": "c"}]' \
+     '["s3", {"s": "d"}]' \
+     '["s4", {"s": "e"}]' \
+     '["s5", {"s": "f"}]' \
+     '["s6", {"s": "g"}]' \
+     '["s7", {"s": "h"}]' \
+     '["s8", {"s": "i"}]' \
+     '["s9", {"s": "j"}]' \
+     '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \
+     '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \
+     '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \
+     '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \
+     '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \
+     '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \
+     '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \
+     '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \
+     '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \
+     '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']],
+  [[null == null2
+null == null3
+null == null4
+null == null5
+hash(null) == hash(null6)
+null2 == null3
+null2 == null4
+null2 == null5
+hash(null2) == hash(null6)
+null3 == null4
+null3 == null5
+hash(null3) == hash(null6)
+null4 == null5
+hash(null4) == hash(null6)
+hash(null5) == hash(null6)]])
+
+OVSDB_CHECK_POSITIVE([row hashing (sets)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '["null0", {"i": ["set", []]}]' \
+    '["i0", {"i": ["set", [0]]}]' \
+    '["i01", {"i": ["set", [0, 1]]}]' \
+    '["i012", {"i": ["set", [0, 1, 2]]}]' \
+    '["i021", {"i": ["set", [0, 2, 1]]}]' \
+    '["i201", {"i": ["set", [2, 0, 1]]}]' \
+    '["i102", {"i": ["set", [1, 0, 2]]}]' \
+    '["i120", {"i": ["set", [1, 2, 0]]}]' \
+    '["i210", {"i": ["set", [2, 1, 0]]}]' \
+    '["r0", {"r": ["set", [0]]}]' \
+    '["r01", {"r": ["set", [0, 1]]}]' \
+    '["r012", {"r": ["set", [0, 1, 2]]}]' \
+    '["r201", {"r": ["set", [2, 0, 1]]}]' \
+    '["null1", {"b": ["set", []]}]' \
+    '["b0", {"b": ["set", [false]]}]' \
+    '["b1", {"b": ["set", [true]]}]' \
+    '["b01", {"b": ["set", [false, true]]}]' \
+    '["b10", {"b": ["set", [true, false]]}]' \
+    '["null2", {"s": ["set", []]}]' \
+    '["sa", {"s": ["set", ["a"]]}]' \
+    '["sb", {"s": ["set", ["b"]]}]' \
+    '["sab", {"s": ["set", ["a", "b"]]}]' \
+    '["sba", {"s": ["set", ["b", "a"]]}]']],
+  [[null0 == null1
+null0 == null2
+i012 == i021
+i012 == i201
+i012 == i102
+i012 == i120
+i012 == i210
+i021 == i201
+i021 == i102
+i021 == i120
+i021 == i210
+i201 == i102
+i201 == i120
+i201 == i210
+i102 == i120
+i102 == i210
+i120 == i210
+r012 == r201
+null1 == null2
+b01 == b10
+sab == sba]])
+
+OVSDB_CHECK_POSITIVE([row hashing (maps)],
+  [[compare-rows \
+    '{"columns":
+        {"ii": {"type": {"key": "integer", "value": "integer", 
+                         "min": 0, "max": "unlimited"}},
+         "rr": {"type": {"key": "real", "value": "real",
+                         "min": 0, "max": "unlimited"}},
+         "bb": {"type": {"key": "boolean", "value": "boolean",
+                         "min": 0, "max": "unlimited"}},
+        "ss": {"type": {"key": "string", "value": "string",
+                         "min": 0, "max": "unlimited"}}}}' \
+    '["null", {}]' \
+    '["ii0", {"ii": ["map", [[0, 0]]]}]' \
+    '["ii1", {"ii": ["map", [[0, 1]]]}]' \
+    '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \
+    '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \
+    '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \
+    '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 0]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 1]]]}]' \
+    '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \
+    '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \
+    '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \
+    '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \
+    '["bb0", {"bb": ["map", [[false, false]]]}]' \
+    '["bb1", {"bb": ["map", [[false, true]]]}]' \
+    '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \
+    '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \
+    '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \
+    '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \
+    '["ss0", {"ss": ["map", [["a", "a"]]]}]' \
+    '["ss1", {"ss": ["map", [["a", "b"]]]}]' \
+    '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \
+    '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \
+    '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \
+    '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo
+]], [[]])
diff --git a/tests/ovsdb-schema.at b/tests/ovsdb-schema.at
new file mode 100644 (file)
index 0000000..6cd2fa2
--- /dev/null
@@ -0,0 +1,47 @@
+AT_BANNER([OVSDB -- schemas])
+
+OVSDB_CHECK_POSITIVE([schema with valid refTables],
+  [[parse-schema \
+      '{"name": "mydb",
+        "tables": {
+          "a": {
+            "columns": {
+              "map": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "b"},
+                  "value": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}},
+          "b": {
+            "columns": {
+              "aRef": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}}}}']],
+  [[{"name":"mydb","tables":{"a":{"columns":{"map":{"type":{"key":{"refTable":"b","type":"uuid"},"value":{"refTable":"a","type":"uuid"}}}}},"b":{"columns":{"aRef":{"type":{"key":{"refTable":"a","type":"uuid"}}}}}}}]])
+     
+OVSDB_CHECK_NEGATIVE([schema with invalid refTables],
+  [[parse-schema \
+      '{"name": "mydb",
+        "tables": {
+          "a": {
+            "columns": {
+              "map": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "c"},
+                  "value": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}},
+          "b": {
+            "columns": {
+              "aRef": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}}}}']],
+  [[test-ovsdb: syntax error: column map key refers to undefined table c]])
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
new file mode 100644 (file)
index 0000000..800506b
--- /dev/null
@@ -0,0 +1,337 @@
+AT_BANNER([OVSDB -- ovsdb-server transactions (Unix sockets)])
+
+m4_define([OVSDB_SERVER_SHUTDOWN], 
+  [cp pid savepid
+   AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore])
+   OVS_WAIT_WHILE([kill -0 `cat savepid`], [kill `cat savepid`])])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive unix $5])
+   AT_DATA([schema], [$2
+])
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0], [stdout], [ignore],
+     [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [ignore],
+            [test ! -e pid || kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
+\f
+AT_SETUP([database multiplexing implementation])
+AT_KEYWORDS([ovsdb server positive])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/unixctl --remote=punix:socket db], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-client list-dbs unix:socket]], 
+  [0], [ordinals
+], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK(
+  [[ovsdb-client get-schema unix:socket nonexistent]], 
+  [1], [], [[ovsdb-client: syntax "{"syntax":"[\"nonexistent\"]","details":"get_schema request specifies unknown database nonexistent","error":"unknown database"}": syntax error: Parsing database schema failed: Required 'name' member is missing.
+]], [test ! -e pid || kill `cat pid`])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([--remote=db: implementation])
+AT_KEYWORDS([ovsdb server positive])
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "Manager": {
+         "columns": {
+           "manager": {"type": "string"}}}}}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '["mydb",
+       {"op": "insert",
+        "table": "Manager",
+        "row": {"manager": "punix:socket"}}]']], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=db:Manager,manager --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-client transact unix:socket \
+     '["mydb",
+       {"op": "select",
+        "table": "Manager",
+        "where": [],
+        "columns": ["manager"]}]']], 
+  [0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK(
+  [perl $srcdir/uuidfilt.pl stdout], 
+  [0], 
+  [[[{"rows":[{"manager":"punix:socket"}]}]
+]], 
+  [ignore], 
+  [test ! -e pid || kill `cat pid`])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([SSL db: implementation])
+AT_KEYWORDS([ovsdb server positive ssl $5])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+AT_SKIP_IF([test "x$RANDOM" = x])
+SSL_PORT=`expr 32767 + \( $RANDOM % 32767 \)`
+PKIDIR=$abs_top_srcdir/tests
+AT_SKIP_IF([expr "$PKIDIR" : ".*[      '\"
+\r\\]"])
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "SSL": {
+         "columns": {
+           "private_key": {"type": "string"},
+           "certificate": {"type": "string"},
+           "ca_cert": {"type": "string"}}}}}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '["mydb",
+       {"op": "insert",
+        "table": "SSL",
+        "row": {"private_key": "'"$PKIDIR/testpki-privkey2.pem"'",
+                "certificate": "'"$PKIDIR/testpki-cert2.pem"'",
+                "ca_cert": "'"$PKIDIR/testpki-cacert.pem"'"}}]']],
+  [0], [ignore], [ignore])
+AT_CHECK(
+  [ovsdb-server --detach --pidfile=$PWD/pid \
+        --private-key=db:SSL,private_key \
+        --certificate=db:SSL,certificate \
+        --ca-cert=db:SSL,ca_cert \
+         --remote=pssl:$SSL_PORT:127.0.0.1 --unixctl=$PWD/unixctl db],
+  [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-client \
+        --private-key=$PKIDIR/testpki-privkey.pem \
+        --certificate=$PKIDIR/testpki-cert.pem \
+        --ca-cert=$PKIDIR/testpki-cacert.pem \
+        transact ssl:127.0.0.1:$SSL_PORT \
+        '["mydb",
+          {"op": "select",
+           "table": "SSL",
+           "where": [],
+           "columns": ["private_key"]}]']], 
+  [0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+AT_CHECK_UNQUOTED(
+  [perl $srcdir/uuidfilt.pl output], [0], 
+  [[[{"rows":[{"private_key":"$PKIDIR/testpki-privkey2.pem"}]}]
+]], [ignore], [test ! -e pid || kill `cat pid`])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
+AT_SETUP([compacting online])
+AT_KEYWORDS([ovsdb server compact])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/unixctl --remote=punix:socket --log-file=$PWD/ovsdb-server.log db], [0], [ignore], [ignore])
+AT_CAPTURE_FILE([ovsdb-server.log])
+dnl Do a bunch of random transactions that put crap in the database log.
+AT_CHECK(
+  [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do
+      set -- $pair
+      ovsdb-client transact unix:socket '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "'$1'", "number": '$2'}},
+         {"op": "comment",
+          "comment": "add row for '"$pair"'"}]'
+      ovsdb-client transact unix:socket '
+        ["ordinals",
+         {"op": "delete",
+          "table": "ordinals",
+          "where": [["number", "==", '$2']]},
+         {"op": "comment",
+          "comment": "delete row for '"$2"'"}]'
+      ovsdb-client transact unix:socket '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "'$1'", "number": '$2'}},
+         {"op": "comment",
+          "comment": "add back row for '"$pair"'"}]'
+    done]],
+  [0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
+dnl Check that all the crap is in fact in the database log.
+AT_CHECK([[perl $srcdir/uuidfilt.pl db | grep -v ^OVSDB | sed 's/"_date":[0-9]*/"_date":0/']], [0],
+  [[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}}}
+{"ordinals":{"<0>":{"name":"zero"}},"_comment":"add row for zero 0","_date":0}
+{"ordinals":{"<0>":null},"_comment":"delete row for 0","_date":0}
+{"ordinals":{"<1>":{"name":"zero"}},"_comment":"add back row for zero 0","_date":0}
+{"ordinals":{"<2>":{"number":1,"name":"one"}},"_comment":"add row for one 1","_date":0}
+{"ordinals":{"<2>":null},"_comment":"delete row for 1","_date":0}
+{"ordinals":{"<3>":{"number":1,"name":"one"}},"_comment":"add back row for one 1","_date":0}
+{"ordinals":{"<4>":{"number":2,"name":"two"}},"_comment":"add row for two 2","_date":0}
+{"ordinals":{"<4>":null},"_comment":"delete row for 2","_date":0}
+{"ordinals":{"<5>":{"number":2,"name":"two"}},"_comment":"add back row for two 2","_date":0}
+{"ordinals":{"<6>":{"number":3,"name":"three"}},"_comment":"add row for three 3","_date":0}
+{"ordinals":{"<6>":null},"_comment":"delete row for 3","_date":0}
+{"ordinals":{"<7>":{"number":3,"name":"three"}},"_comment":"add back row for three 3","_date":0}
+{"ordinals":{"<8>":{"number":4,"name":"four"}},"_comment":"add row for four 4","_date":0}
+{"ordinals":{"<8>":null},"_comment":"delete row for 4","_date":0}
+{"ordinals":{"<9>":{"number":4,"name":"four"}},"_comment":"add back row for four 4","_date":0}
+{"ordinals":{"<10>":{"number":5,"name":"five"}},"_comment":"add row for five 5","_date":0}
+{"ordinals":{"<10>":null},"_comment":"delete row for 5","_date":0}
+{"ordinals":{"<11>":{"number":5,"name":"five"}},"_comment":"add back row for five 5","_date":0}
+]], [], [test ! -e pid || kill `cat pid`])
+dnl Dump out and check the actual database contents.
+AT_CHECK([[ovsdb-client dump unix:socket ordinals]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> one   1     @&t@
+<3> three 3     @&t@
+<4> two   2     @&t@
+<5> zero  0     @&t@
+], [], [test ! -e pid || kill `cat pid`])
+dnl Now compact the database in-place.
+AT_CHECK([[ovs-appctl -t $PWD/unixctl ovsdb-server/compact]],
+  [0], [], [ignore], [test ! -e pid || kill `cat pid`])
+dnl We can't fully re-check the contents of the database log, because the
+dnl order of the records is not predictable, but there should only be 4 lines
+dnl in it now.
+AT_CAPTURE_FILE([db])
+AT_CHECK([wc -l < db], [0], [4
+], [], [test ! -e pid || kill `cat pid`])
+dnl And check that the dumped data is the same too:
+AT_CHECK([ovsdb-client dump unix:socket ordinals], [0], [stdout], [ignore],
+  [test ! -e pid || kill `cat pid`])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> one   1     @&t@
+<3> three 3     @&t@
+<4> two   2     @&t@
+<5> zero  0     @&t@
+], [], [test ! -e pid || kill `cat pid`])
+dnl Now do some more transactions.
+AT_CHECK(
+  [[ovsdb-client transact unix:socket '
+     ["ordinals",
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 3]]}]']],
+  [0], [[[{"count":3}]
+]], [ignore], [test ! -e pid || kill `cat pid`])
+dnl There should be 6 lines in the log now.
+AT_CHECK([wc -l < db], [0], [6
+], [], [test ! -e pid || kill `cat pid`])
+dnl Then check that the dumped data is correct.
+AT_CHECK([ovsdb-client dump unix:socket ordinals], [0], [stdout], [ignore],
+  [test ! -e pid || kill `cat pid`])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> three 3     @&t@
+], [], [test ! -e pid || kill `cat pid`])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+\f
+AT_BANNER([OVSDB -- ovsdb-server transactions (SSL sockets)])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive ssl $5])
+   AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+   AT_SKIP_IF([test "x$RANDOM" = x])
+   AT_DATA([schema], [$2
+])
+   SSL_PORT=`expr 32767 + \( $RANDOM % 32767 \)`
+   PKIDIR=$abs_top_srcdir/tests
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --private-key=$PKIDIR/testpki-privkey2.pem --certificate=$PKIDIR/testpki-cert2.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:$SSL_PORT:127.0.0.1 --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:127.0.0.1:$SSL_PORT 'txn'], [0], [stdout], [ignore],
+     [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [ignore],
+            [test ! -e pid || kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
+\f
+AT_BANNER([OVSDB -- transactions on transient ovsdb-server])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA and runs each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings)
+# against it with ovsdb-client one at a time.  Each ovsdb-client
+# is run against a separately started ovsdb-server that executes
+# only that single transaction.  (The idea is that this should
+# help to ferret out any differences between what ovsdb-server has
+# in memory and what actually gets committed to disk.)
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive transient $5])
+   AT_DATA([schema], [$2
+])
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_DATA([txnfile], [ovsdb-client transact unix:socket 'txn'
+])
+      AT_CHECK([ovsdb-server --remote=punix:socket --unixctl=$PWD/unixctl db --run="sh txnfile"], [0], [stdout], [ignore])
+      cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [ignore])
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at
new file mode 100644 (file)
index 0000000..623dd6d
--- /dev/null
@@ -0,0 +1,35 @@
+AT_BANNER([OVSDB -- tables])
+
+OVSDB_CHECK_POSITIVE([table with one column],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']],
+  [[{"columns":{"name":{"type":"string"}}}]])
+
+OVSDB_CHECK_POSITIVE([immutable table with one column],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "mutable": false}']],
+  [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
+
+OVSDB_CHECK_POSITIVE([table with maxRows of 2],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}, 
+                          "maxRows": 2}']],
+  [[{"columns":{"name":{"type":"string"}},"maxRows":2}]])
+
+OVSDB_CHECK_NEGATIVE([column names may not begin with _],
+  [[parse-table mytable \
+    '{"columns": {"_column": {"type": "integer"}}}']],
+  [[names beginning with "_" are reserved]],
+  [table])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
+  [[parse-table mytable '{}']],
+  [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
+  [[parse-table mytable '{"columns": {}}']],
+  [[table must have at least one column]])
+
+OVSDB_CHECK_NEGATIVE([table maxRows must be positive],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}, 
+                          "maxRows": 0}']],
+  [[syntax "{"columns":{"name":{"type":"string"}},"maxRows":0}": syntax error: maxRows must be at least 1]])
diff --git a/tests/ovsdb-tool.at b/tests/ovsdb-tool.at
new file mode 100644 (file)
index 0000000..5e25fe5
--- /dev/null
@@ -0,0 +1,266 @@
+AT_BANNER([OVSDB -- ovsdb-tool])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA and runs each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings)
+# against it with ovsdb-tool one at a time.  
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb file positive $5])
+   AT_DATA([schema], [$2
+])
+   touch .db.~lock~
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-tool transact db 'txn'], [0], [stdout], [ignore])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4])
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
+
+AT_SETUP([transaction comments])
+AT_KEYWORDS([ovsdb file positive])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+AT_CHECK([[ovsdb-tool transact db '
+    ["ordinals",
+     {"op": "insert",
+      "table": "ordinals",
+      "row": {"name": "five", "number": 5}},
+     {"op": "comment",
+      "comment": "add row for 5"}]']], [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+ [[[{"uuid":["uuid","<0>"]},{}]
+]])
+AT_CHECK([grep -q "add row for 5" db])
+AT_CLEANUP
+
+AT_SETUP([ovsdb-tool compact])
+AT_KEYWORDS([ovsdb file positive])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+dnl Do a bunch of random transactions that put crap in the database log.
+AT_CHECK(
+  [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do
+      set -- $pair
+      ovsdb-tool transact db '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "'$1'", "number": '$2'}},
+         {"op": "comment",
+          "comment": "add row for '"$pair"'"}]'
+      ovsdb-tool transact db '
+        ["ordinals",
+         {"op": "delete",
+          "table": "ordinals",
+          "where": [["number", "==", '$2']]},
+         {"op": "comment",
+          "comment": "delete row for '"$2"'"}]'
+      ovsdb-tool transact db '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "'$1'", "number": '$2'}},
+         {"op": "comment",
+          "comment": "add back row for '"$pair"'"}]'
+    done]],
+  [0], [stdout], [ignore])
+dnl Check that all the crap is in fact in the database log.
+AT_CHECK([[perl $srcdir/uuidfilt.pl db | grep -v ^OVSDB | sed 's/"_date":[0-9]*/"_date":0/']], [0],
+  [[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}}}
+{"ordinals":{"<0>":{"name":"zero"}},"_comment":"add row for zero 0","_date":0}
+{"ordinals":{"<0>":null},"_comment":"delete row for 0","_date":0}
+{"ordinals":{"<1>":{"name":"zero"}},"_comment":"add back row for zero 0","_date":0}
+{"ordinals":{"<2>":{"number":1,"name":"one"}},"_comment":"add row for one 1","_date":0}
+{"ordinals":{"<2>":null},"_comment":"delete row for 1","_date":0}
+{"ordinals":{"<3>":{"number":1,"name":"one"}},"_comment":"add back row for one 1","_date":0}
+{"ordinals":{"<4>":{"number":2,"name":"two"}},"_comment":"add row for two 2","_date":0}
+{"ordinals":{"<4>":null},"_comment":"delete row for 2","_date":0}
+{"ordinals":{"<5>":{"number":2,"name":"two"}},"_comment":"add back row for two 2","_date":0}
+{"ordinals":{"<6>":{"number":3,"name":"three"}},"_comment":"add row for three 3","_date":0}
+{"ordinals":{"<6>":null},"_comment":"delete row for 3","_date":0}
+{"ordinals":{"<7>":{"number":3,"name":"three"}},"_comment":"add back row for three 3","_date":0}
+{"ordinals":{"<8>":{"number":4,"name":"four"}},"_comment":"add row for four 4","_date":0}
+{"ordinals":{"<8>":null},"_comment":"delete row for 4","_date":0}
+{"ordinals":{"<9>":{"number":4,"name":"four"}},"_comment":"add back row for four 4","_date":0}
+{"ordinals":{"<10>":{"number":5,"name":"five"}},"_comment":"add row for five 5","_date":0}
+{"ordinals":{"<10>":null},"_comment":"delete row for 5","_date":0}
+{"ordinals":{"<11>":{"number":5,"name":"five"}},"_comment":"add back row for five 5","_date":0}
+]])
+dnl Dump out and check the actual database contents.
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> one   1     @&t@
+<3> three 3     @&t@
+<4> two   2     @&t@
+<5> zero  0     @&t@
+])
+dnl Now compact the database in-place.
+touch .db.tmp.~lock~
+AT_CHECK([[ovsdb-tool compact db]], [0], [], [ignore])
+dnl We can't fully re-check the contents of the database log, because the
+dnl order of the records is not predictable, but there should only be 4 lines
+dnl in it now.
+AT_CAPTURE_FILE([db])
+AT_CHECK([wc -l < db], [0], [4
+])
+dnl And check that the dumped data is the same too:
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> one   1     @&t@
+<3> three 3     @&t@
+<4> two   2     @&t@
+<5> zero  0     @&t@
+])
+AT_CLEANUP
+
+AT_SETUP([ovsdb-tool convert -- removing a column])
+AT_KEYWORDS([ovsdb file positive])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+AT_DATA([new-schema], 
+  [[{"name": "ordinals",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"}}}}}
+]])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+dnl Put some data in the database.
+AT_CHECK(
+  [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do
+      set -- $pair
+      ovsdb-tool transact db '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"name": "'$1'", "number": '$2'}},
+         {"op": "comment",
+          "comment": "add row for '"$pair"'"}]'
+    done]],
+  [0], [stdout], [ignore])
+dnl Dump out and check the actual database contents.
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name  number
+------------------------------------ ----- ------
+<0> five  5     @&t@
+<1> four  4     @&t@
+<2> one   1     @&t@
+<3> three 3     @&t@
+<4> two   2     @&t@
+<5> zero  0     @&t@
+])
+dnl Now convert the database in-place.
+touch .db.tmp.~lock~
+AT_CHECK([[ovsdb-tool convert db new-schema]], [0], [], [ignore])
+dnl We can't fully re-check the contents of the database log, because the
+dnl order of the records is not predictable, but there should only be 4 lines
+dnl in it now.
+AT_CAPTURE_FILE([db])
+AT_CHECK([wc -l < db], [0], [4
+])
+dnl And check that the dumped data is the same except for the removed column:
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                number
+------------------------------------ ------
+<0> 0     @&t@
+<1> 1     @&t@
+<2> 2     @&t@
+<3> 3     @&t@
+<4> 4     @&t@
+<5> 5     @&t@
+])
+AT_CLEANUP
+
+AT_SETUP([ovsdb-tool convert -- adding a column])
+AT_KEYWORDS([ovsdb file positive])
+AT_DATA([schema], 
+  [[{"name": "ordinals",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"}}}}}
+]])
+AT_DATA([new-schema], [ORDINAL_SCHEMA
+])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+dnl Put some data in the database.
+AT_CHECK(
+  [[for number in 0 1 2 3 4 5; do
+      ovsdb-tool transact db '
+        ["ordinals",
+         {"op": "insert",
+          "table": "ordinals",
+          "row": {"number": '$number'}},
+         {"op": "comment",
+          "comment": "add row for '"$number"'"}]'
+    done]],
+  [0], [stdout], [ignore])
+dnl Dump out and check the actual database contents.
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                number
+------------------------------------ ------
+<0> 0     @&t@
+<1> 1     @&t@
+<2> 2     @&t@
+<3> 3     @&t@
+<4> 4     @&t@
+<5> 5     @&t@
+])
+dnl Now convert the database in-place.
+touch .db.tmp.~lock~
+AT_CHECK([[ovsdb-tool convert db new-schema]], [0], [], [ignore])
+dnl We can't fully re-check the contents of the database log, because the
+dnl order of the records is not predictable, but there should only be 4 lines
+dnl in it now.
+AT_CAPTURE_FILE([db])
+AT_CHECK([wc -l < db], [0], [4
+])
+dnl And check that the dumped data is the same except for the added column:
+AT_CHECK([[ovsdb-server --unixctl=$PWD/unixctl --remote=punix:socket --run "ovsdb-client dump unix:socket ordinals" db]],
+  [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+  [_uuid                                name number
+------------------------------------ ---- ------
+<0> ""   0     @&t@
+<1> ""   1     @&t@
+<2> ""   2     @&t@
+<3> ""   3     @&t@
+<4> ""   4     @&t@
+<5> ""   5     @&t@
+])
+AT_CLEANUP
diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at
new file mode 100644 (file)
index 0000000..8abc5d3
--- /dev/null
@@ -0,0 +1,410 @@
+AT_BANNER([OVSDB -- transactions])
+
+OVSDB_CHECK_POSITIVE([empty table, empty transaction],
+  [[transact \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+print:
+commit:
+print:
+abort:
+print:])
+
+OVSDB_CHECK_POSITIVE([nonempty table, empty transaction],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+print:
+1: i=2, j=3
+2: i=2, j=3
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3])
+
+OVSDB_CHECK_POSITIVE([insert, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+commit:
+print:
+1: i=2, j=4
+2: i=5, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([deletes are aborted cleanly],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["abort"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+commit:
+print:
+1: i=2, j=3
+delete 1:
+abort:
+print:
+1: i=2, j=3
+delete 1:
+abort:
+print:
+1: i=2, j=3],
+  [transaction])
+
diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at
new file mode 100644 (file)
index 0000000..c2b1b96
--- /dev/null
@@ -0,0 +1,187 @@
+AT_BANNER([OVSDB -- triggers])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_TRIGGER], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution trigger positive $4])
+   AT_CHECK([test-ovsdb trigger $2], [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+   AT_CLEANUP])
+
+OVSDB_CHECK_TRIGGER([trigger fires immediately],
+  ['ORDINAL_SCHEMA' [\
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger times out],
+  ['ORDINAL_SCHEMA' [\
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 10]']],
+  [[t=0: new trigger 0
+t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger fires after delay],
+  ['ORDINAL_SCHEMA' [\
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{}]
+]])
+
+OVSDB_CHECK_TRIGGER([delayed trigger modifies database],
+  ['ORDINAL_SCHEMA' [\
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{},{"count":2}]
+t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}]
+]])
+
+OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another],
+  ['ORDINAL_SCHEMA' [\
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 2]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 3, "name": "three"}}]' \
+    '["ordinals",
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '["ordinals",
+      {"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=5: new trigger 2
+t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 2 (delayed): [{},{"count":2}]
+t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}]
+t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}]
+]])
+
diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at
new file mode 100644 (file)
index 0000000..7122e9d
--- /dev/null
@@ -0,0 +1,167 @@
+AT_BANNER([OVSDB -- atomic types])
+
+OVSDB_CHECK_POSITIVE([integer], 
+  [[parse-atomic-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real], 
+  [[parse-atomic-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-atomic-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([string], 
+  [[parse-atomic-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([uuid], 
+  [[parse-atomic-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
+  [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- base types])
+
+OVSDB_CHECK_POSITIVE([integer enum],
+  [[parse-base-type '{"type": "integer", "enum": ["set", [-1, 4, 5]]}' ]],
+  [[{"enum":["set",[-1,4,5]],"type":"integer"}]])
+OVSDB_CHECK_POSITIVE([integer >= 5], 
+  [[parse-base-type '{"type": "integer", "minInteger": 5}' ]],
+  [{"minInteger":5,"type":"integer"}])
+OVSDB_CHECK_POSITIVE([integer <= 7], 
+  [[parse-base-type '{"type": "integer", "maxInteger": 7}' ]],
+  [{"maxInteger":7,"type":"integer"}])
+OVSDB_CHECK_POSITIVE([integer between -5 and 10], 
+  [[parse-base-type '{"type": "integer", "minInteger": -5, "maxInteger": 10}']],
+  [{"maxInteger":10,"minInteger":-5,"type":"integer"}])
+OVSDB_CHECK_NEGATIVE([integer max may not be less than min],
+  [[parse-base-type '{"type": "integer", "minInteger": 5, "maxInteger": 3}']],
+  [minInteger exceeds maxInteger])
+
+OVSDB_CHECK_POSITIVE([real enum],
+  [[parse-base-type '{"type": "real", "enum": ["set", [1.5, 0, 2.75]]}' ]],
+  [[{"enum":["set",[0,1.5,2.75]],"type":"real"}]])
+OVSDB_CHECK_POSITIVE([real >= -1.5], 
+  [[parse-base-type '{"type": "real", "minReal": -1.5}']],
+  [{"minReal":-1.5,"type":"real"}])
+OVSDB_CHECK_POSITIVE([real <= 1e5], 
+  [[parse-base-type '{"type": "real", "maxReal": 1e5}']],
+  [{"maxReal":100000,"type":"real"}])
+OVSDB_CHECK_POSITIVE([real between -2.5 and 3.75], 
+  [[parse-base-type '{"type": "real", "minReal": -2.5, "maxReal": 3.75}']],
+  [{"maxReal":3.75,"minReal":-2.5,"type":"real"}])
+OVSDB_CHECK_NEGATIVE([real max may not be less than min], 
+  [[parse-base-type '{"type": "real", "minReal": 555, "maxReal": 444}']],
+  [minReal exceeds maxReal])
+
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-base-type '[{"type": "boolean"}]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([boolean enum],
+  [[parse-base-type '{"type": "boolean", "enum": true}' ]],
+  [[{"enum":true,"type":"boolean"}]])
+
+OVSDB_CHECK_POSITIVE([string enum], 
+  [[parse-base-type '{"type": "string", "enum": ["set", ["def", "abc"]]}']],
+  [[{"enum":["set",["abc","def"]],"type":"string"}]])
+OVSDB_CHECK_POSITIVE([string minLength], 
+  [[parse-base-type '{"type": "string", "minLength": 1}']],
+  [{"minLength":1,"type":"string"}])
+OVSDB_CHECK_POSITIVE([string maxLength], 
+  [[parse-base-type '{"type": "string", "maxLength": 5}']],
+  [{"maxLength":5,"type":"string"}])
+OVSDB_CHECK_POSITIVE([string minLength and maxLength], 
+  [[parse-base-type '{"type": "string", "minLength": 1, "maxLength": 5}']],
+  [{"maxLength":5,"minLength":1,"type":"string"}])
+OVSDB_CHECK_NEGATIVE([maxLength must not be less than minLength], 
+  [[parse-base-type '{"type": "string", "minLength": 5, "maxLength": 3}']],
+  [minLength exceeds maxLength])
+OVSDB_CHECK_NEGATIVE([maxLength must not be negative], 
+  [[parse-base-type '{"type": "string", "maxLength": -1}']],
+  [maxLength out of valid range 0 to 4294967295])
+
+OVSDB_CHECK_POSITIVE([uuid enum], 
+  [[parse-base-type '{"type": "uuid", "enum": ["uuid", "36bf19c0-ad9d-4232-bb85-b3d73dfe2123"]}' ]],
+  [[{"enum":["uuid","36bf19c0-ad9d-4232-bb85-b3d73dfe2123"],"type":"uuid"}]])
+OVSDB_CHECK_POSITIVE([uuid refTable], 
+  [[parse-base-type '{"type": "uuid", "refTable": "myTable"}' ]],
+  [{"refTable":"myTable","type":"uuid"}])
+OVSDB_CHECK_NEGATIVE([uuid refTable must be valid id], 
+  [[parse-base-type '{"type": "uuid", "refTable": "a-b-c"}' ]],
+  [Type mismatch for member 'refTable'])
+
+OVSDB_CHECK_NEGATIVE([void is not a valid base-type],
+  [[parse-base-type '["void"]' ]], ["void" is not an atomic-type])
+OVSDB_CHECK_NEGATIVE(["type" member must be present],
+  [[parse-base-type '{}']], [Parsing ovsdb type failed: Required 'type' member is missing.])
+
+AT_BANNER([OVSDB -- simple types])
+
+OVSDB_CHECK_POSITIVE([simple integer], 
+  [[parse-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([simple real], 
+  [[parse-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([simple boolean], 
+  [[parse-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([simple string], 
+  [[parse-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([simple uuid], 
+  [[parse-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_POSITIVE([integer in object],
+  [[parse-type '{"key": "integer"}' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real in object with explicit min and max],
+  [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"])
+
+OVSDB_CHECK_NEGATIVE([key type is required],
+  [[parse-type '{}' ]], [Required 'key' member is missing.])
+OVSDB_CHECK_NEGATIVE([void is not a valid type],
+  [[parse-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- set types])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-type '{"key": "boolean", "min": 0}' ]], 
+  [[{"key":"boolean","min":0}]],
+  [set])
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]], 
+  [[{"key":"uuid","max":3}]])
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-type '{"key": "string", "min": 0, "max": 3}' ]], 
+  [[{"key":"string","max":3,"min":0}]])
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']],
+  [[{"key":"integer","max":"unlimited","min":0}]])
+OVSDB_CHECK_POSITIVE([set of 1 or more reals],
+  [[parse-type '{"key": "real", "min": 1, "max": "unlimited"}']],
+  [[{"key":"real","max":"unlimited"}]])
+
+OVSDB_CHECK_NEGATIVE([set max cannot be less than min],
+  [[parse-type '{"key": "real", "min": 5, "max": 3}' ]],
+  [ovsdb type fails constraint checks])
+OVSDB_CHECK_NEGATIVE([set max cannot be negative],
+  [[parse-type '{"key": "real", "max": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be negative],
+  [[parse-type '{"key": "real", "min": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be greater than one],
+  [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']],
+  [ovsdb type fails constraint checks])
+
+AT_BANNER([OVSDB -- map types])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-type '{"key": "integer", "value": "boolean"}' ]],
+ [[{"key":"integer","value":"boolean"}]])
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max],
+ [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]],
+ [[{"key":"boolean","value":"integer"}]])
+OVSDB_CHECK_POSITIVE([map of 1 to 5 uuid to real],
+ [[parse-type '{"key": "uuid", "value": "real", "min": 1, "max": 5}' ]],
+ [[{"key":"uuid","max":5,"value":"real"}]])
+OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid],
+ [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]],
+ [[{"key":"string","max":10,"min":0,"value":"uuid"}]])
+OVSDB_CHECK_POSITIVE([map of 1 to 20 real to string],
+ [[parse-type '{"key": "real", "value": "string", "min": 1, "max": 20}' ]],
+ [[{"key":"real","max":20,"value":"string"}]])
+OVSDB_CHECK_POSITIVE([map of 0 or more string to real],
+ [[parse-type '{"key": "string", "value": "real", "min": 0, "max": "unlimited"}' ]],
+ [[{"key":"string","max":"unlimited","min":0,"value":"real"}]])
+
+OVSDB_CHECK_NEGATIVE([map key type is required],
+ [[parse-type '{"value": "integer"}' ]],
+ [Required 'key' member is missing.])
diff --git a/tests/ovsdb.at b/tests/ovsdb.at
new file mode 100644 (file)
index 0000000..1ee9c03
--- /dev/null
@@ -0,0 +1,50 @@
+# OVSDB_CHECK_POSITIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 0 and prints OUTPUT on stdout.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb positive $4])
+   AT_CHECK([test-ovsdb $2], [0], [$3
+], [])
+   AT_CLEANUP])
+
+# OVSDB_CHECK_NEGATIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 1 and that its output on stdout contains substring OUTPUT.
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.  
+m4_define([OVSDB_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb negative $4])
+   AT_CHECK([test-ovsdb $2], [1], [], [stderr])
+   m4_assert(m4_len([$3]))
+   AT_CHECK(
+     [if grep -F -e "AS_ESCAPE([$3])" stderr
+      then
+        :
+      else
+        exit 99
+      fi], 
+            [0], [ignore], [ignore])
+   AT_CLEANUP])
+
+m4_include([tests/ovsdb-log.at])
+m4_include([tests/ovsdb-types.at])
+m4_include([tests/ovsdb-data.at])
+m4_include([tests/ovsdb-column.at])
+m4_include([tests/ovsdb-table.at])
+m4_include([tests/ovsdb-row.at])
+m4_include([tests/ovsdb-schema.at])
+m4_include([tests/ovsdb-condition.at])
+m4_include([tests/ovsdb-mutation.at])
+m4_include([tests/ovsdb-query.at])
+m4_include([tests/ovsdb-transaction.at])
+m4_include([tests/ovsdb-execution.at])
+m4_include([tests/ovsdb-trigger.at])
+m4_include([tests/ovsdb-tool.at])
+m4_include([tests/ovsdb-server.at])
+m4_include([tests/ovsdb-monitor.at])
+m4_include([tests/ovsdb-idl.at])
diff --git a/tests/reconnect.at b/tests/reconnect.at
new file mode 100644 (file)
index 0000000..7c1cc6a
--- /dev/null
@@ -0,0 +1,1111 @@
+AT_BANNER([reconnect library])
+
+######################################################################
+AT_SETUP([nothing happens if not enabled])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+run
+timeout
+  no timeout
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([quick connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection succeeds.
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection succeeds.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([slow connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Start connecting.
+run
+connecting
+
+# Connect after 500 ms.
+advance 500
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Start connecting.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+
+# Connect after 500 ms.
+advance 500
+
+### t=1500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (0 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  created 1000, last received 1000, last connected 1500
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6500 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11500 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connect backs off])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt fails after 2000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 4000 ms.
+timeout
+run
+
+# Third connection attempt fails after 4000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Third connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 2000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+timeout
+  advance 2000 ms
+
+### t=8000 ###
+  in CONNECT_IN_PROGRESS for 2000 ms (2000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (4000 ms backoff)
+  0 successful connections out of 3 attempts, seqno 0
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=12000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 4000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (4000 ms backoff)
+timeout
+  advance 4000 ms
+
+### t=16000 ###
+  in CONNECT_IN_PROGRESS for 4000 ms (4000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 4 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=24000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 8000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=32000 ###
+  in CONNECT_IN_PROGRESS for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 5 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=40000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=48000 ###
+  in CONNECT_IN_PROGRESS for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 6 attempts, seqno 0
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connections with no data preserve backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 4000 ms.
+timeout
+], [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=12000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (1000 ms backoff)
+  created 1000, last received 1000, last connected 12000
+  2 successful connections out of 2 attempts, seqno 3
+  connected (0 ms), total 10000 ms connected
+timeout
+  advance 5000 ms
+
+### t=17000 ###
+  in ACTIVE for 5000 ms (1000 ms backoff)
+  connected (5000 ms), total 15000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (1000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=22000 ###
+  in IDLE for 5000 ms (1000 ms backoff)
+  connected (10000 ms), total 20000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (2000 ms backoff)
+  2 successful connections out of 2 attempts, seqno 4
+  not connected (0 ms), total 20000 ms connected
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=24000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 24000
+  3 successful connections out of 3 attempts, seqno 5
+  connected (0 ms), total 20000 ms connected
+timeout
+  advance 5000 ms
+
+### t=29000 ###
+  in ACTIVE for 5000 ms (2000 ms backoff)
+  connected (5000 ms), total 25000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (2000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=34000 ###
+  in IDLE for 5000 ms (2000 ms backoff)
+  connected (10000 ms), total 30000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  3 successful connections out of 3 attempts, seqno 6
+  not connected (0 ms), total 30000 ms connected
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=38000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection drops after another 250 ms.
+advance 250
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection drops after another 250 ms.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 250 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=10750 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection with data preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+run
+received
+advance 250
+run
+received
+advance 250
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+run
+received
+  created 1000, last received 6750, last connected 6500
+advance 250
+
+### t=7000 ###
+  in ACTIVE for 500 ms (2000 ms backoff)
+  connected (500 ms), total 500 ms connected
+run
+received
+  created 1000, last received 7000, last connected 6500
+advance 250
+
+### t=7250 ###
+  in ACTIVE for 750 ms (2000 ms backoff)
+  connected (750 ms), total 750 ms connected
+run
+received
+  created 1000, last received 7250, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 750 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=11250 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([long connection resets backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+run
+received
+advance 2000
+run
+received
+advance 2000
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 1000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+
+### t=8500 ###
+  in ACTIVE for 2000 ms (2000 ms backoff)
+  connected (2000 ms), total 2000 ms connected
+run
+received
+  created 1000, last received 8500, last connected 6500
+advance 2000
+
+### t=10500 ###
+  in ACTIVE for 4000 ms (2000 ms backoff)
+  connected (4000 ms), total 4000 ms connected
+run
+received
+  created 1000, last received 10500, last connected 6500
+advance 2000
+
+### t=12500 ###
+  in ACTIVE for 6000 ms (2000 ms backoff)
+  connected (6000 ms), total 6000 ms connected
+run
+received
+  created 1000, last received 12500, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 6000 ms connected
+run
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=13500 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connection attempt fails quickly])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection fails quickly.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 1000 ms.
+run
+timeout
+
+# Connection fails quickly again.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 2000 ms.
+run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection fails quickly.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+
+# Connection fails quickly again.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+run
+timeout
+  advance 2000 ms
+
+### t=4000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([max-tries of 1 honored])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [set-max-tries 1
+enable
+
+# Connection succeeds.
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+set-max-tries 1
+  1 tries left
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+  0 tries left
+
+# Connection succeeds.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in VOID for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([max-tries of 0 honored])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [set-max-tries 0
+enable
+run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+set-max-tries 0
+  0 tries left
+enable
+run
+timeout
+  no timeout
+])
+AT_CLEANUP
diff --git a/tests/stp.at b/tests/stp.at
new file mode 100644 (file)
index 0000000..4e25af7
--- /dev/null
@@ -0,0 +1,303 @@
+AT_BANNER([Spanning Tree Protocol unit tests])
+
+AT_SETUP([STP example from IEEE 802.1D-1998])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-ieee802.1d-1998],
+[bridge 0 0x42 = a b
+bridge 1 0x97 = c:5 a d:5
+bridge 2 0x45 = b e
+bridge 3 0x57 = b:5 e:5
+bridge 4 0x83 = a:5 e:5
+run 1000
+check 0 = root
+check 1 = F F:10 F
+check 2 = F:10 B
+check 3 = F:5 F
+check 4 = F:5 B
+])
+AT_CHECK([test-stp test-stp-ieee802.1d-1998])
+AT_CLEANUP
+
+AT_SETUP([STP example from IEEE 802.1D-2004 figures 17.4 and 17.5])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-ieee802.1d-2004-fig17.4],
+[bridge 0 0x111 = a b e c
+bridge 1 0x222 = a b d f
+bridge 2 0x333 = c d l j h g
+bridge 3 0x444 = e f n m k i
+bridge 4 0x555 = g i 0 0
+bridge 5 0x666 = h k 0 0
+bridge 6 0x777 = j m 0 0
+bridge 7 0x888 = l n 0 0
+run 1000
+check 0 = root
+check 1 = F:10 B F F
+check 2 = F:10 B F F F F
+check 3 = F:10 B F F F F
+check 4 = F:20 B F F
+check 5 = F:20 B F F
+check 6 = F:20 B F F
+check 7 = F:20 B F F
+
+# Now connect two ports of bridge 7 to the same LAN.
+bridge 7 = l n o o
+# Same results except for bridge 7:
+run 1000
+check 0 = root
+check 1 = F:10 B F F
+check 2 = F:10 B F F F F
+check 3 = F:10 B F F F F
+check 4 = F:20 B F F
+check 5 = F:20 B F F
+check 6 = F:20 B F F
+check 7 = F:20 B F B
+])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.4])
+AT_CLEANUP
+
+AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.6])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-ieee802.1d-2004-fig17.6],
+[bridge 0 0x111 = a b l
+bridge 1 0x222 = b c d
+bridge 2 0x333 = d e f
+bridge 3 0x444 = f g h
+bridge 4 0x555 = j h i
+bridge 5 0x666 = l j k
+run 1000
+check 0 = root
+check 1 = F:10 F F
+check 2 = F:20 F F
+check 3 = F:30 F B
+check 4 = F:20 F F
+check 5 = F:10 F F
+])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.6])
+AT_CLEANUP
+
+AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.7])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-ieee802.1d-2004-fig17.7],
+[bridge 0 0xaa = b
+bridge 1 0x111 = a b d f h g e c
+bridge 2 0x222 = g h j l n m k i
+run 1000
+check 0 = root
+check 1 = F F:10 F F F F F F
+check 2 = B F:20 F F F F F F
+
+# This is not the port priority change described in that figure,
+# but I don't understand what port priority change would cause
+# that change.
+bridge 2 = g X j l n m k i
+run 1000
+check 0 = root
+check 1 = F F:10 F F F F F F
+check 2 = F:20 D F F F F F F
+])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.7])
+AT_CLEANUP
+
+AT_SETUP([STP.io.1.1: Link Failure])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-io-1.1],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Interoperability Test Suite
+# Version 1.5":
+# 
+# STP.io.1.1: Link Failure
+bridge 0 0x111 = a b c
+bridge 1 0x222 = a b c
+run 1000
+check 0 = root
+check 1 = F:10 B B
+bridge 1 = 0 _ _
+run 1000
+check 0 = root
+check 1 = F F:10 B
+bridge 1 = X _ _
+run 1000
+check 0 = root
+check 1 = D F:10 B
+bridge 1 = _ 0 _
+run 1000
+check 0 = root
+check 1 = D F F:10
+bridge 1 = _ X _
+run 1000
+check 0 = root
+check 1 = D D F:10
+])
+AT_CHECK([test-stp test-stp-iol-io-1.1])
+AT_CLEANUP
+
+AT_SETUP([STP.io.1.2: Repeated Network])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-io-1.2],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Interoperability Test Suite
+# Version 1.5":
+# STP.io.1.2: Repeated Network
+bridge 0 0x111 = a a
+bridge 1 0x222 = a a
+run 1000
+check 0 = rootid:0x111 F B
+check 1 = rootid:0x111 F:10 B
+bridge 1 = a^0x90 _
+run 1000
+check 0 = rootid:0x111 F B
+check 1 = rootid:0x111 B F:10
+])
+AT_CHECK([test-stp test-stp-iol-io-1.2])
+AT_CLEANUP
+
+AT_SETUP([STP.io.1.4: Network Initialization])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-io-1.4],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Interoperability Test Suite
+# Version 1.5":
+# STP.io.1.4: Network Initialization
+bridge 0 0x111 = a b c
+bridge 1 0x222 = b d e
+bridge 2 0x333 = a d f
+bridge 3 0x444 = c e f
+run 1000
+check 0 = root
+check 1 = F:10 F F
+check 2 = F:10 B F
+check 3 = F:10 B B
+])
+AT_CHECK([test-stp test-stp-iol-io-1.4])
+AT_CLEANUP
+
+AT_SETUP([STP.io.1.5: Topology Change])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-io-1.5],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Interoperability Test Suite
+# Version 1.5":
+# STP.io.1.5: Topology Change
+bridge 0 0x111 = a b d c
+bridge 1 0x222 = a b f e
+bridge 2 0x333 = c d g h
+bridge 3 0x444 = e f g h
+run 1000
+check 0 = root
+check 1 = F:10 B F F
+check 2 = B F:10 F F
+check 3 = B F:20 B B
+bridge 1^0x7000
+run 1000
+check 0 = F:10 B F F
+check 1 = root
+check 2 = B F:20 B B
+check 3 = B F:10 F F
+bridge 2^0x6000
+run 1000
+check 0 = F F B F:10
+check 1 = F:20 B B B
+check 2 = root
+check 3 = F F F:10 B
+bridge 3^0x5000
+run 1000
+check 0 = B B B F:20
+check 1 = F F B F:10
+check 2 = F F F:10 B
+check 3 = root
+bridge 0^0x4000
+bridge 1^0x4001
+bridge 2^0x4002
+bridge 3^0x4003
+run 1000
+check 0 = root
+check 1 = F:10 B F F
+check 2 = B F:10 F F
+check 3 = B F:20 B B
+])
+AT_CHECK([test-stp test-stp-iol-io-1.5])
+AT_CLEANUP
+
+AT_SETUP([STP.op.1.1 and STP.op.1.2])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-op-1.1],
+[# This test file approximates the following tests from "Bridge
+# Functions Consortium Spanning Tree Protocol Operations Test Suite
+# Version 2.3":
+# Test STP.op.1.1: Root ID Initialized to Bridge ID
+# Test STP.op.1.2: Root Path Cost Initialized to Zero
+bridge 0 0x123 =
+check 0 = root
+])
+AT_CHECK([test-stp test-stp-iol-op-1.1])
+AT_CLEANUP
+
+AT_SETUP([STP.op.1.4: All Ports Initialized to Designated Ports])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-op-1.4],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Protocol Operations Test Suite
+# Version 2.3":
+# Test STP.op.1.4: All Ports Initialized to Designated Ports
+bridge 0 0x123 = a b c d e f
+check 0 = Li Li Li Li Li Li
+run 1000
+check 0 = F F F F F F
+])
+AT_CHECK([test-stp test-stp-iol-op-1.4])
+AT_CLEANUP
+
+AT_SETUP([STP.op.3.1: Root Bridge Selection: Root ID Values])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-op-3.1],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Protocol Operations Test Suite
+# Version 2.3":
+# Test STP.op.3.1: Root Bridge Selection: Root ID Values
+bridge 0 0x111 = a
+bridge 1 0x222 = a
+check 0 = rootid:0x111 Li
+check 1 = rootid:0x222 Li
+run 1000
+check 0 = rootid:0x111 root
+check 1 = rootid:0x111 F:10
+])
+AT_CHECK([test-stp test-stp-iol-op-3.1])
+AT_CLEANUP
+
+AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-op-3.3],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Protocol Operations Test Suite
+# Version 2.3":
+# Test STP.op.3.3: Root Bridge Selection: Bridge ID Values
+bridge 0 0x333^0x6000 = a
+bridge 1 0x222^0x7000 = b
+bridge 2 0x111 = a b
+run 1000
+check 0 = rootid:0x333^0x6000 root
+check 1 = rootid:0x333^0x6000 F:20
+check 2 = rootid:0x333^0x6000 F:10 F
+])
+AT_CHECK([test-stp test-stp-iol-op-3.3])
+AT_CLEANUP
+
+AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values])
+AT_KEYWORDS([STP])
+AT_DATA([test-stp-iol-op-3.4],
+[# This test file approximates the following test from "Bridge
+# Functions Consortium Spanning Tree Protocol Operations Test Suite
+# Version 2.3":
+# Test STP.op.3.3: Root Bridge Selection: Bridge ID Values
+bridge 0 0x333^0x6000 = a
+bridge 1 0x222^0x7000 = b
+bridge 2 0x111 = a b
+run 1000
+check 0 = rootid:0x333^0x6000 root
+check 1 = rootid:0x333^0x6000 F:20
+check 2 = rootid:0x333^0x6000 F:10 F
+])
+AT_CHECK([test-stp test-stp-iol-op-3.4])
+AT_CLEANUP
+
diff --git a/tests/test-aes128.c b/tests/test-aes128.c
new file mode 100644 (file)
index 0000000..8a6c28c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include "aes128.h"
+#include "util.h"
+
+static void
+hex_to_uint8(const char *input, uint8_t *output, size_t n)
+{
+    size_t i;
+
+    if (strlen(input) != n * 2) {
+        goto error;
+    }
+    for (i = 0; i < n; i++) {
+        unsigned char hi = input[i * 2];
+        unsigned char lo = input[i * 2 + 1];
+
+        if (!isxdigit(hi) || !isxdigit(lo)) {
+            goto error;
+        }
+        output[i] = (hexit_value(hi) << 4) + hexit_value(lo);
+    }
+    return;
+
+error:
+    ovs_fatal(0, "\"%s\" is not exactly %zu hex digits", input, n * 2);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct aes128 aes;
+    uint8_t plaintext[16];
+    uint8_t ciphertext[16];
+    uint8_t key[16];
+    size_t i;
+
+    if (argc != 3) {
+        ovs_fatal(0, "usage: %s KEY PLAINTEXT, where KEY and PLAINTEXT each "
+                  "consist of 32 hex digits", argv[0]);
+    }
+
+    hex_to_uint8(argv[1], key, 16);
+    hex_to_uint8(argv[2], plaintext, 16);
+
+    aes128_schedule(&aes, key);
+    aes128_encrypt(&aes, plaintext, ciphertext);
+    for (i = 0; i < 16; i++) {
+        printf("%02x", ciphertext[i]);
+    }
+    putchar('\n');
+
+    return 0;
+}
index a44e3d5..563690d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -111,7 +111,7 @@ tcls_insert(struct tcls *tcls, const struct test_rule *rule)
             free(tcls->rules[i]);
             tcls->rules[i] = xmemdup(rule, sizeof *rule);
             return tcls->rules[i];
-        } else if (pos->priority <= rule->cls_rule.priority) {
+        } else if (pos->priority < rule->cls_rule.priority) {
             break;
         }
     }
@@ -223,31 +223,34 @@ tcls_delete_matches(struct tcls *cls,
 }
 \f
 #ifdef WORDS_BIGENDIAN
-#define HTONL(VALUE) ((uint32_t) (VALUE))
-#define HTONS(VALUE) ((uint32_t) (VALUE))
+#define T_HTONL(VALUE) ((uint32_t) (VALUE))
+#define T_HTONS(VALUE) ((uint32_t) (VALUE))
 #else
-#define HTONL(VALUE) (((((uint32_t) (VALUE)) & 0x000000ff) << 24) | \
+#define T_HTONL(VALUE) (((((uint32_t) (VALUE)) & 0x000000ff) << 24) | \
                       ((((uint32_t) (VALUE)) & 0x0000ff00) <<  8) | \
                       ((((uint32_t) (VALUE)) & 0x00ff0000) >>  8) | \
                       ((((uint32_t) (VALUE)) & 0xff000000) >> 24))
-#define HTONS(VALUE) (((((uint16_t) (VALUE)) & 0xff00) >> 8) |  \
+#define T_HTONS(VALUE) (((((uint16_t) (VALUE)) & 0xff00) >> 8) |  \
                       ((((uint16_t) (VALUE)) & 0x00ff) << 8))
 #endif
 
-static uint32_t nw_src_values[] = { HTONL(0xc0a80001),
-                                    HTONL(0xc0a04455) };
-static uint32_t nw_dst_values[] = { HTONL(0xc0a80002),
-                                    HTONL(0xc0a04455) };
-static uint16_t in_port_values[] = { HTONS(1), HTONS(OFPP_LOCAL) };
-static uint16_t dl_vlan_values[] = { HTONS(101), HTONS(0) };
-static uint16_t dl_type_values[] = { HTONS(ETH_TYPE_IP), HTONS(ETH_TYPE_ARP) };
-static uint16_t tp_src_values[] = { HTONS(49362), HTONS(80) };
-static uint16_t tp_dst_values[] = { HTONS(6667), HTONS(22) };
+static uint32_t nw_src_values[] = { T_HTONL(0xc0a80001),
+                                    T_HTONL(0xc0a04455) };
+static uint32_t nw_dst_values[] = { T_HTONL(0xc0a80002),
+                                    T_HTONL(0xc0a04455) };
+static uint16_t in_port_values[] = { T_HTONS(1), T_HTONS(OFPP_LOCAL) };
+static uint16_t dl_vlan_values[] = { T_HTONS(101), T_HTONS(0) };
+static uint8_t dl_vlan_pcp_values[] = { 7, 0 };
+static uint16_t dl_type_values[]
+            = { T_HTONS(ETH_TYPE_IP), T_HTONS(ETH_TYPE_ARP) };
+static uint16_t tp_src_values[] = { T_HTONS(49362), T_HTONS(80) };
+static uint16_t tp_dst_values[] = { T_HTONS(6667), T_HTONS(22) };
 static uint8_t dl_src_values[][6] = { { 0x00, 0x02, 0xe3, 0x0f, 0x80, 0xa4 },
                                       { 0x5e, 0x33, 0x7f, 0x5f, 0x1e, 0x99 } };
 static uint8_t dl_dst_values[][6] = { { 0x4a, 0x27, 0x71, 0xae, 0x64, 0xc1 },
                                       { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
 static uint8_t nw_proto_values[] = { IP_TYPE_TCP, IP_TYPE_ICMP };
+static uint8_t nw_tos_values[] = { 49, 0 };
 
 static void *values[CLS_N_FIELDS][2];
 
@@ -260,6 +263,9 @@ init_values(void)
     values[CLS_F_IDX_DL_VLAN][0] = &dl_vlan_values[0];
     values[CLS_F_IDX_DL_VLAN][1] = &dl_vlan_values[1];
 
+    values[CLS_F_IDX_DL_VLAN_PCP][0] = &dl_vlan_pcp_values[0];
+    values[CLS_F_IDX_DL_VLAN_PCP][1] = &dl_vlan_pcp_values[1];
+
     values[CLS_F_IDX_DL_SRC][0] = dl_src_values[0];
     values[CLS_F_IDX_DL_SRC][1] = dl_src_values[1];
 
@@ -278,6 +284,9 @@ init_values(void)
     values[CLS_F_IDX_NW_PROTO][0] = &nw_proto_values[0];
     values[CLS_F_IDX_NW_PROTO][1] = &nw_proto_values[1];
 
+    values[CLS_F_IDX_NW_TOS][0] = &nw_tos_values[0];
+    values[CLS_F_IDX_NW_TOS][1] = &nw_tos_values[1];
+
     values[CLS_F_IDX_TP_SRC][0] = &tp_src_values[0];
     values[CLS_F_IDX_TP_SRC][1] = &tp_src_values[1];
 
@@ -289,23 +298,27 @@ init_values(void)
 #define N_NW_DST_VALUES ARRAY_SIZE(nw_dst_values)
 #define N_IN_PORT_VALUES ARRAY_SIZE(in_port_values)
 #define N_DL_VLAN_VALUES ARRAY_SIZE(dl_vlan_values)
+#define N_DL_VLAN_PCP_VALUES ARRAY_SIZE(dl_vlan_pcp_values)
 #define N_DL_TYPE_VALUES ARRAY_SIZE(dl_type_values)
 #define N_TP_SRC_VALUES ARRAY_SIZE(tp_src_values)
 #define N_TP_DST_VALUES ARRAY_SIZE(tp_dst_values)
 #define N_DL_SRC_VALUES ARRAY_SIZE(dl_src_values)
 #define N_DL_DST_VALUES ARRAY_SIZE(dl_dst_values)
 #define N_NW_PROTO_VALUES ARRAY_SIZE(nw_proto_values)
+#define N_NW_TOS_VALUES ARRAY_SIZE(nw_tos_values)
 
 #define N_FLOW_VALUES (N_NW_SRC_VALUES *        \
                        N_NW_DST_VALUES *        \
                        N_IN_PORT_VALUES *       \
                        N_DL_VLAN_VALUES *       \
+                       N_DL_VLAN_PCP_VALUES *   \
                        N_DL_TYPE_VALUES *       \
                        N_TP_SRC_VALUES *        \
                        N_TP_DST_VALUES *        \
                        N_DL_SRC_VALUES *        \
                        N_DL_DST_VALUES *        \
-                       N_NW_PROTO_VALUES)
+                       N_NW_PROTO_VALUES *      \
+                       N_NW_TOS_VALUES)
 
 static unsigned int
 get_value(unsigned int *x, unsigned n_values)
@@ -349,6 +362,8 @@ compare_classifiers(struct classifier *cls, struct tcls *tcls)
         flow.nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)];
         flow.in_port = in_port_values[get_value(&x, N_IN_PORT_VALUES)];
         flow.dl_vlan = dl_vlan_values[get_value(&x, N_DL_VLAN_VALUES)];
+        flow.dl_vlan_pcp = dl_vlan_pcp_values[get_value(&x,
+                N_DL_VLAN_PCP_VALUES)];
         flow.dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)];
         flow.tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)];
         flow.tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)];
@@ -357,7 +372,8 @@ compare_classifiers(struct classifier *cls, struct tcls *tcls)
         memcpy(flow.dl_dst, dl_dst_values[get_value(&x, N_DL_DST_VALUES)],
                ETH_ADDR_LEN);
         flow.nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)];
-        flow.reserved = 0;
+        flow.nw_tos = nw_tos_values[get_value(&x, N_NW_TOS_VALUES)];
+        memset(flow.reserved, 0, sizeof flow.reserved);
 
         for (include = 1; include <= 3; include++) {
             cr0 = lookup_with_include_bits(cls, &flow, include);
@@ -445,7 +461,7 @@ make_rule(int wc_fields, unsigned int priority, int value_pat)
         }
     }
 
-    rule = xcalloc(1, sizeof *rule);
+    rule = xzalloc(sizeof *rule);
     cls_rule_from_flow(&rule->cls_rule, &flow, wildcards,
                        !wildcards ? UINT_MAX : priority);
     return rule;
@@ -531,8 +547,8 @@ test_rule_replacement(void)
 
     for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) {
         struct classifier cls;
-        struct test_rule *rule1, *tcls_rule1;
-        struct test_rule *rule2, *tcls_rule2;
+        struct test_rule *rule1;
+        struct test_rule *rule2;
         struct tcls tcls;
 
         rule1 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX);
@@ -542,14 +558,14 @@ test_rule_replacement(void)
 
         classifier_init(&cls);
         tcls_init(&tcls);
-        tcls_rule1 = tcls_insert(&tcls, rule1);
+        tcls_insert(&tcls, rule1);
         assert(!classifier_insert(&cls, &rule1->cls_rule));
         check_tables(&cls, 1, 1, 1);
         compare_classifiers(&cls, &tcls);
         tcls_destroy(&tcls);
 
         tcls_init(&tcls);
-        tcls_rule2 = tcls_insert(&tcls, rule2);
+        tcls_insert(&tcls, rule2);
         assert(test_rule_from_cls_rule(
                    classifier_insert(&cls, &rule2->cls_rule)) == rule1);
         free(rule1);
index 7ea476c..79d1773 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
 #include "command-line.h"
 #include "dhcp.h"
 #include "fatal-signal.h"
-#include "fault.h"
 #include "poll-loop.h"
 #include "util.h"
 #include "vlog.h"
@@ -51,7 +50,6 @@ main(int argc, char *argv[])
     int error;
 
     set_program_name(argv[0]);
-    register_fault_handlers();
     vlog_init();
     parse_options(argc, argv);
 
@@ -67,10 +65,9 @@ main(int argc, char *argv[])
         ovs_fatal(error, "dhclient_create failed");
     }
     dhclient_init(cli, request_ip.s_addr);
-    fatal_signal_add_hook(release, cli, true);
+    fatal_signal_add_hook(release, NULL, cli, true);
 
     for (;;) {
-        fatal_signal_block();
         dhclient_run(cli);
         if (dhclient_changed(cli)) {
             dhclient_configure_netdev(cli);
@@ -79,7 +76,6 @@ main(int argc, char *argv[])
             }
         }
         dhclient_wait(cli);
-        fatal_signal_unblock();
         poll_block();
     }
 }
@@ -95,7 +91,7 @@ release(void *cli_)
 }
 
 static void
-modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
+modify_dhcp_request(struct dhcp_msg *msg, void *aux OVS_UNUSED)
 {
     if (vendor_class) {
         dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, vendor_class);
@@ -176,8 +172,8 @@ usage(void)
            "\nDHCP options:\n"
            "  --request-ip=IP         request specified IP address (default:\n"
            "                          do not request a specific IP)\n"
-           "  --vendor-class=STRING   use STRING as vendor class (default:\n"
-           "                          none); use OpenFlow to imitate secchan\n"
+           "  --vendor-class=STRING   use STRING as vendor class; use\n"
+           "                          OpenFlow to imitate ovs-openflowd\n"
            "  --no-resolv-conf        do not update /etc/resolv.conf\n",
            program_name, program_name);
     vlog_usage();
similarity index 67%
rename from lib/fault.h
rename to tests/test-dir_name.c
index 0d12927..627ecf1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef FAULT_H
-#define FAULT_H 1
+#include <config.h>
+#include "util.h"
+#include <stdlib.h>
 
-void register_fault_handlers(void);
-void log_backtrace(void);
+int
+main(int argc, char *argv[])
+{
+    int i;
 
-#endif /* fault.h */
+    for (i = 1; i < argc; i++) {
+        char *dir = dir_name(argv[i]);
+        puts(dir);
+        free(dir);
+    }
+
+    return 0;
+}
index 6e0b773..451ca1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
 #include <assert.h>
 
 int
-main(int argc UNUSED, char *argv[])
+main(int argc OVS_UNUSED, char *argv[])
 {
     struct ofp_match expected_match;
     FILE *flows, *pcap;
diff --git a/tests/test-flows.sh b/tests/test-flows.sh
deleted file mode 100755 (executable)
index 833f28e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#! /bin/sh -e
-
-# Copyright (c) 2009 Nicira Networks.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-srcdir=`cd $srcdir && pwd`
-trap 'rm -f flows$$ pcap$$ out$$' 0 1 2 13 15
-cd tests
-"$srcdir"/tests/flowgen.pl >/dev/null 3>flows$$ 4>pcap$$
-./test-flows <flows$$ 3<pcap$$ >out$$ || true
-diff -u - out$$ <<EOF
-checked 247 packets, 0 errors
-EOF
index cf6ee1c..18d8f46 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -157,7 +157,7 @@ good_hash(int value)
 }
 
 static size_t
-constant_hash(int value UNUSED)
+constant_hash(int value OVS_UNUSED)
 {
     return 123;
 }
diff --git a/tests/test-json.c b/tests/test-json.c
new file mode 100644 (file)
index 0000000..f297dc2
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include "util.h"
+
+/* --pretty: If set, the JSON output is pretty-printed, instead of printed as
+ * compactly as possible.  */
+static int pretty = 0;
+
+/* --multiple: If set, the input is a sequence of JSON objects or arrays,
+ * instead of exactly one object or array. */
+static int multiple = 0;
+
+static bool
+print_and_free_json(struct json *json)
+{
+    bool ok;
+    if (json->type == JSON_STRING) {
+        printf("error: %s\n", json->u.string);
+        ok = false;
+    } else {
+        char *s = json_to_string(json, JSSF_SORT | (pretty ? JSSF_PRETTY : 0));
+        puts(s);
+        free(s);
+        ok = true;
+    }
+    json_destroy(json);
+    return ok;
+}
+
+static bool
+refill(FILE *file, void *buffer, size_t buffer_size, size_t *n, size_t *used)
+{
+    *used = 0;
+    if (feof(file)) {
+        *n = 0;
+        return false;
+    } else {
+        *n = fread(buffer, 1, buffer_size, file);
+        if (ferror(file)) {
+            ovs_fatal(errno, "Error reading input file");
+        }
+        return *n > 0;
+    }
+}
+
+static bool
+parse_multiple(FILE *stream)
+{
+    struct json_parser *parser;
+    char buffer[BUFSIZ];
+    size_t n, used;
+    bool ok;
+
+    parser = NULL;
+    n = used = 0;
+    ok = true;
+    while (used < n || refill(stream, buffer, sizeof buffer, &n, &used)) {
+        if (!parser && isspace((unsigned char) buffer[used])) {
+            /* Skip white space. */
+            used++;
+        } else {
+            if (!parser) {
+                parser = json_parser_create(0);
+            }
+
+            used += json_parser_feed(parser, &buffer[used], n - used);
+            if (used < n) {
+                if (!print_and_free_json(json_parser_finish(parser))) {
+                    ok = false;
+                }
+                parser = NULL;
+            }
+        }
+    }
+    if (parser) {
+        if (!print_and_free_json(json_parser_finish(parser))) {
+            ok = false;
+        }
+    }
+    return ok;
+}
+
+int
+main(int argc, char *argv[])
+{
+    const char *input_file;
+    FILE *stream;
+    bool ok;
+
+    set_program_name(argv[0]);
+
+    for (;;) {
+        static const struct option options[] = {
+            {"pretty", no_argument, &pretty, 1},
+            {"multiple", no_argument, &multiple, 1},
+        };
+        int option_index = 0;
+        int c = getopt_long (argc, argv, "", options, &option_index);
+
+        if (c == -1) {
+            break;
+        }
+        switch (c) {
+        case 0:
+            break;
+
+        case '?':
+            exit(1);
+
+        default:
+            abort();
+        }
+    }
+
+    if (argc - optind != 1) {
+        ovs_fatal(0, "usage: %s [--pretty] [--multiple] INPUT.json",
+                  program_name);
+    }
+
+    input_file = argv[optind];
+    stream = !strcmp(input_file, "-") ? stdin : fopen(input_file, "r");
+    if (!stream) {
+        ovs_fatal(errno, "Cannot open \"%s\"", input_file);
+    }
+
+    if (multiple) {
+        ok = parse_multiple(stream);
+    } else {
+        ok = print_and_free_json(json_from_stream(stream));
+    }
+
+    fclose(stream);
+
+    return !ok;
+}
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
new file mode 100644 (file)
index 0000000..06b1cf4
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "json.h"
+#include "poll-loop.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+    };
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        STREAM_SSL_LONG_OPTIONS
+#endif
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+#endif
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: JSON-RPC test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  listen LOCAL             listen for connections on LOCAL\n"
+           "  request REMOTE METHOD PARAMS   send request, print reply\n"
+           "  notify REMOTE METHOD PARAMS  send notification and exit\n",
+           program_name, program_name);
+    stream_usage("JSON-RPC", true, true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+\f
+/* Command implementations. */
+
+static void
+handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done)
+{
+    struct jsonrpc_msg *reply = NULL;
+    if (msg->type == JSONRPC_REQUEST) {
+        if (!strcmp(msg->method, "echo")) {
+            reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+        } else {
+            struct json *error = json_object_create();
+            json_object_put_string(error, "error", "unknown method");
+            reply = jsonrpc_create_error(error, msg->id);
+            ovs_error(0, "unknown request %s", msg->method);
+        }
+
+    } else if (msg->type == JSONRPC_NOTIFY) {
+        if (!strcmp(msg->method, "shutdown")) {
+            *done = true;
+        } else {
+            jsonrpc_error(rpc, ENOTTY);
+            ovs_error(0, "unknown notification %s", msg->method);
+        }
+    } else {
+        jsonrpc_error(rpc, EPROTO);
+        ovs_error(0, "unsolicited JSON-RPC reply or error");
+    }
+
+    if (reply) {
+        jsonrpc_send(rpc, reply);
+    }
+}
+
+static void
+do_listen(int argc OVS_UNUSED, char *argv[])
+{
+    struct pstream *pstream;
+    struct jsonrpc **rpcs;
+    size_t n_rpcs, allocated_rpcs;
+    bool done;
+    int error;
+
+    die_if_already_running();
+
+    error = pstream_open(argv[1], &pstream);
+    if (error) {
+        ovs_fatal(error, "could not listen on \"%s\"", argv[1]);
+    }
+
+    daemonize();
+
+    rpcs = NULL;
+    n_rpcs = allocated_rpcs = 0;
+    done = false;
+    for (;;) {
+        struct stream *stream;
+        size_t i;
+
+        /* Accept new connections. */
+        error = pstream_accept(pstream, &stream);
+        if (!error) {
+            if (n_rpcs >= allocated_rpcs) {
+                rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs);
+            }
+            rpcs[n_rpcs++] = jsonrpc_open(stream);
+        } else if (error != EAGAIN) {
+            ovs_fatal(error, "pstream_accept failed");
+        }
+
+        /* Service existing connections. */
+        for (i = 0; i < n_rpcs; ) {
+            struct jsonrpc *rpc = rpcs[i];
+            struct jsonrpc_msg *msg;
+
+            jsonrpc_run(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                error = jsonrpc_recv(rpc, &msg);
+                if (!error) {
+                    handle_rpc(rpc, msg, &done);
+                    jsonrpc_msg_destroy(msg);
+                }
+            }
+
+            error = jsonrpc_get_status(rpc);
+            if (error) {
+                jsonrpc_close(rpc);
+                ovs_error(error, "connection closed");
+                memmove(&rpcs[i], &rpcs[i + 1],
+                        (n_rpcs - i - 1) * sizeof *rpcs);
+                n_rpcs--;
+            } else {
+                i++;
+            }
+        }
+
+        /* Wait for something to do. */
+        if (done && !n_rpcs) {
+            break;
+        }
+        pstream_wait(pstream);
+        for (i = 0; i < n_rpcs; i++) {
+            struct jsonrpc *rpc = rpcs[i];
+
+            jsonrpc_wait(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                jsonrpc_recv_wait(rpc);
+            }
+        }
+        poll_block();
+    }
+    free(rpcs);
+    pstream_close(pstream);
+}
+
+static void
+do_request(int argc OVS_UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_request(method, params, NULL);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a valid JSON-RPC request: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+
+    error = jsonrpc_recv_block(rpc, &msg);
+    if (error) {
+        ovs_fatal(error, "error waiting for reply");
+    }
+    print_and_free_json(jsonrpc_msg_to_json(msg));
+
+    jsonrpc_close(rpc);
+}
+
+static void
+do_notify(int argc OVS_UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_notify(method, params);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a JSON RPC-valid notification: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send_block(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+    jsonrpc_close(rpc);
+}
+
+static void
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    usage();
+}
+
+static struct command all_commands[] = {
+    { "listen", 1, 1, do_listen },
+    { "request", 3, 3, do_request },
+    { "notify", 3, 3, do_notify },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-lockfile.c b/tests/test-lockfile.c
new file mode 100644 (file)
index 0000000..f0f4b01
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "process.h"
+#include "timeval.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct test {
+    const char *name;
+    void (*function)(void);
+};
+
+static const struct test tests[];
+
+static void
+run_lock_and_unlock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_and_unlock_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static enum { PARENT, CHILD }
+do_fork(void)
+{
+    switch (fork()) {
+    case 0:
+        time_postfork();
+        lockfile_postfork();
+        return CHILD;
+
+    default:
+        return PARENT;
+
+    case -1:
+        /* Error. */
+        ovs_fatal(errno, "fork failed");
+    }
+}
+
+static void
+run_lock_blocks_other_process(void)
+{
+    /* Making this static prevents a memory leak warning from valgrind for the
+     * parent process, which cannot easily unlock (and free) 'lockfile' because
+     * it can only do so after the child has exited, and it's the caller of
+     * this function that does the wait() call. */
+    static struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", 0, &lockfile) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_twice_blocks_other_process(void)
+{
+    struct lockfile *lockfile, *dummy;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &dummy) == EDEADLK);
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &dummy) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_and_unlock_allows_other_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &lockfile) == 0);
+        exit(11);
+    }
+}
+
+static void
+run_lock_timeout_gets_the_lock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL * 3,
+                             &lockfile) == 0);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_timeout_runs_out(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL,
+                             &lockfile) == ETIMEDOUT);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL * 3) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_multiple(void)
+{
+    struct lockfile *a, *b, *c, *dummy;
+
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("b", 0, &b) == 0);
+    assert(lockfile_lock("c", 0, &c) == 0);
+
+    lockfile_unlock(a);
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("a", 0, &dummy) == EDEADLK);
+    lockfile_unlock(a);
+
+    lockfile_unlock(b);
+    assert(lockfile_lock("a", 0, &a) == 0);
+
+    lockfile_unlock(c);
+    lockfile_unlock(a);
+}
+
+static void
+run_help(void)
+{
+    size_t i;
+
+    printf("usage: %s TESTNAME\n"
+           "where TESTNAME is one of the following:\n",
+           program_name);
+    for (i = 0; tests[i].name; i++) {
+        fprintf(stderr, "\t%s\n", tests[i].name);
+    }
+}
+
+static const struct test tests[] = {
+#define TEST(NAME) { #NAME, run_##NAME }
+    TEST(lock_and_unlock),
+    TEST(lock_and_unlock_twice),
+    TEST(lock_blocks_same_process),
+    TEST(lock_blocks_same_process_twice),
+    TEST(lock_blocks_other_process),
+    TEST(lock_twice_blocks_other_process),
+    TEST(lock_and_unlock_allows_other_process),
+    TEST(lock_timeout_gets_the_lock),
+    TEST(lock_timeout_runs_out),
+    TEST(lock_multiple),
+    TEST(help),
+    { 0, 0 }
+#undef TEST
+};
+
+int
+main(int argc, char *argv[])
+{
+    size_t i;
+
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        ovs_fatal(0, "exactly one argument required; use \"%s help\" for help",
+                  program_name);
+        return 1;
+    }
+
+    for (i = 0; tests[i].name; i++) {
+        if (!strcmp(argv[1], tests[i].name)) {
+            int n_children;
+            int status;
+
+            (tests[i].function)();
+
+            n_children = 0;
+            while (wait(&status) > 0) {
+                if (WIFEXITED(status) && WEXITSTATUS(status) == 11) {
+                    n_children++;
+                } else {
+                    ovs_fatal(0, "child exited in unexpected way: %s",
+                              process_status_msg(status));
+                }
+            }
+            if (errno != ECHILD) {
+                ovs_fatal(errno, "wait");
+            }
+
+            printf("%s: success (%d child%s)\n",
+                   tests[i].name, n_children, n_children != 1 ? "ren" : "");
+            exit(0);
+        }
+    }
+    ovs_fatal(0, "unknown test \"%s\"; use \"%s help\" for help",
+              argv[1], program_name);
+}
+
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
new file mode 100644 (file)
index 0000000..b2ab4c6
--- /dev/null
@@ -0,0 +1,1850 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/log.h"
+#include "ovsdb/mutation.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "stream.h"
+#include "svec.h"
+#include "tests/idltest.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"timeout", required_argument, 0, 't'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        unsigned long int timeout;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout <= 0) {
+                ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+                          optarg);
+            } else {
+                time_alarm(timeout);
+            }
+            break;
+
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+           "  log-io FILE FLAGS COMMAND...\n"
+           "    open FILE with FLAGS, run COMMANDs\n"
+           "  parse-atomic-type TYPE\n"
+           "    parse TYPE as OVSDB atomic type, and re-serialize\n"
+           "  parse-base-type TYPE\n"
+           "    parse TYPE as OVSDB base type, and re-serialize\n"
+           "  parse-type JSON\n"
+           "    parse JSON as OVSDB type, and re-serialize\n"
+           "  parse-atoms TYPE ATOM...\n"
+           "    parse JSON ATOMs as atoms of TYPE, and re-serialize\n"
+           "  parse-atom-strings TYPE ATOM...\n"
+           "    parse string ATOMs as atoms of given TYPE, and re-serialize\n"
+           "  sort-atoms TYPE ATOM...\n"
+           "    print JSON ATOMs in sorted order\n"
+           "  parse-data TYPE DATUM...\n"
+           "    parse JSON DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-data-strings TYPE DATUM...\n"
+           "    parse string DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-column NAME OBJECT\n"
+           "    parse column NAME with info OBJECT, and re-serialize\n"
+           "  parse-table NAME OBJECT\n"
+           "    parse table NAME with info OBJECT\n"
+           "  parse-row TABLE ROW..., and re-serialize\n"
+           "    parse each ROW of defined TABLE\n"
+           "  compare-row TABLE ROW...\n"
+           "    mutually compare all of the ROWs, print those that are equal\n"
+           "  parse-conditions TABLE CONDITION...\n"
+           "    parse each CONDITION on TABLE, and re-serialize\n"
+           "  evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
+           "    test CONDITIONS on TABLE against each ROW, print results\n"
+           "  parse-mutations TABLE MUTATION...\n"
+           "    parse each MUTATION on TABLE, and re-serialize\n"
+           "  execute-mutations TABLE [MUTATION,...] [ROW,...]\n"
+           "    execute MUTATIONS on TABLE on each ROW, print results\n"
+           "  query TABLE [ROW,...] [CONDITION,...]\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION.\n"
+           "  query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION and have distinct COLUMNS.\n"
+           "  parse-schema JSON\n"
+           "    parse JSON as an OVSDB schema, and re-serialize\n"
+           "  transact COMMAND\n"
+           "    execute each specified transactional COMMAND:\n"
+           "      commit\n"
+           "      abort\n"
+           "      insert UUID I J\n"
+           "      delete UUID\n"
+           "      modify UUID I J\n"
+           "      print\n"
+           "  execute SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA\n"
+           "  trigger SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA.   A TRANSACTION of the form\n"
+           "    [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+           "    simulated time, for causing triggers to time out.\n"
+           "  idl SERVER [TRANSACTION...]\n"
+           "    connect to SERVER and dump the contents of the database\n"
+           "    as seen initially by the IDL implementation and after\n"
+           "    executing each TRANSACTION.  (Each TRANSACTION must modify\n"
+           "    the database or this command will hang.)\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -t, --timeout=SECS          give up after SECS seconds\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+    if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+        struct json *inner = json->u.array.elems[0];
+        json->u.array.elems[0] = NULL;
+        json_destroy(json);
+        return inner;
+    } else {
+        return json;
+    }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+print_and_free_ovsdb_error(struct ovsdb_error *error)
+{
+    char *string = ovsdb_error_to_string(error);
+    ovsdb_error_destroy(error);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        char *s = ovsdb_error_to_string(error);
+        ovsdb_error_destroy(error);
+        ovs_fatal(0, "%s", s);
+    }
+}
+
+static void
+die_if_error(char *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", error);
+    }
+}
+\f
+/* Command implementations. */
+
+static void
+do_log_io(int argc, char *argv[])
+{
+    const char *name = argv[1];
+    char *mode_string = argv[2];
+
+    struct ovsdb_error *error;
+    enum ovsdb_log_open_mode mode;
+    struct ovsdb_log *log;
+    int i;
+
+    if (!strcmp(mode_string, "read-only")) {
+        mode = OVSDB_LOG_READ_ONLY;
+    } else if (!strcmp(mode_string, "read/write")) {
+        mode = OVSDB_LOG_READ_WRITE;
+    } else if (!strcmp(mode_string, "create")) {
+        mode = OVSDB_LOG_CREATE;
+    } else {
+        ovs_fatal(0, "unknown log-io open mode \"%s\"", mode_string);
+    }
+
+    check_ovsdb_error(ovsdb_log_open(name, mode, -1, &log));
+    printf("%s: open successful\n", name);
+
+    for (i = 3; i < argc; i++) {
+        const char *command = argv[i];
+        if (!strcmp(command, "read")) {
+            struct json *json;
+
+            error = ovsdb_log_read(log, &json);
+            if (!error) {
+                printf("%s: read: ", name);
+                if (json) {
+                    print_and_free_json(json);
+                } else {
+                    printf("end of log\n");
+                }
+                continue;
+            }
+        } else if (!strncmp(command, "write:", 6)) {
+            struct json *json = parse_json(command + 6);
+            error = ovsdb_log_write(log, json);
+            json_destroy(json);
+        } else if (!strcmp(command, "commit")) {
+            error = ovsdb_log_commit(log);
+        } else {
+            ovs_fatal(0, "unknown log-io command \"%s\"", command);
+        }
+        if (error) {
+            char *s = ovsdb_error_to_string(error);
+            printf("%s: %s failed: %s\n", name, command, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        } else {
+            printf("%s: %s successful\n", name, command);
+        }
+    }
+
+    ovsdb_log_close(log);
+}
+
+static void
+do_parse_atomic_type(int argc OVS_UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
+do_parse_base_type(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_base_type base;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_base_type_to_json(&base));
+    ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_type(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_type_to_json(&type));
+    ovsdb_type_destroy(&type);
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+    struct ovsdb_base_type base;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_error *error;
+        union ovsdb_atom atom;
+
+        json = unbox_json(parse_json(argv[i]));
+        error = ovsdb_atom_from_json(&atom, &base, json, NULL);
+        json_destroy(json);
+
+        if (error) {
+            print_and_free_ovsdb_error(error);
+        } else {
+            print_and_free_json(ovsdb_atom_to_json(&atom, base.type));
+            ovsdb_atom_destroy(&atom, base.type);
+        }
+    }
+    ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_atom_strings(int argc, char *argv[])
+{
+    struct ovsdb_base_type base;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        union ovsdb_atom atom;
+        struct ds out;
+
+        die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i]));
+
+        ds_init(&out);
+        ovsdb_atom_to_string(&atom, base.type, &out);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+        ovsdb_atom_destroy(&atom, base.type);
+    }
+    ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+    ovsdb_type_destroy(&type);
+}
+
+static void
+do_parse_data_strings(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+        struct ds out;
+
+        die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i]));
+
+        ds_init(&out);
+        ovsdb_datum_to_string(&datum, &type, &out);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+    ovsdb_type_destroy(&type);
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+    const union ovsdb_atom *a = a_;
+    const union ovsdb_atom *b = b_;
+
+    return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
+do_sort_atoms(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_base_type base;
+    union ovsdb_atom *atoms;
+    struct json *json, **json_atoms;
+    size_t n_atoms;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+    json_destroy(json);
+
+    json = unbox_json(parse_json(argv[2]));
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "second argument must be array");
+    }
+
+    /* Convert JSON atoms to internal representation. */
+    n_atoms = json->u.array.n;
+    atoms = xmalloc(n_atoms * sizeof *atoms);
+    for (i = 0; i < n_atoms; i++) {
+        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], &base,
+                                               json->u.array.elems[i], NULL));
+    }
+    json_destroy(json);
+
+    /* Sort atoms. */
+    compare_atoms_atomic_type = base.type;
+    qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+    /* Convert internal representation back to JSON. */
+    json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+    for (i = 0; i < n_atoms; i++) {
+        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], base.type);
+        ovsdb_atom_destroy(&atoms[i], base.type);
+    }
+    print_and_free_json(json_array_create(json_atoms, n_atoms));
+    free(atoms);
+    ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_column(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_column *column;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+    json_destroy(json);
+    print_and_free_json(ovsdb_column_to_json(column));
+    ovsdb_column_destroy(column);
+}
+
+static void
+do_parse_table(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+    json_destroy(json);
+    print_and_free_json(ovsdb_table_schema_to_json(ts));
+    ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_column_set columns;
+        struct ovsdb_row *row;
+
+        ovsdb_column_set_init(&columns);
+        row = ovsdb_row_create(table);
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+        if (columns.n_columns) {
+            struct svec names;
+            size_t j;
+            char *s;
+
+            svec_init(&names);
+            for (j = 0; j < columns.n_columns; j++) {
+                svec_add(&names, columns.columns[j]->name);
+            }
+            svec_sort(&names);
+            s = svec_join(&names, ", ", "");
+            puts(s);
+            free(s);
+            svec_destroy(&names);
+        } else {
+            printf("<none>\n");
+        }
+
+        ovsdb_column_set_destroy(&columns);
+        ovsdb_row_destroy(row);
+    }
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_row **rows;
+    struct json *json;
+    char **names;
+    int n_rows;
+    int i, j;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    n_rows = argc - 2;
+    rows = xmalloc(sizeof *rows * n_rows);
+    names = xmalloc(sizeof *names * n_rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+
+        json = parse_json(argv[i + 2]);
+        if (json->type != JSON_ARRAY || json->u.array.n != 2
+            || json->u.array.elems[0]->type != JSON_STRING) {
+            ovs_fatal(0, "\"%s\" does not have expected form "
+                      "[\"name\", {data}]", argv[i]);
+        }
+        names[i] = xstrdup(json->u.array.elems[0]->u.string);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+                                              NULL, NULL));
+        json_destroy(json);
+    }
+    for (i = 0; i < n_rows; i++) {
+        uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+        for (j = i + 1; j < n_rows; j++) {
+            uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+            if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+                printf("%s == %s\n", names[i], names[j]);
+                if (i_hash != j_hash) {
+                    printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+                    abort();
+                }
+            } else if (i_hash == j_hash) {
+                printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+            }
+        }
+    }
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+        free(names[i]);
+    }
+    free(rows);
+    free(names);
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_condition cnd;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+        if (!error) {
+            print_and_free_json(ovsdb_condition_to_json(&cnd));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_evaluate_conditions(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_condition *conditions;
+    size_t n_conditions;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse conditions. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    n_conditions = json->u.array.n;
+    conditions = xmalloc(n_conditions * sizeof *conditions);
+    for (i = 0; i < n_conditions; i++) {
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &conditions[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_conditions; i++) {
+        printf("condition %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            putchar(result ? 'T' : '-');
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_conditions; i++) {
+        ovsdb_condition_destroy(&conditions[i]);
+    }
+    free(conditions);
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    free(rows);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_mutations(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_mutation_set set;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_mutation_set_from_json(ts, json, NULL, &set);
+        if (!error) {
+            print_and_free_json(ovsdb_mutation_set_to_json(&set));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_mutation_set_destroy(&set);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_execute_mutations(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_mutation_set *sets;
+    size_t n_sets;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse mutations. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "MUTATION argument is not JSON array");
+    }
+    n_sets = json->u.array.n;
+    sets = xmalloc(n_sets * sizeof *sets);
+    for (i = 0; i < n_sets; i++) {
+        check_ovsdb_error(ovsdb_mutation_set_from_json(ts,
+                                                       json->u.array.elems[i],
+                                                       NULL, &sets[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_sets; i++) {
+        printf("mutation %2d:\n", i);
+        for (j = 0; j < n_rows; j++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_clone(rows[j]);
+            error = ovsdb_mutation_set_execute(row, &sets[i]);
+
+            printf("row %zu: ", j);
+            if (error) {
+                print_and_free_ovsdb_error(error);
+            } else {
+                struct ovsdb_column_set columns;
+                struct shash_node *node;
+
+                ovsdb_column_set_init(&columns);
+                SHASH_FOR_EACH (node, &ts->columns) {
+                    struct ovsdb_column *c = node->data;
+                    if (!ovsdb_datum_equals(&row->fields[c->index],
+                                            &rows[j]->fields[c->index],
+                                            &c->type)) {
+                        ovsdb_column_set_add(&columns, c);
+                    }
+                }
+                if (columns.n_columns) {
+                    print_and_free_json(ovsdb_row_to_json(row, &columns));
+                } else {
+                    printf("no change\n");
+                }
+                ovsdb_column_set_destroy(&columns);
+            }
+            ovsdb_row_destroy(row);
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_sets; i++) {
+        ovsdb_mutation_set_destroy(&sets[i]);
+    }
+    free(sets);
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    free(rows);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+struct do_query_cbdata {
+    struct uuid *row_uuids;
+    int *counts;
+    size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+    struct do_query_cbdata *cbdata = cbdata_;
+    size_t i;
+
+    for (i = 0; i < cbdata->n_rows; i++) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+            cbdata->counts[i]++;
+        }
+    }
+
+    return true;
+}
+
+static void
+do_query(int argc OVS_UNUSED, char *argv[])
+{
+    struct do_query_cbdata cbdata;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int exit_code = 0;
+    size_t i;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    cbdata.n_rows = json->u.array.n;
+    cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+    cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+    for (i = 0; i < cbdata.n_rows; i++) {
+        struct ovsdb_row *row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+        ovsdb_table_put_row(table, row);
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_condition cnd;
+        size_t j;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+        ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+        printf("query %2d:", i);
+        for (j = 0; j < cbdata.n_rows; j++) {
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (cbdata.counts[j]) {
+                printf("%d", cbdata.counts[j]);
+                if (cbdata.counts[j] > 1) {
+                    /* Dup! */
+                    exit_code = 1;
+                }
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+struct do_query_distinct_class {
+    struct ovsdb_row *example;
+    int count;
+};
+
+struct do_query_distinct_row {
+    struct uuid uuid;
+    struct do_query_distinct_class *class;
+};
+
+static void
+do_query_distinct(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_column_set columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct do_query_distinct_row *rows;
+    size_t n_rows;
+    struct do_query_distinct_class *classes;
+    size_t n_classes;
+    struct json *json;
+    int exit_code = 0;
+    size_t i, j, k;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse column set. */
+    json = parse_json(argv[4]);
+    check_ovsdb_error(ovsdb_column_set_from_json(json, table, &columns));
+    json_destroy(json);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    classes = xmalloc(n_rows * sizeof *classes);
+    n_classes = 0;
+    for (i = 0; i < n_rows; i++) {
+        struct ovsdb_row *row;
+        size_t j;
+
+        /* Parse row. */
+        row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+
+        /* Initialize row and find equivalence class. */
+        rows[i].uuid = *ovsdb_row_get_uuid(row);
+        rows[i].class = NULL;
+        for (j = 0; j < n_classes; j++) {
+            if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+                rows[i].class = &classes[j];
+                break;
+            }
+        }
+        if (!rows[i].class) {
+            rows[i].class = &classes[n_classes];
+            classes[n_classes].example = ovsdb_row_clone(row);
+            n_classes++;
+        }
+
+        /* Add row to table. */
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        ovsdb_table_put_row(table, row);
+
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_row_set results;
+        struct ovsdb_condition cnd;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        for (j = 0; j < n_classes; j++) {
+            classes[j].count = 0;
+        }
+        ovsdb_row_set_init(&results);
+        ovsdb_query_distinct(table, &cnd, &columns, &results);
+        for (j = 0; j < results.n_rows; j++) {
+            for (k = 0; k < n_rows; k++) {
+                if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+                                &rows[k].uuid)) {
+                    rows[k].class->count++;
+                }
+            }
+        }
+        ovsdb_row_set_destroy(&results);
+
+        printf("query %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            int count = rows[j].class->count;
+
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (count > 1) {
+                /* Dup! */
+                printf("%d", count);
+                exit_code = 1;
+            } else if (count == 1) {
+                putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+static void
+do_parse_schema(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    print_and_free_json(ovsdb_schema_to_json(schema));
+    ovsdb_schema_destroy(schema);
+}
+
+static void
+do_execute(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+    struct ovsdb *db;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    for (i = 2; i < argc; i++) {
+        struct json *params, *result;
+        char *s;
+
+        params = parse_json(argv[i]);
+        result = ovsdb_execute(db, params, 0, NULL);
+        s = json_to_string(result, JSSF_SORT);
+        printf("%s\n", s);
+        free(s);
+        json_destroy(params);
+        json_destroy(result);
+    }
+
+    ovsdb_destroy(db);
+}
+
+struct test_trigger {
+    struct ovsdb_trigger trigger;
+    int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+    struct json *result;
+    char *s;
+
+    result = ovsdb_trigger_steal_result(&t->trigger);
+    s = json_to_string(result, JSSF_SORT);
+    printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+    free(s);
+    json_destroy(result);
+    ovsdb_trigger_destroy(&t->trigger);
+    free(t);
+}
+
+static void
+do_trigger(int argc OVS_UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct list completions;
+    struct json *json;
+    struct ovsdb *db;
+    long long int now;
+    int number;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    list_init(&completions);
+    now = 0;
+    number = 0;
+    for (i = 2; i < argc; i++) {
+        struct json *params = parse_json(argv[i]);
+        if (params->type == JSON_ARRAY
+            && json_array(params)->n == 2
+            && json_array(params)->elems[0]->type == JSON_STRING
+            && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+            && json_array(params)->elems[1]->type == JSON_INTEGER) {
+            now += json_integer(json_array(params)->elems[1]);
+            json_destroy(params);
+        } else {
+            struct test_trigger *t = xmalloc(sizeof *t);
+            ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+            t->number = number++;
+            if (ovsdb_trigger_is_complete(&t->trigger)) {
+                do_trigger_dump(t, now, "immediate");
+            } else {
+                printf("t=%lld: new trigger %d\n", now, t->number);
+            }
+        }
+
+        ovsdb_trigger_run(db, now);
+        while (!list_is_empty(&completions)) {
+            do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+                                         struct test_trigger, trigger.node),
+                            now, "delayed");
+        }
+
+        ovsdb_trigger_wait(db, now);
+        poll_immediate_wake();
+        poll_block();
+    }
+
+    ovsdb_destroy(db);
+}
+
+static void
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    usage();
+}
+\f
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
+do_transact_commit(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    ovsdb_txn_commit(do_transact_txn, false);
+    do_transact_txn = NULL;
+}
+
+static void
+do_transact_abort(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    ovsdb_txn_abort(do_transact_txn);
+    do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+    uuid_zero(uuid);
+    uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+    const struct ovsdb_row *row;
+    struct uuid uuid;
+
+    uuid_from_integer(atoi(uuid_string), &uuid);
+    row = ovsdb_table_get_row(do_transact_table, &uuid);
+    if (!row) {
+        ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+                  UUID_ARGS(&uuid));
+    }
+    return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+                        int integer)
+{
+    if (integer != -1) {
+        const struct ovsdb_column *column;
+
+        column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                               column_name);
+        row->fields[column->index].keys[0].integer = integer;
+    }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+    const struct ovsdb_column *column;
+
+    column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                           column_name);
+    return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+                    const char *i_string, const char *j_string)
+{
+    do_transact_set_integer(row, "i", atoi(i_string));
+    do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
+do_transact_insert(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    struct ovsdb_row *row;
+    struct uuid *uuid;
+
+    row = ovsdb_row_create(do_transact_table);
+
+    /* Set UUID. */
+    uuid = ovsdb_row_get_uuid_rw(row);
+    uuid_from_integer(atoi(argv[1]), uuid);
+    if (ovsdb_table_get_row(do_transact_table, uuid)) {
+        ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+                  UUID_ARGS(uuid));
+    }
+
+    do_transact_set_i_j(row, argv[2], argv[3]);
+
+    /* Insert row. */
+    ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
+do_transact_delete(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+    ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
+do_transact_modify(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    const struct ovsdb_row *row_ro;
+    struct ovsdb_row *row_rw;
+
+    row_ro = do_transact_find_row(argv[1]);
+    row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+    do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+    struct ovsdb_row *const *ap = a_;
+    struct ovsdb_row *const *bp = b_;
+
+    return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
+do_transact_print(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    const struct ovsdb_row **rows;
+    const struct ovsdb_row *row;
+    size_t n_rows;
+    size_t i;
+
+    n_rows = hmap_count(&do_transact_table->rows);
+    rows = xmalloc(n_rows * sizeof *rows);
+    i = 0;
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                   &do_transact_table->rows) {
+        rows[i++] = row;
+    }
+    assert(i == n_rows);
+
+    qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+    for (i = 0; i < n_rows; i++) {
+        printf("\n%"PRId32": i=%d, j=%d",
+               ovsdb_row_get_uuid(rows[i])->parts[3],
+               do_transact_get_integer(rows[i], "i"),
+               do_transact_get_integer(rows[i], "j"));
+    }
+
+    free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+    static const struct command do_transact_commands[] = {
+        { "commit", 0, 0, do_transact_commit },
+        { "abort", 0, 0, do_transact_abort },
+        { "insert", 2, 3, do_transact_insert },
+        { "delete", 1, 1, do_transact_delete },
+        { "modify", 2, 3, do_transact_modify },
+        { "print", 0, 0, do_transact_print },
+        { NULL, 0, 0, NULL },
+    };
+
+    struct ovsdb_schema *schema;
+    struct json *json;
+    int i;
+
+    /* Create table. */
+    json = parse_json("{\"name\": \"testdb\", "
+                      " \"tables\": "
+                      "  {\"mytable\": "
+                      "    {\"columns\": "
+                      "      {\"i\": {\"type\": \"integer\"}, "
+                      "       \"j\": {\"type\": \"integer\"}}}}}");
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    do_transact_db = ovsdb_create(schema);
+    do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+    assert(do_transact_table != NULL);
+
+    for (i = 1; i < argc; i++) {
+        struct json *command;
+        size_t n_args;
+        char **args;
+        int j;
+
+        command = parse_json(argv[i]);
+        if (command->type != JSON_ARRAY) {
+            ovs_fatal(0, "transaction %d must be JSON array "
+                      "with at least 1 element", i);
+        }
+
+        n_args = command->u.array.n;
+        args = xmalloc((n_args + 1) * sizeof *args);
+        for (j = 0; j < n_args; j++) {
+            struct json *s = command->u.array.elems[j];
+            if (s->type != JSON_STRING) {
+                ovs_fatal(0, "transaction %d argument %d must be JSON string",
+                          i, j);
+            }
+            args[j] = xstrdup(json_string(s));
+        }
+        args[n_args] = NULL;
+
+        if (!do_transact_txn) {
+            do_transact_txn = ovsdb_txn_create(do_transact_db);
+        }
+
+        for (j = 0; j < n_args; j++) {
+            if (j) {
+                putchar(' ');
+            }
+            fputs(args[j], stdout);
+        }
+        fputs(":", stdout);
+        run_command(n_args, args, do_transact_commands);
+        putchar('\n');
+
+        for (j = 0; j < n_args; j++) {
+            free(args[j]);
+        }
+        free(args);
+        json_destroy(command);
+    }
+    ovsdb_txn_abort(do_transact_txn);
+    ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static int
+compare_link1(const void *a_, const void *b_)
+{
+    const struct idltest_link1 *const *ap = a_;
+    const struct idltest_link1 *const *bp = b_;
+    const struct idltest_link1 *a = *ap;
+    const struct idltest_link1 *b = *bp;
+
+    return a->i < b->i ? -1 : a->i > b->i;
+}
+
+static void
+print_idl(struct ovsdb_idl *idl, int step)
+{
+    const struct idltest_simple *s;
+    const struct idltest_link1 *l1;
+    const struct idltest_link2 *l2;
+    int n = 0;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        size_t i;
+
+        printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[",
+               step, s->i, s->r, s->b ? "true" : "false",
+               s->s, UUID_ARGS(&s->u));
+        for (i = 0; i < s->n_ia; i++) {
+            printf("%s%"PRId64, i ? " " : "", s->ia[i]);
+        }
+        printf("] ra=[");
+        for (i = 0; i < s->n_ra; i++) {
+            printf("%s%g", i ? " " : "", s->ra[i]);
+        }
+        printf("] ba=[");
+        for (i = 0; i < s->n_ba; i++) {
+            printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false");
+        }
+        printf("] sa=[");
+        for (i = 0; i < s->n_sa; i++) {
+            printf("%s%s", i ? " " : "", s->sa[i]);
+        }
+        printf("] ua=[");
+        for (i = 0; i < s->n_ua; i++) {
+            printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i]));
+        }
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid));
+        n++;
+    }
+    IDLTEST_LINK1_FOR_EACH (l1, idl) {
+        struct idltest_link1 **links;
+        size_t i;
+
+        printf("%03d: i=%"PRId64" k=", step, l1->i);
+        if (l1->k) {
+            printf("%"PRId64, l1->k->i);
+        }
+        printf(" ka=[");
+        links = xmemdup(l1->ka, l1->n_ka * sizeof *l1->ka);
+        qsort(links, l1->n_ka, sizeof *links, compare_link1);
+        for (i = 0; i < l1->n_ka; i++) {
+            printf("%s%"PRId64, i ? " " : "", links[i]->i);
+        }
+        free(links);
+        printf("] l2=");
+        if (l1->l2) {
+            printf("%"PRId64, l1->l2->i);
+        }
+        printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l1->header_.uuid));
+        n++;
+    }
+    IDLTEST_LINK2_FOR_EACH (l2, idl) {
+        printf("%03d: i=%"PRId64" l1=", step, l2->i);
+        if (l2->l1) {
+            printf("%"PRId64, l2->l1->i);
+        }
+        printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l2->header_.uuid));
+        n++;
+    }
+    if (!n) {
+        printf("%03d: empty\n", step);
+    }
+}
+
+static unsigned int
+print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc,
+                  int step, unsigned int seqno)
+{
+    for (;;) {
+        unsigned int new_seqno;
+
+        if (rpc) {
+            jsonrpc_run(rpc);
+        }
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            print_idl(idl, step);
+            return new_seqno;
+        }
+
+        if (rpc) {
+            jsonrpc_wait(rpc);
+        }
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab,
+            size_t *n)
+{
+    struct uuid uuid;
+
+    if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) {
+        char *name = xasprintf("#%d#", *n);
+        fprintf(stderr, "%s = "UUID_FMT"\n", name, UUID_ARGS(&uuid));
+        ovsdb_symbol_table_put(symtab, name, &uuid, false);
+        free(name);
+        *n += 1;
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            parse_uuids(json->u.array.elems[i], symtab, n);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            parse_uuids(node->data, symtab, n);
+        }
+    }
+}
+
+static void
+substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab)
+{
+    if (json->type == JSON_STRING) {
+        const struct ovsdb_symbol *symbol;
+
+        symbol = ovsdb_symbol_table_get(symtab, json->u.string);
+        if (symbol) {
+            free(json->u.string);
+            json->u.string = xasprintf(UUID_FMT, UUID_ARGS(&symbol->uuid));
+        }
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            substitute_uuids(json->u.array.elems[i], symtab);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            substitute_uuids(node->data, symtab);
+        }
+    }
+}
+
+static const struct idltest_simple *
+idltest_find_simple(struct ovsdb_idl *idl, int i)
+{
+    const struct idltest_simple *s;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        if (s->i == i) {
+            return s;
+        }
+    }
+    return NULL;
+}
+
+static void
+idl_set(struct ovsdb_idl *idl, char *commands, int step)
+{
+    char *cmd, *save_ptr1 = NULL;
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+    bool increment = false;
+
+    txn = ovsdb_idl_txn_create(idl);
+    for (cmd = strtok_r(commands, ",", &save_ptr1); cmd;
+         cmd = strtok_r(NULL, ",", &save_ptr1)) {
+        char *save_ptr2 = NULL;
+        char *name, *arg1, *arg2, *arg3;
+
+        name = strtok_r(cmd, " ", &save_ptr2);
+        arg1 = strtok_r(NULL, " ", &save_ptr2);
+        arg2 = strtok_r(NULL, " ", &save_ptr2);
+        arg3 = strtok_r(NULL, " ", &save_ptr2);
+
+        if (!strcmp(name, "set")) {
+            const struct idltest_simple *s;
+
+            if (!arg3) {
+                ovs_fatal(0, "\"set\" command requires 3 arguments");
+            }
+
+            s = idltest_find_simple(idl, atoi(arg1));
+            if (!s) {
+                ovs_fatal(0, "\"set\" command asks for nonexistent "
+                          "i=%d", atoi(arg1));
+            }
+
+            if (!strcmp(arg2, "b")) {
+                idltest_simple_set_b(s, atoi(arg3));
+            } else if (!strcmp(arg2, "s")) {
+                idltest_simple_set_s(s, arg3);
+            } else if (!strcmp(arg2, "u")) {
+                struct uuid uuid;
+                uuid_from_string(&uuid, arg3);
+                idltest_simple_set_u(s, uuid);
+            } else if (!strcmp(arg2, "r")) {
+                idltest_simple_set_r(s, atof(arg3));
+            } else {
+                ovs_fatal(0, "\"set\" command asks for unknown column %s",
+                          arg2);
+            }
+        } else if (!strcmp(name, "insert")) {
+            struct idltest_simple *s;
+
+            if (!arg1 || arg2) {
+                ovs_fatal(0, "\"set\" command requires 1 argument");
+            }
+
+            s = idltest_simple_insert(txn);
+            idltest_simple_set_i(s, atoi(arg1));
+        } else if (!strcmp(name, "delete")) {
+            const struct idltest_simple *s;
+
+            if (!arg1 || arg2) {
+                ovs_fatal(0, "\"set\" command requires 1 argument");
+            }
+
+            s = idltest_find_simple(idl, atoi(arg1));
+            if (!s) {
+                ovs_fatal(0, "\"set\" command asks for nonexistent "
+                          "i=%d", atoi(arg1));
+            }
+            idltest_simple_delete(s);
+        } else if (!strcmp(name, "increment")) {
+            if (!arg2 || arg3) {
+                ovs_fatal(0, "\"set\" command requires 2 arguments");
+            }
+            ovsdb_idl_txn_increment(txn, arg1, arg2, NULL);
+            increment = true;
+        } else {
+            ovs_fatal(0, "unknown command %s", name);
+        }
+    }
+
+    status = ovsdb_idl_txn_commit_block(txn);
+    printf("%03d: commit, status=%s",
+           step, ovsdb_idl_txn_status_to_string(status));
+    if (increment) {
+        printf(", increment=%"PRId64,
+               ovsdb_idl_txn_get_increment_new_value(txn));
+    }
+    putchar('\n');
+    ovsdb_idl_txn_destroy(txn);
+}
+
+static void
+do_idl(int argc, char *argv[])
+{
+    struct jsonrpc *rpc;
+    struct ovsdb_idl *idl;
+    unsigned int seqno = 0;
+    struct ovsdb_symbol_table *symtab;
+    size_t n_uuids = 0;
+    int step = 0;
+    int error;
+    int i;
+
+    idltest_init();
+
+    idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
+    if (argc > 2) {
+        struct stream *stream;
+
+        error = stream_open_block(argv[1], &stream);
+        if (error) {
+            ovs_fatal(error, "failed to connect to \"%s\"", argv[1]);
+        }
+        rpc = jsonrpc_open(stream);
+    } else {
+        rpc = NULL;
+    }
+
+    setvbuf(stdout, NULL, _IOLBF, 0);
+
+    symtab = ovsdb_symbol_table_create();
+    for (i = 2; i < argc; i++) {
+        char *arg = argv[i];
+        struct jsonrpc_msg *request, *reply;
+        int error;
+
+        if (*arg == '+') {
+            /* The previous transaction didn't change anything. */
+            arg++;
+        } else {
+            seqno = print_updated_idl(idl, rpc, step++, seqno);
+        }
+
+        if (!strcmp(arg, "reconnect")) {
+            printf("%03d: reconnect\n", step++);
+            ovsdb_idl_force_reconnect(idl);
+        } else if (arg[0] != '[') {
+            idl_set(idl, arg, step++);
+        } else {
+            struct json *json = parse_json(arg);
+            substitute_uuids(json, symtab);
+            request = jsonrpc_create_request("transact", json, NULL);
+            error = jsonrpc_transact_block(rpc, request, &reply);
+            if (error) {
+                ovs_fatal(error, "jsonrpc transaction failed");
+            }
+            printf("%03d: ", step++);
+            if (reply->result) {
+                parse_uuids(reply->result, symtab, &n_uuids);
+            }
+            json_destroy(reply->id);
+            reply->id = NULL;
+            print_and_free_json(jsonrpc_msg_to_json(reply));
+        }
+    }
+    ovsdb_symbol_table_destroy(symtab);
+
+    if (rpc) {
+        jsonrpc_close(rpc);
+    }
+    print_updated_idl(idl, NULL, step++, seqno);
+    ovsdb_idl_destroy(idl);
+    printf("%03d: done\n", step);
+}
+
+static struct command all_commands[] = {
+    { "log-io", 2, INT_MAX, do_log_io },
+    { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+    { "parse-base-type", 1, 1, do_parse_base_type },
+    { "parse-type", 1, 1, do_parse_type },
+    { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+    { "parse-atom-strings", 2, INT_MAX, do_parse_atom_strings },
+    { "parse-data", 2, INT_MAX, do_parse_data },
+    { "parse-data-strings", 2, INT_MAX, do_parse_data_strings },
+    { "sort-atoms", 2, 2, do_sort_atoms },
+    { "parse-column", 2, 2, do_parse_column },
+    { "parse-table", 2, 2, do_parse_table },
+    { "parse-rows", 2, INT_MAX, do_parse_rows },
+    { "compare-rows", 2, INT_MAX, do_compare_rows },
+    { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+    { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+    { "parse-mutations", 2, INT_MAX, do_parse_mutations },
+    { "execute-mutations", 3, 3, do_execute_mutations },
+    { "query", 3, 3, do_query },
+    { "query-distinct", 4, 4, do_query_distinct },
+    { "transact", 1, INT_MAX, do_transact },
+    { "parse-schema", 1, 1, do_parse_schema },
+    { "execute", 2, INT_MAX, do_execute },
+    { "trigger", 2, INT_MAX, do_trigger },
+    { "idl", 1, INT_MAX, do_idl },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
new file mode 100644 (file)
index 0000000..93991ff
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "reconnect.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "svec.h"
+#include "util.h"
+
+static struct reconnect *reconnect;
+static int now;
+
+static const struct command commands[];
+
+static void diff_stats(const struct reconnect_stats *old,
+                       const struct reconnect_stats *new);
+
+int
+main(void)
+{
+    struct reconnect_stats prev;
+    unsigned int old_max_tries;
+    int old_time;
+    char line[128];
+
+    now = 1000;
+    reconnect = reconnect_create(now);
+    reconnect_set_name(reconnect, "remote");
+    reconnect_get_stats(reconnect, now, &prev);
+    printf("### t=%d ###\n", now);
+    old_time = now;
+    old_max_tries = reconnect_get_max_tries(reconnect);
+    while (fgets(line, sizeof line, stdin)) {
+        struct reconnect_stats cur;
+        struct svec args;
+
+        fputs(line, stdout);
+        if (line[0] == '#') {
+            continue;
+        }
+
+        svec_init(&args);
+        svec_parse_words(&args, line);
+        svec_terminate(&args);
+        if (!svec_is_empty(&args)) {
+            run_command(args.n, args.names, commands);
+        }
+        svec_destroy(&args);
+
+        if (old_time != now) {
+            printf("\n### t=%d ###\n", now);
+            old_time = now;
+        }
+
+        reconnect_get_stats(reconnect, now, &cur);
+        diff_stats(&prev, &cur);
+        prev = cur;
+        if (reconnect_get_max_tries(reconnect) != old_max_tries) {
+            old_max_tries = reconnect_get_max_tries(reconnect);
+            printf("  %u tries left\n", old_max_tries);
+        }
+    }
+
+    return 0;
+}
+
+static void
+do_enable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_enable(reconnect, now);
+}
+
+static void
+do_disable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_disable(reconnect, now);
+}
+
+static void
+do_force_reconnect(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_force_reconnect(reconnect, now);
+}
+
+static int
+error_from_string(const char *s)
+{
+    if (!s) {
+        return 0;
+    } else if (!strcmp(s, "ECONNREFUSED")) {
+        return ECONNREFUSED;
+    } else if (!strcmp(s, "EOF")) {
+        return EOF;
+    } else {
+        ovs_fatal(0, "unknown error '%s'", s);
+    }
+}
+
+static void
+do_disconnected(int argc OVS_UNUSED, char *argv[])
+{
+    reconnect_disconnected(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connecting(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_connecting(reconnect, now);
+}
+
+static void
+do_connect_failed(int argc OVS_UNUSED, char *argv[])
+{
+    reconnect_connect_failed(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connected(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_connected(reconnect, now);
+}
+
+static void
+do_received(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    reconnect_received(reconnect, now);
+}
+
+static void
+do_run(int argc, char *argv[])
+{
+    enum reconnect_action action;
+
+    if (argc > 1) {
+        now += atoi(argv[1]);
+    }
+
+    action = reconnect_run(reconnect, now);
+    switch (action) {
+    default:
+        if (action != 0) {
+            NOT_REACHED();
+        }
+        break;
+
+    case RECONNECT_CONNECT:
+        printf("  should connect\n");
+        break;
+
+    case RECONNECT_DISCONNECT:
+        printf("  should disconnect\n");
+        break;
+
+    case RECONNECT_PROBE:
+        printf("  should send probe\n");
+        break;
+    }
+}
+
+static void
+do_advance(int argc OVS_UNUSED, char *argv[])
+{
+    now += atoi(argv[1]);
+}
+
+static void
+do_timeout(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    int timeout = reconnect_timeout(reconnect, now);
+    if (timeout >= 0) {
+        printf("  advance %d ms\n", timeout);
+        now += timeout;
+    } else {
+        printf("  no timeout\n");
+    }
+}
+
+static void
+do_set_max_tries(int argc OVS_UNUSED, char *argv[])
+{
+    reconnect_set_max_tries(reconnect, atoi(argv[1]));
+}
+
+static void
+diff_stats(const struct reconnect_stats *old,
+           const struct reconnect_stats *new)
+{
+    if (old->state != new->state
+        || old->state_elapsed != new->state_elapsed
+        || old->backoff != new->backoff) {
+        printf("  in %s for %u ms (%d ms backoff)\n",
+               new->state, new->state_elapsed, new->backoff);
+    }
+    if (old->creation_time != new->creation_time
+        || old->last_received != new->last_received
+        || old->last_connected != new->last_connected) {
+        printf("  created %lld, last received %lld, last connected %lld\n",
+               new->creation_time, new->last_received, new->last_connected);
+    }
+    if (old->n_successful_connections != new->n_successful_connections
+        || old->n_attempted_connections != new->n_attempted_connections
+        || old->seqno != new->seqno) {
+        printf("  %u successful connections out of %u attempts, seqno %u\n",
+               new->n_successful_connections, new->n_attempted_connections,
+               new->seqno);
+    }
+    if (old->is_connected != new->is_connected
+        || old->current_connection_duration != new->current_connection_duration
+        || old->total_connected_duration != new->total_connected_duration) {
+        printf("  %sconnected (%u ms), total %u ms connected\n",
+               new->is_connected ? "" : "not ",
+               new->current_connection_duration,
+               new->total_connected_duration);
+    }
+}
+
+static const struct command commands[] = {
+    { "enable", 0, 0, do_enable },
+    { "disable", 0, 0, do_disable },
+    { "force-reconnect", 0, 0, do_force_reconnect },
+    { "disconnected", 0, 1, do_disconnected },
+    { "connecting", 0, 0, do_connecting },
+    { "connect-failed", 0, 1, do_connect_failed },
+    { "connected", 0, 0, do_connected },
+    { "received", 0, 0, do_received },
+    { "run", 0, 1, do_run },
+    { "advance", 1, 1, do_advance },
+    { "timeout", 0, 0, do_timeout },
+    { "set-max-tries", 1, 1, do_set_max_tries },
+    { NULL, 0, 0, NULL },
+};
+
diff --git a/tests/test-stp-ieee802.1d-1998 b/tests/test-stp-ieee802.1d-1998
deleted file mode 100644 (file)
index f1982a0..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# This is the STP example from IEEE 802.1D-1998.
-bridge 0 0x42 = a b
-bridge 1 0x97 = c:5 a d:5
-bridge 2 0x45 = b e
-bridge 3 0x57 = b:5 e:5
-bridge 4 0x83 = a:5 e:5
-run 1000
-check 0 = root
-check 1 = F F:10 F
-check 2 = F:10 B
-check 3 = F:5 F
-check 4 = F:5 B
diff --git a/tests/test-stp-ieee802.1d-2004-fig17.4 b/tests/test-stp-ieee802.1d-2004-fig17.4
deleted file mode 100644 (file)
index 1f70863..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# This is the STP example from IEEE 802.1D-2004 figures 17.4 and 17.5.
-bridge 0 0x111 = a b e c
-bridge 1 0x222 = a b d f
-bridge 2 0x333 = c d l j h g
-bridge 3 0x444 = e f n m k i
-bridge 4 0x555 = g i 0 0
-bridge 5 0x666 = h k 0 0
-bridge 6 0x777 = j m 0 0
-bridge 7 0x888 = l n 0 0
-run 1000
-check 0 = root
-check 1 = F:10 B F F
-check 2 = F:10 B F F F F
-check 3 = F:10 B F F F F
-check 4 = F:20 B F F
-check 5 = F:20 B F F
-check 6 = F:20 B F F
-check 7 = F:20 B F F
-
-# Now connect two ports of bridge 7 to the same LAN.
-bridge 7 = l n o o
-# Same results except for bridge 7:
-run 1000
-check 0 = root
-check 1 = F:10 B F F
-check 2 = F:10 B F F F F
-check 3 = F:10 B F F F F
-check 4 = F:20 B F F
-check 5 = F:20 B F F
-check 6 = F:20 B F F
-check 7 = F:20 B F B
diff --git a/tests/test-stp-ieee802.1d-2004-fig17.6 b/tests/test-stp-ieee802.1d-2004-fig17.6
deleted file mode 100644 (file)
index 6ed5917..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-# This is the STP example from IEEE 802.1D-2004 figure 17.6.
-bridge 0 0x111 = a b l
-bridge 1 0x222 = b c d
-bridge 2 0x333 = d e f
-bridge 3 0x444 = f g h
-bridge 4 0x555 = j h i
-bridge 5 0x666 = l j k
-run 1000
-check 0 = root
-check 1 = F:10 F F
-check 2 = F:20 F F
-check 3 = F:30 F B
-check 4 = F:20 F F
-check 5 = F:10 F F
diff --git a/tests/test-stp-ieee802.1d-2004-fig17.7 b/tests/test-stp-ieee802.1d-2004-fig17.7
deleted file mode 100644 (file)
index daa0cdf..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-# This is the STP example from IEEE 802.1D-2004 figure 17.7.
-bridge 0 0xaa = b
-bridge 1 0x111 = a b d f h g e c
-bridge 2 0x222 = g h j l n m k i
-run 1000
-check 0 = root
-check 1 = F F:10 F F F F F F
-check 2 = B F:20 F F F F F F
-
-# This is not the port priority change described in that figure,
-# but I don't understand what port priority change would cause
-# that change.
-bridge 2 = g X j l n m k i
-run 1000
-check 0 = root
-check 1 = F F:10 F F F F F F
-check 2 = F:20 D F F F F F F
diff --git a/tests/test-stp-iol-io-1.1 b/tests/test-stp-iol-io-1.1
deleted file mode 100644 (file)
index 186d6c4..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Interoperability Test Suite
-# Version 1.5":
-# STP.io.1.1: Link Failure
-bridge 0 0x111 = a b c
-bridge 1 0x222 = a b c
-run 1000
-check 0 = root
-check 1 = F:10 B B
-bridge 1 = 0 _ _
-run 1000
-check 0 = root
-check 1 = F F:10 B
-bridge 1 = X _ _
-run 1000
-check 0 = root
-check 1 = D F:10 B
-bridge 1 = _ 0 _
-run 1000
-check 0 = root
-check 1 = D F F:10
-bridge 1 = _ X _
-run 1000
-check 0 = root
-check 1 = D D F:10
diff --git a/tests/test-stp-iol-io-1.2 b/tests/test-stp-iol-io-1.2
deleted file mode 100644 (file)
index 285bbd8..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Interoperability Test Suite
-# Version 1.5":
-# STP.io.1.2: Repeated Network
-bridge 0 0x111 = a a
-bridge 1 0x222 = a a
-run 1000
-check 0 = rootid:0x111 F B
-check 1 = rootid:0x111 F:10 B
-bridge 1 = a^0x90 _
-run 1000
-check 0 = rootid:0x111 F B
-check 1 = rootid:0x111 B F:10
-
diff --git a/tests/test-stp-iol-io-1.4 b/tests/test-stp-iol-io-1.4
deleted file mode 100644 (file)
index 0065aaf..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Interoperability Test Suite
-# Version 1.5":
-# STP.io.1.4: Network Initialization
-bridge 0 0x111 = a b c
-bridge 1 0x222 = b d e
-bridge 2 0x333 = a d f
-bridge 3 0x444 = c e f
-run 1000
-check 0 = root
-check 1 = F:10 F F
-check 2 = F:10 B F
-check 3 = F:10 B B
diff --git a/tests/test-stp-iol-io-1.5 b/tests/test-stp-iol-io-1.5
deleted file mode 100644 (file)
index 285d29d..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Interoperability Test Suite
-# Version 1.5":
-# STP.io.1.5: Topology Change
-bridge 0 0x111 = a b d c
-bridge 1 0x222 = a b f e
-bridge 2 0x333 = c d g h
-bridge 3 0x444 = e f g h
-run 1000
-check 0 = root
-check 1 = F:10 B F F
-check 2 = B F:10 F F
-check 3 = B F:20 B B
-bridge 1^0x7000
-run 1000
-check 0 = F:10 B F F
-check 1 = root
-check 2 = B F:20 B B
-check 3 = B F:10 F F
-bridge 2^0x6000
-run 1000
-check 0 = F F B F:10
-check 1 = F:20 B B B
-check 2 = root
-check 3 = F F F:10 B
-bridge 3^0x5000
-run 1000
-check 0 = B B B F:20
-check 1 = F F B F:10
-check 2 = F F F:10 B
-check 3 = root
-bridge 0^0x4000
-bridge 1^0x4001
-bridge 2^0x4002
-bridge 3^0x4003
-run 1000
-check 0 = root
-check 1 = F:10 B F F
-check 2 = B F:10 F F
-check 3 = B F:20 B B
diff --git a/tests/test-stp-iol-op-1.1 b/tests/test-stp-iol-op-1.1
deleted file mode 100644 (file)
index 8432bf3..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# This test file approximates the following tests from "Bridge
-# Functions Consortium Spanning Tree Protocol Operations Test Suite
-# Version 2.3":
-# Test STP.op.1.1 ­ Root ID Initialized to Bridge ID
-# Test STP.op.1.2 ­ Root Path Cost Initialized to Zero
-bridge 0 0x123 =
-check 0 = root
diff --git a/tests/test-stp-iol-op-1.4 b/tests/test-stp-iol-op-1.4
deleted file mode 100644 (file)
index 6a12116..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Protocol Operations Test Suite
-# Version 2.3":
-# Test STP.op.1.4 ­ All Ports Initialized to Designated Ports
-bridge 0 0x123 = a b c d e f
-check 0 = Li Li Li Li Li Li
-run 1000
-check 0 = F F F F F F
diff --git a/tests/test-stp-iol-op-3.1 b/tests/test-stp-iol-op-3.1
deleted file mode 100644 (file)
index 3e1099c..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Protocol Operations Test Suite
-# Version 2.3":
-# Test STP.op.3.1 ­ Root Bridge Selection: Root ID Values
-bridge 0 0x111 = a
-bridge 1 0x222 = a
-check 0 = rootid:0x111 Li
-check 1 = rootid:0x222 Li
-run 1000
-check 0 = rootid:0x111 root
-check 1 = rootid:0x111 F:10
diff --git a/tests/test-stp-iol-op-3.3 b/tests/test-stp-iol-op-3.3
deleted file mode 100644 (file)
index 2bcd45e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Protocol Operations Test Suite
-# Version 2.3":
-# Test STP.op.3.3 ­ Root Bridge Selection: Bridge ID Values
-bridge 0 0x333^0x6000 = a
-bridge 1 0x222^0x7000 = b
-bridge 2 0x111 = a b
-run 1000
-check 0 = rootid:0x333^0x6000 root
-check 1 = rootid:0x333^0x6000 F:20
-check 2 = rootid:0x333^0x6000 F:10 F
diff --git a/tests/test-stp-iol-op-3.4 b/tests/test-stp-iol-op-3.4
deleted file mode 100644 (file)
index 2bcd45e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# This test file approximates the following test from "Bridge
-# Functions Consortium Spanning Tree Protocol Operations Test Suite
-# Version 2.3":
-# Test STP.op.3.3 ­ Root Bridge Selection: Bridge ID Values
-bridge 0 0x333^0x6000 = a
-bridge 1 0x222^0x7000 = b
-bridge 2 0x111 = a b
-run 1000
-check 0 = rootid:0x333^0x6000 root
-check 1 = rootid:0x333^0x6000 F:20
-check 2 = rootid:0x333^0x6000 F:10 F
index c2a5837..eab13d6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -301,6 +301,7 @@ simulate(struct test_case *tc, int granularity)
                     struct bpdu *bpdu = &b->rxq[b->rxq_tail % RXQ_SIZE];
                     stp_received_bpdu(stp_get_port(b->stp, bpdu->port_no),
                                       bpdu->data, bpdu->size);
+                    free(bpdu->data);
                     any = true;
                 }
             }
@@ -357,6 +358,7 @@ get_token(void)
         pos++;
     }
     if (*pos == '\0') {
+        free(token);
         token = NULL;
         return false;
     }
@@ -501,7 +503,8 @@ main(int argc, char *argv[])
 
                         if (!strcmp(token, "0")) {
                             lan = NULL;
-                        } else if (strlen(token) == 1 && islower(*token)) {
+                        } else if (strlen(token) == 1
+                                && islower((unsigned char)*token)) {
                             lan = tc->lans[*token - 'a']; 
                         } else {
                             err("%s is not a valid LAN name "
@@ -643,6 +646,19 @@ main(int argc, char *argv[])
             err("trailing garbage on line");
         }
     }
+    free(token);
+
+    for (i = 0; i < tc->n_lans; i++) {
+        struct lan *lan = tc->lans[i];
+        free((char *) lan->name);
+        free(lan);
+    }
+    for (i = 0; i < tc->n_bridges; i++) {
+        struct bridge *bridge = tc->bridges[i];
+        stp_destroy(bridge->stp);
+        free(bridge);
+    }
+    free(tc);
 
     return 0;
 }
diff --git a/tests/test-stp.sh b/tests/test-stp.sh
deleted file mode 100755 (executable)
index 4f194ac..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#! /bin/sh
-
-# Copyright (c) 2008 Nicira Networks.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-progress=
-for d in ${stp_files}; do
-    echo "Testing $d..."
-    $SUPERVISOR ./tests/test-stp ${srcdir}/$d
-done
diff --git a/tests/test-strtok_r.c b/tests/test-strtok_r.c
new file mode 100644 (file)
index 0000000..9f8d898
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Some versions of glibc 2.7 has a bug in strtok_r when with optimization that
+ * can cause segfaults:
+ *      http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614.
+ *
+ * Open vSwitch works around this problem by supplying a replacement string.h.
+ * This test program verifies that the workaround is in place.
+ */
+int
+main(void)
+{
+    char string[] = ":::";
+    char *save_ptr = (char *) 0xc0ffee;
+    char *token1, *token2;
+    token1 = strtok_r(string, ":", &save_ptr);
+    token2 = strtok_r(NULL, ":", &save_ptr);
+    printf ("%s %s\n", token1, token2);
+    return 0;
+}
diff --git a/tests/test-timeval.c b/tests/test-timeval.c
new file mode 100644 (file)
index 0000000..533f81a
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "timeval.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+static long long int
+gettimeofday_in_msec(void)
+{
+    struct timeval tv;
+
+    assert(!gettimeofday(&tv, NULL));
+    return timeval_to_msec(&tv);
+}
+
+static void
+do_test(void)
+{
+    /* Wait until we are awakened by a signal (typically EINTR due to the
+     * setitimer()).  Then ensure that, if time has really advanced by
+     * TIME_UPDATE_INTERVAL, then time_msec() reports that it advanced.
+     */
+    long long int start_time_msec;
+    long long int start_gtod;
+
+    start_time_msec = time_msec();
+    start_gtod = gettimeofday_in_msec();
+    for (;;) {
+        /* Wait up to 1 second.  Using select() to do the timeout avoids
+         * interfering with the interval timer. */
+        struct timeval timeout;
+        int retval;
+
+        timeout.tv_sec = 1;
+        timeout.tv_usec = 0;
+        retval = select(0, NULL, NULL, NULL, &timeout);
+        if (retval != -1) {
+            ovs_fatal(0, "select returned %d", retval);
+        } else if (errno != EINTR) {
+            ovs_fatal(errno, "select reported unexpected error");
+        }
+
+        if (gettimeofday_in_msec() - start_gtod >= TIME_UPDATE_INTERVAL) {
+            assert(time_msec() - start_time_msec >= TIME_UPDATE_INTERVAL);
+            break;
+        }
+    }
+}
+
+static void
+usage(void)
+{
+    ovs_fatal(0, "usage: %s TEST, where TEST is \"plain\" or \"daemon\"",
+              program_name);
+}
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        usage();
+    } else if (!strcmp(argv[1], "plain")) {
+        do_test();
+    } else if (!strcmp(argv[1], "daemon")) {
+        /* Test that time still advances even in a daemon.  This is an
+         * interesting test because fork() cancels the interval timer. */
+        char cwd[1024], *pidfile;
+        FILE *success;
+
+        assert(getcwd(cwd, sizeof cwd) == cwd);
+
+        unlink("test-timeval.success");
+
+        /* Daemonize, with a pidfile in the current directory. */
+        set_detach();
+        pidfile = xasprintf("%s/test-timeval.pid", cwd);
+        set_pidfile(pidfile);
+        free(pidfile);
+        set_no_chdir();
+        daemonize();
+
+        /* Run the test. */
+        do_test();
+
+        /* Report success by writing out a file, since the ultimate invoker of
+         * test-timeval can't wait on the daemonized process. */
+        success = fopen("test-timeval.success", "w");
+        if (!success) {
+            ovs_fatal(errno, "test-timeval.success: create failed");
+        }
+        fprintf(success, "success\n");
+        fclose(success);
+    } else {
+        usage();
+    }
+
+    return 0;
+}
diff --git a/tests/test-uuid.c b/tests/test-uuid.c
new file mode 100644 (file)
index 0000000..750b74e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "uuid.h"
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+    struct uuid uuid;
+
+    if (argc == 1) {
+        uuid_generate(&uuid);
+    } else if (argc == 2) {
+        if (!uuid_from_string(&uuid, argv[1])) {
+            ovs_fatal(0, "\"%s\" is not a valid UUID", argv[1]);
+        }
+    } else {
+        ovs_fatal(0, "usage: %s [UUID]", argv[0]);
+    }
+
+    printf(UUID_FMT"\n", UUID_ARGS(&uuid));
+
+    return 0;
+}
diff --git a/tests/test-vconn.c b/tests/test-vconn.c
new file mode 100644 (file)
index 0000000..a394f6c
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "vconn.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct fake_pvconn {
+    const char *type;
+    char *pvconn_name;
+    char *vconn_name;
+    struct pstream *pstream;
+};
+
+static void
+check(int a, int b, const char *as, const char *file, int line)
+{
+    if (a != b) {
+        ovs_fatal(0, "%s:%d: %s is %d but should be %d", file, line, as, a, b);
+    }
+}
+
+
+#define CHECK(A, B) check(A, B, #A, __FILE__, __LINE__)
+
+static void
+check_errno(int a, int b, const char *as, const char *file, int line)
+{
+    if (a != b) {
+        ovs_fatal(0, "%s:%d: %s is %d (%s) but should be %d (%s)",
+                  file, line, as, a, strerror(abs(a)), b, strerror(abs(b)));
+    }
+}
+
+#define CHECK_ERRNO(A, B) check_errno(A, B, #A, __FILE__, __LINE__)
+
+static void
+fpv_create(const char *type, struct fake_pvconn *fpv)
+{
+#ifdef HAVE_OPENSSL
+    if (!strcmp(type, "ssl")) {
+        stream_ssl_set_private_key_file("testpki-privkey.pem");
+        stream_ssl_set_certificate_file("testpki-cert.pem");
+        stream_ssl_set_ca_cert_file("testpki-cacert.pem", false);
+    }
+#endif
+
+    fpv->type = type;
+    if (!strcmp(type, "unix")) {
+        static int unix_count = 0;
+        char *bind_path;
+
+        bind_path = xasprintf("fake-pvconn.%d", unix_count++);
+        fpv->pvconn_name = xasprintf("punix:%s", bind_path);
+        fpv->vconn_name = xasprintf("unix:%s", bind_path);
+        CHECK_ERRNO(pstream_open(fpv->pvconn_name, &fpv->pstream), 0);
+        free(bind_path);
+    } else if (!strcmp(type, "tcp") || !strcmp(type, "ssl")) {
+        char *s, *port, *save_ptr = NULL;
+        char *open_name;
+
+        open_name = xasprintf("p%s:0:127.0.0.1", type);
+        CHECK_ERRNO(pstream_open(open_name, &fpv->pstream), 0);
+
+        /* Extract bound port number from pstream name. */
+        s = xstrdup(pstream_get_name(fpv->pstream));
+        strtok_r(s, ":", &save_ptr);
+        port = strtok_r(NULL, ":", &save_ptr);
+
+        /* Save info. */
+        fpv->pvconn_name = xstrdup(pstream_get_name(fpv->pstream));
+        fpv->vconn_name = xasprintf("%s:127.0.0.1:%s", type, port);
+
+        free(open_name);
+        free(s);
+    } else {
+        abort();
+    }
+}
+
+static struct stream *
+fpv_accept(struct fake_pvconn *fpv)
+{
+    struct stream *stream;
+
+    CHECK_ERRNO(pstream_accept_block(fpv->pstream, &stream), 0);
+
+    return stream;
+}
+
+static void
+fpv_close(struct fake_pvconn *fpv)
+{
+    pstream_close(fpv->pstream);
+    fpv->pstream = NULL;
+}
+
+static void
+fpv_destroy(struct fake_pvconn *fpv)
+{
+    fpv_close(fpv);
+    free(fpv->pvconn_name);
+    free(fpv->vconn_name);
+}
+
+/* Connects to a fake_pvconn with vconn_open(), then closes the listener and
+ * verifies that vconn_connect() reports 'expected_error'. */
+static void
+test_refuse_connection(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    int expected_error;
+    struct fake_pvconn fpv;
+    struct vconn *vconn;
+
+    expected_error = !strcmp(type, "unix") ? EPIPE : ECONNRESET;
+
+    fpv_create(type, &fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    fpv_close(&fpv);
+    vconn_run(vconn);
+    CHECK_ERRNO(vconn_connect(vconn), expected_error);
+    vconn_close(vconn);
+    fpv_destroy(&fpv);
+}
+
+/* Connects to a fake_pvconn with vconn_open(), accepts that connection and
+ * closes it immediately, and verifies that vconn_connect() reports
+ * 'expected_error'. */
+static void
+test_accept_then_close(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    int expected_error;
+    struct fake_pvconn fpv;
+    struct vconn *vconn;
+
+    expected_error = (!strcmp(type, "unix") ? EPIPE
+                      : !strcmp(type, "tcp") ? ECONNRESET
+                      : EPROTO);
+
+    fpv_create(type, &fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream_close(fpv_accept(&fpv));
+    fpv_close(&fpv);
+    CHECK_ERRNO(vconn_connect(vconn), expected_error);
+    vconn_close(vconn);
+    fpv_destroy(&fpv);
+}
+
+/* Connects to a fake_pvconn with vconn_open(), accepts that connection and
+ * reads the hello message from it, then closes the connection and verifies
+ * that vconn_connect() reports 'expected_error'. */
+static void
+test_read_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct fake_pvconn fpv;
+    struct vconn *vconn;
+    struct stream *stream;
+
+    fpv_create(type, &fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream = fpv_accept(&fpv);
+    fpv_destroy(&fpv);
+    for (;;) {
+       struct ofp_header hello;
+       int retval;
+
+       retval = stream_recv(stream, &hello, sizeof hello);
+       if (retval == sizeof hello) {
+           CHECK(hello.version, OFP_VERSION);
+           CHECK(hello.type, OFPT_HELLO);
+           CHECK(hello.length, htons(sizeof hello));
+           break;
+       } else {
+           CHECK_ERRNO(retval, -EAGAIN);
+       }
+
+       vconn_run(vconn);
+       CHECK_ERRNO(vconn_connect(vconn), EAGAIN);
+       vconn_run_wait(vconn);
+       vconn_connect_wait(vconn);
+       stream_recv_wait(stream);
+       poll_block();
+    }
+    stream_close(stream);
+    CHECK_ERRNO(vconn_connect(vconn), ECONNRESET);
+    vconn_close(vconn);
+}
+
+/* Connects to a fake_pvconn with vconn_open(), accepts that connection and
+ * sends the 'out' bytes in 'out_size' to it (presumably an OFPT_HELLO
+ * message), then verifies that vconn_connect() reports
+ * 'expect_connect_error'. */
+static void
+test_send_hello(const char *type, const void *out, size_t out_size,
+                int expect_connect_error)
+{
+    struct fake_pvconn fpv;
+    struct vconn *vconn;
+    bool read_hello, connected;
+    struct ofpbuf *msg;
+    struct stream *stream;
+    size_t n_sent;
+
+    fpv_create(type, &fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream = fpv_accept(&fpv);
+    fpv_destroy(&fpv);
+
+    n_sent = 0;
+    while (n_sent < out_size) {
+        int retval;
+
+        retval = stream_send(stream, (char *) out + n_sent, out_size - n_sent);
+        if (retval > 0) {
+            n_sent += retval;
+        } else if (retval == -EAGAIN) {
+            stream_run(stream);
+            vconn_run(vconn);
+            stream_recv_wait(stream);
+            vconn_connect_wait(vconn);
+            vconn_run_wait(vconn);
+            poll_block();
+        } else {
+            ovs_fatal(0, "stream_send returned unexpected value %d", retval);
+        }
+    }
+
+    read_hello = connected = false;
+    for (;;) {
+       if (!read_hello) {
+           struct ofp_header hello;
+           int retval = stream_recv(stream, &hello, sizeof hello);
+           if (retval == sizeof hello) {
+               CHECK(hello.version, OFP_VERSION);
+               CHECK(hello.type, OFPT_HELLO);
+               CHECK(hello.length, htons(sizeof hello));
+               read_hello = true;
+           } else {
+               CHECK_ERRNO(retval, -EAGAIN);
+           }
+       }
+
+       vconn_run(vconn);
+       if (!connected) {
+           int error = vconn_connect(vconn);
+           if (error == expect_connect_error) {
+               if (!error) {
+                   connected = true;
+               } else {
+                   stream_close(stream);
+                   vconn_close(vconn);
+                   return;
+               }
+           } else {
+               CHECK_ERRNO(error, EAGAIN);
+           }
+       }
+
+       if (read_hello && connected) {
+           break;
+       }
+
+       vconn_run_wait(vconn);
+       if (!connected) {
+           vconn_connect_wait(vconn);
+       }
+       if (!read_hello) {
+           stream_recv_wait(stream);
+       }
+       poll_block();
+    }
+    stream_close(stream);
+    CHECK_ERRNO(vconn_recv(vconn, &msg), EOF);
+    vconn_close(vconn);
+}
+
+/* Try connecting and sending a normal hello, which should succeed. */
+static void
+test_send_plain_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct ofp_header hello;
+
+    hello.version = OFP_VERSION;
+    hello.type = OFPT_HELLO;
+    hello.length = htons(sizeof hello);
+    hello.xid = htonl(0x12345678);
+    test_send_hello(type, &hello, sizeof hello, 0);
+}
+
+/* Try connecting and sending an extra-long hello, which should succeed (since
+ * the specification says that implementations must accept and ignore extra
+ * data). */
+static void
+test_send_long_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct ofp_header hello;
+    char buffer[sizeof hello * 2];
+
+    hello.version = OFP_VERSION;
+    hello.type = OFPT_HELLO;
+    hello.length = htons(sizeof buffer);
+    hello.xid = htonl(0x12345678);
+    memset(buffer, 0, sizeof buffer);
+    memcpy(buffer, &hello, sizeof hello);
+    test_send_hello(type, buffer, sizeof buffer, 0);
+}
+
+/* Try connecting and sending an echo request instead of a hello, which should
+ * fail with EPROTO. */
+static void
+test_send_echo_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct ofp_header echo;
+
+    echo.version = OFP_VERSION;
+    echo.type = OFPT_ECHO_REQUEST;
+    echo.length = htons(sizeof echo);
+    echo.xid = htonl(0x89abcdef);
+    test_send_hello(type, &echo, sizeof echo, EPROTO);
+}
+
+/* Try connecting and sending a hello packet that has its length field as 0,
+ * which should fail with EPROTO. */
+static void
+test_send_short_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct ofp_header hello;
+
+    memset(&hello, 0, sizeof hello);
+    test_send_hello(type, &hello, sizeof hello, EPROTO);
+}
+
+/* Try connecting and sending a hello packet that has a bad version, which
+ * should fail with EPROTO. */
+static void
+test_send_invalid_version_hello(int argc OVS_UNUSED, char *argv[])
+{
+    const char *type = argv[1];
+    struct ofp_header hello;
+
+    hello.version = OFP_VERSION - 1;
+    hello.type = OFPT_HELLO;
+    hello.length = htons(sizeof hello);
+    hello.xid = htonl(0x12345678);
+    test_send_hello(type, &hello, sizeof hello, EPROTO);
+}
+
+static const struct command commands[] = {
+    {"refuse-connection", 1, 1, test_refuse_connection},
+    {"accept-then-close", 1, 1, test_accept_then_close},
+    {"read-hello", 1, 1, test_read_hello},
+    {"send-plain-hello", 1, 1, test_send_plain_hello},
+    {"send-long-hello", 1, 1, test_send_long_hello},
+    {"send-echo-hello", 1, 1, test_send_echo_hello},
+    {"send-short-hello", 1, 1, test_send_short_hello},
+    {"send-invalid-version-hello", 1, 1, test_send_invalid_version_hello},
+    {NULL, 0, 0, NULL},
+};
+
+int
+main(int argc OVS_UNUSED, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_EMER);
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_DBG);
+    signal(SIGPIPE, SIG_IGN);
+
+    time_alarm(10);
+
+    run_command(argc - 1, argv + 1, commands);
+
+    return 0;
+}
diff --git a/tests/testpki-cacert.pem b/tests/testpki-cacert.pem
new file mode 100644 (file)
index 0000000..e888505
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan  7 01:08:32 2010 GMT
+            Not After : Jan  7 01:08:32 2016 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cc:b2:25:ba:07:b8:d6:e1:23:20:1e:41:a1:49:
+                    35:68:09:71:19:ef:68:a0:45:e0:bd:33:41:0d:2b:
+                    b7:7d:33:16:57:d4:16:da:ba:a0:7e:ae:9c:76:5b:
+                    92:93:96:a9:5b:bd:6f:b3:fd:6a:62:b9:10:46:98:
+                    d9:b4:ea:ab:99:f3:72:4b:d1:11:81:77:75:09:be:
+                    fd:9f:55:f7:6c:78:0a:b1:9d:f8:c5:c5:a0:de:05:
+                    0d:78:62:66:ed:b1:0f:b3:9a:69:fd:13:9f:43:a7:
+                    aa:e4:3c:a1:63:68:46:c2:a1:56:56:eb:62:b5:0e:
+                    2b:be:7b:8e:c9:aa:c2:6f:04:af:7b:5a:ed:4b:16:
+                    fb:47:4d:45:81:d8:b8:2e:08:21:a7:4d:cc:78:9b:
+                    b0:b0:a0:18:91:53:ab:64:c3:eb:66:74:93:cc:8a:
+                    b4:40:c5:4e:2e:cc:c5:63:c0:6b:2d:6e:cd:b9:1c:
+                    a9:45:ad:82:0a:d2:1f:5d:84:bc:29:a1:82:0d:75:
+                    1c:1a:21:8b:15:03:88:94:e5:89:ed:48:22:e0:7e:
+                    b9:15:f1:13:fb:6c:a2:48:c7:2d:e5:01:04:b7:23:
+                    6a:06:45:7b:e3:14:59:ac:1d:87:e6:a5:ec:7c:86:
+                    80:17:64:71:a0:43:27:27:f5:2c:bd:34:60:c7:a1:
+                    22:3f
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        c7:85:13:17:b6:ca:c8:1b:8a:8e:eb:3b:64:05:e4:d5:a2:2c:
+        6b:ee:83:d8:e0:67:f9:99:59:15:59:9d:6d:16:c0:6b:c3:ed:
+        61:31:0a:40:1b:63:1d:57:a5:67:3d:46:55:6b:9f:ed:18:79:
+        45:fc:db:d9:48:d2:86:0f:aa:e0:43:18:3f:f4:e3:71:a3:28:
+        d4:00:ae:7c:0e:91:2c:5b:5b:ff:be:ad:b6:4b:b7:0e:e3:ea:
+        7a:66:69:6c:83:90:0c:59:c1:d7:4d:1a:b9:69:0d:ac:6e:07:
+        b3:42:3c:3e:54:ac:85:c5:58:67:51:2a:c0:05:1f:70:6a:07:
+        86:2c:42:56:ee:3b:69:7b:db:35:e6:c6:5b:eb:25:66:ca:89:
+        bb:d7:37:ae:d2:b9:e8:56:38:a2:ec:ff:45:38:97:ae:43:20:
+        c8:55:c9:c8:0f:45:37:70:97:b9:8a:2e:56:52:6f:20:f3:08:
+        b7:1a:26:98:b9:d9:7d:52:69:b3:95:2b:c5:4e:0c:7b:fd:cd:
+        6a:a2:23:cf:eb:ee:de:74:17:0b:cc:a3:91:f1:41:0b:1e:94:
+        e2:ea:52:85:c1:3d:de:f9:e6:44:5a:f6:fe:7d:2f:fb:6f:60:
+        89:2c:f0:0c:c7:c7:fb:6f:23:4d:a1:18:89:28:ea:61:f4:3a:
+        9d:ca:1f:60
+-----BEGIN CERTIFICATE-----
+MIIDeDCCAmACAQEwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTA3MDEwODMyWhcNMTYwMTA3MDEwODMyWjCB
+gTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxETAPBgNVBAsTCHN3aXRjaGNhMTswOQYDVQQDEzJPVlMgc3dpdGNoY2EgQ0Eg
+Q2VydGlmaWNhdGUgKDIwMTAgSmFuIDA2IDE3OjA4OjMwKTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMyyJboHuNbhIyAeQaFJNWgJcRnvaKBF4L0zQQ0r
+t30zFlfUFtq6oH6unHZbkpOWqVu9b7P9amK5EEaY2bTqq5nzckvREYF3dQm+/Z9V
+92x4CrGd+MXFoN4FDXhiZu2xD7Oaaf0Tn0OnquQ8oWNoRsKhVlbrYrUOK757jsmq
+wm8Er3ta7UsW+0dNRYHYuC4IIadNzHibsLCgGJFTq2TD62Z0k8yKtEDFTi7MxWPA
+ay1uzbkcqUWtggrSH12EvCmhgg11HBohixUDiJTlie1IIuB+uRXxE/tsokjHLeUB
+BLcjagZFe+MUWawdh+al7HyGgBdkcaBDJyf1LL00YMehIj8CAwEAATANBgkqhkiG
+9w0BAQQFAAOCAQEAx4UTF7bKyBuKjus7ZAXk1aIsa+6D2OBn+ZlZFVmdbRbAa8Pt
+YTEKQBtjHVelZz1GVWuf7Rh5Rfzb2UjShg+q4EMYP/TjcaMo1ACufA6RLFtb/76t
+tku3DuPqemZpbIOQDFnB100auWkNrG4Hs0I8PlSshcVYZ1EqwAUfcGoHhixCVu47
+aXvbNebGW+slZsqJu9c3rtK56FY4ouz/RTiXrkMgyFXJyA9FN3CXuYouVlJvIPMI
+txommLnZfVJps5UrxU4Me/3NaqIjz+vu3nQXC8yjkfFBCx6U4upShcE93vnmRFr2
+/n0v+29giSzwDMfH+28jTaEYiSjqYfQ6ncofYA==
+-----END CERTIFICATE-----
diff --git a/tests/testpki-cert.pem b/tests/testpki-cert.pem
new file mode 100644 (file)
index 0000000..75d815d
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan  7 01:08:59 2010 GMT
+            Not After : Jan  7 01:08:59 2011 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ac:3f:c6:b1:ef:a3:e3:68:98:2c:91:a1:3a:21:
+                    02:38:87:5b:75:7a:1c:17:c9:b0:64:a9:f7:80:17:
+                    08:0f:b5:25:b4:46:80:6b:7e:92:ab:f8:93:05:17:
+                    77:e4:12:86:eb:54:5d:a7:a0:45:70:16:5e:d7:4f:
+                    6b:7c:9f:fe:83:a4:c1:62:83:33:71:6f:4f:4e:68:
+                    84:a6:92:a5:77:8f:ad:cd:ee:bf:61:72:24:c0:64:
+                    df:73:98:de:37:6b:b8:4d:78:f4:ba:06:95:64:ef:
+                    82:b1:2f:71:01:44:ca:3c:de:fa:32:28:b6:ea:72:
+                    7b:4d:d6:a0:fb:4b:73:de:a9:7f:25:ad:20:02:3d:
+                    5f:7f:7f:8e:91:34:97:0a:10:96:be:3d:ee:37:5b:
+                    a9:91:9e:7f:d5:ac:7b:e3:56:47:a4:14:15:dd:48:
+                    ce:32:6f:c4:83:09:07:31:bb:34:77:4d:f7:12:70:
+                    86:b8:b2:64:16:3b:ea:d2:72:e0:73:6b:6f:ce:59:
+                    cf:56:6d:a8:94:3c:10:d7:47:7e:b2:91:9d:c7:65:
+                    23:8a:b1:ca:9c:15:36:c5:d9:db:b1:e7:b8:1f:09:
+                    20:1d:da:97:de:93:7c:e2:5d:94:ea:38:d8:ce:60:
+                    c9:9e:43:da:6d:9d:c9:d2:a0:e9:6d:5a:9b:57:53:
+                    86:7d
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        19:a9:2a:66:fc:09:78:c9:87:e6:73:be:9a:d2:b7:87:07:7b:
+        93:70:04:cd:f2:c9:47:a3:8f:9f:c4:af:92:ef:cf:07:d3:83:
+        90:f7:8a:f0:55:f6:8a:2e:af:57:b9:e4:9c:72:37:b7:af:12:
+        fb:dc:07:9b:94:7b:18:c8:53:86:6d:02:77:eb:e3:ac:21:e1:
+        6d:b5:fe:04:6b:a1:d2:78:a6:58:4b:5d:a7:17:e1:3b:d9:94:
+        ab:81:5e:c1:9a:b5:34:a5:a7:9a:2b:1b:74:d7:a4:aa:fa:81:
+        5c:e5:5f:1a:07:54:36:21:76:04:a9:5e:11:38:46:b8:1c:11:
+        15:78:f8:0c:31:8d:9a:a3:e4:d0:72:a8:29:80:c2:3d:9d:f6:
+        61:dd:ca:c9:6c:7e:ca:c0:0d:61:28:4d:3e:ea:51:9d:c2:c4:
+        7c:47:da:cc:24:35:9c:2a:0d:ac:ea:5f:33:5a:ab:b7:94:cb:
+        3f:91:38:92:a3:62:3b:40:ef:79:55:96:b3:24:5a:19:a2:53:
+        99:63:f9:85:d4:b6:48:b8:9a:f8:bc:b7:74:f8:cf:95:dc:1a:
+        f2:66:cd:2b:4b:d4:c1:19:69:77:f9:f6:08:04:61:cd:80:ee:
+        46:44:27:82:49:60:a9:be:4b:51:75:ca:15:16:0b:97:c2:2f:
+        26:f2:dd:42
+-----BEGIN CERTIFICATE-----
+MIIDeDCCAmACAQIwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTA3MDEwODU5WhcNMTEwMTA3MDEwODU5WjCB
+gTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLTArBgNVBAMTJE9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpY2F0ZSBmb3IgdGVzdHBraTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKw/xrHvo+NomCyRoTohAjiHW3V6HBfJsGSp94AX
+CA+1JbRGgGt+kqv4kwUXd+QShutUXaegRXAWXtdPa3yf/oOkwWKDM3FvT05ohKaS
+pXePrc3uv2FyJMBk33OY3jdruE149LoGlWTvgrEvcQFEyjze+jIotupye03WoPtL
+c96pfyWtIAI9X39/jpE0lwoQlr497jdbqZGef9Wse+NWR6QUFd1IzjJvxIMJBzG7
+NHdN9xJwhriyZBY76tJy4HNrb85Zz1ZtqJQ8ENdHfrKRncdlI4qxypwVNsXZ27Hn
+uB8JIB3al96TfOJdlOo42M5gyZ5D2m2dydKg6W1am1dThn0CAwEAATANBgkqhkiG
+9w0BAQQFAAOCAQEAGakqZvwJeMmH5nO+mtK3hwd7k3AEzfLJR6OPn8Svku/PB9OD
+kPeK8FX2ii6vV7nknHI3t68S+9wHm5R7GMhThm0Cd+vjrCHhbbX+BGuh0nimWEtd
+pxfhO9mUq4FewZq1NKWnmisbdNekqvqBXOVfGgdUNiF2BKleEThGuBwRFXj4DDGN
+mqPk0HKoKYDCPZ32Yd3KyWx+ysANYShNPupRncLEfEfazCQ1nCoNrOpfM1qrt5TL
+P5E4kqNiO0DveVWWsyRaGaJTmWP5hdS2SLia+Ly3dPjPldwa8mbNK0vUwRlpd/n2
+CARhzYDuRkQngklgqb5LUXXKFRYLl8IvJvLdQg==
+-----END CERTIFICATE-----
diff --git a/tests/testpki-cert2.pem b/tests/testpki-cert2.pem
new file mode 100644 (file)
index 0000000..b282bd0
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 3 (0x3)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan 11 20:06:58 2010 GMT
+            Not After : Jan 11 20:06:58 2011 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:b0:0a:97:1e:d8:48:76:57:32:e6:4d:d7:84:86:
+                    7a:d8:0c:b7:a4:13:9c:23:bc:d5:01:94:10:81:f7:
+                    5f:b7:70:ba:1e:85:c6:5c:72:ca:cc:2a:d5:fe:cc:
+                    94:0b:54:5e:ae:40:fd:d9:c1:cc:8b:58:7e:87:73:
+                    5e:ac:98:17:f2:93:b4:cd:d7:dd:12:59:b7:8e:31:
+                    d8:63:c9:7a:14:27:f6:67:64:cb:8f:b1:d4:c1:3c:
+                    be:30:e2:08:1e:e2:db:12:9b:23:53:9f:6b:46:9b:
+                    08:46:80:6f:89:f5:77:88:b8:e0:48:89:be:0e:47:
+                    c5:5e:64:28:7e:c3:f3:10:cb:e2:95:20:6a:81:7a:
+                    14:e3:8f:e8:b8:d3:f9:31:b2:98:0c:a6:5e:9f:ea:
+                    25:89:34:22:f2:fc:a1:5d:2b:2d:0a:40:85:7a:ff:
+                    4f:5e:5a:51:72:f2:b8:03:17:db:d2:3e:40:7f:1b:
+                    6b:f1:ad:e8:ae:d3:33:d4:ad:9c:05:d4:b1:1f:53:
+                    1d:45:18:50:31:28:88:56:93:a7:f9:b2:cd:90:d5:
+                    91:dc:14:a9:33:2d:0b:8c:74:7d:94:1e:be:58:d7:
+                    fe:1e:6d:a5:a5:3b:e2:66:f0:06:f9:d9:5c:2c:66:
+                    fb:7b:85:38:13:65:ff:38:ba:1b:59:f4:08:a0:49:
+                    03:3f
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        6b:36:9d:38:52:14:c7:59:a4:3d:39:eb:7f:47:53:8a:f2:3e:
+        42:b3:ba:f9:1b:9f:72:3f:3d:38:7f:c7:41:2d:e9:9d:ed:94:
+        50:79:00:55:d0:52:8a:c4:2f:0b:74:36:05:d4:0a:d5:fd:a6:
+        87:6e:3a:e2:12:c0:4e:0e:92:9f:98:8d:77:68:0a:bf:21:0d:
+        78:20:03:0e:13:9f:49:cf:bd:c3:42:b2:69:a6:d0:67:e8:33:
+        e0:f2:9e:1c:ff:04:2f:1b:fd:37:68:a9:23:54:a5:a2:83:9d:
+        cb:ee:70:3a:c5:03:30:51:c6:90:01:39:f4:e1:29:be:53:2e:
+        fd:71:90:b7:31:33:95:70:48:98:08:4b:2f:3b:33:11:ba:4c:
+        ce:ed:f5:d8:f7:02:e7:da:f3:e9:56:9d:3a:e2:af:ec:61:e5:
+        9c:06:8a:21:18:64:5c:b8:71:e5:4e:64:cc:2d:35:65:e7:cb:
+        96:f3:8b:bc:51:79:42:92:70:e2:e4:28:70:58:44:81:45:83:
+        e2:c3:2c:3b:5b:01:04:94:ce:25:40:8e:15:a3:b3:05:e4:68:
+        17:4b:50:41:1a:58:51:75:81:0c:72:bd:4c:bc:b3:d1:dd:d9:
+        aa:8f:8c:b4:bb:61:b7:55:c1:3f:74:2c:76:73:1e:25:cc:3d:
+        5c:ac:d5:22
+-----BEGIN CERTIFICATE-----
+MIIDeTCCAmECAQMwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTExMjAwNjU4WhcNMTEwMTExMjAwNjU4WjCB
+gjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLjAsBgNVBAMTJU9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpY2F0ZSBmb3IgdGVzdHBraTIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCwCpce2Eh2VzLmTdeEhnrYDLekE5wjvNUBlBCB
+91+3cLoehcZccsrMKtX+zJQLVF6uQP3ZwcyLWH6Hc16smBfyk7TN190SWbeOMdhj
+yXoUJ/ZnZMuPsdTBPL4w4gge4tsSmyNTn2tGmwhGgG+J9XeIuOBIib4OR8VeZCh+
+w/MQy+KVIGqBehTjj+i40/kxspgMpl6f6iWJNCLy/KFdKy0KQIV6/09eWlFy8rgD
+F9vSPkB/G2vxreiu0zPUrZwF1LEfUx1FGFAxKIhWk6f5ss2Q1ZHcFKkzLQuMdH2U
+Hr5Y1/4ebaWlO+Jm8Ab52VwsZvt7hTgTZf84uhtZ9AigSQM/AgMBAAEwDQYJKoZI
+hvcNAQEEBQADggEBAGs2nThSFMdZpD05639HU4ryPkKzuvkbn3I/PTh/x0Et6Z3t
+lFB5AFXQUorELwt0NgXUCtX9poduOuISwE4Okp+YjXdoCr8hDXggAw4Tn0nPvcNC
+smmm0GfoM+Dynhz/BC8b/TdoqSNUpaKDncvucDrFAzBRxpABOfThKb5TLv1xkLcx
+M5VwSJgISy87MxG6TM7t9dj3Aufa8+lWnTrir+xh5ZwGiiEYZFy4ceVOZMwtNWXn
+y5bzi7xReUKScOLkKHBYRIFFg+LDLDtbAQSUziVAjhWjswXkaBdLUEEaWFF1gQxy
+vUy8s9Hd2aqPjLS7YbdVwT90LHZzHiXMPVys1SI=
+-----END CERTIFICATE-----
diff --git a/tests/testpki-privkey.pem b/tests/testpki-privkey.pem
new file mode 100644 (file)
index 0000000..759f58a
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArD/Gse+j42iYLJGhOiECOIdbdXocF8mwZKn3gBcID7UltEaA
+a36Sq/iTBRd35BKG61Rdp6BFcBZe109rfJ/+g6TBYoMzcW9PTmiEppKld4+tze6/
+YXIkwGTfc5jeN2u4TXj0ugaVZO+CsS9xAUTKPN76Mii26nJ7Tdag+0tz3ql/Ja0g
+Aj1ff3+OkTSXChCWvj3uN1upkZ5/1ax741ZHpBQV3UjOMm/EgwkHMbs0d033EnCG
+uLJkFjvq0nLgc2tvzlnPVm2olDwQ10d+spGdx2UjirHKnBU2xdnbsee4HwkgHdqX
+3pN84l2U6jjYzmDJnkPabZ3J0qDpbVqbV1OGfQIDAQABAoIBADr/MSAa82hdl9mU
+G8PcMHWKLxJCu8KOC0O/T41o1hMDOaHQkAXBeZ07a6fPzPmqOtn5sIZMh9wHXX6j
+ri4mYrdWRAJo68LLnD8/30dqbRBRfvdM8fH/dYUMR9jBIEOdOqgWaMQaoyrKOlpT
+5IHJvPcybEGn3lbY1VDo1YSc6Ff36AGLdORVH8dY9tYx/IKbyzRmDvzai6EVSDtl
+yp2zinXRNJ+AVwB0epsKbOVZa0WaYN1KclqOtFn7xANoUvy5YBHZDedC3yWxuZvZ
+dNeTjUniauukz7ivKg9/rWZFfYZ2251mrOfO9aIHOUzBurbDS/rzjVgwQmv483T9
+2cDL/IUCgYEA3tXDA0Mcv1d7IzP6A4CQ6o49JyWVkMHxKkhZy1cR+pBRc7tgpQrF
+YrtEWdsDvUJLGMUQBmm7VMpMjTRQ/YuBcdIB2USkJcDHPaZRAA1mlDPG6cSsy7yI
+d2qZFOOkUEjLqKicxiHTrCOz9HBb1McolTo9h5SdfBy5bHb9LPul3E8CgYEAxeKk
+L3m1C1rFVpVF8zoHF+zK3/d9zwzdLzmFfFrKqzIT3/6cJKEwHLJN69mja60+MKLZ
+6F1G+R4/JxE8TnCSXHh7UYULhyFolZaWwZn5xVld0210QU+f4EBUZMt4bZjDxEyr
+/vxDZaqu7SB5Mmqq++C9YzdeIk1GGJ0TE2MmwXMCgYEAuPJ+ayS2pXD8ONmY9nMs
+1CC+TNF686ykd02ZiZV4zJgfooiwzArGjQ2Uy2dmER0Gq0ZT6J605skJBGGZnva8
+tzVwZ137R4JbW6XAsORucS8QN1IPgQG32jVVXOsbo67nqdJYXHIS91qir4zaCx5J
+ZqHyE6ebljlZBNc1hrJOlS0CgYEAhoc/626oYCHDistMlMBcVi2K9pwAkaRDMnm+
+f/4RTjVrQZqMeHKEjN3DD5YT/X33i4UK82eGepHPiTW0c/cf6XGXFKKIZcOWoCuS
+LegJ39qTaMs+f7AsFn5lYWjaZFe4r1kYjO7eut1AssCi5F2UBEyTNEJN4q/5+X2/
+nCyKCnUCgYBzxbUnJBhuA8ivNb/lXdGCvqnBaZb1Bjb+Ljv0yDhMVEJwKpGpm5H3
+DySodzklBrU+eL9TLPcFM6N+okmDsMQqUygUJ1PXLRZKnpLVRILmHcMWbgCrbw4Q
+pd22A01NncdtPY2107ZeVZnjzqHF+5CXxMlKBl4QG07KFtzbNOcQuA==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testpki-privkey2.pem b/tests/testpki-privkey2.pem
new file mode 100644 (file)
index 0000000..21b2698
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsAqXHthIdlcy5k3XhIZ62Ay3pBOcI7zVAZQQgfdft3C6HoXG
+XHLKzCrV/syUC1RerkD92cHMi1h+h3NerJgX8pO0zdfdElm3jjHYY8l6FCf2Z2TL
+j7HUwTy+MOIIHuLbEpsjU59rRpsIRoBvifV3iLjgSIm+DkfFXmQofsPzEMvilSBq
+gXoU44/ouNP5MbKYDKZen+oliTQi8vyhXSstCkCFev9PXlpRcvK4Axfb0j5Afxtr
+8a3ortMz1K2cBdSxH1MdRRhQMSiIVpOn+bLNkNWR3BSpMy0LjHR9lB6+WNf+Hm2l
+pTviZvAG+dlcLGb7e4U4E2X/OLobWfQIoEkDPwIDAQABAoIBAQCcFVe2Dnf5DQlh
+LyVmKEIk6umr/YMUIKoPkzMruKRSJg2vYFbmwxTR/yzrlIGypeSoxZENkIoGBrlw
+6TbI+rVI6/OECt/FFGzuE33Tw+CMnH4ZYEIoLrj/eBb9ins+v08T6R9iVlesK2sj
+7151yIFqZNjfF9m+GZ6COXW6J4yMl3Aq+x72IBq46pyu/WoBkxBPfXcIfImoaoUI
+2dbid5S074qmPpyfaVLkS6s77nxypH1T2dhi1TkpQmIIx+aCyxdlTmpCwa7Uik6l
+ZQxpzQPtT1aiBixJbEHmvyTnnptNTzWXQP+mxnfGUZ6TyCNtW2V3o2I9/EoHc7I4
+OXb1p/WpAoGBAOHgNlyYanlm0ezvROA/JDMPgizJJrYv9BGJez+1NaEw+gSRBFrZ
+IpVU9OuiCnkJCcNAH1KO/f4InnbJyvSweQd0CUOvNPAwqMHLjH2FnL2WQLv+9afY
+tt0oZpDT9Wj+VskYoVu2jPhGfKzSrtjevmCU7w7heUjdQM2WCYuSHLktAoGBAMeE
+8bGq+v5yfjn8/8SYvxYe3iRtpEgn8hg1XEl0VN7zVXVOVfastdh9xuNiAnHn16GT
++4VeqYCBfb9ygcRyid048dXhLCUsD84EMsdCtKHgG0iJh2RJ7L67z4ZFgEZw03K3
+sRfRo/2QxFMIG30ZFip7MLiTZc1OvP+OZ/lovZmbAoGARqaN+mqGK1D46qZwob++
+P+Zi7kVAwY3ARtf84BF7I98g5NrDRPNT+Oeo8CcwJWmpTxQ4d+dYFOR3RGqb++tS
+//zQhQZBhbuCnZNgb0ainz5nIyZ9ijGkCQsBAd36jgu385Crr/cqouHRT3Fa1WTe
+oXEUUVA/UoY6JdP/SlO6fkUCgYBfTFHBYgCm3nsKKZzlA2xqHW5Pigso5+OLypj9
+ANK09xc/g54tx4rIEDOaUisGyw1EwREnP/LITZGJiyEOewL8poFkfjv+uVAHQBwc
+7vCmTQvbFs2TinfJFp3l7XZ6rtNgfPrafKjOqYIMgtfWZdAflF3OG6FJci12B0gE
+ahH9twKBgQDgMuZ2RomZST/x8ClWpE6jDJzwBRAO8RwFDQqTV3OXqu1fImTrB8x0
+Qtclr6NeHWLgb00njgZTCk4/Yo7htdMoL8CsuUJ3Rz3hzjdN2fLO5SUNNI5vJu5/
+iHCAqSlDUmPuTSIE+xvfwZrHAxPNO+xCPoAgvFXUs7BWaGJNf16HSQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testpki-req.pem b/tests/testpki-req.pem
new file mode 100644 (file)
index 0000000..da53b10
--- /dev/null
@@ -0,0 +1,63 @@
+Certificate Request:
+    Data:
+        Version: 0 (0x0)
+        Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ac:3f:c6:b1:ef:a3:e3:68:98:2c:91:a1:3a:21:
+                    02:38:87:5b:75:7a:1c:17:c9:b0:64:a9:f7:80:17:
+                    08:0f:b5:25:b4:46:80:6b:7e:92:ab:f8:93:05:17:
+                    77:e4:12:86:eb:54:5d:a7:a0:45:70:16:5e:d7:4f:
+                    6b:7c:9f:fe:83:a4:c1:62:83:33:71:6f:4f:4e:68:
+                    84:a6:92:a5:77:8f:ad:cd:ee:bf:61:72:24:c0:64:
+                    df:73:98:de:37:6b:b8:4d:78:f4:ba:06:95:64:ef:
+                    82:b1:2f:71:01:44:ca:3c:de:fa:32:28:b6:ea:72:
+                    7b:4d:d6:a0:fb:4b:73:de:a9:7f:25:ad:20:02:3d:
+                    5f:7f:7f:8e:91:34:97:0a:10:96:be:3d:ee:37:5b:
+                    a9:91:9e:7f:d5:ac:7b:e3:56:47:a4:14:15:dd:48:
+                    ce:32:6f:c4:83:09:07:31:bb:34:77:4d:f7:12:70:
+                    86:b8:b2:64:16:3b:ea:d2:72:e0:73:6b:6f:ce:59:
+                    cf:56:6d:a8:94:3c:10:d7:47:7e:b2:91:9d:c7:65:
+                    23:8a:b1:ca:9c:15:36:c5:d9:db:b1:e7:b8:1f:09:
+                    20:1d:da:97:de:93:7c:e2:5d:94:ea:38:d8:ce:60:
+                    c9:9e:43:da:6d:9d:c9:d2:a0:e9:6d:5a:9b:57:53:
+                    86:7d
+                Exponent: 65537 (0x10001)
+        Attributes:
+            a0:00
+    Signature Algorithm: sha1WithRSAEncryption
+        21:46:4c:7a:a9:da:58:cf:ee:d3:0a:81:ee:cd:bf:73:cf:05:
+        93:2b:ef:f5:c7:7d:5e:96:a5:82:d2:62:34:26:8f:1e:f6:db:
+        6f:0e:05:39:a5:3c:df:bb:51:02:2f:bc:5b:a8:a0:a5:5e:ce:
+        e4:55:21:73:92:1d:bf:53:a4:f5:dc:7e:0e:f8:b1:05:57:3d:
+        0c:04:5e:a6:35:c6:ae:81:59:6c:28:c5:19:4b:8c:da:dd:1e:
+        97:51:bf:8b:f8:21:dc:c9:23:07:7a:66:66:fc:e2:b6:c6:e7:
+        f3:b4:e4:3e:7e:be:72:3e:3a:65:98:f1:6c:4f:79:8a:3c:11:
+        59:3d:9f:28:c8:80:eb:9d:e3:1d:6c:4e:b7:59:4e:48:b9:f8:
+        87:cf:35:13:f8:15:d3:6f:fb:1c:89:6f:ec:2c:24:5c:7b:9f:
+        fa:f0:a9:61:7b:4d:ab:40:84:dc:f8:5a:13:13:7a:b2:f4:09:
+        36:95:76:1d:c8:d8:33:eb:67:c8:c9:a9:de:98:9a:77:33:46:
+        83:37:19:60:d0:38:6f:dd:39:14:d7:a0:74:40:91:1f:60:bc:
+        0b:f8:ca:81:7d:88:67:c7:89:cf:4c:c5:95:65:66:f5:c2:98:
+        29:77:a2:93:b6:37:55:cc:f4:85:01:58:30:30:54:9a:c4:57:
+        35:e4:21:bd
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC2zCCAcMCAQAwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UE
+BxMJUGFsbyBBbHRvMRUwEwYDVQQKEwxPcGVuIHZTd2l0Y2gxHzAdBgNVBAsTFk9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLTArBgNVBAMTJE9wZW4gdlN3aXRjaCBjZXJ0
+aWZpY2F0ZSBmb3IgdGVzdHBraTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKw/xrHvo+NomCyRoTohAjiHW3V6HBfJsGSp94AXCA+1JbRGgGt+kqv4kwUX
+d+QShutUXaegRXAWXtdPa3yf/oOkwWKDM3FvT05ohKaSpXePrc3uv2FyJMBk33OY
+3jdruE149LoGlWTvgrEvcQFEyjze+jIotupye03WoPtLc96pfyWtIAI9X39/jpE0
+lwoQlr497jdbqZGef9Wse+NWR6QUFd1IzjJvxIMJBzG7NHdN9xJwhriyZBY76tJy
+4HNrb85Zz1ZtqJQ8ENdHfrKRncdlI4qxypwVNsXZ27HnuB8JIB3al96TfOJdlOo4
+2M5gyZ5D2m2dydKg6W1am1dThn0CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQAh
+Rkx6qdpYz+7TCoHuzb9zzwWTK+/1x31elqWC0mI0Jo8e9ttvDgU5pTzfu1ECL7xb
+qKClXs7kVSFzkh2/U6T13H4O+LEFVz0MBF6mNcaugVlsKMUZS4za3R6XUb+L+CHc
+ySMHemZm/OK2xufztOQ+fr5yPjplmPFsT3mKPBFZPZ8oyIDrneMdbE63WU5IufiH
+zzUT+BXTb/sciW/sLCRce5/68Klhe02rQITc+FoTE3qy9Ak2lXYdyNgz62fIyane
+mJp3M0aDNxlg0Dhv3TkU16B0QJEfYLwL+MqBfYhnx4nPTMWVZWb1wpgpd6KTtjdV
+zPSFAVgwMFSaxFc15CG9
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/testpki-req2.pem b/tests/testpki-req2.pem
new file mode 100644 (file)
index 0000000..0ec61cd
--- /dev/null
@@ -0,0 +1,63 @@
+Certificate Request:
+    Data:
+        Version: 0 (0x0)
+        Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:b0:0a:97:1e:d8:48:76:57:32:e6:4d:d7:84:86:
+                    7a:d8:0c:b7:a4:13:9c:23:bc:d5:01:94:10:81:f7:
+                    5f:b7:70:ba:1e:85:c6:5c:72:ca:cc:2a:d5:fe:cc:
+                    94:0b:54:5e:ae:40:fd:d9:c1:cc:8b:58:7e:87:73:
+                    5e:ac:98:17:f2:93:b4:cd:d7:dd:12:59:b7:8e:31:
+                    d8:63:c9:7a:14:27:f6:67:64:cb:8f:b1:d4:c1:3c:
+                    be:30:e2:08:1e:e2:db:12:9b:23:53:9f:6b:46:9b:
+                    08:46:80:6f:89:f5:77:88:b8:e0:48:89:be:0e:47:
+                    c5:5e:64:28:7e:c3:f3:10:cb:e2:95:20:6a:81:7a:
+                    14:e3:8f:e8:b8:d3:f9:31:b2:98:0c:a6:5e:9f:ea:
+                    25:89:34:22:f2:fc:a1:5d:2b:2d:0a:40:85:7a:ff:
+                    4f:5e:5a:51:72:f2:b8:03:17:db:d2:3e:40:7f:1b:
+                    6b:f1:ad:e8:ae:d3:33:d4:ad:9c:05:d4:b1:1f:53:
+                    1d:45:18:50:31:28:88:56:93:a7:f9:b2:cd:90:d5:
+                    91:dc:14:a9:33:2d:0b:8c:74:7d:94:1e:be:58:d7:
+                    fe:1e:6d:a5:a5:3b:e2:66:f0:06:f9:d9:5c:2c:66:
+                    fb:7b:85:38:13:65:ff:38:ba:1b:59:f4:08:a0:49:
+                    03:3f
+                Exponent: 65537 (0x10001)
+        Attributes:
+            a0:00
+    Signature Algorithm: sha1WithRSAEncryption
+        6e:d7:7c:0b:91:75:9a:36:25:44:cf:9b:0c:6b:8f:a5:ed:f2:
+        4f:cc:bd:9f:fd:43:dd:b4:27:0a:0d:61:4c:54:99:b0:4c:a9:
+        52:3e:39:93:68:63:e1:15:fc:47:27:54:17:08:eb:b0:6a:61:
+        61:d8:d5:d4:95:fc:1e:50:ba:6a:7f:7b:33:87:a9:b4:27:6b:
+        f4:12:05:c3:90:ca:bf:98:ea:ae:d2:1e:26:bb:cd:23:cd:38:
+        c8:f0:a1:03:9e:d7:e1:e4:d9:c0:ea:b0:31:5c:ba:7c:53:d1:
+        5e:23:c2:e8:74:57:0b:fb:23:79:89:5a:88:74:96:37:7e:3a:
+        06:8d:76:fe:e0:f0:d1:8e:60:a3:7d:0c:99:16:a0:a3:ec:f9:
+        62:c0:7d:22:18:33:16:b6:a8:31:39:07:6e:8f:d7:3f:5c:c4:
+        44:53:c6:1c:09:1a:f7:f6:27:3e:d2:73:ce:e9:37:ca:86:8a:
+        58:cc:47:0b:5c:c9:58:12:92:88:4e:6b:13:f9:b4:44:db:9c:
+        e3:f2:0a:61:8e:f8:49:59:44:35:ba:7d:b8:eb:6c:ea:72:e3:
+        cf:39:12:fc:df:08:af:b5:67:91:08:06:f6:2c:69:e9:de:b6:
+        a4:95:0a:30:72:bc:0e:f4:72:ab:0b:bc:68:ad:90:cf:c7:2d:
+        71:6e:4f:9f
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC3DCCAcQCAQAwgZYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UE
+BxMJUGFsbyBBbHRvMRUwEwYDVQQKEwxPcGVuIHZTd2l0Y2gxHzAdBgNVBAsTFk9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLjAsBgNVBAMTJU9wZW4gdlN3aXRjaCBjZXJ0
+aWZpY2F0ZSBmb3IgdGVzdHBraTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwCpce2Eh2VzLmTdeEhnrYDLekE5wjvNUBlBCB91+3cLoehcZccsrMKtX+
+zJQLVF6uQP3ZwcyLWH6Hc16smBfyk7TN190SWbeOMdhjyXoUJ/ZnZMuPsdTBPL4w
+4gge4tsSmyNTn2tGmwhGgG+J9XeIuOBIib4OR8VeZCh+w/MQy+KVIGqBehTjj+i4
+0/kxspgMpl6f6iWJNCLy/KFdKy0KQIV6/09eWlFy8rgDF9vSPkB/G2vxreiu0zPU
+rZwF1LEfUx1FGFAxKIhWk6f5ss2Q1ZHcFKkzLQuMdH2UHr5Y1/4ebaWlO+Jm8Ab5
+2VwsZvt7hTgTZf84uhtZ9AigSQM/AgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEA
+btd8C5F1mjYlRM+bDGuPpe3yT8y9n/1D3bQnCg1hTFSZsEypUj45k2hj4RX8RydU
+FwjrsGphYdjV1JX8HlC6an97M4eptCdr9BIFw5DKv5jqrtIeJrvNI804yPChA57X
+4eTZwOqwMVy6fFPRXiPC6HRXC/sjeYlaiHSWN346Bo12/uDw0Y5go30MmRago+z5
+YsB9IhgzFraoMTkHbo/XP1zERFPGHAka9/YnPtJzzuk3yoaKWMxHC1zJWBKSiE5r
+E/m0RNuc4/IKYY74SVlENbp9uOts6nLjzzkS/N8Ir7VnkQgG9ixp6d62pJUKMHK8
+DvRyqwu8aK2Qz8ctcW5Pnw==
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644 (file)
index 0000000..f4e80ca
--- /dev/null
@@ -0,0 +1,55 @@
+AT_INIT
+
+AT_COPYRIGHT([Copyright (c) 2009, 2010 Nicira Networks.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at:
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.])
+
+AT_TESTED([ovs-vswitchd])
+AT_TESTED([ovs-vsctl])
+AT_TESTED([perl])
+
+m4_define([OVS_WAIT],
+  [AT_CHECK(
+    [# First try a quick sleep, so that the test completes very quickly
+     # in the normal case.  POSIX doesn't require fractional times to
+     # work, so this might not work.
+     sleep 0.1
+     $1
+     # Then wait up to 10 seconds.
+     for d in 0 1 2 3 4 5 6 7 8 9; do
+       sleep 1
+       $1
+     done
+     exit 1], [0], [ignore], [ignore], [$2])])
+
+m4_define([OVS_WAIT_UNTIL], [OVS_WAIT([if $1; then exit 0; fi], [$2])])
+m4_define([OVS_WAIT_WHILE], [OVS_WAIT([if $1; then :; else exit 0; fi], [$2])])
+
+m4_include([tests/ovsdb-macros.at])
+
+m4_include([tests/library.at])
+m4_include([tests/check-structs.at])
+m4_include([tests/daemon.at])
+m4_include([tests/vconn.at])
+m4_include([tests/dir_name.at])
+m4_include([tests/aes128.at])
+m4_include([tests/uuid.at])
+m4_include([tests/json.at])
+m4_include([tests/jsonrpc.at])
+m4_include([tests/timeval.at])
+m4_include([tests/lockfile.at])
+m4_include([tests/reconnect.at])
+m4_include([tests/ovsdb.at])
+m4_include([tests/stp.at])
+m4_include([tests/ovs-vsctl.at])
+m4_include([tests/interface-reconfigure.at])
diff --git a/tests/timeval.at b/tests/timeval.at
new file mode 100644 (file)
index 0000000..0d61bff
--- /dev/null
@@ -0,0 +1,22 @@
+AT_BANNER([timeval unit tests])
+
+AT_SETUP([check that time advances])
+AT_KEYWORDS([timeval])
+AT_CHECK([test-timeval plain], [0])
+AT_CLEANUP
+
+AT_SETUP([check that time advances after daemonize()])
+AT_KEYWORDS([timeval])
+AT_CHECK([test-timeval daemon], [0])
+AT_CHECK(
+  [# First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if test -e test-timeval.success; then echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   echo failure; exit 1],
+  [0], [success
+], [])
+AT_CLEANUP
diff --git a/tests/uuid.at b/tests/uuid.at
new file mode 100644 (file)
index 0000000..24f7180
--- /dev/null
@@ -0,0 +1,59 @@
+AT_BANNER([UUID unit tests])
+
+m4_define([UUID_REGEX], 
+  [[[0-9a-f]\{8\}-[0-9a-f]\{4\}-4[0-9a-f]\{3\}-[89ab][0-9a-f]\{3\}-[0-9a-f]\{12\}$]])
+
+m4_define([CHECK_UUID],
+  [if expr "$uuid" : 'UUID_REGEX' > /dev/null
+   then
+      :
+   else
+     echo "$uuid: not a random UUID"
+     exit 1
+   fi])
+
+# This test is a strict subset of the larger test down below, but it
+# completes in a realistic amount of time with the "lcov" wrapper.
+AT_SETUP([UUID generation])
+AT_KEYWORDS([UUID])
+AT_CHECK([test-uuid > uuid])
+AT_CHECK([
+  uuid=`cat uuid`
+  CHECK_UUID])
+AT_CLEANUP
+
+# This test is a strict subset of the larger test down below, but it
+# completes in a realistic amount of time with the "lcov" wrapper.
+AT_SETUP([UUID parsing and serialization])
+AT_KEYWORDS([UUID])
+AT_CHECK([test-uuid f47ac10b-58cc-4372-a567-0e02b2c3d479], [0],
+         [f47ac10b-58cc-4372-a567-0e02b2c3d479
+])
+AT_CLEANUP
+
+AT_SETUP([UUID generation, parsing, serialization])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov makes this test absurdly slow
+AT_KEYWORDS([UUID])
+AT_CHECK([
+  uuids=
+  for i in m4_for([count], [1], [100], [1], [count ]); do
+     # Generate random UUID and check that it is in the expected format.
+     uuid=`test-uuid`
+     CHECK_UUID
+
+     # Verify that $uuid does not duplicate any UUID generated so far.
+     case $uuids in
+       *$uuid*) 
+         echo "$uuid: generated duplicate UUID"
+         exit 1
+     esac
+     uuids="$uuids $uuid"
+
+     # Verify that test-uuid parses and re-serializes this UUID correctly.
+     serialized=`test-uuid $uuid`
+     if test "$uuid" != "$serialized"; then
+       echo "$uuid: test-uuid serialized this as $serialized"
+       exit 1
+     fi
+   done], [0])
+AT_CLEANUP
diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl
new file mode 100755 (executable)
index 0000000..835f13b
--- /dev/null
@@ -0,0 +1,33 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+our %uuids;
+our $n_uuids = 0;
+sub lookup_uuid {
+    my ($uuid) = @_;
+    if (!exists($uuids{$uuid})) {
+        $uuids{$uuid} = $n_uuids++;
+    }
+    return "<$uuids{$uuid}>";
+}
+
+sub sort_set {
+    my ($s) = @_;
+    my (@uuids) = sort { $a <=> $b } (grep(/\d+/, split(/(\d+)/, $s)));
+    return '["set",[' . join(',', map('["uuid","<' . $_ . '>"]', @uuids)) . ']]';
+}
+
+my $u = '[0-9a-fA-F]';
+my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+while (<>) {
+    s/($uuid_re)/lookup_uuid($1)/eg;
+
+    # Sort sets like this:
+    #    [["uuid","<1>"],["uuid","<0>"]]
+    # to look like this:
+    #    [["uuid","<0>"],["uuid","<1>"]]
+    s/(\["set",\[(,?\["uuid","<\d+>"\])+\]\])/sort_set($1)/ge;
+    print $_;
+}
diff --git a/tests/valgrind-wrapper.in b/tests/valgrind-wrapper.in
new file mode 100755 (executable)
index 0000000..ed9a053
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/sh
+
+wrap_program=`basename '@wrap_program@'`
+
+# Strip the first directory from $PATH that contains $wrap_program,
+# so that below we run the real $wrap_program, not ourselves.
+not_found=true
+new_path=
+first=true
+save_IFS=$IFS
+IFS=:
+for dir in $PATH; do
+    IFS=$save_IFS
+    if $not_found && test -x "$dir/$wrap_program"; then
+        not_found=false
+    else
+        if $first; then
+            first=false
+            new_path=$dir
+        else
+            new_path=$new_path:$dir
+        fi
+    fi
+done
+IFS=$save_IFS
+if $not_found; then
+    echo "$0: error: cannot find $wrap_program in \$PATH" >&2
+    exit 1
+fi
+PATH=$new_path
+export PATH
+
+: ${VALGRIND:=valgrind -q --log-file=valgrind.%p --leak-check=full}
+exec $VALGRIND $wrap_program "$@"
+echo "$0: failed to execute $VALGRIND $wrap_program" "$@" >&2
+exit 1
diff --git a/tests/vconn.at b/tests/vconn.at
new file mode 100644 (file)
index 0000000..2f64f82
--- /dev/null
@@ -0,0 +1,22 @@
+m4_define([TEST_VCONN_CLASS],
+  [AT_BANNER([vconn library -- $1 class])
+   m4_foreach(
+     [testname], 
+     [[refuse-connection], 
+      [accept-then-close],
+      [read-hello],
+      [send-plain-hello],
+      [send-long-hello],
+      [send-echo-hello],
+      [send-short-hello],
+      [send-invalid-version-hello]],
+     [AT_SETUP([$1 vconn - m4_bpatsubst(testname, [-], [ ])])
+      m4_if([$1], [ssl], [
+        AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+        AT_CHECK([cp $abs_top_srcdir/tests/testpki*.pem .])])
+      AT_CHECK([test-vconn testname $1], [0], [], [ignore])
+      AT_CLEANUP])])
+
+TEST_VCONN_CLASS([unix])
+TEST_VCONN_CLASS([tcp])
+TEST_VCONN_CLASS([ssl])
index 32a7f2e..fbaba1e 100644 (file)
 /ovs-kill.8
 /ovs-ofctl
 /ovs-ofctl.8
+/ovs-openflowd
+/ovs-openflowd.8
 /ovs-parse-leaks
 /ovs-pki
 /ovs-pki-cgi
 /ovs-pki.8
+/ovs-vsctl
+/ovs-vsctl.8
 /ovs-wdt
index 97b827a..5feb01c 100644 (file)
@@ -1,72 +1,83 @@
 bin_PROGRAMS += \
        utilities/ovs-appctl \
-       utilities/ovs-cfg-mod \
        utilities/ovs-controller \
        utilities/ovs-discover \
        utilities/ovs-dpctl \
        utilities/ovs-kill \
        utilities/ovs-ofctl \
+       utilities/ovs-openflowd \
+       utilities/ovs-vsctl \
        utilities/ovs-wdt
 noinst_PROGRAMS += utilities/nlmon
-bin_SCRIPTS += utilities/ovs-pki
+bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl
 noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
 dist_sbin_SCRIPTS += utilities/ovs-monitor 
 
 EXTRA_DIST += \
        utilities/ovs-appctl.8.in \
-       utilities/ovs-cfg-mod.8.in \
        utilities/ovs-controller.8.in \
        utilities/ovs-discover.8.in \
        utilities/ovs-dpctl.8.in \
        utilities/ovs-kill.8.in \
        utilities/ovs-ofctl.8.in \
+       utilities/ovs-openflowd.8.in \
        utilities/ovs-parse-leaks.in \
        utilities/ovs-pki-cgi.in \
        utilities/ovs-pki.8.in \
-       utilities/ovs-pki.in
+       utilities/ovs-pki.in \
+       utilities/ovs-vsctl.8.in
 DISTCLEANFILES += \
        utilities/ovs-appctl.8 \
-       utilities/ovs-cfg-mod.8 \
        utilities/ovs-controller.8 \
        utilities/ovs-discover.8 \
        utilities/ovs-dpctl.8 \
        utilities/ovs-kill.8 \
        utilities/ovs-ofctl.8 \
+       utilities/ovs-openflowd.8 \
        utilities/ovs-parse-leaks \
        utilities/ovs-pki \
+       utilities/ovs-pki-cgi \
        utilities/ovs-pki.8 \
-       utilities/ovs-pki-cgi
+       utilities/ovs-vsctl.8
 
 man_MANS += \
        utilities/ovs-appctl.8 \
-       utilities/ovs-cfg-mod.8 \
        utilities/ovs-controller.8 \
        utilities/ovs-discover.8 \
        utilities/ovs-dpctl.8 \
        utilities/ovs-kill.8 \
        utilities/ovs-ofctl.8 \
-       utilities/ovs-pki.8
+       utilities/ovs-openflowd.8 \
+       utilities/ovs-pki.8 \
+       utilities/ovs-vsctl.8
 
 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c
 utilities_ovs_appctl_LDADD = lib/libopenvswitch.a
 
-utilities_ovs_cfg_mod_SOURCES = utilities/ovs-cfg-mod.c
-utilities_ovs_cfg_mod_LDADD = lib/libopenvswitch.a
-
 utilities_ovs_controller_SOURCES = utilities/ovs-controller.c
-utilities_ovs_controller_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+utilities_ovs_controller_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 utilities_ovs_discover_SOURCES = utilities/ovs-discover.c
 utilities_ovs_discover_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_dpctl_SOURCES = utilities/ovs-dpctl.c
-utilities_ovs_dpctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS)
+utilities_ovs_dpctl_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_kill_SOURCES = utilities/ovs-kill.c
 utilities_ovs_kill_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_ofctl_SOURCES = utilities/ovs-ofctl.c
-utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
+utilities_ovs_openflowd_SOURCES = utilities/ovs-openflowd.c
+utilities_ovs_openflowd_LDADD = \
+       ofproto/libofproto.a \
+       lib/libsflow.a \
+       lib/libopenvswitch.a \
+       $(SSL_LIBS)
+
+utilities_ovs_vsctl_SOURCES = utilities/ovs-vsctl.c vswitchd/vswitch-idl.c
+utilities_ovs_vsctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 utilities_ovs_wdt_SOURCES = utilities/ovs-wdt.c
 
index 1da026b..d5e743f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ static const struct nl_policy rtnlgrp_link_policy[] = {
 };
 
 int
-main(int argc UNUSED, char *argv[])
+main(int argc OVS_UNUSED, char *argv[])
 {
     struct nl_sock *sock;
     int error;
index 88fb295..3248e2c 100644 (file)
@@ -4,58 +4,64 @@
 .  ns
 .  IP "\\$1"
 ..
-.TH ovs\-appctl 8 "April 2009" "Open vSwitch" "Open vSwitch Manual"
+.TH ovs\-appctl 8 "November 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-appctl
-
+.
 .SH NAME
 ovs\-appctl \- utility for configuring running Open vSwitch daemons
-
+.
 .SH SYNOPSIS
-\fBovs\-appctl\fR [\fB-h\fR | \fB--help\fR] [\fItarget\fR...] [\fIaction\fR...]
-.sp 1
-The available \fItarget\fR options are:
+\fBovs\-appctl\fR [\fB--target=\fItarget\fR | \fB-t\fR \fItarget\fR]
+\fIcommand \fR[\fIarg\fR...]
 .br
-[\fB-t\fR \fIsocket\fR | \fB--target=\fIsocket\fR]
-.sp 1
-The available \fIaction\fR options are:
+\fBovs\-appctl\fR --help
 .br
-[\fB-l\fR | \fB--list\fR] [\fB-s\fR
-\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] |
-\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]
-[\fB-r\fR | \fB--reopen\fR]
-[\fB-e\fR | \fB--execute=\fIcommand\fR]
-
+\fBovs\-appctl\fR --version
 .SH DESCRIPTION
-The \fBovs\-appctl\fR program connects to one or more running
-Open vSwitch daemons (such as \fBovs\-vswitchd\fR(8)), as specified by the
-user, and sends them commands to query or modify their behavior.
-Its primary purpose is currently to adjust daemons' logging levels.
-
-\fBovs\-appctl\fR applies one or more actions to each of one or more
-target processes.  Targets may be specified using:
-
-.IP "\fB-t \fIsocket\fR"
-.IQ "\fB--target=\fIsocket\fR"
-The specified \fIsocket\fR must be the name of a Unix domain socket
-for a \fBovs\-appctl\fR-controllable process.  If \fIsocket\fR does not
-begin with \fB/\fR, it is treated as relative to \fB@RUNDIR@\fR.
-
-Each Open vSwitch daemon by default creates a socket named
-\fB@RUNDIR@/\fIprogram\fB.\fIpid\fB.ctl\fR, where \fIprogram\fR is
-the program's name (such as \fBovs\-vswitchd\fR) and \fIpid\fR is the
-daemon's PID.
-
+Open vSwitch daemons accept certain commands at runtime to control
+their behavior and query their settings.  Every daemon accepts the
+commands for querying and adjusting its logging settings documented
+under \fBLOGGING COMMANDS\fR below, and \fBovs\-vswitchd\fR in
+particular accepts a number of additional commands documented in
+\fBovs\-vswitchd\fR(8).
 .PP
-The available actions are:
-
-.IP "\fB-l\fR"
-.IQ "\fB--list\fR"
-Print the list of known logging modules and their current levels to
-stdout.
-
-.IP "\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
-.IQ "\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
-
+The \fBovs\-appctl\fR program provides a simple way to invoke these
+commands.  The command to be sent is specified on \fBovs\-appctl\fR's
+command line as non-option arguments.  \fBovs\-appctl\fR sends the
+command and prints the daemon's response on standard output.
+.PP
+In normal use only a single option is accepted:
+.IP "\fB\-t \fItarget\fR"
+.IQ "\fB\-\-target=\fItarget\fR"
+Tells \fBovs\-appctl\fR which daemon to contact.
+.IP
+If \fItarget\fR begins with \fB/\fR it must name a Unix domain socket
+on which an Open vSwitch daemon is listening for control channel
+connections.  By default, each daemon listens on a Unix domain socket
+named \fB@RUNDIR@/\fIprogram\fB.\fIpid\fB.ctl\fR, where \fIprogram\fR
+is the program's name and \fIpid\fR is its process ID.  For example,
+if \fBovs-vswitchd\fR has PID 123, it would listen on
+\fB@RUNDIR@/ovs-vswitchd.123.ctl\fR.
+.IP
+Otherwise, \fBovs\-appctl\fR looks for a pidfile, that is, a file
+whose contents are the process ID of a running process as a decimal
+number, named \fB@RUNDIR@/\fItarget\fB.pid\fR.  (The \fB\-\-pidfile\fR
+option makes an Open vSwitch daemon create a pidfile.)
+\fBovs\-appctl\fR reads the pidfile, then looks for a Unix socket
+named \fB@RUNDIR@/\fItarget\fB.\fIpid\fB.ctl\fR, where \fIpid\fR is
+replaced by the process ID read from the pidfile, and uses that file
+as if it had been specified directly as the target.
+.IP
+The default target is \fBovs\-vswitchd\fR.
+.
+.SH LOGGING COMMANDS
+Every Open vSwitch daemon supports the following commands for
+examining and adjusting log levels.
+.
+.IP "\fBvlog/list\fR"
+Lists the known logging modules and their current levels.
+.
+.IP "\fBvlog/set\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
 Sets the logging level for \fImodule\fR in \fIfacility\fR to
 \fIlevel\fR.  The \fImodule\fR may be any valid module name (as
 displayed by the \fB--list\fR option) or the special name \fBANY\fR to
@@ -67,100 +73,100 @@ logging levels for both facilities.  If it is omitted,
 \fBemer\fR, \fBerr\fR, \fBwarn\fR, \fBinfo\fR, or \fBdbg\fR, designating the
 minimum severity of a message for it to be logged.  If it is omitted,
 \fIlevel\fR defaults to \fBdbg\fR.
-
-.IP "\fB-s PATTERN:\fIfacility\fB:\fIpattern\fR"
-.IQ "\fB--set=PATTERN:\fIfacility\fB:\fIpattern\fR"
-
+.
+.IP "\fBvlog/set PATTERN:\fIfacility\fB:\fIpattern\fR"
 Sets the log pattern for \fIfacility\fR to \fIpattern\fR.  Each time a
 message is logged to \fIfacility\fR, \fIpattern\fR determines the
 message's formatting.  Most characters in \fIpattern\fR are copied
 literally to the log, but special escapes beginning with \fB%\fR are
 expanded as follows:
-
+.
 .RS
 .IP \fB%A\fR
-The name of the application logging the message, e.g. \fBsecchan\fR.
-
+The name of the application logging the message, e.g. \fBovs-vswitchd\fR.
+.
 .IP \fB%c\fR
 The name of the module (as shown by \fBovs\-appctl --list\fR) logging
 the message.
-
+.
 .IP \fB%d\fR
 The current date and time in ISO 8601 format (YYYY-MM-DD HH:MM:SS).
-
+.
 .IP \fB%d{\fIformat\fB}\fR
 The current date and time in the specified \fIformat\fR, which takes
 the same format as the \fItemplate\fR argument to \fBstrftime\fR(3).
-
+.
 .IP \fB%m\fR
 The message being logged.
-
+.
 .IP \fB%N\fR
 A serial number for this message within this run of the program, as a
 decimal number.  The first message a program logs has serial number 1,
 the second one has serial number 2, and so on.
-
+.
 .IP \fB%n\fR
 A new-line.
-
+.
 .IP \fB%p\fR
 The level at which the message is logged, e.g. \fBDBG\fR.
-
+.
 .IP \fB%P\fR
 The program's process ID (pid), as a decimal number.
-
+.
 .IP \fB%r\fR
 The number of milliseconds elapsed from the start of the application
 to the time the message was logged.
-
+.
 .IP \fB%%\fR
 A literal \fB%\fR.
 .RE
-
+.
 .IP
 A few options may appear between the \fB%\fR and the format specifier
 character, in this order:
-
+.
 .RS
 .IP \fB-\fR
 Left justify the escape's expansion within its field width.  Right
 justification is the default.
-
+.
 .IP \fB0\fR
 Pad the field to the field width with \fB0\fRs.  Padding with spaces
 is the default.
-
+.
 .IP \fIwidth\fR
 A number specifies the minimum field width.  If the escape expands to
 fewer characters than \fIwidth\fR then it is padded to fill the field
 width.  (A field wider than \fIwidth\fR is not truncated to fit.)
 .RE
-
+.
 .IP
 The default pattern for console output is \fB%d{%b %d
 %H:%M:%S}|%05N|%c|%p|%m\fR; for syslog output, \fB%05N|%c|%p|%m\fR.
-
-.IP \fB-r\fR
-.IQ \fB--reopen\fR
-Causes the target application to close and reopen its log file.  (This
+.
+.IP "\fBvlog/reopen\fR"
+Causes the daemon to close and reopen its log file.  (This
 is useful after rotating log files, to cause a new log file to be
 used.)
-
+.IP
 This has no effect if the target application was not invoked with the
 \fB--log-file\fR option.
-
-.IP "\fB-e \fIcommand\fR"
-.IQ "\fB--execute=\fIcommand\fR"
-Passes the specified \fIcommand\fR literally to the target application
-and prints its response to stdout, if successful, or to stderr if an
-error occurs.  Use \fB-e help\fR to print a list of available commands.
-
+.
 .SH OPTIONS
-
+.
 .so lib/common.man
-
+.
+.SH BUGS
+.
+The protocol used to speak to Open vSwitch daemons does not contain a
+quoting mechanism, so command arguments should not generally contain
+white space.
+.
 .SH "SEE ALSO"
-
+.
+\fBovs\-appctl\fR can control the following daemons:
+.BR ovs\-vswitchd (8),
+.BR ovs\-openflowd (8),
 .BR ovs\-controller (8),
-.BR ovs\-dpctl (8),
-.BR secchan (8)
+.BR ovs\-brcompatd (8),
+.BR ovs\-discover (8).
index 9349662..7a3d91e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include <config.h>
-#include "vlog.h"
 
-#include <dirent.h>
 #include <errno.h>
 #include <getopt.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 
 #include "command-line.h"
-#include "compiler.h"
+#include "daemon.h"
+#include "dirs.h"
+#include "dynamic-string.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
 
-static void
-usage(char *prog_name, int exit_code)
-{
-    printf("Usage: %s [TARGET] [ACTION...]\n"
-           "Targets:\n"
-           "  -t, --target=TARGET  Path to Unix domain socket\n"
-           "Actions:\n"
-           "  -l, --list         List current settings\n"
-           "  -s, --set=MODULE[:FACILITY[:LEVEL]]\n"
-           "        Set MODULE and FACILITY log level to LEVEL\n"
-           "        MODULE may be any valid module name or 'ANY'\n"
-           "        FACILITY may be 'syslog', 'console', 'file', or 'ANY' (default)\n"
-           "        LEVEL may be 'emer', 'err', 'warn', 'info', or 'dbg' (default)\n"
-           "  -r, --reopen       Make the program reopen its log file\n"
-           "  -e, --execute=COMMAND  Execute control COMMAND and print its output\n"
-           "Other options:\n"
-           "  -h, --help         Print this helpful information\n"
-           "  -V, --version      Display version information\n",
-           prog_name);
-    exit(exit_code);
-}
+static void usage(void);
+static const char *parse_command_line(int argc, char *argv[]);
+static struct unixctl_client *connect_to_target(const char *target);
 
-static char *
-transact(struct unixctl_client *client, const char *request, bool *ok)
+int
+main(int argc, char *argv[])
 {
-    int code;
+    struct unixctl_client *client;
+    const char *target;
+    struct ds request;
+    int code, error;
     char *reply;
-    int error = unixctl_client_transact(client, request, &code, &reply);
-    if (error) {
-        fprintf(stderr, "%s: transaction error: %s\n",
-                unixctl_client_target(client), strerror(error));
-        *ok = false;
-        return xstrdup("");
-    } else {
-        if (code / 100 != 2) {
-            fprintf(stderr, "%s: server returned reply code %03d\n",
-                    unixctl_client_target(client), code);
+    int i;
+
+    set_program_name(argv[0]);
+    time_init();
+
+    /* Parse command line and connect to target. */
+    target = parse_command_line(argc, argv);
+    client = connect_to_target(target);
+
+    /* Compose request. */
+    ds_init(&request);
+    for (i = optind; i < argc; i++) {
+        if (i != optind) {
+            ds_put_char(&request, ' ');
         }
-        return reply;
+        ds_put_cstr(&request, argv[i]);
     }
-}
-
-static void
-transact_ack(struct unixctl_client *client, const char *request, bool *ok)
-{
-    free(transact(client, request, ok));
-}
 
-static void
-execute_command(struct unixctl_client *client, const char *request, bool *ok)
-{
-    int code;
-    char *reply;
-    int error = unixctl_client_transact(client, request, &code, &reply);
+    /* Transact request and process reply. */
+    error = unixctl_client_transact(client, ds_cstr(&request), &code, &reply);
     if (error) {
-        fprintf(stderr, "%s: transaction error: %s\n",
-                unixctl_client_target(client), strerror(error));
-        *ok = false;
-    } else {
-        if (code / 100 != 2) {
-            fprintf(stderr, "%s: server returned reply code %03d\n",
-                    unixctl_client_target(client), code);
-            fputs(reply, stderr);
-            *ok = false;
-        } else {
-            fputs(reply, stdout);
-        }
+        ovs_fatal(error, "%s: transaction error", target);
+    }
+    if (code / 100 != 2) {
+        ovs_error(0, "%s: server returned reply code %03d", target, code);
+        exit(2);
     }
+    fputs(reply, stdout);
+
+    unixctl_client_destroy(client);
+    free(reply);
+    ds_destroy(&request);
+
+    return 0;
 }
 
 static void
-add_target(struct unixctl_client ***clients, size_t *n_clients,
-           const char *path, bool *ok)
+usage(void)
 {
-    struct unixctl_client *client;
-    int error = unixctl_client_create(path, &client);
-    if (error) {
-        fprintf(stderr, "Error connecting to \"%s\": %s\n",
-                path, strerror(error));
-        *ok = false;
-    } else {
-        *clients = xrealloc(*clients, sizeof *clients * (*n_clients + 1));
-        (*clients)[*n_clients] = client;
-        ++*n_clients;
-    }
+    printf("%s, for querying and controlling Open vSwitch daemon\n"
+           "usage: %s [TARGET] COMMAND [ARG...]\n"
+           "Targets:\n"
+           "  -t, --target=TARGET  pidfile or socket to contact\n"
+           "Common commands:"
+           "  help               List commands supported by the target\n"
+           "  vlog/list          List current logging levels\n"
+           "  vlog/set MODULE[:FACILITY[:LEVEL]]\n"
+           "        Set MODULE and FACILITY log level to LEVEL\n"
+           "        MODULE may be any valid module name or 'ANY'\n"
+           "        FACILITY may be 'syslog', 'console', 'file', or 'ANY' (default)\n"
+           "        LEVEL may be 'emer', 'err', 'warn', 'info', or 'dbg' (default)\n"
+           "  vlog/reopen        Make the program reopen its log file\n"
+           "Other options:\n"
+           "  -h, --help         Print this helpful information\n"
+           "  -V, --version      Display version information\n",
+           program_name, program_name);
+    exit(EXIT_SUCCESS);
 }
 
-int main(int argc, char *argv[])
+static const char *
+parse_command_line(int argc, char *argv[])
 {
     static const struct option long_options[] = {
-        /* Target options must come first. */
         {"target", required_argument, NULL, 't'},
+        {"execute", no_argument, NULL, 'e'},
         {"help", no_argument, NULL, 'h'},
         {"version", no_argument, NULL, 'V'},
-
-        /* Action options come afterward. */
-        {"list", no_argument, NULL, 'l'},
-        {"set", required_argument, NULL, 's'},
-        {"reopen", no_argument, NULL, 'r'},
-        {"execute", required_argument, NULL, 'e'},
         {0, 0, 0, 0},
     };
-    char *short_options;
+    const char *target;
+    int e_options;
 
-    /* Determine targets. */
-    bool ok = true;
-    int n_actions = 0;
-    struct unixctl_client **clients = NULL;
-    size_t n_clients = 0;
-
-    set_program_name(argv[0]);
-    time_init();
-
-    short_options = long_options_to_short_options(long_options);
+    target = NULL;
+    e_options = 0;
     for (;;) {
         int option;
-        size_t i;
 
-        option = getopt_long(argc, argv, short_options, long_options, NULL);
+        option = getopt_long(argc, argv, "+t:hVe", long_options, NULL);
         if (option == -1) {
             break;
         }
-        if (!strchr("thV", option) && n_clients == 0) {
-            ovs_fatal(0, "no targets specified (use --help for help)");
-        } else {
-            ++n_actions;
-        }
         switch (option) {
         case 't':
-            add_target(&clients, &n_clients, optarg, &ok);
-            break;
-
-        case 'l':
-            for (i = 0; i < n_clients; i++) {
-                struct unixctl_client *client = clients[i];
-                char *reply;
-
-                printf("%s:\n", unixctl_client_target(client));
-                reply = transact(client, "vlog/list", &ok);
-                fputs(reply, stdout);
-                free(reply);
-            }
-            break;
-
-        case 's':
-            for (i = 0; i < n_clients; i++) {
-                struct unixctl_client *client = clients[i];
-                char *request = xasprintf("vlog/set %s", optarg);
-                transact_ack(client, request, &ok);
-                free(request);
-            }
-            break;
-
-        case 'r':
-            for (i = 0; i < n_clients; i++) {
-                struct unixctl_client *client = clients[i];
-                char *request = xstrdup("vlog/reopen");
-                transact_ack(client, request, &ok);
-                free(request);
+            if (target) {
+                ovs_fatal(0, "-t or --target may be specified only once");
             }
+            target = optarg;
             break;
 
         case 'e':
-            for (i = 0; i < n_clients; i++) {
-                execute_command(clients[i], optarg, &ok);
+            /* We ignore -e for compatibility.  Older versions specified the
+             * command as the argument to -e.  Since the current version takes
+             * the command as non-option arguments and we say that -e has no
+             * arguments, this just works in the common case. */
+            if (e_options++) {
+                ovs_fatal(0, "-e or --execute may be speciifed only once");
             }
             break;
 
         case 'h':
-            usage(argv[0], EXIT_SUCCESS);
+            usage();
             break;
 
         case 'V':
@@ -213,9 +156,44 @@ int main(int argc, char *argv[])
             NOT_REACHED();
         }
     }
-    if (!n_actions) {
-        fprintf(stderr,
-                "warning: no actions specified (use --help for help)\n");
+
+    if (optind >= argc) {
+        ovs_fatal(0, "at least one non-option argument is required "
+                  "(use --help for help)");
     }
-    exit(ok ? 0 : 1);
+
+    return target ? target : "ovs-vswitchd";
 }
+
+static struct unixctl_client *
+connect_to_target(const char *target)
+{
+    struct unixctl_client *client;
+    char *socket_name;
+    int error;
+
+    if (target[0] != '/') {
+        char *pidfile_name;
+        pid_t pid;
+
+        pidfile_name = xasprintf("%s/%s.pid", ovs_rundir, target);
+        pid = read_pidfile(pidfile_name);
+        if (pid < 0) {
+            ovs_fatal(-pid, "cannot read pidfile \"%s\"", pidfile_name);
+        }
+        free(pidfile_name);
+        socket_name = xasprintf("%s/%s.%ld.ctl",
+                                ovs_rundir, target, (long int) pid);
+    } else {
+        socket_name = xstrdup(target);
+    }
+
+    error = unixctl_client_create(socket_name, &client);
+    if (error) {
+        ovs_fatal(error, "cannot connect to \"%s\"", socket_name);
+    }
+    free(socket_name);
+
+    return client;
+}
+
diff --git a/utilities/ovs-cfg-mod.8.in b/utilities/ovs-cfg-mod.8.in
deleted file mode 100644 (file)
index 5b96f28..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-.\" -*- nroff -*-
-.de IQ
-.  br
-.  ns
-.  IP "\\$1"
-..
-.TH ovs\-cfg\-mod 8 "June 2009" "Open vSwitch" "Open vSwitch Manual"
-.ds PN ovs\-cfg\-mod
-.
-.SH NAME
-ovs\-cfg\-mod \- Safely manage a ovs\-vswitchd.conf\-style configuration file
-.
-.SH SYNOPSIS
-\fB ovs\-cfg\-mod \fR[\fB\-T \fItimeout\fR] \fB\-F \fIfile\fR
-[\fIaction\fR] [\fIaction\fR...\fR]
-.
-.SH DESCRIPTION
-A program for managing a \fovs\-vswitchd.conf\fR(5)\-style configuration
-file.  \fBovs\-cfg\-mod\fR uses the same locking mechanisms as
-\fBovs\-vswitchd\fR and its related utilities.  This allows it to be
-run safely on ``live'' configurations.
-.
-.SH OPTIONS
-.SS "Specifying the Configuration File"
-.
-.IP "\fB\-T\fR \fItimeout\fR
-.IQ "\fB\-\-timeout=\fItimeout\fR
-By default, \fBovs\-cfg\-mod\fR will wait forever to lock the
-configuration file specified on \fB\-F\fR or \fB\-\-config\-file\fR.  This
-option makes \fBovs\-cfg\-mod\fR wait no more than \fItimeout\fR
-milliseconds to obtain the lock, after which it exits unsuccessfully.
-.
-If it is present, this option must be specified before \fB\-F\fR or
-\fB\-\-config\-file\fR.
-.
-.IP "\fB\-F\fR \fIfile\fR"
-.IQ "\fB\-\-config\-file=\fIfile\fR"
-Use \fIfile\fR as the configuration file to query or modify.
-.
-This option is required.  It must be specified before any action
-options.
-.
-.SS "Specifying Actions"
-A series of one or more action options may follow the configuration
-file options.  These are executed in the order provided and under a
-single lock instance, so they appear atomic to external viewers of
-\fIfile\fR.
-.
-As discussed in \fBovs\-vswitchd.conf\fR(5), each line in the
-configuration file consists of a key\-value pair.  Actions generally
-take either a \fIkey\fR or \fIentry\fR argument.  A \fIkey\fR is a
-dot\-separated description of a configuration option.  A \fIentry\fR is
-a key\-value pair, separated by the \fB=\fR sign.
-.
-The following actions are supported:
-.
-.IP "\fB\-a\fR \fIentry\fR"
-.IQ "\fB\-\-add=\fIentry\fR"
-Add \fIentry\fR to \fIfile\fR.  Please note that duplicates are
-allowed, so if a unique key is required, a delete must be done first.
-.
-.IP "\fB\-d\fR \fIentry\fR"
-.IQ "\fB\-\-del\-entry=\fIentry\fR"
-Delete \fIentry\fR from \fIfile\fR.  Deletes only the first entry 
-that matches \fIentry\fR.  
-.
-.IP "\fB\-D\fR \fIkey\fR"
-.IQ "\fB\-\-del\-section=\fIkey\fR"
-Delete section \fIkey\fR from \fIfile\fR.  
-.
-.IP "\fB\-\-del\-match=\fIpattern\fR"
-Deletes every entry that matches the given shell glob \fIpattern\fR.
-For example, \fB\-\-del\-match=bridge.*.port=*\fR deletes all the ports
-from every bridge, and \fB\-\-del\-match=bonding.bond0.*\fR is equivalent
-to \fB\-\-del\-section=bonding.bond0\fR.
-.
-.IP "\fB\-q\fR \fIkey\fR"
-.IQ "\fB\-\-query=\fIkey\fR"
-Queries \fIfile\fR for entries that match \fIkey\fR.  Each matching
-value is printed on a separate line.  Duplicates will be printed
-multiple times.  
-.
-.IP "\fB\-c\fR"
-.IQ "\fB\-\-changes\fR"
-Logs all of the changes made to the configuration file in a ``unified
-diff''\-like format.  Only actual changes are logged, so that if, for
-example, a \fB\-\-del\-match\fR action did not match any key\-value pairs,
-then nothing will be logged due to that action.  Furthermore, only the
-net effects of changes are logged: if a key\-value pair was deleted and
-then an identical key\-value pair was added back, then nothing would be
-logged due to those changes.
-.
-This action logs changes that have taken effect at the point where it
-is inserted.  Thus, if it is given before any other action, it will
-not log any changes.  If \fB\-\-changes\fR is given more than once,
-instances after the first log only the changes since the previous
-instance.
-.
-.SH "SEE ALSO"
-.BR ovs\-vswitchd (8),
-.BR ovs\-vswitchd.conf (5)
diff --git a/utilities/ovs-cfg-mod.c b/utilities/ovs-cfg-mod.c
deleted file mode 100644 (file)
index 1b52a7b..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/* Copyright (c) 2008, 2009  Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <config.h>
-
-#include <dirent.h>
-#include <errno.h>
-#include <getopt.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "cfg.h"
-#include "command-line.h"
-#include "svec.h"
-#include "timeval.h"
-#include "util.h"
-
-#define THIS_MODULE VLM_cfg_mod
-#include "vlog.h"
-
-/* Configuration when we first read the configuration file. */
-static struct svec orig_cfg = SVEC_EMPTY_INITIALIZER;
-
-static void
-usage(char *prog_name, int exit_code)
-{
-    printf("Usage: %s --config-file=FILE ACTIONS\n"
-           "\nConfig:\n"
-           "  -T, --timeout=MS        wait at most MS milliseconds for lock\n"
-           "  -F, --config-file=FILE  use configuration FILE\n"
-           "\nActions:\n"
-           "  -a, --add=ENTRY         add ENTRY\n"
-           "  -d, --del-entry=ENTRY   delete ENTRY\n"
-           "  -D, --del-section=KEY   delete section matching KEY\n"
-           "  --del-match=PATTERN     delete entries matching shell PATTERN\n"
-           "  -q, --query=KEY         return all entries matching KEY\n"
-           "  -c, --log-changes       log changes up to this point\n"
-           "\nOther options:\n"
-           "  -h, --help              display this help message\n"
-           "  -V, --version           display version information\n",
-           prog_name);
-    exit(exit_code);
-}
-
-static void 
-open_config(char *config_file, int timeout) 
-{
-    int error;
-
-    cfg_init();
-    error = cfg_set_file(config_file);
-    if (error) {
-        ovs_fatal(error, "failed to add configuration file \"%s\"",
-                config_file);
-    }
-
-    error = cfg_lock(NULL, timeout);
-    if (error) {
-        ovs_fatal(error, "could not lock configuration file\n");
-    }
-
-    cfg_get_all(&orig_cfg);
-}
-
-static void
-print_vals(char *key)
-{
-    struct svec vals;
-    int i;
-
-    svec_init(&vals);
-    cfg_get_all_strings(&vals, "%s", key);
-
-    for (i=0; i<vals.n; i++) {
-        printf("%s\n", vals.names[i]);
-    }
-}
-
-static void
-log_diffs(void)
-{
-    struct svec new_cfg, removed, added;
-    size_t i;
-
-    svec_init(&new_cfg);
-    cfg_get_all(&new_cfg);
-    svec_diff(&orig_cfg, &new_cfg, &removed, NULL, &added);
-    if (removed.n || added.n) {
-        VLOG_INFO("configuration changes:");
-        for (i = 0; i < removed.n; i++) {
-            VLOG_INFO("-%s", removed.names[i]);
-        }
-        for (i = 0; i < added.n; i++) {
-            VLOG_INFO("+%s", added.names[i]);
-        }
-    } else {
-        VLOG_INFO("configuration unchanged");
-    }
-    svec_destroy(&added);
-    svec_destroy(&removed);
-    svec_swap(&new_cfg, &orig_cfg);
-    svec_destroy(&new_cfg);
-}
-
-int main(int argc, char *argv[])
-{
-    enum {
-        OPT_DEL_MATCH = UCHAR_MAX + 1,
-    };
-    static const struct option long_options[] = {
-        {"config-file",  required_argument, 0, 'F'},
-        {"timeout",      required_argument, 0, 'T'},
-        {"add",          required_argument, 0, 'a'},
-        {"del-entry",    required_argument, 0, 'd'},
-        {"del-section",  required_argument, 0, 'D'},
-        {"del-match",    required_argument, 0, OPT_DEL_MATCH},
-        {"query",        required_argument, 0, 'q'},
-        {"changes",      no_argument, 0, 'c'},
-        {"verbose",      optional_argument, 0, 'v'},
-        {"help",         no_argument, 0, 'h'},
-        {"version",      no_argument, 0, 'V'},
-        {0, 0, 0, 0},
-    };
-    char *short_options;
-    bool config_set = false;
-    int timeout = INT_MAX;
-
-    set_program_name(argv[0]);
-    time_init();
-    vlog_init();
-
-    short_options = long_options_to_short_options(long_options);
-    for (;;) {
-        int option;
-
-        option = getopt_long(argc, argv, short_options, long_options, NULL);
-        if (option == -1) {
-            break;
-        }
-
-        if ((option > UCHAR_MAX || !strchr("FhVv?", option))
-            && config_set == false) {
-            ovs_fatal(0, "no config file specified (use --help for help)");
-        }
-
-        switch (option) {
-        case 'T':
-            if (config_set) {
-                ovs_fatal(0, "--timeout or -T must be specified "
-                          "before --file or -F");
-            }
-            timeout = atoi(optarg);
-            break;
-
-        case 'F': 
-            open_config(optarg, timeout);
-            config_set = true;
-            break;
-
-       case 'a':
-            cfg_add_entry("%s", optarg);
-            break;
-
-        case 'd':
-            cfg_del_entry("%s", optarg);
-            break;
-
-        case 'D':
-            cfg_del_section("%s", optarg);
-            break;
-
-        case OPT_DEL_MATCH:
-            cfg_del_match("%s", optarg);
-            break;
-
-        case 'q':
-            print_vals(optarg);
-            break;
-
-        case 'c':
-            log_diffs();
-            break;
-
-        case 'h':
-            usage(argv[0], EXIT_SUCCESS);
-            break;
-
-        case 'V':
-            OVS_PRINT_VERSION(0, 0);
-            exit(EXIT_SUCCESS);
-
-        case 'v':
-            vlog_set_verbosity(optarg);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            NOT_REACHED();
-        }
-    }
-    free(short_options);
-
-    if (optind != argc) {
-        ovs_fatal(0, "non-option arguments not accepted "
-                  "(use --help for help)");
-    }
-
-    if (cfg_is_dirty()) {
-        cfg_write();
-    }
-    cfg_unlock();
-
-    exit(0);
-}
index 43bc43b..e0c1540 100644 (file)
 .TH ovs\-controller 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-controller
-
+.
 .SH NAME
 ovs\-controller \- simple OpenFlow controller reference implementation
-
+.
 .SH SYNOPSIS
 .B ovs\-controller
 [\fIoptions\fR] \fImethod\fR \fB[\fImethod\fR]\&...
-
+.
 .SH DESCRIPTION
 \fBovs\-controller\fR manages any number of remote switches over OpenFlow
 protocol, causing them to function as L2 MAC-learning switches or hub.
-
+.PP
 \fBovs\-controller\fR controls one or more OpenFlow switches, specified as
 one or more of the following OpenFlow connection methods:
-
-.TP
-\fBpssl:\fR[\fIport\fR]
-Listens for SSL connections from remote OpenFlow switches on
-\fIport\fR (default: 6633).  The \fB--private-key\fR,
-\fB--certificate\fR, and \fB--ca-cert\fR options are mandatory when
-this form is used.
-
-.TP
-\fBptcp:\fR[\fIport\fR]
-Listens for TCP connections from remote OpenFlow switches on
-\fIport\fR (default: 6633).
-
-.TP
-\fBpunix:\fIfile\fR
-Listens for connections from OpenFlow switches on the Unix domain
-server socket named \fIfile\fR.
-
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
-
+.
+.RS
+.so lib/vconn-passive.man
+.so lib/vconn-active.man
+.RE
+.
 .SH OPTIONS
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the switch's
-identity for SSL connections to the controller.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the switch's
-private key to identify a trustworthy switch.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIswitch-cacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the switch is connected to a trustworthy controller.
-
-.TP
-\fB--peer-ca-cert=\fIcontroller-cacert.pem\fR
-Specifies a PEM file that contains one or more additional certificates
-to send to switches.  \fIcontroller-cacert.pem\fR should be the CA
-certificate used to sign the controller's own certificate (the
-certificate specified on \fB-c\fR or \fB--certificate\fR).
-
-This option is not useful in normal operation, because the switch must
-already have the controller CA certificate for it to have any
-confidence in the controller's identity.  However, this option allows
-a newly installed switch to obtain the controller CA certificate on
-first boot using, e.g., the \fB--bootstrap-ca-cert\fR option to
-\fBsecchan\fR(8).
-
 .IP "\fB-n\fR, \fB--noflow\fR"
 By default, \fBovs\-controller\fR sets up a flow in each OpenFlow switch
 whenever it receives a packet whose destination is known due through
 MAC learning.  This option disables flow setup, so that every packet
 in the network passes through the controller.
-
+.IP
 This option is most useful for debugging.  It reduces switching
 performance, so it should not be used in production.
-
+.
 .TP
 \fB--max-idle=\fIsecs\fR|\fBpermanent\fR
 Sets \fIsecs\fR as the number of seconds that a flow set up by the
 controller will remain in the switch's flow table without any matching
 packets being seen.  If \fBpermanent\fR is specified, which is not
 recommended, flows will never expire.  The default is 60 seconds.
-
+.IP
 This option affects only flows set up by the OpenFlow controller.  In
 some configurations, the switch can set up some flows
 on its own.  To set the idle time for those flows, pass
-\fB--max-idle\fR to \fBsecchan\fR (on the switch).
-
+\fB--max-idle\fR to \fBovs\-openflowd\fR (on the switch).
+.IP
 This option has no effect when \fB-n\fR (or \fB--noflow\fR) is in use
 (because the controller does not set up flows in that case).
-
+.
 .IP "\fB-H\fR, \fB--hub\fR"
 By default, the controller acts as an L2 MAC-learning switch.  This
 option changes its behavior to that of a hub that floods packets on
 all but the incoming port.
-
+.IP
 If \fB-H\fR (or \fB--hub\fR) and \fB-n\fR (or \fB--noflow\fR) are used
 together, then the cumulative effect is that every packet passes
 through the controller and every packet is flooded.
-
+.IP
 This option is most useful for debugging.  It reduces switching
 performance, so it should not be used in production.
-
+.
+.IP "\fB-w\fR, \fB--wildcard\fR"
+By default, \fBovs\-controller\fR sets up exact-match flows.  This
+option allows it to set up wildcarded flows, which may reduce
+flow-setup latency by causing less traffic to be sent up to the
+controller.
+.IP
+This option has no effect when \fB-n\fR (or \fB--noflow\fR) is in use
+(because the controller does not set up flows in that case).
+.
+.IP "\fB-N\fR, \fB--normal\fR"
+By default, \fBovs\-controller\fR directs packets to a particular port
+or floods them.  This option causes it to direct non-flooded packets
+to the OpenFlow \fBOFPP_NORMAL\fR port.  This allows the switch itself
+to make decisions about packet destinations.  Support for
+\fBOFPP_NORMAL\fR is optional in OpenFlow, so this option may not well
+with some non-Open vSwitch switches.
+.
 .IP "\fB--mute\fR"
 Prevents ovs\-controller from replying to any OpenFlow messages sent
 to it by switches.
 .IP
 This option is only for debugging the Open vSwitch implementation of
 ``fail open'' mode.  It must not be used in production.
-
+.
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
+.so lib/ssl-peer-ca-cert.man
 .so lib/daemon.man
 .so lib/vlog.man
 .so lib/common.man
-
+.
 .SH EXAMPLES
-
+.
 .TP
 To bind locally to port 6633 (the default) and wait for incoming connections from OpenFlow switches:
-
+.PP
 .B % ovs\-controller ptcp:
-
+.
 .SH "SEE ALSO"
-
-.BR secchan (8),
+.
+.BR ovs\-openflowd (8),
 .BR ovs\-appctl (8),
 .BR ovs\-dpctl (8)
index 30ab52d..0497d9a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
-#include "fault.h"
 #include "learning-switch.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "poll-loop.h"
 #include "rconn.h"
+#include "stream-ssl.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
 
 #include "vlog.h"
@@ -55,6 +54,12 @@ static bool learn_macs = true;
 /* Set up flows?  (If not, every packet is processed at the controller.) */
 static bool set_up_flows = true;
 
+/* -N, --normal: Use "NORMAL" action instead of explicit port? */
+static bool action_normal = false;
+
+/* -w, --wildcard: Set up exact match or wildcard flow entries? */
+static bool exact_flows = true;
+
 /* --max-idle: Maximum idle time, in seconds, before flows expire. */
 static int max_idle = 60;
 
@@ -77,8 +82,8 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
     parse_options(argc, argv);
@@ -121,13 +126,15 @@ main(int argc, char *argv[])
     }
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
+    daemonize_complete();
+
     while (n_switches > 0 || n_listeners > 0) {
         int iteration;
         int i;
@@ -201,8 +208,9 @@ static void
 new_switch(struct switch_ *sw, struct vconn *vconn, const char *name)
 {
     sw->rconn = rconn_new_from_vconn(name, vconn);
-    sw->lswitch = lswitch_create(sw->rconn, learn_macs,
-                                 set_up_flows ? max_idle : -1);
+    sw->lswitch = lswitch_create(sw->rconn, learn_macs, exact_flows,
+                                 set_up_flows ? max_idle : -1,
+                                 action_normal);
 }
 
 static int
@@ -239,6 +247,8 @@ parse_options(int argc, char *argv[])
     static struct option long_options[] = {
         {"hub",         no_argument, 0, 'H'},
         {"noflow",      no_argument, 0, 'n'},
+        {"normal",      no_argument, 0, 'N'},
+        {"wildcard",    no_argument, 0, 'w'},
         {"max-idle",    required_argument, 0, OPT_MAX_IDLE},
         {"mute",        no_argument, 0, OPT_MUTE},
         {"help",        no_argument, 0, 'h'},
@@ -246,7 +256,7 @@ parse_options(int argc, char *argv[])
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
 #endif
         {0, 0, 0, 0},
@@ -275,6 +285,14 @@ parse_options(int argc, char *argv[])
             mute = true;
             break;
 
+        case 'N':
+            action_normal = true;
+            break;
+
+        case 'w':
+            exact_flows = false;
+            break;
+
         case OPT_MAX_IDLE:
             if (!strcmp(optarg, "permanent")) {
                 max_idle = OFP_FLOW_PERMANENT;
@@ -298,10 +316,10 @@ parse_options(int argc, char *argv[])
         DAEMON_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case OPT_PEER_CA_CERT:
-            vconn_ssl_set_peer_ca_cert_file(optarg);
+            stream_ssl_set_peer_ca_cert_file(optarg);
             break;
 #endif
 
@@ -329,6 +347,8 @@ usage(void)
            "  -H, --hub               act as hub instead of learning switch\n"
            "  -n, --noflow            pass traffic, but don't add flows\n"
            "  --max-idle=SECS         max idle time for new flows\n"
+           "  -N, --normal            use OFPAT_NORMAL action\n"
+           "  -w, --wildcard          use wildcards, not exact-match rules\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
     exit(EXIT_SUCCESS);
index e9685d9..61e4b12 100644 (file)
@@ -17,7 +17,7 @@ receives an acceptable DHCP response.  It will accept any valid DHCP
 reply that has the same vendor class identifier and includes a
 vendor-specific option with code 1 whose contents are a string
 specifying the location of the controller in the same format used on
-the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR).
+the \fBovs\-openflowd\fR command line (e.g. \fBssl:192.168.0.1\fR).
 
 When \fBovs\-discover\fR receives an acceptable response, it prints
 the details of the response on \fBstdout\fR.  Then, by default, it
@@ -28,15 +28,16 @@ itself to the background.
 .SH OPTIONS
 .TP
 \fB--accept-vconn=\fIregex\fR
-By default, \fBovs\-discover\fR accepts any controller location
-advertised over DHCP.  With this option, only controllers whose names
-match POSIX extended regular expression \fIregex\fR will be accepted.
-Specifying \fBssl:.*\fR for \fIregex\fR, for example, would cause only
-SSL controller connections to be accepted.
+With this option, only controllers whose names match POSIX extended
+regular expression \fIregex\fR will be accepted.  Specifying
+\fBssl:.*\fR for \fIregex\fR, for example, would cause only SSL
+controller connections to be accepted.
 
 The \fIregex\fR is implicitly anchored at the beginning of the
 controller location string, as if it begins with \fB^\fR.
 
+When this option is not given, the default \fIregex\fR is
+\fBtcp:.*\fR.
 .TP
 \fB--exit-without-bind\fR
 By default, \fBovs\-discover\fR binds the network device that receives
@@ -113,5 +114,5 @@ arriving IP packets, will not.
 
 .SH "SEE ALSO"
 
-.BR secchan (8),
-.BR ovs-pki (8)
+.BR ovs\-openflowd (8),
+.BR ovs\-pki (8)
index 308c405..3830e07 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ static int n_ifaces;
 
 /* --accept-vconn: Regular expression specifying the class of controller vconns
  * that we will accept during autodiscovery. */
-static const char *accept_controller_re = ".*";
+static const char *accept_controller_re = "tcp:.*";
 static regex_t accept_controller_regex;
 
 /* --exit-without-bind: Exit after discovering the controller, without binding
@@ -60,7 +60,7 @@ static bool exit_without_bind;
 static bool exit_after_bind;
 
 static bool iface_init(struct iface *, const char *netdev_name);
-static void release_ifaces(void *aux UNUSED);
+static void release_ifaces(void *aux OVS_UNUSED);
 
 static void parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
@@ -75,6 +75,7 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
@@ -102,7 +103,7 @@ main(int argc, char *argv[])
         struct iface *iface = &ifaces[i];
         dhclient_init(iface->dhcp, 0);
     }
-    fatal_signal_add_hook(release_ifaces, NULL, true);
+    fatal_signal_add_hook(release_ifaces, NULL, NULL, true);
 
     retval = regcomp(&accept_controller_regex, accept_controller_re,
                      REG_NOSUB | REG_EXTENDED);
@@ -115,14 +116,13 @@ main(int argc, char *argv[])
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
     die_if_already_running();
 
     signal(SIGPIPE, SIG_IGN);
     for (;;) {
-        fatal_signal_block();
         for (i = 0; i < n_ifaces; i++) {
             struct iface *iface = &ifaces[i];
             dhclient_run(iface->dhcp);
@@ -195,7 +195,6 @@ main(int argc, char *argv[])
             dhclient_wait(iface->dhcp);
         }
         unixctl_server_wait(unixctl);
-        fatal_signal_unblock();
         poll_block();
     }
 
@@ -215,7 +214,7 @@ iface_init(struct iface *iface, const char *netdev_name)
          * persists past program termination. */
         struct netdev *netdev;
 
-        retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
+        retval = netdev_open_default(iface->name, &netdev);
         if (retval) {
             ovs_error(retval, "Could not open %s device", iface->name);
             return false;
@@ -239,7 +238,7 @@ iface_init(struct iface *iface, const char *netdev_name)
 }
 
 static void
-release_ifaces(void *aux UNUSED)
+release_ifaces(void *aux OVS_UNUSED)
 {
     int i;
 
@@ -253,13 +252,13 @@ release_ifaces(void *aux UNUSED)
 }
 
 static void
-modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
+modify_dhcp_request(struct dhcp_msg *msg, void *aux OVS_UNUSED)
 {
     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
 }
 
 static bool
-validate_dhcp_offer(const struct dhcp_msg *msg, void *aux UNUSED)
+validate_dhcp_offer(const struct dhcp_msg *msg, void *aux OVS_UNUSED)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
     char *vconn_name;
@@ -282,7 +281,8 @@ parse_options(int argc, char *argv[])
         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
         OPT_EXIT_WITHOUT_BIND,
         OPT_EXIT_AFTER_BIND,
-        OPT_NO_DETACH
+        OPT_NO_DETACH,
+        VLOG_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
@@ -292,9 +292,9 @@ parse_options(int argc, char *argv[])
         {"timeout",     required_argument, 0, 't'},
         {"pidfile",     optional_argument, 0, OPT_PIDFILE},
         {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE},
-        {"verbose",     optional_argument, 0, 'v'},
         {"help",        no_argument, 0, 'h'},
         {"version",     no_argument, 0, 'V'},
+        VLOG_LONG_OPTIONS,
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
@@ -354,9 +354,7 @@ parse_options(int argc, char *argv[])
             OVS_PRINT_VERSION(0, 0);
             exit(EXIT_SUCCESS);
 
-        case 'v':
-            vlog_set_verbosity(optarg);
-            break;
+        VLOG_OPTION_HANDLERS
 
         case '?':
             exit(EXIT_FAILURE);
index a1719f8..752a447 100644 (file)
@@ -1,62 +1,57 @@
 .TH ovs\-dpctl 8 "August 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-dpctl
-
+.
 .SH NAME
 ovs\-dpctl \- administer Open vSwitch datapaths
-
+.
 .SH SYNOPSIS
 .B ovs\-dpctl
 [\fIoptions\fR] \fIcommand \fR[\fIswitch\fR] [\fIargs\fR\&...]
-
+.
 .SH DESCRIPTION
-
+.PP
 The \fBovs\-dpctl\fR program can create, modify, and delete Open vSwitch
 datapaths.  A single machine may host up to 256 datapaths (numbered 0
 to 255).
-
+.PP
 A newly created datapath is associated with only one network device, a
 virtual network device sometimes called the datapath's ``local port''.
 A newly created datapath is not, however, associated with any of the
 host's other network devices.  To intercept and process traffic on a
 given network device, use the \fBadd\-if\fR command to explicitly add
 that network device to the datapath.
-
+.PP
 Do not use \fBovs\-dpctl\fR commands to modify datapaths if
 \fBovs\-vswitchd\fR(8) is in use.  Instead, modify the
 \fBovs\-vswitchd\fR configuration file and send \fBSIGHUP\fR to the
 \fBovs\-vswitchd\fR process.
-
 .PP
 Most \fBovs\-dpctl\fR commands that work with datapaths take an argument
 that specifies the name of the datapath, in one of the following
 forms:
-
 .so lib/dpif.man
-
 .PP
 The following commands manage datapaths.
-
+.
 .TP
 \fBadd\-dp \fIdp\fR [\fInetdev\fR...]
-
 Creates datapath \fIdp\fR.  The name of the new datapath's local port
 depends on how \fIdp\fR is specified: if it takes the form
-\fBdp\fIN\fR, the local port will be named \fBdp\fIN\fR; if \fIdp\fR
-is \fBnl:\fI, the local port will be named \fBof\fIN\fR; otherwise,
+\fBdp\fIN\fR, the local port will be named \fBdp\fIN\fR; otherwise,
 the local port's name will be \fIdp\fR.
-
+.IP
 This will fail if the host already has 256 datapaths, if a network
 device with the same name as the new datapath's local port already
-exists, or if \fIdp\fR is given in the form \fBdp\fIN\fR or
-\fBnl:\fIN\fR and a datapath numbered \fIN\fR already exists.
-
+exists, or if \fIdp\fR is given in the form \fBdp\fIN\fR
+and a datapath numbered \fIN\fR already exists.
+.IP
 If \fInetdev\fRs are specified, \fBovs\-dpctl\fR adds them to the datapath.
-
+.
 .TP
 \fBdel\-dp \fIdp\fR
 Deletes datapath \fIdp\fR.  If \fIdp\fR is associated with any network
 devices, they are automatically removed.
-
+.
 .TP
 \fBadd\-if \fIdp netdev\fR[\fIoption\fR...]...
 Adds each \fInetdev\fR to the set of network devices datapath
@@ -66,105 +61,99 @@ network devices, e.g. \fBeth0\fR.  Once a network device has been added
 to a datapath, the datapath has complete ownership of the network device's
 traffic and the network device appears silent to the rest of the
 system.
-
+.IP
 A \fInetdev\fR may be followed by a comma-separated list of options.
 The following options are currently supported:
-
+.
 .RS
-.IP "\fBport=\fIportno\fR"
-Specifies \fIportno\fR (a number between 1 and 255) as the port number
-at which \fInetdev\fR will be attached.  By default, \fBadd\-if\fR
-automatically selects the lowest available port number.
-
 .IP "\fBinternal\fR"
 Instead of attaching an existing \fInetdev\fR, creates an internal
 port (analogous to the local port) with that name.
 .RE
-
+.
 .TP
 \fBdel\-if \fIdp netdev\fR...
 Removes each \fInetdev\fR from the list of network devices datapath
 \fIdp\fR monitors.
-
+.
 .TP
 \fBdump-dps\fR
 Prints the name of each configured datapath on a separate line.
-
+.
 .TP
 \fBshow \fR[\fIdp\fR...]
 Prints a summary of configured datapaths, including their datapath
 numbers and a list of ports connected to each datapath.  (The local
 port is identified as port 0.)
-
+.IP
 If one or more datapaths are specified, information on only those
 datapaths are displayed.  Otherwise, \fBovs\-dpctl\fR displays information
 about all configured datapaths.
-
+.
 .IP "\fBdump-flows \fIdp\fR"
 Prints to the console all flow entries in datapath \fIdp\fR's
 flow table.
-
+.IP
 This command is primarily useful for debugging Open vSwitch.  The flow
 table entries that it displays are not
 OpenFlow flow entries.  Instead, they are different and considerably
 simpler flows maintained by the Open vSwitch kernel module.
-
 .IP "\fBdel-flows \fIdp\fR"
 Deletes all flow entries from datapath \fIdp\fR's flow table.
-
+.IP
 This command is primarily useful for debugging Open vSwitch.  As
 discussed in \fBdump-flows\fR, these entries are
 not OpenFlow flow entries.  By deleting them, the process that set them
 up may be confused about their disappearance.
-
+.
 .IP "\fBdump-groups \fIdp\fR"
 Prints to the console the sets of port groups maintained by datapath
 \fIdp\fR.  Ordinarily there are at least 2 port groups in a datapath
-that \fBsecchan\fR or \fBvswitch\fR is controlling: group 0 contains
+that \fBovs\-openflowd\fR or \fBovs\-vswitch\fR is controlling: group
+0 contains
 all ports except those disabled by STP, and group 1 contains all
-ports.  Additional groups might be used in the future.
-
+ports.  Additional or different groups might be used in the future.
+.IP
 This command is primarily useful for debugging Open vSwitch.  OpenFlow
 does not have a concept of port groups.
-
 .SH OPTIONS
 .TP
 \fB-t\fR, \fB--timeout=\fIsecs\fR
 Limits \fBovs\-dpctl\fR runtime to approximately \fIsecs\fR seconds.  If
 the timeout expires, \fBovs\-dpctl\fR will exit with a \fBSIGALRM\fR
 signal.
-
+.
 .so lib/vlog.man
 .so lib/common.man
-
+.
 .SH EXAMPLES
-
+.
 A typical \fBovs\-dpctl\fR command sequence for controlling an
 Open vSwitch kernel module:
-
+.
 .TP
 \fBovs\-dpctl add\-dp dp0\fR
 Creates datapath number 0.
-
+.
 .TP
 \fBovs\-dpctl add\-if dp0 eth0 eth1\fR
 Adds two network devices to the new datapath.
-
+.
 .PP
-At this point one would ordinarily start \fBsecchan\fR(8) on
+At this point one would ordinarily start \fBovs\-openflowd\fR(8) on
 \fBdp0\fR, transforming \fBdp0\fR into an OpenFlow switch.  Then, when
 the switch and the datapath is no longer needed:
-
+.
 .TP
 \fBovs\-dpctl del\-if dp0 eth0 eth1\fR
 Removes network devices from the datapath.
-
+.
 .TP
 \fBovs\-dpctl del\-dp dp0\fR
 Deletes the datapath.
-
+.
 .SH "SEE ALSO"
-
-.BR secchan (8),
+.
 .BR ovs\-appctl (8),
+.BR ovs\-openflowd (8),
 .BR ovs\-vswitchd (8)
index 0dd9335..eb78a57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "vlog.h"
 #define THIS_MODULE VLM_dpctl
 
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
 static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
     parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
 static void
 parse_options(int argc, char *argv[])
 {
+    enum {
+        OPT_DUMMY = UCHAR_MAX + 1,
+        VLOG_OPTION_ENUMS
+    };
     static struct option long_options[] = {
         {"timeout", required_argument, 0, 't'},
-        {"verbose", optional_argument, 0, 'v'},
         {"help", no_argument, 0, 'h'},
         {"version", no_argument, 0, 'V'},
+        VLOG_LONG_OPTIONS,
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
@@ -135,9 +103,7 @@ parse_options(int argc, char *argv[])
             OVS_PRINT_VERSION(0, 0);
             exit(EXIT_SUCCESS);
 
-        case 'v':
-            vlog_set_verbosity(optarg);
-            break;
+        VLOG_OPTION_HANDLERS
 
         case '?':
             exit(EXIT_FAILURE);
@@ -202,7 +168,7 @@ static int if_up(const char *netdev_name)
     struct netdev *netdev;
     int retval;
 
-    retval = netdev_open(netdev_name, NETDEV_ETH_TYPE_NONE, &netdev);
+    retval = netdev_open_default(netdev_name, &netdev);
     if (!retval) {
         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
         netdev_close(netdev);
@@ -210,24 +176,43 @@ static int if_up(const char *netdev_name)
     return retval;
 }
 
+static int
+parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
+{
+    int result;
+    char *name, *type;
+
+    dp_parse_name(arg_, &name, &type);
+
+    if (create) {
+        result = dpif_create(name, type, dpifp);
+    } else {
+        result = dpif_open(name, type, dpifp);
+    }
+
+    free(name);
+    free(type);
+    return result;
+}
+
 static void
-do_add_dp(int argc UNUSED, char *argv[])
+do_add_dp(int argc OVS_UNUSED, char *argv[])
 {
-    struct dpif dpif;
-    run(dpif_create(argv[1], &dpif), "add_dp");
-    dpif_close(&dpif);
+    struct dpif *dpif;
+    run(parsed_dpif_open(argv[1], true, &dpif), "add_dp");
+    dpif_close(dpif);
     if (argc > 2) {
         do_add_if(argc, argv);
     }
 }
 
 static void
-do_del_dp(int argc UNUSED, char *argv[])
+do_del_dp(int argc OVS_UNUSED, char *argv[])
 {
-    struct dpif dpif;
-    run(dpif_open(argv[1], &dpif), "opening datapath");
-    run(dpif_delete(&dpif), "del_dp");
-    dpif_close(&dpif);
+    struct dpif *dpif;
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
+    run(dpif_delete(dpif), "del_dp");
+    dpif_close(dpif);
 }
 
 static int
@@ -245,41 +230,17 @@ query_ports(struct dpif *dpif, struct odp_port **ports, size_t *n_ports)
     qsort(*ports, *n_ports, sizeof **ports, compare_ports);
 }
 
-static uint16_t
-get_free_port(struct dpif *dpif)
-{
-    struct odp_port *ports;
-    size_t n_ports;
-    int port_no;
-
-    query_ports(dpif, &ports, &n_ports);
-    for (port_no = 0; port_no <= UINT16_MAX; port_no++) {
-        size_t i;
-        for (i = 0; i < n_ports; i++) {
-            if (ports[i].port == port_no) {
-                goto next_portno;
-            }
-        }
-        free(ports);
-        return port_no;
-
-    next_portno: ;
-    }
-    ovs_fatal(0, "no free datapath ports");
-}
-
 static void
-do_add_if(int argc UNUSED, char *argv[])
+do_add_if(int argc OVS_UNUSED, char *argv[])
 {
     bool failure = false;
-    struct dpif dpif;
+    struct dpif *dpif;
     int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     for (i = 2; i < argc; i++) {
         char *save_ptr = NULL;
         char *devname, *suboptions;
-        int port = -1;
         int flags = 0;
         int error;
 
@@ -292,11 +253,9 @@ do_add_if(int argc UNUSED, char *argv[])
         suboptions = strtok_r(NULL, "", &save_ptr);
         if (suboptions) {
             enum {
-                AP_PORT,
                 AP_INTERNAL
             };
             static char *options[] = {
-                "port",
                 "internal"
             };
 
@@ -304,13 +263,6 @@ do_add_if(int argc UNUSED, char *argv[])
                 char *value;
 
                 switch (getsubopt(&suboptions, options, &value)) {
-                case AP_PORT:
-                    if (!value) {
-                        ovs_error(0, "'port' suboption requires a value");
-                    }
-                    port = atoi(value);
-                    break;
-
                 case AP_INTERNAL:
                     flags |= ODP_PORT_INTERNAL;
                     break;
@@ -321,20 +273,16 @@ do_add_if(int argc UNUSED, char *argv[])
                 }
             }
         }
-        if (port < 0) {
-            port = get_free_port(&dpif);
-        }
 
-        error = dpif_port_add(&dpif, devname, port, flags);
+        error = dpif_port_add(dpif, devname, flags, NULL);
         if (error) {
-            ovs_error(error, "adding %s as port %"PRIu16" of %s failed",
-                      devname, port, argv[1]);
+            ovs_error(error, "adding %s to %s failed", devname, argv[1]);
             failure = true;
         } else if (if_up(devname)) {
             failure = true;
         }
     }
-    dpif_close(&dpif);
+    dpif_close(dpif);
     if (failure) {
         exit(EXIT_FAILURE);
     }
@@ -361,13 +309,13 @@ get_port_number(struct dpif *dpif, const char *name, uint16_t *port)
 }
 
 static void
-do_del_if(int argc UNUSED, char *argv[])
+do_del_if(int argc OVS_UNUSED, char *argv[])
 {
     bool failure = false;
-    struct dpif dpif;
+    struct dpif *dpif;
     int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     for (i = 2; i < argc; i++) {
         const char *name = argv[i];
         uint16_t port;
@@ -375,18 +323,18 @@ do_del_if(int argc UNUSED, char *argv[])
 
         if (!name[strspn(name, "0123456789")]) {
             port = atoi(name);
-        } else if (!get_port_number(&dpif, name, &port)) {
+        } else if (!get_port_number(dpif, name, &port)) {
             failure = true;
             continue;
         }
 
-        error = dpif_port_del(&dpif, port);
+        error = dpif_port_del(dpif, port);
         if (error) {
             ovs_error(error, "deleting port %s from %s failed", name, argv[1]);
             failure = true;
         }
     }
-    dpif_close(&dpif);
+    dpif_close(dpif);
     if (failure) {
         exit(EXIT_FAILURE);
     }
@@ -400,7 +348,7 @@ show_dpif(struct dpif *dpif)
     size_t n_ports;
     size_t i;
 
-    printf("dp%u:\n", dpif_id(dpif));
+    printf("%s:\n", dpif_name(dpif));
     if (!dpif_get_dp_stats(dpif, &stats)) {
         printf("\tflows: cur:%"PRIu32", soft-max:%"PRIu32", "
                "hard-max:%"PRIu32"\n",
@@ -436,12 +384,12 @@ do_show(int argc, char *argv[])
         int i;
         for (i = 1; i < argc; i++) {
             const char *name = argv[i];
-            struct dpif dpif;
+            struct dpif *dpif;
             int error;
 
-            error = dpif_open(name, &dpif);
+            error = parsed_dpif_open(name, false, &dpif);
             if (!error) {
-                show_dpif(&dpif);
+                show_dpif(dpif);
             } else {
                 ovs_error(error, "opening datapath %s failed", name);
                 failure = true;
@@ -451,13 +399,13 @@ do_show(int argc, char *argv[])
         unsigned int i;
         for (i = 0; i < ODP_MAX; i++) {
             char name[128];
-            struct dpif dpif;
+            struct dpif *dpif;
             int error;
 
             sprintf(name, "dp%u", i);
-            error = dpif_open(name, &dpif);
+            error = parsed_dpif_open(name, false, &dpif);
             if (!error) {
-                show_dpif(&dpif);
+                show_dpif(dpif);
             } else if (error != ENODEV) {
                 ovs_error(error, "opening datapath %s failed", name);
                 failure = true;
@@ -470,45 +418,52 @@ do_show(int argc, char *argv[])
 }
 
 static void
-do_dump_dps(int argc UNUSED, char *argv[] UNUSED)
+do_dump_dps(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
-    struct svec all_dps;
+    struct svec dpif_names, dpif_types;
     unsigned int i;
-    int error;
+    int error = 0;
 
-    svec_init(&all_dps);
-    error = dp_enumerate(&all_dps);
+    svec_init(&dpif_names);
+    svec_init(&dpif_types);
+    dp_enumerate_types(&dpif_types);
 
-    for (i = 0; i < all_dps.n; i++) {
-        struct dpif dpif;
-        char dpif_name[IF_NAMESIZE];
+    for (i = 0; i < dpif_types.n; i++) {
+        unsigned int j;
+        int retval;
 
-        if (dpif_open(all_dps.names[i], &dpif)) {
-            continue;
+        retval = dp_enumerate_names(dpif_types.names[i], &dpif_names);
+        if (retval) {
+            error = retval;
         }
-        if (!dpif_get_name(&dpif, dpif_name, sizeof dpif_name)) {
-            printf("%s\n", dpif_name);
+
+        for (j = 0; j < dpif_names.n; j++) {
+            struct dpif *dpif;
+            if (!dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif)) {
+                printf("%s\n", dpif_name(dpif));
+                dpif_close(dpif);
+            }
         }
-        dpif_close(&dpif);
     }
 
-    svec_destroy(&all_dps);
+    svec_destroy(&dpif_names);
+    svec_destroy(&dpif_types);
     if (error) {
         exit(EXIT_FAILURE);
     }
 }
 
 static void
-do_dump_flows(int argc UNUSED, char *argv[])
+do_dump_flows(int argc OVS_UNUSED, char *argv[])
 {
     struct odp_flow *flows;
-    struct dpif dpif;
+    struct dpif *dpif;
     size_t n_flows;
     struct ds ds;
     size_t i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
-    run(dpif_flow_list_all(&dpif, &flows, &n_flows), "listing all flows");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
+    run(dpif_flow_list_all(dpif, &flows, &n_flows), "listing all flows");
 
     ds_init(&ds);
     for (i = 0; i < n_flows; i++) {
@@ -518,41 +473,40 @@ do_dump_flows(int argc UNUSED, char *argv[])
 
         f->actions = actions;
         f->n_actions = MAX_ACTIONS;
-        dpif_flow_get(&dpif, f);
+        dpif_flow_get(dpif, f);
 
         ds_clear(&ds);
         format_odp_flow(&ds, f);
         printf("%s\n", ds_cstr(&ds));
     }
     ds_destroy(&ds);
-    dpif_close(&dpif);
+    dpif_close(dpif);
 }
 
 static void
-do_del_flows(int argc UNUSED, char *argv[])
+do_del_flows(int argc OVS_UNUSED, char *argv[])
 {
-    struct dpif dpif;
+    struct dpif *dpif;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
-    run(dpif_flow_flush(&dpif), "deleting all flows");
-    dpif_close(&dpif);
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
+    run(dpif_flow_flush(dpif), "deleting all flows");
+    dpif_close(dpif);
 }
 
 static void
-do_dump_groups(int argc UNUSED, char *argv[])
+do_dump_groups(int argc OVS_UNUSED, char *argv[])
 {
     struct odp_stats stats;
-    struct dpif dpif;
+    struct dpif *dpif;
     unsigned int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
-    run(dpif_get_dp_stats(&dpif, &stats), "get datapath stats");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
+    run(dpif_get_dp_stats(dpif, &stats), "get datapath stats");
     for (i = 0; i < stats.max_groups; i++) {
-        uint16_t ports[UINT16_MAX];
+        uint16_t *ports;
         size_t n_ports;
 
-        if (!dpif_port_group_get(&dpif, i, ports,
-                                 ARRAY_SIZE(ports), &n_ports) && n_ports) {
+        if (!dpif_port_group_get(dpif, i, &ports, &n_ports) && n_ports) {
             size_t j;
 
             printf("group %u:", i);
@@ -561,17 +515,18 @@ do_dump_groups(int argc UNUSED, char *argv[])
             }
             printf("\n");
         }
+        free(ports);
     }
-    dpif_close(&dpif);
+    dpif_close(dpif);
 }
 
 static void
-do_help(int argc UNUSED, char *argv[] UNUSED)
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "add-dp", 1, INT_MAX, do_add_dp },
     { "del-dp", 1, 1, do_del_dp },
     { "add-if", 2, INT_MAX, do_add_if },
index 215032a..40ad64b 100755 (executable)
@@ -16,8 +16,8 @@
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
 
-SECCHAN_PID=/var/run/secchan.pid
-SECCHAN_SOCK=/var/run/secchan.mgmt
+OPENFLOWD_PID=/var/run/ovs-openflowd.pid
+OPENFLOWD_SOCK=/var/run/ovs-openflowd.mgmt
 LOG_FILE=/var/log/openflow/monitor
 INTERVAL=1
 FAIL_THRESH=3
@@ -27,8 +27,8 @@ usage() {
     echo
     echo "OPTIONS:"
     echo "  -h   Show this message"
-    echo "  -p   PID file for secchan (default: $SECCHAN_PID)"
-    echo "  -s   Unix socket for secchan (default: $SECCHAN_SOCK)"
+    echo "  -p   PID file for ovs-openflowd (default: $OPENFLOWD_PID)"
+    echo "  -s   Unix socket for ovs-openflowd (default: $OPENFLOWD_SOCK)"
     echo "  -l   File to log messages (default: $LOG_FILE)"
     echo "  -i   Interval to send probes in seconds (default: $INTERVAL)"
     echo "  -c   Number of failed probes before reboot (default: $FAIL_THRESH)"
@@ -48,11 +48,11 @@ while getopts "hp:s:l:i:c:" OPTION; do
             ;;
 
         p) 
-            SECCHAN_PID=$OPTARG
+            OPENFLOWD_PID=$OPTARG
             ;;
 
         s) 
-            SECCHAN_SOCK=$OPTARG
+            OPENFLOWD_SOCK=$OPTARG
             ;;
 
         l) 
@@ -73,14 +73,14 @@ while getopts "hp:s:l:i:c:" OPTION; do
 done
 
 
-if [ ! -f $SECCHAN_PID ]; then
-    log "No secchan pid file: ${SECCHAN_PID}" 
-    echo "No secchan pid file: ${SECCHAN_PID}" 
+if [ ! -f $OPENFLOWD_PID ]; then
+    log "No ovs-openflowd pid file: ${OPENFLOWD_PID}"
+    echo "No ovs-openflowd pid file: ${OPENFLOWD_PID}"
 fi
 
-if [ ! -S $SECCHAN_SOCK ]; then
-    log "No secchan sock file: ${SECCHAN_SOCK}" 
-    echo "No secchan sock file: ${SECCHAN_SOCK}" 
+if [ ! -S $OPENFLOWD_SOCK ]; then
+    log "No ovs-openflowd sock file: ${OPENFLOWD_SOCK}"
+    echo "No ovs-openflowd sock file: ${OPENFLOWD_SOCK}"
 fi
 
 if [ ! -d `dirname $LOG_FILE` ]; then
@@ -88,17 +88,17 @@ if [ ! -d `dirname $LOG_FILE` ]; then
 fi
 
 let DP_DOWN=0
-let SECCHAN_DOWN=0
+let OPENFLOWD_DOWN=0
 log "===== Starting Monitor ===="
 while `/bin/true`; do
-    # Only check for liveness if the secchan's PID file exists.  The PID
-    # file is removed when secchan is brought down gracefully.
-    if [ -f $SECCHAN_PID ]; then
-        pid=`cat $SECCHAN_PID`
+    # Only check for liveness if ovs-openflowd's PID file exists.  The PID
+    # file is removed when ovs-openflowd is brought down gracefully.
+    if [ -f $OPENFLOWD_PID ]; then
+        pid=`cat $OPENFLOWD_PID`
         if [ -d /proc/$pid ]; then
-            # Check if the secchan and datapath still can communicate
-            if [ -S $SECCHAN_SOCK ]; then
-                ovs-ofctl probe -t 2 unix:$SECCHAN_SOCK 
+            # Check if the ovs-openflowd and datapath still can communicate
+            if [ -S $OPENFLOWD_SOCK ]; then
+                ovs-ofctl probe -t 2 unix:$OPENFLOWD_SOCK
                 if [ $? -ne 0 ]; then
                     log "datapath probe failed"
                     let DP_DOWN++
@@ -106,15 +106,15 @@ while `/bin/true`; do
                     let DP_DOWN=0
                 fi
             fi
-            let SECCHAN_DOWN=0
+            let OPENFLOWD_DOWN=0
         else
-            log "secchan probe failed"
-            let SECCHAN_DOWN++
+            log "ovs-openflowd probe failed"
+            let OPENFLOWD_DOWN++
         fi
     fi
 
-    if [ $SECCHAN_DOWN -ge $FAIL_THRESH ]; then
-        log "Failed to probe secchan after ${SECCHAN_DOWN} tries...rebooting!"
+    if [ $OPENFLOWD_DOWN -ge $FAIL_THRESH ]; then
+        log "Failed to probe ovs-openflowd after ${OPENFLOWD_DOWN} tries...rebooting!"
         reboot
     fi
 
index 6b39765..e7d7fc5 100644 (file)
-.TH ovs\-ofctl 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.TH ovs\-ofctl 8 "January 2010" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-ofctl
-
+.
 .SH NAME
 ovs\-ofctl \- administer OpenFlow switches
-
+.
 .SH SYNOPSIS
 .B ovs\-ofctl
 [\fIoptions\fR] \fIcommand \fR[\fIswitch\fR] [\fIargs\fR\&...]
-
+.
 .SH DESCRIPTION
 The
 .B ovs\-ofctl
 program is a command line tool for monitoring and administering
 OpenFlow switches.  It can also show the current state of an OpenFlow
 switch, including features, configuration, and table entries.
-
+.
 .SS "OpenFlow Switch Management Commands"
-
+.PP
 These commands allow \fBovs\-ofctl\fR to monitor and administer an OpenFlow
 switch.  It is able to show the current state of a switch, including
 features, configuration, and table entries.
-
+.PP
 Most of these commands take an argument that specifies the method for
 connecting to an OpenFlow switch.  The following connection methods
 are supported:
-
+.
 .RS
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.  
-
+.so lib/vconn-active.man
+.
 .IP "\fIfile\fR"
 This is short for \fBunix:\fIfile\fR, as long as \fIfile\fR does not
 contain a colon.
-
-.IP \fIdp\fR
-This is short for \fBunix:@RUNDIR@/\fIdp\fB.mgmt\fR, as long as
-\fIdp\fR does not contain a colon.
+.
+.IP \fIbridge\fR
+This is short for \fBunix:@RUNDIR@/\fIbridge\fB.mgmt\fR, as long as
+\fIbridge\fR does not contain a colon.
+.
+.IP [\fItype\fB@\fR]\fIdp\fR
+Attempts to look up the bridge associated with \fIdp\fR and open as
+above.  If \fItype\fR is given, it specifies the datapath provider of
+\fIdp\fR, otherwise the default provider \fBsystem\fR is assumed.
 .RE
-
+.
 .TP
 \fBshow \fIswitch\fR
 Prints to the console information on \fIswitch\fR, including
 information on its flow tables and ports.
-
+.
 .TP
 \fBstatus \fIswitch\fR [\fIkey\fR]
 Prints to the console a series of key-value pairs that report the
 status of \fIswitch\fR.  If \fIkey\fR is specified, only the key-value
 pairs whose key names begin with \fIkey\fR are printed.  If \fIkey\fR is
 omitted, all key-value pairs are printed.
-
+.
 .TP
 \fBdump-tables \fIswitch\fR
 Prints to the console statistics for each of the flow tables used by
 \fIswitch\fR.
-
+.
 .TP
-\fBdump-ports \fIswitch\fR
-Prints to the console statistics for each of the network devices
-associated with \fIswitch\fR.
-
+\fBdump-ports \fIswitch\fR [\fInetdev\fR]
+Prints to the console statistics for network devices associated with 
+\fIswitch\fR.  If \fInetdev\fR is specified, only the statistics
+associated with that device will be printed.  \fInetdev\fR can be an
+OpenFlow assigned port number or device name, e.g. \fBeth0\fR.
+.
 .TP
 \fBmod-port \fIswitch\fR \fInetdev\fR \fIaction\fR
 Modify characteristics of an interface monitored by \fIswitch\fR.  
 \fInetdev\fR can be referred to by its OpenFlow assigned port number or 
 the device name, e.g. \fBeth0\fR.  The \fIaction\fR may be any one of the
 following:
-
+.
 .RS
 .IP \fBup\fR
 Enables the interface.  This is equivalent to ``ifconfig up'' on a Unix
 system.
-
+.
 .IP \fBdown\fR
 Disables the interface.  This is equivalent to ``ifconfig down'' on a Unix
 system.
-
+.
 .IP \fBflood\fR
 When a \fIflood\fR action is specified, traffic will be sent out this
 interface.  This is the default posture for monitored ports.
-
+.
 .IP \fBnoflood\fR
 When a \fIflood\fR action is specified, traffic will not be sent out 
 this interface.  This is primarily useful to prevent loops when a
 spanning tree protocol is not in use.
-
+.
 .RE
-
+.
 .TP
 \fBdump-flows \fIswitch \fR[\fIflows\fR]
 Prints to the console all flow entries in \fIswitch\fR's
@@ -105,7 +106,7 @@ tables that match \fIflows\fR.  If \fIflows\fR is omitted, all flows
 in the switch are retrieved.  See \fBFlow Syntax\fR, below, for the
 syntax of \fIflows\fR.  The output format is described in 
 \fBTable Entry Output\fR.
-
+.
 .TP
 \fBdump-aggregate \fIswitch \fR[\fIflows\fR]
 Prints to the console aggregate statistics for flows in 
@@ -113,26 +114,26 @@ Prints to the console aggregate statistics for flows in
 the statistics are aggregated across all flows in the switch's flow
 tables.  See \fBFlow Syntax\fR, below, for the syntax of \fIflows\fR.
 The output format is descrbed in \fBTable Entry Output\fR.
-
+.
 .TP
 \fBadd-flow \fIswitch flow\fR
 Add the flow entry as described by \fIflow\fR to the \fIswitch\fR's 
 tables.  The flow entry is in the format described in \fBFlow Syntax\fR, 
 below.
-
+.
 .TP
 \fBadd-flows \fIswitch file\fR
 Add flow entries as described in \fIfile\fR to \fIswitch\fR's 
 tables.  Each line in \fIfile\fR is a flow entry in the format
 described in \fBFlow Syntax\fR, below.
-
+.
 .TP
 \fBmod-flows \fIswitch flow\fR
 Modify the actions in entries from the \fIswitch\fR's tables 
 that match \fIflow\fR.  When invoked with the \fB--strict\fR option,
 wildcards are not treated as active for matching purposes.  See 
 \fBFlow Syntax\fR, below, for the syntax of \fIflows\fR.
-
+.
 .TP
 \fBdel-flows \fIswitch \fR[\fIflow\fR]
 Deletes entries from the \fIswitch\fR's tables that match
@@ -141,59 +142,45 @@ not treated as active for matching purposes.  If \fIflow\fR is
 omitted and the \fB--strict\fR option is not used, all flows in the 
 switch's tables are removed.  See \fBFlow Syntax\fR, below, for the 
 syntax of \fIflows\fR.
-
+.
 .TP
-\fBmonitor \fIswitch\fR [\fImiss-len\fR [\fIsend-exp]]
+\fBmonitor \fIswitch\fR [\fImiss-len\fR]
 Connects to \fIswitch\fR and prints to the console all OpenFlow
 messages received.  Usually, \fIswitch\fR should specify a connection
-named on \fBsecchan\fR(8)'s \fB-l\fR or \fB--listen\fR command line
+named on \fBovs\-openflowd\fR(8)'s \fB-l\fR or \fB--listen\fR command line
 option.
-
+.IP
 If \fImiss-len\fR is provided, \fBovs\-ofctl\fR sends an OpenFlow ``set
 configuration'' message at connection setup time that requests
 \fImiss-len\fR bytes of each packet that misses the flow table.  The
 OpenFlow reference implementation does not send these messages to the
 \fBovs\-ofctl monitor\fR client connection unless a nonzero value is
 specified on this argument.
-
-If \fIsend-exp\fR is specified as \fB1\fR, \fBovs\-ofctl\fR will also
-request to be sent flow expiration messages.  If this argument is
-omitted, or \fB0\fR is specified, then \fRovs\-ofctl\fR will not request
-flow expirations.
-
+.IP
 This command may be useful for debugging switch or controller
 implementations.
-
-.TP
-\fBexecute \fIswitch command \fR[\fIarg\fR...]
-Sends a request to \fIswitch\fR to execute \fIcommand\fR along with
-each \fIarg\fR, if any, then waits for the command to complete and
-reports its completion status on \fBstderr\fR and its output, if any,
-on \fBstdout\fR.  The set of available commands and their argument is
-switch-dependent.  (This command uses a Nicira extension to OpenFlow
-that may not be available on all switches.)
-
+.
 .SS "OpenFlow Switch and Controller Commands"
-
+.
 The following commands, like those in the previous section, may be
 applied to OpenFlow switches, using any of the connection methods
 described in that section.  Unlike those commands, these may also be
 applied to OpenFlow controllers.
-
+.
 .TP
 \fBprobe \fItarget\fR
 Sends a single OpenFlow echo-request message to \fItarget\fR and waits
 for the response.  With the \fB-t\fR or \fB--timeout\fR option, this
 command can test whether an OpenFlow switch or controller is up and
 running.
-
+.
 .TP
 \fBping \fItarget \fR[\fIn\fR]
 Sends a series of 10 echo request packets to \fItarget\fR and times
 each reply.  The echo request packets consist of an OpenFlow header
 plus \fIn\fR bytes (default: 64) of randomly generated payload.  This
 measures the latency of individual requests.
-
+.
 .TP
 \fBbenchmark \fItarget n count\fR
 Sends \fIcount\fR echo request packets that each consist of an
@@ -201,167 +188,219 @@ OpenFlow header plus \fIn\fR bytes of payload and waits for each
 response.  Reports the total time required.  This is a measure of the
 maximum bandwidth to \fItarget\fR for round-trips of \fIn\fR-byte
 messages.
-
+.
 .SS "Flow Syntax"
-
+.PP
 Some \fBovs\-ofctl\fR commands accept an argument that describes a flow or
 flows.  Such flow descriptions comprise a series
 \fIfield\fB=\fIvalue\fR assignments, separated by commas or white
 space.  (Embedding spaces into a flow description normally requires
 quoting to prevent the shell from breaking the description into
 multiple arguments.)
-
+.PP
 The following field assignments describe how a flow matches a packet.
 If any of these assignments is omitted from the flow syntax, the field
 is treated as a wildcard; thus, if all of them are omitted, the
 resulting flow matches all packets.  The string \fB*\fR or \fBANY\fR
 may be specified to explicitly mark any of these fields as a wildcard.  
 (\fB*\fR should be quoted to protect it from shell expansion.)
-
+.
 .IP \fBin_port=\fIport_no\fR
 Matches physical port \fIport_no\fR.  Switch ports are numbered as
 displayed by \fBovs\-ofctl show\fR.
-
+.
 .IP \fBdl_vlan=\fIvlan\fR
-Matches IEEE 802.1q virtual LAN tag \fIvlan\fR.  Specify \fB0xffff\fR
-as \fIvlan\fR to match packets that are not tagged with a virtual LAN;
+Matches IEEE 802.1q Virtual LAN tag \fIvlan\fR.  Specify \fB0xffff\fR
+as \fIvlan\fR to match packets that are not tagged with a Virtual LAN;
 otherwise, specify a number between 0 and 4095, inclusive, as the
 12-bit VLAN ID to match.
-
-.IP \fBdl_src=\fImac\fR
-Matches Ethernet source address \fImac\fR, which is specified as 6 pairs 
-of hexadecimal digits delimited by colons (e.g. \fB00:0A:E4:25:6B:B0\fR).
-
-.IP \fBdl_dst=\fImac\fR
-Matches Ethernet destination address \fImac\fR.
-
+.
+.IP \fBdl_vlan_pcp=\fIpriority\fR
+Matches IEEE 802.1q Priority Code Point (PCP) \fIpriority\fR, which is
+specified as a value between 0 and 7, inclusive.  A higher value
+indicates a higher frame priority level.
+.
+.IP \fBdl_src=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+.IQ \fBdl_dst=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+Matches an Ethernet source (or destination) address specified as 6
+pairs of hexadecimal digits delimited by colons
+(e.g. \fB00:0A:E4:25:6B:B0\fR).
+.
 .IP \fBdl_type=\fIethertype\fR
 Matches Ethernet protocol type \fIethertype\fR, which is specified as an
 integer between 0 and 65535, inclusive, either in decimal or as a 
 hexadecimal number prefixed by \fB0x\fR (e.g. \fB0x0806\fR to match ARP 
 packets).
-
+.
 .IP \fBnw_src=\fIip\fR[\fB/\fInetmask\fR]
-Matches IPv4 source address \fIip\fR, which may be specified as an
-IP address or host name (e.g. \fB192.168.1.1\fR or
-\fBwww.example.com\fR).  The optional \fInetmask\fR allows restricting a
-match to an IPv4 address prefix.  The netmask may be specified as a dotted 
-quad (e.g. \fB192.168.1.0/255.255.255.0\fR) or as a CIDR block 
+.IQ \fBnw_dst=\fIip\fR[\fB/\fInetmask\fR]
+When \fBdl_type\fR is 0x0800 (possibly via shorthand, e.g. \fBip\fR
+or \fBtcp\fR), matches IPv4 source (or destination) address \fIip\fR,
+which may be specified as an IP address or host name
+(e.g. \fB192.168.1.1\fR or \fBwww.example.com\fR).  The optional
+\fInetmask\fR allows restricting a match to an IPv4 address prefix.
+The netmask may be specified as a dotted quad
+(e.g. \fB192.168.1.0/255.255.255.0\fR) or as a CIDR block
 (e.g. \fB192.168.1.0/24\fR).
-
-.IP \fBnw_dst=\fIip\fR[\fB/\fInetmask\fR]
-Matches IPv4 destination address \fIip\fR.
-
+.IP
+When \fBdl_type=0x0806\fR or \fBarp\fR is specified, matches the
+\fBar_spa\fR or \fBar_tpa\fR field, respectively, in ARP packets for
+IPv4 and Ethernet.
+.IP
+When \fBdl_type\fR is wildcarded or set to a value other than 0x0800
+or 0x0806, the values of \fBnw_src\fR and \fBnw_dst\fR are silently
+ignored.
+.
 .IP \fBnw_proto=\fIproto\fR
-Matches IP protocol type \fIproto\fR, which is specified as a decimal 
-number between 0 and 255, inclusive (e.g. 6 to match TCP packets).
-
+When \fBip\fR or \fBdl_type=0x0800\fR is specified, matches IP
+protocol type \fIproto\fR, which is specified as a decimal number
+between 0 and 255, inclusive (e.g. 6 to match TCP packets).
+.IP
+When \fBarp\fR or \fBdl_type=0x0806\fR is specified, matches the lower
+8 bits of the ARP opcode.  ARP opcodes greater than 255 are treated as
+0.
+.IP
+When \fBdl_type\fR is wildcarded or set to a value other than 0x0800
+or 0x0806, the value of \fBnw_proto\fR is silently ignored.
+.
+.IP \fBnw_tos=\fItos\fR
+Matches IP ToS/DSCP field \fItos\fR, which is specified as a decimal 
+number between 0 and 255, inclusive.  Note that the two lower reserved
+bits are ignored for matching purposes.
+.IP
+The value of \fBnw_proto\fR is silently ignored unless
+\fBdl_type=0x0800\fR, \fBip\fR, \fBicmp\fR, \fBtcp\fR, or \fBudp\fR is
+also specified.
+.
 .IP \fBtp_src=\fIport\fR
-Matches UDP or TCP source port \fIport\fR, which is specified as a decimal 
-number between 0 and 65535, inclusive (e.g. 80 to match packets originating 
+.IQ \fBtp_dst=\fIport\fR
+When \fBdl_type\fR and \fBnw_proto\fR specify TCP or UDP, \fBtp_src\fR
+and \fBtp_dst\fR match the UDP or TCP source or destination port
+\fIport\fR, respectively. which is specified as a decimal number
+between 0 and 65535, inclusive (e.g. 80 to match packets originating
 from a HTTP server).
-
-.IP \fBtp_dst=\fIport\fR
-Matches UDP or TCP destination port \fIport\fR.
-
+.IP
+When \fBdl_type\fR and \fBnw_proto\fR take other values, the values of
+these settings are silently ignored.
+.
 .IP \fBicmp_type=\fItype\fR
-Matches ICMP message with \fItype\fR, which is specified as a decimal 
-number between 0 and 255, inclusive.
-
-.IP \fBicmp_code=\fIcode\fR
-Matches ICMP messages with \fIcode\fR.
-
+.IQ \fBicmp_code=\fIcode\fR
+When \fBdl_type\fR and \fBnw_proto\fR specify ICMP, \fItype\fR matches
+the ICMP type and \fIcode\fR matches the ICMP code.  Each is specified
+as a decimal number between 0 and 255, inclusive.
+.IP
+When \fBdl_type\fR and \fBnw_proto\fR take other values, the values of
+these settings are silently ignored.
+.
 .PP
 The following shorthand notations are also available:
-
+.
 .IP \fBip\fR
 Same as \fBdl_type=0x0800\fR.
-
+.
 .IP \fBicmp\fR
 Same as \fBdl_type=0x0800,nw_proto=1\fR.
-
+.
 .IP \fBtcp\fR
 Same as \fBdl_type=0x0800,nw_proto=6\fR.
-
+.
 .IP \fBudp\fR
 Same as \fBdl_type=0x0800,nw_proto=17\fR.
-
+.
 .IP \fBarp\fR
 Same as \fBdl_type=0x0806\fR.
-
+.
 .PP
 The \fBadd-flow\fR and \fBadd-flows\fR commands require an additional field:
-
+.
 .IP \fBactions=\fR[\fItarget\fR][\fB,\fItarget\fR...]\fR
 Specifies a comma-separated list of actions to take on a packet when the 
 flow entry matches.  If no \fItarget\fR is specified, then packets
 matching the flow are dropped.  The \fItarget\fR may be a decimal port 
 number designating the physical port on which to output the packet, or one 
 of the following keywords:
-
+.
 .RS
 .IP \fBoutput\fR:\fIport\fR
 Outputs the packet on the port specified by \fIport\fR.
-
+.
 .IP \fBnormal\fR
 Subjects the packet to the device's normal L2/L3 processing.  (This
 action is not implemented by all OpenFlow switches.)
-
+.
 .IP \fBflood\fR
 Outputs the packet on all switch physical ports other than the port on
 which it was received and any ports on which flooding is disabled
 (typically, these would be ports disabled by the IEEE 802.1D spanning
 tree protocol).
-
+.
 .IP \fBall\fR
 Outputs the packet on all switch physical ports other than the port on
 which it was received.
-
+.
 .IP \fBcontroller\fR:\fImax_len\fR
 Sends the packet to the OpenFlow controller as a ``packet in''
 message.  If \fImax_len\fR is a number, then it specifies the maximum
 number of bytes that should be sent.  If \fImax_len\fR is \fBALL\fR or
 omitted, then the entire packet is sent.
-
+.
 .IP \fBlocal\fR
 Outputs the packet on the ``local port,'' which corresponds to the
 \fBof\fIn\fR network device (see \fBCONTACTING THE CONTROLLER\fR in
-\fBsecchan\fR(8) for information on the \fBof\fIn\fR network device).
-
+\fBovs\-openflowd\fR(8) for information on the \fBof\fIn\fR network device).
+.
 .IP \fBdrop\fR
 Discards the packet, so no further processing or forwarding takes place.
 If a drop action is used, no other actions may be specified.
-
+.
 .IP \fBmod_vlan_vid\fR:\fIvlan_vid\fR
 Modifies the VLAN id on a packet.  The VLAN tag is added or modified 
 as necessary to match the value specified.  If the VLAN tag is added,
 a priority of zero is used (see the \fBmod_vlan_pcp\fR action to set
 this).
-
+.
 .IP \fBmod_vlan_pcp\fR:\fIvlan_pcp\fR
 Modifies the VLAN priority on a packet.  The VLAN tag is added or modified 
 as necessary to match the value specified.  Valid values are between 0
 (lowest) and 7 (highest).  If the VLAN tag is added, a vid of zero is used 
 (see the \fBmod_vlan_vid\fR action to set this).
-
+.
 .IP \fBstrip_vlan\fR
 Strips the VLAN tag from a packet if it is present.
-
+.
 .IP \fBmod_dl_src\fB:\fImac\fR
 Sets the source Ethernet address to \fImac\fR.
-
+.
 .IP \fBmod_dl_dst\fB:\fImac\fR
 Sets the destination Ethernet address to \fImac\fR.
+.
+.IP \fBmod_nw_src\fB:\fIip\fR
+Sets the IPv4 source address to \fIip\fR.
+.
+.IP \fBmod_nw_dst\fB:\fIip\fR
+Sets the IPv4 destination address to \fIip\fR.
+.
+.IP \fBmod_tp_src\fB:\fIport\fR
+Sets the TCP or UDP source port to \fIport\fR.
+.
+.IP \fBmod_tp_dst\fB:\fIport\fR
+Sets the TCP or UDP destination port to \fIport\fR.
+.
+.IP \fBmod_nw_tos\fB:\fItos\fR
+Sets the IP ToS/DSCP field to \fItos\fR.  Valid values are between 0 and
+255, inclusive.  Note that the two lower reserved bits are never
+modified.
+.
 .RE
-
+.
 .IP
 (The OpenFlow protocol supports other actions that \fBovs\-ofctl\fR does
 not yet expose to the user.)
-
+.
 .PP
 The \fBadd-flow\fR, \fBadd-flows\fR, and \fBdel-flows\fR commands
 support an additional optional field:
-
+.
 .IP \fBpriority=\fIvalue\fR
 The priority at which a wildcarded entry will match in comparison to
 others.  \fIvalue\fR is a number between 0 and 65535, inclusive.  A higher 
@@ -369,120 +408,100 @@ others.  \fIvalue\fR is a number between 0 and 65535, inclusive.  A higher
 have priority over an entry containing wildcards, so it has an implicit 
 priority value of 65535.  When adding a flow, if the field is not specified, 
 the flow's priority will default to 32768.
-
+.
 .PP
 The \fBadd-flow\fR and \fBadd-flows\fR commands support additional
 optional fields:
-
+.
 .TP
 \fBidle_timeout=\fIseconds\fR
 Causes the flow to expire after the given number of seconds of
 inactivity.  A value of 0 prevents a flow from expiring due to
 inactivity.  The default is 60 seconds.
-
+.
 .IP \fBhard_timeout=\fIseconds\fR
 Causes the flow to expire after the given number of seconds,
 regardless of activity.  A value of 0 (the default) gives the flow no
 hard expiration deadline.
-
+.
 .PP
 The \fBdump-flows\fR, \fBdump-aggregate\fR, \fBdel-flow\fR 
 and \fBdel-flows\fR commands support one additional optional field:
-
+.
 .TP
 \fBout_port=\fIport\fR
 If set, a matching flow must include an output action to \fIport\fR.
-
+.
 .PP
 The \fBdump-flows\fR and \fBdump-aggregate\fR commands support an
 additional optional field:
-
+.
 .IP \fBtable=\fInumber\fR
 If specified, limits the flows about which statistics are gathered to
 those in the table with the given \fInumber\fR.  Tables are numbered
 as shown by the \fBdump-tables\fR command.
-
+.
 If this field is not specified, or if \fInumber\fR is given as
 \fB255\fR, statistics are gathered about flows from all tables.
-
+.
 .SS "Table Entry Output"
-
+.
 The \fBdump-tables\fR and \fBdump-aggregate\fR commands print information 
 about the entries in a datapath's tables.  Each line of output is a 
 unique flow entry, which begins with some common information:
-
+.
 .IP \fBduration\fR
 The number of seconds the entry has been in the table.
-
+.
 .IP \fBtable_id\fR
 The table that contains the flow.  When a packet arrives, the switch 
 begins searching for an entry at the lowest numbered table.  Tables are 
 numbered as shown by the \fBdump-tables\fR command.
-
+.
 .IP \fBpriority\fR
 The priority of the entry in relation to other entries within the same
 table.  A higher value will match before a lower one.
-
+.
 .IP \fBn_packets\fR
 The number of packets that have matched the entry.
-
+.
 .IP \fBn_bytes\fR
 The total number of bytes from packets that have matched the entry.
-
+.
 .PP
 The rest of the line consists of a description of the flow entry as 
 described in \fBFlow Syntax\fR, above.
-
-
+.
+.
 .SH OPTIONS
 .TP
 \fB--strict\fR
 Uses strict matching when running flow modification commands.
-
-.TP
-\fB-t\fR, \fB--timeout=\fIsecs\fR
-Limits \fBovs\-ofctl\fR runtime to approximately \fIsecs\fR seconds.  If
-the timeout expires, \fBovs\-ofctl\fR will exit with a \fBSIGALRM\fR
-signal.
-
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the
-identity for SSL connections to a switch.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the
-private key to identify a trustworthy controller.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-a switch is trustworthy.
-
+.
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
 .so lib/vlog.man
 .so lib/common.man
-
+.
 .SH EXAMPLES
-
+.
 The following examples assume that an OpenFlow switch on the local
 host has been configured to listen for management connections on a
 Unix domain socket named \fB@RUNDIR@/openflow.sock\fR, e.g. by
 specifying \fB--listen=punix:@RUNDIR@/openflow.sock\fR on the
-\fBsecchan\fR(8) command line.
-
+\fBovs\-openflowd\fR(8) command line.
+.
 .TP
 \fBovs\-ofctl dump-tables unix:@RUNDIR@/openflow.sock\fR
 Prints out the switch's table stats.  (This is more interesting after
 some traffic has passed through.)
-
+.
 .TP
 \fBovs\-ofctl dump-flows unix:@RUNDIR@/openflow.sock\fR
 Prints the flow entries in the switch.
-
+.
 .SH "SEE ALSO"
-
+.
 .BR ovs\-appctl (8),
 .BR ovs\-controller (8),
 .BR ovs\-vswitchd (8)
index 7c20d7a..9a75969 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "packets.h"
 #include "random.h"
 #include "socket-util.h"
+#include "stream-ssl.h"
 #include "timeval.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
+#include "xtoxll.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_ofctl
 #define MOD_PORT_CMD_FLOOD   "flood"
 #define MOD_PORT_CMD_NOFLOOD "noflood"
 
+/* Use strict matching for flow mod commands? */
+static bool strict;
 
-/* Settings that may be configured by the user. */
-struct settings {
-    bool strict;        /* Use strict matching for flow mod commands */
-};
-
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(const struct settings *, int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
-static void parse_options(int argc, char *argv[], struct settings *);
+static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct settings s;
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
-    parse_options(argc, argv, &s);
+    parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(&s, argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
 static void
-parse_options(int argc, char *argv[], struct settings *s)
+parse_options(int argc, char *argv[])
 {
     enum {
-        OPT_STRICT = UCHAR_MAX + 1
+        OPT_STRICT = UCHAR_MAX + 1,
+        VLOG_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"timeout", required_argument, 0, 't'},
-        {"verbose", optional_argument, 0, 'v'},
         {"strict", no_argument, 0, OPT_STRICT},
         {"help", no_argument, 0, 'h'},
         {"version", no_argument, 0, 'V'},
-        VCONN_SSL_LONG_OPTIONS
+        VLOG_LONG_OPTIONS,
+        STREAM_SSL_LONG_OPTIONS
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
 
-    /* Set defaults that we can figure out before parsing options. */
-    s->strict = false;
-
     for (;;) {
         unsigned long int timeout;
         int c;
@@ -166,15 +125,12 @@ parse_options(int argc, char *argv[], struct settings *s)
             OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION);
             exit(EXIT_SUCCESS);
 
-        case 'v':
-            vlog_set_verbosity(optarg);
-            break;
-
         case OPT_STRICT:
-            s->strict = true;
+            strict = true;
             break;
 
-        VCONN_SSL_OPTION_HANDLERS
+        VLOG_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case '?':
             exit(EXIT_FAILURE);
@@ -197,7 +153,7 @@ usage(void)
            "  dump-desc SWITCH            print switch description\n"
            "  dump-tables SWITCH          print table stats\n"
            "  mod-port SWITCH IFACE ACT   modify port behavior\n"
-           "  dump-ports SWITCH           print port statistics\n"
+           "  dump-ports SWITCH [PORT]    print port statistics\n"
            "  dump-flows SWITCH           print all flow entries\n"
            "  dump-flows SWITCH FLOW      print matching FLOWs\n"
            "  dump-aggregate SWITCH       print aggregate flow statistics\n"
@@ -207,7 +163,6 @@ usage(void)
            "  mod-flows SWITCH FLOW       modify actions of matching FLOWs\n"
            "  del-flows SWITCH [FLOW]     delete matching FLOWs\n"
            "  monitor SWITCH MISSLEN EXP  print packets received from SWITCH\n"
-           "  execute SWITCH CMD [ARG...] execute CMD with ARGS on SWITCH\n"
            "\nFor OpenFlow switches and controllers:\n"
            "  probe VCONN                 probe whether VCONN is up\n"
            "  ping VCONN [N]              latency of N-byte echos\n"
@@ -248,29 +203,40 @@ static void run(int retval, const char *message, ...)
 \f
 /* Generic commands. */
 
+static void
+open_vconn_socket(const char *name, struct vconn **vconnp)
+{
+    char *vconn_name = xasprintf("unix:%s", name);
+    VLOG_INFO("connecting to %s", vconn_name);
+    run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
+        "connecting to %s", vconn_name);
+    free(vconn_name);
+}
+
 static void
 open_vconn(const char *name, struct vconn **vconnp)
 {
-    struct dpif dpif;
+    struct dpif *dpif;
     struct stat s;
+    char *bridge_path, *datapath_name, *datapath_type;
+
+    bridge_path = xasprintf("%s/%s.mgmt", ovs_rundir, name);
+    dp_parse_name(name, &datapath_name, &datapath_type);
 
     if (strstr(name, ":")) {
         run(vconn_open_block(name, OFP_VERSION, vconnp),
             "connecting to %s", name);
     } else if (!stat(name, &s) && S_ISSOCK(s.st_mode)) {
-        char *vconn_name = xasprintf("unix:%s", name);
-        VLOG_INFO("connecting to %s", vconn_name);
-        run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
-            "connecting to %s", vconn_name);
-        free(vconn_name);
-    } else if (!dpif_open(name, &dpif)) {
+        open_vconn_socket(name, vconnp);
+    } else if (!stat(bridge_path, &s) && S_ISSOCK(s.st_mode)) {
+        open_vconn_socket(bridge_path, vconnp);
+    } else if (!dpif_open(datapath_name, datapath_type, &dpif)) {
         char dpif_name[IF_NAMESIZE + 1];
         char *socket_name;
-        char *vconn_name;
 
-        run(dpif_get_name(&dpif, dpif_name, sizeof dpif_name),
+        run(dpif_port_get_name(dpif, ODPP_LOCAL, dpif_name, sizeof dpif_name),
             "obtaining name of %s", dpif_name);
-        dpif_close(&dpif);
+        dpif_close(dpif);
         if (strcmp(dpif_name, name)) {
             VLOG_INFO("datapath %s is named %s", name, dpif_name);
         }
@@ -284,15 +250,15 @@ open_vconn(const char *name, struct vconn **vconnp)
                       name, socket_name);
         }
 
-        vconn_name = xasprintf("unix:%s", socket_name);
-        VLOG_INFO("connecting to %s", vconn_name);
-        run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
-            "connecting to %s", vconn_name);
+        open_vconn_socket(socket_name, vconnp);
         free(socket_name);
-        free(vconn_name);
     } else {
         ovs_fatal(0, "%s is not a valid connection method", name);
     }
+
+    free(datapath_name);
+    free(datapath_type);
+    free(bridge_path);
 }
 
 static void *
@@ -374,14 +340,14 @@ dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type)
 }
 
 static void
-do_show(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_show(int argc OVS_UNUSED, char *argv[])
 {
     dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST);
     dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
 }
 
 static void
-do_status(const struct settings *s UNUSED, int argc, char *argv[])
+do_status(int argc, char *argv[])
 {
     struct nicira_header *request, *reply;
     struct vconn *vconn;
@@ -413,13 +379,13 @@ do_status(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_desc(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_desc(int argc OVS_UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_DESC);
 }
 
 static void
-do_dump_tables(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_tables(int argc OVS_UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_TABLE);
 }
@@ -503,6 +469,48 @@ str_to_ip(const char *str_, uint32_t *ip)
     return n_wild;
 }
 
+static uint16_t
+str_to_port_no(const char *vconn_name, const char *str)
+{
+    struct ofpbuf *request, *reply;
+    struct ofp_switch_features *osf;
+    struct vconn *vconn;
+    int n_ports;
+    int port_idx;
+    unsigned int port_no;
+    
+
+    /* Check if the argument is a port index.  Otherwise, treat it as
+     * the port name. */
+    if (str_to_uint(str, 10, &port_no)) {
+        return port_no;
+    }
+
+    /* Send a "Features Request" to resolve the name into a number. */
+    make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &request);
+    open_vconn(vconn_name, &vconn);
+    run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name);
+
+    osf = reply->data;
+    n_ports = (reply->size - sizeof *osf) / sizeof *osf->ports;
+
+    for (port_idx = 0; port_idx < n_ports; port_idx++) {
+        /* Check argument as an interface name */
+        if (!strncmp((char *)osf->ports[port_idx].name, str,
+                    sizeof osf->ports[0].name)) {
+            break;
+        }
+    }
+    if (port_idx == n_ports) {
+        ovs_fatal(0, "couldn't find monitored port: %s", str);
+    }
+
+    ofpbuf_delete(reply);
+    vconn_close(vconn);
+
+    return port_idx;
+}
+
 static void *
 put_action(struct ofpbuf *b, size_t size, uint16_t type)
 {
@@ -599,6 +607,26 @@ str_to_action(char *str, struct ofpbuf *b)
             put_dl_addr_action(b, OFPAT_SET_DL_SRC, arg);
         } else if (!strcasecmp(act, "mod_dl_dst")) {
             put_dl_addr_action(b, OFPAT_SET_DL_DST, arg);
+        } else if (!strcasecmp(act, "mod_nw_src")) {
+            struct ofp_action_nw_addr *na;
+            na = put_action(b, sizeof *na, OFPAT_SET_NW_SRC);
+            str_to_ip(arg, &na->nw_addr);
+        } else if (!strcasecmp(act, "mod_nw_dst")) {
+            struct ofp_action_nw_addr *na;
+            na = put_action(b, sizeof *na, OFPAT_SET_NW_DST);
+            str_to_ip(arg, &na->nw_addr);
+        } else if (!strcasecmp(act, "mod_tp_src")) {
+            struct ofp_action_tp_port *ta;
+            ta = put_action(b, sizeof *ta, OFPAT_SET_TP_SRC);
+            ta->tp_port = htons(str_to_u32(arg));
+        } else if (!strcasecmp(act, "mod_tp_dst")) {
+            struct ofp_action_tp_port *ta;
+            ta = put_action(b, sizeof *ta, OFPAT_SET_TP_DST);
+            ta->tp_port = htons(str_to_u32(arg));
+        } else if (!strcasecmp(act, "mod_nw_tos")) {
+            struct ofp_action_nw_tos *nt;
+            nt = put_action(b, sizeof *nt, OFPAT_SET_NW_TOS);
+            nt->nw_tos = str_to_u32(arg);
         } else if (!strcasecmp(act, "output")) {
             put_output_action(b, str_to_u32(arg));
         } else if (!strcasecmp(act, "drop")) {
@@ -615,8 +643,10 @@ str_to_action(char *str, struct ofpbuf *b)
 
             /* Unless a numeric argument is specified, we send the whole
              * packet to the controller. */
-            if (arg && (strspn(act, "0123456789") == strlen(act))) {
+            if (arg && (strspn(arg, "0123456789") == strlen(arg))) {
                oao->max_len = htons(str_to_u32(arg));
+            } else {
+                oao->max_len = htons(UINT16_MAX);
             }
         } else if (parse_port_name(act, &port)) {
             put_output_action(b, port);
@@ -670,6 +700,7 @@ parse_field(const char *name, const struct field **f_out)
     static const struct field fields[] = { 
         { "in_port", OFPFW_IN_PORT, F_U16, F_OFS(in_port), 0 },
         { "dl_vlan", OFPFW_DL_VLAN, F_U16, F_OFS(dl_vlan), 0 },
+        { "dl_vlan_pcp", OFPFW_DL_VLAN_PCP, F_U8, F_OFS(dl_vlan_pcp), 0 },
         { "dl_src", OFPFW_DL_SRC, F_MAC, F_OFS(dl_src), 0 },
         { "dl_dst", OFPFW_DL_DST, F_MAC, F_OFS(dl_dst), 0 },
         { "dl_type", OFPFW_DL_TYPE, F_U16, F_OFS(dl_type), 0 },
@@ -678,6 +709,7 @@ parse_field(const char *name, const struct field **f_out)
         { "nw_dst", OFPFW_NW_DST_MASK, F_IP,
           F_OFS(nw_dst), OFPFW_NW_DST_SHIFT },
         { "nw_proto", OFPFW_NW_PROTO, F_U8, F_OFS(nw_proto), 0 },
+        { "nw_tos", OFPFW_NW_TOS, F_U8, F_OFS(nw_tos), 0 },
         { "tp_src", OFPFW_TP_SRC, F_U16, F_OFS(tp_src), 0 },
         { "tp_dst", OFPFW_TP_DST, F_U16, F_OFS(tp_dst), 0 },
         { "icmp_type", OFPFW_ICMP_TYPE, F_U16, F_OFS(icmp_type), 0 },
@@ -698,7 +730,8 @@ parse_field(const char *name, const struct field **f_out)
 static void
 str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
             uint8_t *table_idx, uint16_t *out_port, uint16_t *priority, 
-            uint16_t *idle_timeout, uint16_t *hard_timeout)
+            uint16_t *idle_timeout, uint16_t *hard_timeout, 
+            uint64_t *cookie)
 {
     char *save_ptr = NULL;
     char *name;
@@ -719,6 +752,9 @@ str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
     if (hard_timeout) {
         *hard_timeout = OFP_FLOW_PERMANENT;
     }
+    if (cookie) {
+        *cookie = 0;
+    }
     if (actions) {
         char *act_str = strstr(string, "action");
         if (!act_str) {
@@ -767,6 +803,8 @@ str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
                 *idle_timeout = atoi(value);
             } else if (hard_timeout && !strcmp(name, "hard_timeout")) {
                 *hard_timeout = atoi(value);
+            } else if (cookie && !strcmp(name, "cookie")) {
+                *cookie = atoi(value);
             } else if (parse_field(name, &f)) {
                 void *data = (char *) match + f->offset;
                 if (!strcmp(value, "*") || !strcmp(value, "ANY")) {
@@ -797,7 +835,7 @@ str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
 }
 
 static void
-do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_flows(int argc, char *argv[])
 {
     struct ofp_flow_stats_request *req;
     uint16_t out_port;
@@ -805,7 +843,7 @@ do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
 
     req = alloc_stats_request(sizeof *req, OFPST_FLOW, &request);
     str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL,
-                &req->table_id, &out_port, NULL, NULL, NULL);
+                &req->table_id, &out_port, NULL, NULL, NULL, NULL);
     memset(&req->pad, 0, sizeof req->pad);
     req->out_port = htons(out_port);
 
@@ -813,7 +851,7 @@ do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_aggregate(int argc, char *argv[])
 {
     struct ofp_aggregate_stats_request *req;
     struct ofpbuf *request;
@@ -821,7 +859,7 @@ do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
 
     req = alloc_stats_request(sizeof *req, OFPST_AGGREGATE, &request);
     str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL,
-                &req->table_id, &out_port, NULL, NULL, NULL);
+                &req->table_id, &out_port, NULL, NULL, NULL, NULL);
     memset(&req->pad, 0, sizeof req->pad);
     req->out_port = htons(out_port);
 
@@ -829,27 +867,29 @@ do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flow(int argc OVS_UNUSED, char *argv[])
 {
     struct vconn *vconn;
     struct ofpbuf *buffer;
     struct ofp_flow_mod *ofm;
     uint16_t priority, idle_timeout, hard_timeout;
+    uint64_t cookie;
     struct ofp_match match;
 
     /* Parse and send.  str_to_flow() will expand and reallocate the data in
      * 'buffer', so we can't keep pointers to across the str_to_flow() call. */
     make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
     str_to_flow(argv[2], &match, buffer,
-                NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+                NULL, NULL, &priority, &idle_timeout, &hard_timeout,
+                &cookie);
     ofm = buffer->data;
     ofm->match = match;
     ofm->command = htons(OFPFC_ADD);
+    ofm->cookie = htonll(cookie);
     ofm->idle_timeout = htons(idle_timeout);
     ofm->hard_timeout = htons(hard_timeout);
     ofm->buffer_id = htonl(UINT32_MAX);
     ofm->priority = htons(priority);
-    ofm->reserved = htonl(0);
 
     open_vconn(argv[1], &vconn);
     send_openflow_buffer(vconn, buffer);
@@ -857,7 +897,7 @@ do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flows(int argc OVS_UNUSED, char *argv[])
 {
     struct vconn *vconn;
     FILE *file;
@@ -873,6 +913,7 @@ do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
         struct ofpbuf *buffer;
         struct ofp_flow_mod *ofm;
         uint16_t priority, idle_timeout, hard_timeout;
+        uint64_t cookie;
         struct ofp_match match;
 
         char *comment;
@@ -891,17 +932,18 @@ do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
         /* Parse and send.  str_to_flow() will expand and reallocate the data
          * in 'buffer', so we can't keep pointers to across the str_to_flow()
          * call. */
-        ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
+        make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
         str_to_flow(line, &match, buffer,
-                    NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+                    NULL, NULL, &priority, &idle_timeout, &hard_timeout,
+                    &cookie);
         ofm = buffer->data;
         ofm->match = match;
         ofm->command = htons(OFPFC_ADD);
+        ofm->cookie = htonll(cookie);
         ofm->idle_timeout = htons(idle_timeout);
         ofm->hard_timeout = htons(hard_timeout);
         ofm->buffer_id = htonl(UINT32_MAX);
         ofm->priority = htons(priority);
-        ofm->reserved = htonl(0);
 
         send_openflow_buffer(vconn, buffer);
     }
@@ -910,9 +952,10 @@ do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
+do_mod_flows(int argc OVS_UNUSED, char *argv[])
 {
     uint16_t priority, idle_timeout, hard_timeout;
+    uint64_t cookie;
     struct vconn *vconn;
     struct ofpbuf *buffer;
     struct ofp_flow_mod *ofm;
@@ -922,26 +965,27 @@ do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
      * 'buffer', so we can't keep pointers to across the str_to_flow() call. */
     make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
     str_to_flow(argv[2], &match, buffer,
-                NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+                NULL, NULL, &priority, &idle_timeout, &hard_timeout,
+                &cookie);
     ofm = buffer->data;
     ofm->match = match;
-    if (s->strict) {
+    if (strict) {
         ofm->command = htons(OFPFC_MODIFY_STRICT);
     } else {
         ofm->command = htons(OFPFC_MODIFY);
     }
     ofm->idle_timeout = htons(idle_timeout);
     ofm->hard_timeout = htons(hard_timeout);
+    ofm->cookie = htonll(cookie);
     ofm->buffer_id = htonl(UINT32_MAX);
     ofm->priority = htons(priority);
-    ofm->reserved = htonl(0);
 
     open_vconn(argv[1], &vconn);
     send_openflow_buffer(vconn, buffer);
     vconn_close(vconn);
 }
 
-static void do_del_flows(const struct settings *s, int argc, char *argv[])
+static void do_del_flows(int argc, char *argv[])
 {
     struct vconn *vconn;
     uint16_t priority;
@@ -952,8 +996,8 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
     /* Parse and send. */
     ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
     str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL, 
-                &out_port, &priority, NULL, NULL);
-    if (s->strict) {
+                &out_port, &priority, NULL, NULL, NULL);
+    if (strict) {
         ofm->command = htons(OFPFC_DELETE_STRICT);
     } else {
         ofm->command = htons(OFPFC_DELETE);
@@ -963,7 +1007,6 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
     ofm->buffer_id = htonl(UINT32_MAX);
     ofm->out_port = htons(out_port);
     ofm->priority = htons(priority);
-    ofm->reserved = htonl(0);
 
     open_vconn(argv[1], &vconn);
     send_openflow_buffer(vconn, buffer);
@@ -971,19 +1014,17 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
 }
 
 static void
-do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_monitor(int argc OVS_UNUSED, char *argv[])
 {
     struct vconn *vconn;
 
     open_vconn(argv[1], &vconn);
     if (argc > 2) {
         int miss_send_len = atoi(argv[2]);
-        int send_flow_exp = argc > 3 ? atoi(argv[3]) : 0;
         struct ofp_switch_config *osc;
         struct ofpbuf *buf;
 
         osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &buf);
-        osc->flags = htons(send_flow_exp ? OFPC_SEND_FLOW_EXP : 0);
         osc->miss_send_len = htons(miss_send_len);
         send_openflow_buffer(vconn, buf);
     }
@@ -996,13 +1037,20 @@ do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_dump_ports(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_ports(int argc, char *argv[])
 {
-    dump_trivial_stats_transaction(argv[1], OFPST_PORT);
+    struct ofp_port_stats_request *req;
+    struct ofpbuf *request;
+    uint16_t port;
+
+    req = alloc_stats_request(sizeof *req, OFPST_PORT, &request);
+    port = argc > 2 ? str_to_port_no(argv[1], argv[2]) : OFPP_NONE;
+    req->port_no = htons(port);
+    dump_stats_transaction(argv[1], request);
 }
 
 static void
-do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_probe(int argc OVS_UNUSED, char *argv[])
 {
     struct ofpbuf *request;
     struct vconn *vconn;
@@ -1019,7 +1067,7 @@ do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_mod_port(int argc OVS_UNUSED, char *argv[])
 {
     struct ofpbuf *request, *reply;
     struct ofp_switch_features *osf;
@@ -1099,7 +1147,7 @@ do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_ping(const struct settings *s UNUSED, int argc, char *argv[])
+do_ping(int argc, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     unsigned int payload;
@@ -1146,7 +1194,7 @@ do_ping(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_benchmark(int argc OVS_UNUSED, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     struct timeval start, end;
@@ -1189,77 +1237,12 @@ do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_execute(const struct settings *s UNUSED, int argc, char *argv[])
-{
-    struct vconn *vconn;
-    struct ofpbuf *request;
-    struct nicira_header *nicira;
-    struct nx_command_reply *ncr;
-    uint32_t xid;
-    int i;
-
-    nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request);
-    xid = nicira->header.xid;
-    nicira->vendor = htonl(NX_VENDOR_ID);
-    nicira->subtype = htonl(NXT_COMMAND_REQUEST);
-    ofpbuf_put(request, argv[2], strlen(argv[2]));
-    for (i = 3; i < argc; i++) {
-        ofpbuf_put_zeros(request, 1);
-        ofpbuf_put(request, argv[i], strlen(argv[i]));
-    }
-    update_openflow_length(request);
-
-    open_vconn(argv[1], &vconn);
-    run(vconn_send_block(vconn, request), "send");
-
-    for (;;) {
-        struct ofpbuf *reply;
-        uint32_t status;
-
-        run(vconn_recv_xid(vconn, xid, &reply), "recv_xid");
-        if (reply->size < sizeof *ncr) {
-            ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)",
-                      reply->size, sizeof *ncr);
-        }
-        ncr = reply->data;
-        if (ncr->nxh.header.type != OFPT_VENDOR
-            || ncr->nxh.vendor != htonl(NX_VENDOR_ID)
-            || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) {
-            ovs_fatal(0, "reply is invalid");
-        }
-
-        status = ntohl(ncr->status);
-        if (status & NXT_STATUS_STARTED) {
-            /* Wait for a second reply. */
-            continue;
-        } else if (status & NXT_STATUS_EXITED) {
-            fprintf(stderr, "process terminated normally with exit code %d",
-                    status & NXT_STATUS_EXITSTATUS);
-        } else if (status & NXT_STATUS_SIGNALED) {
-            fprintf(stderr, "process terminated by signal %d",
-                    status & NXT_STATUS_TERMSIG);
-        } else if (status & NXT_STATUS_ERROR) {
-            fprintf(stderr, "error executing command");
-        } else {
-            fprintf(stderr, "process terminated for unknown reason");
-        }
-        if (status & NXT_STATUS_COREDUMP) {
-            fprintf(stderr, " (core dumped)");
-        }
-        putc('\n', stderr);
-
-        fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout);
-        break;
-    }
-}
-
-static void
-do_help(const struct settings *s UNUSED, int argc UNUSED, char *argv[] UNUSED)
+do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "show", 1, 1, do_show },
     { "status", 1, 2, do_status },
     { "monitor", 1, 3, do_monitor },
@@ -1271,12 +1254,11 @@ static struct command all_commands[] = {
     { "add-flows", 2, 2, do_add_flows },
     { "mod-flows", 2, 2, do_mod_flows },
     { "del-flows", 1, 2, do_del_flows },
-    { "dump-ports", 1, 1, do_dump_ports },
+    { "dump-ports", 1, 2, do_dump_ports },
     { "mod-port", 3, 3, do_mod_port },
     { "probe", 1, 1, do_probe },
     { "ping", 1, 2, do_ping },
     { "benchmark", 3, 3, do_benchmark },
-    { "execute", 2, INT_MAX, do_execute },
     { "help", 0, INT_MAX, do_help },
     { NULL, 0, 0, NULL },
 };
similarity index 61%
rename from secchan/secchan.8.in
rename to utilities/ovs-openflowd.8.in
index 2ad50ae..6247bf0 100644 (file)
@@ -1,62 +1,48 @@
-.TH secchan 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
-.ds PN secchan
-
+.TH ovs\-openflowd 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-openflowd
+.
 .SH NAME
-secchan \- OpenFlow switch implementation
-
+ovs\-openflowd \- OpenFlow switch implementation
+.
 .SH SYNOPSIS
-.B secchan
+.B ovs\-openflowd
 [\fIoptions\fR] \fIdatapath\fR [\fIcontroller\fR]
-
+.
 .SH DESCRIPTION
-The \fBsecchan\fR program implements an OpenFlow switch using a
-flow-based datapath.  \fBsecchan\fR connects to an OpenFlow controller
+The \fBovs\-openflowd\fR program implements an OpenFlow switch using a
+flow-based datapath.  \fBovs\-openflowd\fR connects to an OpenFlow controller
 over TCP or SSL.
-
+.PP
 The mandatory \fIdatapath\fR argument argument specifies the local datapath
 to relay.  It takes one of the following forms:
-
+.
 .so lib/dpif.man
-
+.
 .PP
 The optional \fIcontroller\fR argument specifies how to connect to
 the OpenFlow controller.  It takes one of the following forms:
-
-.RS
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
-.RE
-
+.
+.so lib/vconn-active.man
+.
 .PP
-If \fIcontroller\fR is omitted, \fBsecchan\fR attempts to discover the
+If \fIcontroller\fR is omitted, \fBovs\-openflowd\fR attempts to discover the
 location of the controller automatically (see below).
-
+.
 .SS "Contacting the Controller"
 The OpenFlow switch must be able to contact the OpenFlow controller
 over the network.  It can do so in one of two ways:
-
+.
 .IP out-of-band
 In this configuration, OpenFlow traffic uses a network separate from
 the data traffic that it controls, that is, the switch does not use
 any of the network devices added to the datapath with \fBovs\-dpctl
 add\-if\fR in its communication with the controller.
-
-To use \fBsecchan\fR in a network with out-of-band control, specify
-\fB--out-of-band\fR on the \fBsecchan\fR command line.  The control
-network must be configured separately, before or after \fBsecchan\fR
+.IP
+To use \fBovs\-openflowd\fR in a network with out-of-band control, specify
+\fB--out-of-band\fR on the \fBovs\-openflowd\fR command line.  The control
+network must be configured separately, before or after \fBovs\-openflowd\fR
 is started.
-
+.
 .IP in-band
 In this configuration, a single network is used for OpenFlow traffic
 and other data traffic, that is, the switch contacts the controller
@@ -64,38 +50,38 @@ over one of the network devices added to the datapath with \fBovs\-dpctl
 add\-if\fR.  This configuration is often more convenient than
 out-of-band control, because it is not necessary to maintain two
 independent networks.
-
-In-band control is the default for \fBsecchan\fR, so no special
+.IP
+In-band control is the default for \fBovs\-openflowd\fR, so no special
 command-line option is required.
-
+.IP
 With in-band control, the location of the controller can be configured
 manually or discovered automatically:
-
+.
 .RS
 .IP "controller discovery"
-To make \fBsecchan\fR discover the location of the controller
+To make \fBovs\-openflowd\fR discover the location of the controller
 automatically, do not specify the location of the controller on the
-\fBsecchan\fR command line.
-
-In this mode, \fBsecchan\fR will broadcast a DHCP request with vendor
+\fBovs\-openflowd\fR command line.
+.IP
+In this mode, \fBovs\-openflowd\fR will broadcast a DHCP request with vendor
 class identifier \fBOpenFlow\fR across the network devices added to
 the datapath with \fBovs\-dpctl add\-if\fR.  It will accept any valid DHCP
 reply that has the same vendor class identifier and includes a
 vendor-specific option with code 1 whose contents are a string
 specifying the location of the controller in the same format used on
-the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR).
-
+the \fBovs\-openflowd\fR command line (e.g. \fBssl:192.168.0.1\fR).
+.IP
 The DHCP reply may also, optionally, include a vendor-specific option
 with code 2 whose contents are a string specifying the URI to the base
 of the OpenFlow PKI (e.g. \fBhttp://192.168.0.1/openflow/pki\fR).
 This URI is used only for bootstrapping the OpenFlow PKI at initial
-switch setup; \fBsecchan\fR does not use it at all.
-
+switch setup; \fBovs\-openflowd\fR does not use it at all.
+.IP
 The following ISC DHCP server configuration file assigns the IP
 address range 192.168.0.20 through 192.168.0.30 to OpenFlow switches
 that follow the switch protocol and addresses 192.168.0.1 through
 192.168.0.10 to all other DHCP clients:
-
+.IP
 default-lease-time 600;
 .br
 max-lease-time 7200;
@@ -140,58 +126,104 @@ subnet 192.168.0.0 netmask 255.255.255.0 {
 .br
 }
 .br
-
+.
 .IP "manual configuration"
 To configure in-band control manually, specify the location of the
-controller on the \fBsecchan\fR command line as the \fIcontroller\fR
+controller on the \fBovs\-openflowd\fR command line as the \fIcontroller\fR
 argument.  You must also configure the network device for the OpenFlow
-``local port'' to allow \fBsecchan\fR to connect to that controller.
-The OpenFlow local port is a virtual network port that \fBsecchan\fR
+``local port'' to allow \fBovs\-openflowd\fR to connect to that controller.
+The OpenFlow local port is a virtual network port that \fBovs\-openflowd\fR
 bridges to the physical switch ports.  The name of the local port for
 a given \fIdatapath\fR may be seen by running \fBovs\-dpctl show
 \fIdatapath\fR; the local port is listed as port 0 in \fBshow\fR's
 output.
-
+.
 .IP
-Before \fBsecchan\fR starts, the local port network device is not
+Before \fBovs\-openflowd\fR starts, the local port network device is not
 bridged to any physical network, so the next step depends on whether
 connectivity is required to configure the device's IP address.  If the
 switch has a static IP address, you may configure its IP address now
 with a command such as 
 .B ifconfig of0 192.168.1.1
-and then invoke \fBsecchan\fR.
-
+and then invoke \fBovs\-openflowd\fR.
+.IP
 On the other hand, if the switch does not have a static IP address,
 e.g. it obtains its IP address dynamically via DHCP, the DHCP client
-will not be able to contact the DHCP server until the secure channel
-has started up.  Thus, start \fBsecchan\fR without configuring
+will not be able to contact the DHCP server until the OpenFlow switch
+has started up.  Thus, start \fBovs\-openflowd\fR without configuring
 the local port network device, and start the DHCP client afterward.
 .RE
-
+.
 .SH OPTIONS
+.SS "OpenFlow Options"
+.TP
+\fB--datapath-id=\fIdpid\fR
+Sets \fIdpid\fR, which must consist of exactly 16 hexadecimal digits,
+as the datapath ID that the switch will use to identify itself to the
+OpenFlow controller.
+.IP
+If this option is omitted, the default datapath ID is taken from the
+Ethernet address of the datapath's local port (which is typically
+randomly generated) in the lower 48 bits and zeros in the upper 16.
+.
+.TP
+\fB--mgmt-id=\fImgmtid\fR
+Sets \fImgmtid\fR, which must consist of exactly 12 hexadecimal
+digits, as the switch's management ID.
+.IP
+If this option is omitted, the management ID defaults to 0, signaling
+to the controller that management is supported but not configured.
+.
+.TP
+\fB--mfr-desc=\fIdesc\fR
+Set the description of the switch's manufacturer to \fIdesc\fR, which
+may contain up to 255 ASCII characters.
+.
+.TP
+\fB--hw-desc=\fIdesc\fR
+Set the description of the switch's hardware revision to \fIdesc\fR, which
+may contain up to 255 ASCII characters.
+.
+.TP
+\fB--sw-desc=\fIdesc\fR
+Set the description of the switch's software revision to \fIdesc\fR, which
+may contain up to 255 ASCII characters.
+.
+.TP
+\fB--serial-desc=\fIdesc\fR
+Set the description of the switch's serial number to \fIdesc\fR, which
+may contain up to 31 ASCII characters.
+.
+.TP
+\fB--dp-desc=\fIdesc\fR
+Set the description of the datapath to \fIdesc\fR, which may contain up to
+255 ASCII characters.  Note that this field is intended for debugging
+purposes and is not guaranteed to be unique and should not be used as
+the primary identifier of the datapath.
+.
 .SS "Controller Discovery Options"
 .TP
 \fB--accept-vconn=\fIregex\fR
-When \fBsecchan\fR performs controller discovery (see \fBContacting
+When \fBovs\-openflowd\fR performs controller discovery (see \fBContacting
 the Controller\fR, above, for more information about controller
 discovery), it validates the controller location obtained via DHCP
 with a POSIX extended regular expression.  Only controllers whose
 names match the regular expression will be accepted.
-
+.IP
 The default regular expression is \fBssl:.*\fR (meaning that only SSL
 controller connections will be accepted) when any of the SSL
 configuration options \fB--private-key\fR, \fB--certificate\fR, or
-\fB--ca-cert\fR is specified.  The default is \fB.*\fR otherwise
-(meaning that any controller will be accepted).
-
+\fB--ca-cert\fR is specified.  The default is \fB^tcp:.*\fR otherwise
+(meaning that only TCP controller connections will be accepted).
+.IP
 The \fIregex\fR is implicitly anchored at the beginning of the
 controller location string, as if it begins with \fB^\fR.
-
+.IP
 When controller discovery is not performed, this option has no effect.
-
+.
 .TP
 \fB--no-resolv-conf\fR
-When \fBsecchan\fR performs controller discovery (see \fBContacting
+When \fBovs\-openflowd\fR performs controller discovery (see \fBContacting
 the Controller\fR, above, for more information about controller
 discovery), by default it overwrites the system's
 \fB/etc/resolv.conf\fR with domain information and DNS servers
@@ -199,33 +231,25 @@ obtained via DHCP.  If the location of the controller is specified
 using a hostname, rather than an IP address, and the network's DNS
 servers ever change, this behavior is essential.  But because it also
 interferes with any administrator or process that manages
-\fB/etc/resolv.conf\fR, when this option is specified, \fBsecchan\fR
+\fB/etc/resolv.conf\fR, when this option is specified, \fBovs\-openflowd\fR
 will not modify \fB/etc/resolv.conf\fR.
-
-\fBsecchan\fR will only modify \fBresolv.conf\fR if the DHCP response
+.IP
+\fBovs\-openflowd\fR will only modify \fBresolv.conf\fR if the DHCP response
 that it receives specifies one or more DNS servers.
-
+.IP
 When controller discovery is not performed, this option has no effect.
-
+.
 .SS "Networking Options"
 .TP
 \fB--datapath-id=\fIdpid\fR
-Sets \fIdpid\fR, which must consist of exactly 12 hexadecimal digits,
+Sets \fIdpid\fR, which must consist of exactly 16 hexadecimal digits,
 as the datapath ID that the switch will use to identify itself to the
 OpenFlow controller.
-
+.IP
 If this option is omitted, the default datapath ID is taken from the
 Ethernet address of the datapath's local port (which is typically
-randomly generated).
-
-.TP
-\fB--mgmt-id=\fImgmtid\fR
-Sets \fImgmtid\fR, which must consist of exactly 12 hexadecimal
-digits, as the switch's management ID.
-
-If this option is omitted, the management ID defaults to 0, signaling
-to the controller that management is supported but not configured.
-
+randomly generated) in the lower 48 bits and zeros in the upper 16.
+.
 .TP
 \fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
 The controller is, ordinarily, responsible for setting up all flows on
@@ -233,61 +257,61 @@ the OpenFlow switch.  Thus, if the connection to the controller fails,
 no new network connections can be set up.  If the connection to the
 controller stays down long enough, no packets can pass through the
 switch at all.
-
-If this option is set to \fBopen\fR (the default), \fBsecchan\fR will
+.IP
+If this option is set to \fBopen\fR (the default), \fBovs\-openflowd\fR will
 take over responsibility for setting up flows in the local datapath
 when no message has been received from the controller for three times
 the inactivity probe interval (see below), or 45 seconds by default.
-In this ``fail open'' mode, \fBsecchan\fR causes the datapath to act
-like an ordinary MAC-learning switch.  \fBsecchan\fR will continue to
+In this ``fail open'' mode, \fBovs\-openflowd\fR causes the datapath to act
+like an ordinary MAC-learning switch.  \fBovs\-openflowd\fR will continue to
 retry connection to the controller in the background and, when the
 connection succeeds, it discontinues its fail-open behavior.
-
-If this option is set to \fBclosed\fR, then \fBsecchan\fR will not
+.IP
+If this option is set to \fBclosed\fR, then \fBovs\-openflowd\fR will not
 set up flows on its own when the controller connection fails.
-
+.
 .TP
 \fB--inactivity-probe=\fIsecs\fR
-When the secure channel is connected to the controller, the secure
-channel waits for a message to be received from the controller for
+When the OpenFlow switch is connected to the controller, the
+switch waits for a message to be received from the controller for
 \fIsecs\fR seconds before it sends a inactivity probe to the
 controller.  After sending the inactivity probe, if no response is
-received for an additional \fIsecs\fR seconds, the secure channel
+received for an additional \fIsecs\fR seconds, the switch
 assumes that the connection has been broken and attempts to reconnect.
 The default and the minimum value are both 5 seconds.
-
+.IP
 When fail-open mode is configured, changing the inactivity probe
 interval also changes the interval before entering fail-open mode (see
 above).
-
+.
 .TP
 \fB--max-idle=\fIsecs\fR|\fBpermanent\fR
 Sets \fIsecs\fR as the number of seconds that a flow set up by the
-secure channel will remain in the switch's flow table without any
+OpenFlow switch will remain in the switch's flow table without any
 matching packets being seen.  If \fBpermanent\fR is specified, which
-is not recommended, flows set up by the secure channel will never
+is not recommended, flows set up by the switch will never
 expire.  The default is 15 seconds.
-
-Most flows are set up by the OpenFlow controller, not by the secure
-channel.  This option affects only the following flows, which the
-secure channel sets up itself:
-
+.IP
+Most flows are set up by the OpenFlow controller, not by the
+switch.  This option affects only the following flows, which the
+OpenFlow switch sets up itself:
+.
 .RS
 .IP \(bu
-When \fB--fail=open\fR is specified, flows set up when the secure
-channel has not been able to contact the controller for the configured
+When \fB--fail=open\fR is specified, flows set up when the
+switch has not been able to contact the controller for the configured
 fail-open delay.
-
+.
 .IP \(bu
 When in-band control is in use, flows set up to bootstrap contacting
 the controller (see \fBContacting the Controller\fR, above, for
 more information about in-band control).
 .RE
-
+.
 .IP
 As a result, when both \fB--fail=closed\fR and \fB--out-of-band\fR are
 specified, this option has no effect.
-
+.
 .TP
 \fB--max-backoff=\fIsecs\fR
 Sets the maximum time between attempts to connect to the controller to
@@ -295,31 +319,24 @@ Sets the maximum time between attempts to connect to the controller to
 connection attempts starts at 1 second and doubles on each failing
 attempt until it reaches the maximum.  The default maximum backoff
 time is 8 seconds.
-
+.
 .TP
 \fB-l\fR, \fB--listen=\fImethod\fR
-Configures the switch to additionally listen for incoming OpenFlow
-connections for switch management with \fBovs\-ofctl\fR.  The \fImethod\fR
+By default, the switch listens for OpenFlow management connections on a
+Unix domain socket named \fB@RUNDIR@/\fIdatapath\fB.mgmt\fR.  This socket 
+can be used to perform local OpenFlow monitoring and administration with
+tools such as \fBovs\-ofctl\fR.  
+.IP
+This option may be used to override the default listener.  The \fImethod\fR
 must be given as one of the passive OpenFlow connection methods listed
 below.  This option may be specified multiple times to listen to
-multiple connection methods.
-
+multiple connection methods.  If a single \fImethod\fR of \fBnone\fR is
+used, no listeners will be created.
+.
 .RS
-.TP
-\fBpssl:\fR[\fIport\fR]
-Listens for SSL connections on \fIport\fR (default: 6633).  The
-\fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR options
-are mandatory when this form is used.
-
-.TP
-\fBptcp:\fR[\fIport\fR]
-Listens for TCP connections on \fIport\fR (default: 6633).
-
-.TP
-\fBpunix:\fIfile\fR
-Listens for connections on Unix domain server socket named \fIfile\fR.
+.so lib/vconn-passive.man
 .RE
-
+.
 .TP
 \fB--snoop=\fImethod\fR
 Configures the switch to additionally listen for incoming OpenFlow
@@ -327,45 +344,45 @@ connections for controller connection snooping.  The \fImethod\fR must
 be given as one of the passive OpenFlow connection methods listed
 under the \fB--listen\fR option above.  This option may be specified
 multiple times to listen to multiple connection methods.
-
+.IP
 If \fBovs\-ofctl monitor\fR is used to connect to \fImethod\fR specified on
 \fB--snoop\fR, it will display all the OpenFlow messages traveling
 between the switch and its controller on the primary OpenFlow
 connection.  This can be useful for debugging switch and controller
 problems.
-
+.
 .TP
 \fB--in-band\fR, \fB--out-of-band\fR
-Configures \fBsecchan\fR to operate in in-band or out-of-band control
+Configures \fBovs\-openflowd\fR to operate in in-band or out-of-band control
 mode (see \fBContacting the Controller\fR above).  When neither option
 is given, the default is in-band control.
-
+.
 .TP
 \fB--netflow=\fIip\fB:\fIport\fR
 Configures the given UDP \fIport\fR on the specified IP \fIip\fR as
 a recipient of NetFlow messages for expired flows.  The \fIip\fR must
 be specified numerically, not as a DNS name.
-
+.IP
 This option may be specified multiple times to configure additional
 NetFlow collectors.
-
+.
 .SS "Rate-Limiting Options"
-
+.
 These options configure how the switch applies a ``token bucket'' to
 limit the rate at which packets in unknown flows are forwarded to an
 OpenFlow controller for flow-setup processing.  This feature prevents
 a single OpenFlow switch from overwhelming a controller.
-
+.
 .TP
 \fB--rate-limit\fR[\fB=\fIrate\fR]
 .
 Limits the maximum rate at which packets will be forwarded to the
 OpenFlow controller to \fIrate\fR packets per second.  If \fIrate\fR
 is not specified then the default of 1,000 packets per second is used.
-
+.IP
 If \fB--rate-limit\fR is not used, then the switch does not limit the
 rate at which packets are forwarded to the controller.
-
+.
 .TP
 \fB--burst-limit=\fIburst\fR
 .
@@ -374,90 +391,43 @@ allow to accumulate during time in which no packets are being
 forwarded to the OpenFlow controller to \fIburst\fR (measured in
 packets).  The default \fIburst\fR is one-quarter of the \fIrate\fR
 specified on \fB--rate-limit\fR.
-
+.
 This option takes effect only when \fB--rate-limit\fR is also specified.
-
-.SS "Remote Command Execution Options"
-
-.TP
-\fB--command-acl=\fR[\fB!\fR]\fIglob\fR[\fB,\fR[\fB!\fR]\fIglob\fR...]
-Configures the commands that remote OpenFlow connections are allowed
-to invoke using (e.g.) \fBovs\-ofctl execute\fR.  The argument is a
-comma-separated sequence of shell glob patterns.  A glob pattern
-specified without a leading \fB!\fR is a ``whitelist'' that specifies
-a set of commands that are that may be invoked, whereas a pattern that
-does begin with \fB!\fR is a ``blacklist'' that specifies commands
-that may not be invoked.  To be permitted, a command name must be
-whitelisted and must not be blacklisted;
-e.g. \fB--command-acl=up*,!upgrade\fR would allow any command whose name
-begins with \fBup\fR except for the command named \fBupgrade\fR.
-Command names that include characters other than upper- and lower-case
-English letters, digits, and the underscore and hyphen characters are
-unconditionally disallowed.
-
-When the whitelist and blacklist permit a command name, \fBsecchan\fR
-looks for a program with the same name as the command in the commands
-directory (see below).  Other directories are not searched.
-
-.TP
-\fB--command-dir=\fIdirectory\fR
-Sets the directory searched for remote command execution to
-\fBdirectory\fR.  The default directory is
-\fB@pkgdatadir@/commands\fR.
-
+.
+.SS "Datapath Options"
+.
+.IP "\fB\-\-ports=\fIport\fR[\fB,\fIport\fR...]"
+Ordinarily, \fBovs\-openflowd\fR expects the administrator to create
+the specified \fIdatapath\fR and add ports to it externally with a
+utility such as \fBovs\-dpctl\fR.  However, the userspace switch
+datapath is implemented inside \fBovs\-openflowd\fR itself and does
+not (currently) have any external interface for \fBovs\-dpctl\fR to
+access.  As a stopgap measure, this option specifies one or more ports
+to add to the datapath at \fBovs\-openflowd\fR startup time.  Multiple
+ports may be specified as a comma-separated list or by specifying
+\fB\-\-ports\fR multiple times.
+.IP
+See \fBINSTALL.userspace\fR for more information about userspace
+switching.
+.
 .SS "Daemon Options"
 .so lib/daemon.man
-
+.
 .SS "Public Key Infrastructure Options"
-
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the switch's
-identity for SSL connections to the controller.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the switch's
-private key to identify a trustworthy switch.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the switch is connected to a trustworthy controller.
-
-.TP
-\fB--bootstrap-ca-cert=\fIcacert.pem\fR
-When \fIcacert.pem\fR exists, this option has the same effect as
-\fB-C\fR or \fB--ca-cert\fR.  If it does not exist, then \fBsecchan\fR
-will attempt to obtain the CA certificate from the controller on its
-first SSL connection and save it to the named PEM file.  If it is
-successful, it will immediately drop the connection and reconnect, and
-from then on all SSL connections must be authenticated by a
-certificate signed by the CA certificate thus obtained.
-
-\fBThis option exposes the SSL connection to a man-in-the-middle
-attack obtaining the initial CA certificate\fR, but it may be useful
-for bootstrapping.
-
-This option is only useful if the controller sends its CA certificate
-as part of the SSL certificate chain.  The SSL protocol does not
-require the controller to send the CA certificate, but
-\fBcontroller\fR(8) can be configured to do so with the
-\fB--peer-ca-cert\fR option.
-
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
+.
 .SS "Logging Options"
 .so lib/vlog.man
 .SS "Other Options"
 .so lib/common.man
 .so lib/leak-checker.man
-
+.
 .SH "SEE ALSO"
-
+.
 .BR ovs\-appctl (8),
 .BR ovs\-controller (8),
 .BR ovs\-discover (8),
 .BR ovs\-dpctl (8),
 .BR ovs\-ofctl (8),
-.BR ovs\-pki (8),
-.BR ovs\-vswitchd.conf (5)
+.BR ovs\-pki (8)
similarity index 79%
rename from secchan/main.c
rename to utilities/ovs-openflowd.c
index ac711ba..858770d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "compiler.h"
 #include "daemon.h"
 #include "dirs.h"
-#include "discovery.h"
 #include "dpif.h"
-#include "fail-open.h"
-#include "fault.h"
-#include "in-band.h"
 #include "leak-checker.h"
 #include "list.h"
 #include "netdev.h"
 #include "ofpbuf.h"
-#include "ofproto.h"
+#include "ofproto/ofproto.h"
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "rconn.h"
-#include "status.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_secchan
+#define THIS_MODULE VLM_openflowd
 
 /* Behavior when the connection to the controller fails. */
 enum fail_mode {
@@ -67,13 +62,16 @@ struct ofsettings {
 
     /* Datapath. */
     uint64_t datapath_id;       /* Datapath ID. */
-    const char *dp_name;        /* Name of local datapath. */
+    char *dp_name;              /* Name of local datapath. */
+    char *dp_type;              /* Type of local datapath. */
+    struct svec ports;          /* Set of ports to add to datapath (if any). */
 
     /* Description strings. */
     const char *mfr_desc;       /* Manufacturer. */
     const char *hw_desc;        /* Hardware. */
     const char *sw_desc;        /* Software version. */
     const char *serial_desc;    /* Serial number. */
+    const char *dp_desc;        /* Datapath description. */
 
     /* Related vconns and network devices. */
     const char *controller_name; /* Controller (if not discovery mode). */
@@ -97,13 +95,6 @@ struct ofsettings {
     /* Spanning tree protocol. */
     bool enable_stp;
 
-    /* Remote command execution. */
-    char *command_acl;          /* Command white/blacklist, as shell globs. */
-    char *command_dir;          /* Directory that contains commands. */
-
-    /* Management. */
-    uint64_t mgmt_id;           /* Management ID. */
-
     /* NetFlow. */
     struct svec netflow;        /* NetFlow targets. */
 };
@@ -118,29 +109,54 @@ main(int argc, char *argv[])
     struct ofproto *ofproto;
     struct ofsettings s;
     int error;
+    struct dpif *dpif;
     struct netflow_options nf_options;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
     parse_options(argc, argv, &s);
     signal(SIGPIPE, SIG_IGN);
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     /* Start listening for ovs-appctl requests. */
     error = unixctl_server_create(NULL, &unixctl);
     if (error) {
-        ovs_fatal(error, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
     VLOG_INFO("Open vSwitch version %s", VERSION BUILDNR);
     VLOG_INFO("OpenFlow protocol version 0x%02x", OFP_VERSION);
 
+    error = dpif_create_and_open(s.dp_name, s.dp_type, &dpif);
+    if (error) {
+        ovs_fatal(error, "could not create datapath");
+    }
+
+    /* Add ports to the datapath if requested by the user. */
+    if (s.ports.n) {
+        const char *port;
+        size_t i;
+        struct netdev *netdev;
+
+        SVEC_FOR_EACH (i, port, &s.ports) {
+            error = netdev_open_default(port, &netdev);
+            if (error) {
+                ovs_fatal(error, "failed to open %s as a device", port);
+            }
+
+            error = dpif_port_add(dpif, port, 0, NULL);
+            if (error) {
+                ovs_fatal(error, "failed to add %s as a port", port);
+            }
+        }
+    }
+
     /* Start OpenFlow processing. */
-    error = ofproto_create(s.dp_name, NULL, NULL, &ofproto);
+    error = ofproto_create(s.dp_name, s.dp_type, NULL, NULL, &ofproto);
     if (error) {
         ovs_fatal(error, "could not initialize openflow switch");
     }
@@ -156,10 +172,14 @@ main(int argc, char *argv[])
     if (s.datapath_id) {
         ofproto_set_datapath_id(ofproto, s.datapath_id);
     }
-    if (s.mgmt_id) {
-        ofproto_set_mgmt_id(ofproto, s.mgmt_id);
+    ofproto_set_desc(ofproto, s.mfr_desc, s.hw_desc, s.sw_desc,
+                     s.serial_desc, s.dp_desc);
+    if (!s.listeners.n) {
+        svec_add_nocopy(&s.listeners, xasprintf("punix:%s/%s.mgmt",
+                                              ovs_rundir, s.dp_name));
+    } else if (s.listeners.n == 1 && !strcmp(s.listeners.names[0], "none")) {
+        svec_clear(&s.listeners);
     }
-    ofproto_set_desc(ofproto, s.mfr_desc, s.hw_desc, s.sw_desc, s.serial_desc);
     error = ofproto_set_listeners(ofproto, &s.listeners);
     if (error) {
         ovs_fatal(error, "failed to configure management connections");
@@ -183,11 +203,6 @@ main(int argc, char *argv[])
     if (error) {
         ovs_fatal(error, "failed to configure STP");
     }
-    error = ofproto_set_remote_execution(ofproto, s.command_acl,
-                                         s.command_dir);
-    if (error) {
-        ovs_fatal(error, "failed to configure remote command execution");
-    }
     if (!s.discovery) {
         error = ofproto_set_controller(ofproto, s.controller_name);
         if (error) {
@@ -195,18 +210,26 @@ main(int argc, char *argv[])
         }
     }
 
+    daemonize_complete();
+
     while (ofproto_is_alive(ofproto)) {
         error = ofproto_run(ofproto);
         if (error) {
             ovs_fatal(error, "unrecoverable datapath error");
         }
         unixctl_server_run(unixctl);
+        dp_run();
+        netdev_run();
 
         ofproto_wait(ofproto);
         unixctl_server_wait(unixctl);
+        dp_wait();
+        netdev_wait();
         poll_block();
     }
 
+    dpif_close(dpif);
+
     return 0;
 }
 \f
@@ -217,10 +240,11 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
 {
     enum {
         OPT_DATAPATH_ID = UCHAR_MAX + 1,
-        OPT_MANUFACTURER,
-        OPT_HARDWARE,
-        OPT_SOFTWARE,
-        OPT_SERIAL,
+        OPT_MFR_DESC,
+        OPT_HW_DESC,
+        OPT_SW_DESC,
+        OPT_SERIAL_DESC,
+        OPT_DP_DESC,
         OPT_ACCEPT_VCONN,
         OPT_NO_RESOLV_CONF,
         OPT_BR_NAME,
@@ -236,19 +260,19 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         OPT_NO_STP,
         OPT_OUT_OF_BAND,
         OPT_IN_BAND,
-        OPT_COMMAND_ACL,
-        OPT_COMMAND_DIR,
         OPT_NETFLOW,
         OPT_MGMT_ID,
+        OPT_PORTS,
         VLOG_OPTION_ENUMS,
         LEAK_CHECKER_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"datapath-id", required_argument, 0, OPT_DATAPATH_ID},
-        {"manufacturer", required_argument, 0, OPT_MANUFACTURER},
-        {"hardware", required_argument, 0, OPT_HARDWARE},
-        {"software", required_argument, 0, OPT_SOFTWARE},
-        {"serial", required_argument, 0, OPT_SERIAL},
+        {"mfr-desc", required_argument, 0, OPT_MFR_DESC},
+        {"hw-desc", required_argument, 0, OPT_HW_DESC},
+        {"sw-desc", required_argument, 0, OPT_SW_DESC},
+        {"serial-desc", required_argument, 0, OPT_SERIAL_DESC},
+        {"dp-desc", required_argument, 0, OPT_DP_DESC},
         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
         {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF},
         {"config",      required_argument, 0, 'F'},
@@ -265,10 +289,8 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         {"no-stp",      no_argument, 0, OPT_NO_STP},
         {"out-of-band", no_argument, 0, OPT_OUT_OF_BAND},
         {"in-band",     no_argument, 0, OPT_IN_BAND},
-        {"command-acl", required_argument, 0, OPT_COMMAND_ACL},
-        {"command-dir", required_argument, 0, OPT_COMMAND_DIR},
         {"netflow",     required_argument, 0, OPT_NETFLOW},
-        {"mgmt-id",     required_argument, 0, OPT_MGMT_ID},
+        {"ports",       required_argument, 0, OPT_PORTS},
         {"verbose",     optional_argument, 0, 'v'},
         {"help",        no_argument, 0, 'h'},
         {"version",     no_argument, 0, 'V'},
@@ -276,7 +298,7 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         VLOG_LONG_OPTIONS,
         LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
 #endif
         {0, 0, 0, 0},
@@ -289,6 +311,7 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
     s->hw_desc = NULL;
     s->sw_desc = NULL;
     s->serial_desc = NULL;
+    s->dp_desc = NULL;
     svec_init(&s->listeners);
     svec_init(&s->snoops);
     s->fail_mode = FAIL_OPEN;
@@ -301,10 +324,8 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
     s->accept_controller_re = NULL;
     s->enable_stp = false;
     s->in_band = true;
-    s->command_acl = "";
-    s->command_dir = NULL;
     svec_init(&s->netflow);
-    s->mgmt_id = 0;
+    svec_init(&s->ports);
     for (;;) {
         int c;
 
@@ -315,33 +336,32 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
 
         switch (c) {
         case OPT_DATAPATH_ID:
-            if (strlen(optarg) != 12
-                || strspn(optarg, "0123456789abcdefABCDEF") != 12) {
+            if (!dpid_from_string(optarg, &s->datapath_id)) {
                 ovs_fatal(0, "argument to --datapath-id must be "
-                          "exactly 12 hex digits");
-            }
-            s->datapath_id = strtoll(optarg, NULL, 16);
-            if (!s->datapath_id) {
-                ovs_fatal(0, "argument to --datapath-id must be nonzero");
+                          "exactly 16 hex digits and may not be all-zero");
             }
             break;
 
-        case OPT_MANUFACTURER:
+        case OPT_MFR_DESC:
             s->mfr_desc = optarg;
             break;
 
-        case OPT_HARDWARE:
+        case OPT_HW_DESC:
             s->hw_desc = optarg;
             break;
 
-        case OPT_SOFTWARE:
+        case OPT_SW_DESC:
             s->sw_desc = optarg;
             break;
 
-        case OPT_SERIAL:
+        case OPT_SERIAL_DESC:
             s->serial_desc = optarg;
             break;
 
+        case OPT_DP_DESC:
+            s->dp_desc = optarg;
+            break;
+
         case OPT_ACCEPT_VCONN:
             s->accept_controller_re = optarg;
             break;
@@ -422,32 +442,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
             s->in_band = true;
             break;
 
-        case OPT_COMMAND_ACL:
-            s->command_acl = (s->command_acl[0]
-                              ? xasprintf("%s,%s", s->command_acl, optarg)
-                              : optarg);
-            break;
-
-        case OPT_COMMAND_DIR:
-            s->command_dir = optarg;
-            break;
-
         case OPT_NETFLOW:
             svec_add(&s->netflow, optarg);
             break;
 
-        case OPT_MGMT_ID:
-            if (strlen(optarg) != 12
-                || strspn(optarg, "0123456789abcdefABCDEF") != 12) {
-                ovs_fatal(0, "argument to --mgmt-id must be "
-                          "exactly 12 hex digits");
-            }
-            s->mgmt_id = strtoll(optarg, NULL, 16);
-            if (!s->mgmt_id) {
-                ovs_fatal(0, "argument to --mgmt-id must be nonzero");
-            }
-            break;
-
         case 'l':
             svec_add(&s->listeners, optarg);
             break;
@@ -456,6 +454,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
             svec_add(&s->snoops, optarg);
             break;
 
+        case OPT_PORTS:
+            svec_split(&s->ports, optarg, ",");
+            break;
+
         case 'h':
             usage();
 
@@ -470,10 +472,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         LEAK_CHECKER_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case OPT_BOOTSTRAP_CA_CERT:
-            vconn_ssl_set_ca_cert_file(optarg, true);
+            stream_ssl_set_ca_cert_file(optarg, true);
             break;
 #endif
 
@@ -494,12 +496,14 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
     }
 
     /* Local and remote vconns. */
-    s->dp_name = argv[0];
+    dp_parse_name(argv[0], &s->dp_name, &s->dp_type);
+
     s->controller_name = argc > 1 ? xstrdup(argv[1]) : NULL;
 
     /* Set accept_controller_regex. */
     if (!s->accept_controller_re) {
-        s->accept_controller_re = vconn_ssl_is_configured() ? "^ssl:.*" : ".*";
+        s->accept_controller_re
+            = stream_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*";
     }
 
     /* Mode of operation. */
@@ -521,18 +525,17 @@ usage(void)
            "usage: %s [OPTIONS] DATAPATH [CONTROLLER]\n"
            "DATAPATH is a local datapath (e.g. \"dp0\").\n"
            "CONTROLLER is an active OpenFlow connection method; if it is\n"
-           "omitted, then secchan performs controller discovery.\n",
+           "omitted, then ovs-openflowd performs controller discovery.\n",
            program_name, program_name);
     vconn_usage(true, true, true);
     printf("\nOpenFlow options:\n"
            "  -d, --datapath-id=ID    Use ID as the OpenFlow switch ID\n"
-           "                          (ID must consist of 12 hex digits)\n"
-           "  --mgmt-id=ID            Use ID as the management ID\n"
-           "                          (ID must consist of 12 hex digits)\n"
-           "  --manufacturer=MFR      Identify manufacturer as MFR\n"
-           "  --hardware=HW           Identify hardware as HW\n"
-           "  --software=SW           Identify software as SW\n"
-           "  --serial=SERIAL         Identify serial number as SERIAL\n"
+           "                          (ID must consist of 16 hex digits)\n"
+           "  --mfr-desc=MFR          Identify manufacturer as MFR\n"
+           "  --hw-desc=HW            Identify hardware as HW\n"
+           "  --sw-desc=SW            Identify software as SW\n"
+           "  --serial-desc=SERIAL    Identify serial number as SERIAL\n"
+           "  --dp-desc=DP_DESC       Identify dp description as DP_DESC\n"
            "\nController discovery options:\n"
            "  --accept-vconn=REGEX    accept matching discovered controllers\n"
            "  --no-resolv-conf        do not update /etc/resolv.conf\n"
@@ -541,7 +544,7 @@ usage(void)
            "                            closed: drop all packets\n"
            "                            open (default): act as learning switch\n"
            "  --inactivity-probe=SECS time between inactivity probes\n"
-           "  --max-idle=SECS         max idle for flows set up by secchan\n"
+           "  --max-idle=SECS         max idle for flows set up by switch\n"
            "  --max-backoff=SECS      max time between controller connection\n"
            "                          attempts (default: 8 seconds)\n"
            "  -l, --listen=METHOD     allow management connections on METHOD\n"
@@ -552,11 +555,7 @@ usage(void)
            "  --netflow=HOST:PORT     configure NetFlow output target\n"
            "\nRate-limiting of \"packet-in\" messages to the controller:\n"
            "  --rate-limit[=PACKETS]  max rate, in packets/s (default: 1000)\n"
-           "  --burst-limit=BURST     limit on packet credit for idle time\n"
-           "\nRemote command execution options:\n"
-           "  --command-acl=[!]GLOB[,[!]GLOB...] set allowed/denied commands\n"
-           "  --command-dir=DIR       set command dir (default: %s/commands)\n",
-           ovs_pkgdatadir);
+           "  --burst-limit=BURST     limit on packet credit for idle time\n");
     daemon_usage();
     vlog_usage();
     printf("\nOther options:\n"
index 27dfccf..0f1c454 100644 (file)
@@ -325,6 +325,6 @@ Prints a help usage message and exits.
 
 .SH "SEE ALSO"
 
-.BR controller (8),
-.BR ovs\-pki\-cgi (8),
-.BR secchan (8)
+.BR ovs\-controller (8),
+.BR ovs\-openflowd (8),
+.BR ovs\-pki\-cgi (8)
index 39d5782..ea959fd 100755 (executable)
@@ -1,6 +1,6 @@
 #! /bin/sh
 
-# Copyright (c) 2008, 2009 Nicira Networks, Inc.
+# Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -348,10 +348,9 @@ make_tmpdir() {
 }
 
 fingerprint() {
-    local file=$1
-    local name=${1-$2}
-    local date=$(date -r $file)
-    local fingerprint
+    file=$1
+    name=${1-$2}
+    date=$(date -r $file)
     if grep -q -e '-BEGIN CERTIFICATE-' "$file"; then
         fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
                       sed 's/SHA1 Fingerprint=//' | tr -d ':')
@@ -472,7 +471,7 @@ sign_request() {
 }
 
 glob() {
-    local files=$(echo $1)
+    files=$(echo $1)
     if test "$files" != "$1"; then
         echo "$files"
     fi
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
new file mode 100644 (file)
index 0000000..19ef558
--- /dev/null
@@ -0,0 +1,577 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+..
+.TH ovs\-vsctl 8 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-vsctl
+.
+.SH NAME
+ovs\-vsctl \- utility for querying and configuring \fBovs\-vswitchd\fR
+.
+.SH SYNOPSIS
+\fBovs\-vsctl\fR [\fIoptions\fR] \fB\-\-\fR [\fIoptions\fR] \fIcommand
+\fR[\fIargs\fR] [\fB\-\-\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR]]...
+.
+.SH DESCRIPTION
+The \fBovs\-vsctl\fR program configures \fBovs\-vswitchd\fR(8) by
+providing a high\-level interface to its configuration
+database.  This program is mainly intended for use when
+\fBovs\-vswitchd\fR is running.  If it is used when
+\fBovs\-vswitchd\fR is not running, then \fB\-\-no\-wait\fR should be
+specified and configuration changes will only take effect when
+\fBovs\-vswitchd\fR is started.
+.PP
+By default, each time \fBovs\-vsctl\fR runs, it connects to an
+\fBovsdb\-server\fR process that maintains an Open vSwitch
+configuration database.  Using this connection, it queries and
+possibly applies changes to the database, depending on the supplied
+commands.  Then, if it applied any changes, it waits until
+\fBovs\-vswitchd\fR has finished reconfiguring itself before it exits.
+.PP
+\fBovs\-vsctl\fR can perform any number of commands in a single run,
+implemented as a single atomic transaction against the database.
+.PP
+The \fBovs\-vsctl\fR command line begins with global options (see
+\fBOPTIONS\fR below for details).  The global options are followed by
+one or more commands.  Each command should begin with \fB\-\-\fR by
+itself as a command-line argument, to separate it from the global
+options and following commands.  (If the first command does not have
+any options, then the first \fB\-\-\fR may be omitted.)  The command
+itself starts with command-specific options, if any, followed by the
+command name and any arguments.  See \fBEXAMPLES\fR below for syntax
+examples.
+.
+.SS "Linux VLAN Bridging Compatibility"
+The \fBovs\-vsctl\fR program supports the model of a bridge
+implemented by Open vSwitch, in which a single bridge supports ports
+on multiple VLANs.  In this model, each port on a bridge is either a
+trunk port that potentially passes packets tagged with 802.1Q headers
+that designate VLANs or it is assigned a single implicit VLAN that is
+never tagged with an 802.1Q header.
+.PP
+For compatibility with software designed for the Linux bridge,
+\fBovs\-vsctl\fR also supports a model in which traffic associated
+with a given 802.1Q VLAN is segregated into a separate bridge.  A
+special form of the \fBadd\-br\fR command (see below) creates a ``fake
+bridge'' within an Open vSwitch bridge to simulate this behavior.
+When such a ``fake bridge'' is active, \fBovs\-vsctl\fR will treat it
+much like a bridge separate from its ``parent bridge,'' but the actual
+implementation in Open vSwitch uses only a single bridge, with ports on
+the fake bridge assigned the implicit VLAN of the fake bridge of which
+they are members.
+.
+.SH OPTIONS
+.
+The following options affect the behavior \fBovs\-vsctl\fR as a whole.
+Some individual commands also accept their own options, which are
+given just before the command name.  If the first command on the
+command line has options, then those options must be separated from
+the global options by \fB\-\-\fR.
+.
+.IP "\fB\-\-db=\fIserver\fR"
+Sets \fIserver\fR as the database server that \fBovs\-vsctl\fR
+contacts to query or modify configuration.  The default is
+\fBunix:@RUNDIR@/ovsdb\-server\fR.  \fIserver\fR must take one of the
+following forms:
+.RS
+.so ovsdb/remote-active.man
+.RE
+.
+.IP "\fB\-\-no\-wait\fR"
+Prevents \fBovs\-vsctl\fR from waiting for \fBovs\-vswitchd\fR to
+reconfigure itself according to the the modified database.  This
+option should be used if \fBovs\-vswitchd\fR is not running;
+otherwise, \fBovs-vsctl\fR will not exit until \fBovs-vswitchd\fR
+starts.
+.IP
+This option has no effect if the commands specified do not change the
+database.
+.
+.IP "\fB\-\-no\-syslog\fR"
+By default, \fBovs\-vsctl\fR logs its arguments and the details of any
+changes that it makes to the system log.  This option disables this
+logging.
+.IP
+This option is equivalent to \fB\-\-verbose=vvsctl:syslog:warn\fR.
+.
+.IP "\fB\-\-oneline\fR"
+Modifies the output format so that the output for each command is printed
+on a single line.  New-line characters that would otherwise separate
+lines are printed as \fB\\n\fR, and any instances of \fB\\\fR that
+would otherwise appear in the output are doubled.
+Prints a blank line for each command that has no output.
+.
+.IP "\fB\-\-dry\-run\fR"
+Prevents \fBovs\-vsctl\fR from actually modifying the database.
+.
+.IP "\fB-t \fIsecs\fR"
+.IQ "\fB--timeout=\fIsecs\fR"
+Limits runtime to approximately \fIsecs\fR seconds.  A value of 
+zero will cause \fBovs\-vsctl\fR to wait forever.  If the timeout expires, 
+\fBovs\-vsctl\fR will exit with a \fBSIGALRM\fR signal.  If this option is
+not used, \fBovs\-vsctl\fR uses a timeout of five seconds.
+(A timeout would normally happen only if the database cannot be contacted.)
+.
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
+.so lib/vlog.man
+.
+.SH COMMANDS
+The commands implemented by \fBovs\-vsctl\fR are described in the
+sections below.
+.SS "Open vSwitch Commands"
+These commands work with an Open vSwitch as a whole.
+.
+.IP "\fBinit\fR"
+Initializes the Open vSwitch database, if it is empty.  If the
+database has already been initialized, this command has no effect.
+.IP
+Any successful \fBovs\-vsctl\fR command automatically initializes the
+Open vSwitch database if it is empty.  This command is provided to
+initialize the database without executing any other command.
+.
+.SS "Bridge Commands"
+These commands examine and manipulate Open vSwitch bridges.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-br \fIbridge\fR"
+Creates a new bridge named \fIbridge\fR.  Initially the bridge will
+have no ports (other than \fIbridge\fR itself).
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a bridge that
+exists is an error.  With \fB\-\-may\-exist\fR, \fIbridge\fR may
+already exist (but it must be a real bridge, not a VLAN bridge).
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-br \fIbridge parent vlan\fR"
+Creates a ``fake bridge'' named \fIbridge\fR within the existing Open
+vSwitch bridge \fIparent\fR, which must already exist and must not
+itself be a fake bridge.  The new fake bridge will be on 802.1Q VLAN
+\fIvlan\fR, which must be an integer between 1 and 4095.  Initially
+\fIbridge\fR will have no ports (other than \fIbridge\fR itself).
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a bridge that
+exists is an error.  With \fB\-\-may\-exist\fR, \fIbridge\fR may
+already exist (but it must have the specified \fIvlan\fR and
+\fIparent\fR).
+.
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-br \fIbridge\fR"
+Deletes \fIbridge\fR and all of its ports.  If \fIbridge\fR is a real
+bridge, this command also deletes any fake bridges that were created
+with \fIbridge\fR as parent, including all of their ports.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a bridge that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a bridge that does not exist has no effect.
+.
+.IP "\fBlist\-br\fR"
+Lists all existing real and fake bridges on standard output, one per
+line.
+.
+.IP "\fBbr\-exists \fIbridge\fR"
+Tests whether \fIbridge\fR exists as a real or fake bridge.  If so,
+\fBovs\-vsctl\fR exits successfully with exit code 0.  If not,
+\fBovs\-vsctl\fR exits unsuccessfully with exit code 2.
+.
+.IP "\fBbr\-to\-vlan \fIbridge\fR"
+If \fIbridge\fR is a fake bridge, prints the bridge's 802.1Q VLAN as a
+decimal integer.  If \fIbridge\fR is a real bridge, prints 0.
+.
+.IP "\fBbr\-to\-parent \fIbridge\fR"
+If \fIbridge\fR is a fake bridge, prints the name of its parent
+bridge.  If \fIbridge\fR is a real bridge, print \fIbridge\fR.
+.
+.IP "\fBbr\-set\-external\-id \fIbridge key\fR [\fIvalue\fR]"
+Sets or clears an ``external ID'' value on \fIbridge\fR.  These values
+are intended to identify entities external to Open vSwitch with which
+\fIbridge\fR is associated, e.g. the bridge's identifier in a
+virtualization management platform.  The Open vSwitch database schema
+specifies well-known \fIkey\fR values, but \fIkey\fR and \fIvalue\fR
+are otherwise arbitrary strings.
+.IP
+If \fIvalue\fR is specified, then \fIkey\fR is set to \fIvalue\fR for
+\fIbridge\fR, overwriting any previous value.  If \fIvalue\fR is
+omitted, then \fIkey\fR is removed from \fIbridge\fR's set of external
+IDs (if it was present).
+.IP
+For real bridges, the effect of this command is similar to that of a
+\fBset\fR or \fBremove\fR command in the \fBexternal\-ids\fR column of
+the \fBBridge\fR table.  For fake bridges, it actually modifies keys
+with names prefixed by \fBfake\-bridge\-\fR in the \fBPort\fR table.
+.
+.IP "\fBbr\-get\-external\-id \fIbridge\fR [\fIkey\fR]"
+Queries the external IDs on \fIbridge\fR.  If \fIkey\fR is specified,
+the output is the value for that \fIkey\fR or the empty string if
+\fIkey\fR is unset.  If \fIkey\fR is omitted, the output is
+\fIkey\fB=\fIvalue\fR, one per line, for each key-value pair.
+.IP
+For real bridges, the effect of this command is similar to that of a
+\fBget\fR command in the \fBexternal\-ids\fR column of the
+\fBBridge\fR table.  For fake bridges, it queries keys with names
+prefixed by \fBfake\-bridge\-\fR in the \fBPort\fR table.
+.
+.SS "Port Commands"
+.
+These commands examine and manipulate Open vSwitch ports.  These
+commands treat a bonded port as a single entity.
+.
+.IP "\fBlist\-ports \fIbridge\fR"
+Lists all of the ports within \fIbridge\fR on standard output, one per
+line.  The local port \fIbridge\fR is not included in the list.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-port \fIbridge port\fR"
+Creates on \fIbridge\fR a new port named \fIport\fR from the network
+device of the same name.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a port that exists
+is an error.  With \fB\-\-may\-exist\fR, \fIport\fR may already exist
+(but it must be on \fIbridge\fR and not be a bonded port).
+.
+.IP "[\fB\-\-fake\-iface\fR] \fBadd\-bond \fIbridge port iface\fR\&..."
+Creates on \fIbridge\fR a new port named \fIport\fR that bonds
+together the network devices given as each \fIiface\fR.  At least two
+interfaces must be named.
+.IP
+With \fB\-\-fake\-iface\fR, a fake interface with the name \fIport\fR is
+created.  This should only be used for compatibility with legacy
+software that requires it.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a port that exists
+is an error.  With \fB\-\-may\-exist\fR, \fIport\fR may already exist
+(but it must be on \fIbridge\fR and bond together exactly the
+specified interface).
+.
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-port \fR[\fIbridge\fR] \fIport\fR"
+Deletes \fIport\fR.  If \fIbridge\fR is omitted, \fIport\fR is removed
+from whatever bridge contains it; if \fIbridge\fR is specified, it
+must be the real or fake bridge that contains \fIport\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a port that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a port that does not exist has no effect.
+.
+.IP "[\fB\-\-if\-exists\fR] \fB\-\-with\-iface del\-port \fR[\fIbridge\fR] \fIiface\fR"
+Deletes the port named \fIiface\fR or that has an interface named
+\fIiface\fR.  If \fIbridge\fR is omitted, the port is removed from
+whatever bridge contains it; if \fIbridge\fR is specified, it must be
+the real or fake bridge that contains the port.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete the port for an
+interface that does not exist is an error.  With \fB\-\-if\-exists\fR,
+attempting to delete the port for an interface that does not exist has
+no effect.
+.
+.IP "\fBport\-to\-br \fIport\fR"
+Prints the name of the bridge that contains \fIport\fR on standard
+output.
+.
+.SS "Interface Commands"
+.
+These commands examine the interfaces attached to an Open vSwitch
+bridge.  These commands treat a bonded port as a collection of two or
+more interfaces, rather than as a single port.
+.
+.IP "\fBlist\-ifaces \fIbridge\fR"
+Lists all of the interfaces within \fIbridge\fR on standard output,
+one per line.  The local port \fIbridge\fR is not included in the
+list.
+.
+.IP "\fBiface\-to\-br \fIiface\fR"
+Prints the name of the bridge that contains \fIiface\fR on standard
+output.
+.
+.SS "OpenFlow Controller Connectivity"
+.
+\fBovs\-vswitchd\fR can perform all configured bridging and switching
+locally, or it can be configured to connect a given bridge to an
+external OpenFlow controller, such as NOX.  
+.
+If a \fIbridge\fR argument is given, the settings apply only to the
+specified bridge.  Otherwise, they apply to the Open vSwitch instance,
+and its configuration applies to any bridge that has not been explicitly
+configured through a \fIbridge\fR argument.
+.
+.IP "\fBget\-controller\fR [\fIbridge\fR]"
+Prints the configured controller target.
+.
+.IP "\fBdel\-controller\fR [\fIbridge\fR]"
+Deletes the configured controller target.
+.
+.IP "\fBset\-controller\fR [\fIbridge\fR] \fItarget\fR"
+Sets the configured controller target.  The \fItarget\fR may use any of
+the following forms:
+.
+.RS
+.so lib/vconn-active.man
+.RE
+.
+.ST "Controller Failure Settings"
+.PP
+When a controller is configured, it is, ordinarily, responsible for
+setting up all flows on the switch.  Thus, if the connection to
+the controller fails, no new network connections can be set up.  If
+the connection to the controller stays down long enough, no packets
+can pass through the switch at all.
+.PP
+If the value is \fBstandalone\fR, or if neither of these settings
+is set, \fBovs\-vswitchd\fR will take over
+responsibility for setting up
+flows when no message has been received from the controller for three
+times the inactivity probe interval (xxx needs to be exposed).  In this mode,
+\fBovs\-vswitchd\fR causes the datapath to act like an ordinary
+MAC-learning switch.  \fBovs\-vswitchd\fR will continue to retry connecting
+to the controller in the background and, when the connection succeeds,
+it discontinues its standalone behavior.
+.PP
+If this option is set to \fBsecure\fR, \fBovs\-vswitchd\fR will not
+set up flows on its own when the controller connection fails.
+.
+.IP "\fBget\-fail\-mode\fR [\fIbridge\fR]"
+Prints the configured failure mode.
+.
+.IP "\fBdel\-fail\-mode\fR [\fIbridge\fR]"
+Deletes the configured failure mode.
+.
+.IP "\fBset\-fail\-mode\fR [\fIbridge\fR] \fBstandalone\fR|\fBsecure\fR"
+Sets the configured failure mode.
+.
+.SS "SSL Configuration"
+When \fBovs\-vswitchd\fR is configured to connect over SSL for management or
+controller connectivity, the following parameters are required:
+.TP
+\fBprivate-key\fR
+Specifies a PEM file containing the private key used as the virtual
+switch's identity for SSL connections to the controller.
+.TP
+\fBcertificate\fR
+Specifies a PEM file containing a certificate, signed by the
+certificate authority (CA) used by the controller and manager, that
+certifies the virtual switch's private key, identifying a trustworthy
+switch.
+.TP
+\fBca-cert\fR
+Specifies a PEM file containing the CA certificate used to verify that
+the virtual switch is connected to a trustworthy controller.
+.PP
+These files are read only once, at \fBovs\-vswitchd\fR startup time.  If
+their contents change, \fBovs\-vswitchd\fR must be killed and restarted.
+.PP
+These SSL settings apply to all SSL connections made by the virtual
+switch.
+.
+.IP "\fBget\-ssl\fR"
+Prints the SSL configuration.
+.
+.IP "\fBdel\-ssl\fR"
+Deletes the current SSL configuration.
+.
+.IP "[\fB\-\-bootstrap\fR] \fBset\-ssl\fR \fIprivate-key\fR \fIcertificate\fR \fIca-cert\fR"
+Sets the SSL configuration.  The \fB\-\-bootstrap\fR option is described 
+below.
+.
+.ST "CA Certificate Bootstrap"
+.PP
+Ordinarily, all of the files named in the SSL configuration must exist
+when \fBovs\-vswitchd\fR starts.  However, if the \fB\-\-bootstrap\fR 
+option is given, then \fBovs\-vswitchd\fR will attempt to obtain the
+CA certificate from the controller on its first SSL connection and
+save it to the named PEM file.  If it is successful, it will
+immediately drop the connection and reconnect, and from then on all
+SSL connections must be authenticated by a certificate signed by the
+CA certificate thus obtained.
+.PP
+\fBThis option exposes the SSL connection to a man-in-the-middle
+attack obtaining the initial CA certificate\fR, but it may be useful
+for bootstrapping.
+.PP
+This option is only useful if the controller sends its CA certificate
+as part of the SSL certificate chain.  The SSL protocol does not
+require the controller to send the CA certificate, but
+\fBcontroller\fR(8) can be configured to do so with the
+\fB--peer-ca-cert\fR option.
+.
+.SS "Database Commands"
+.
+These commands query and modify the contents of \fBovsdb\fR tables.
+They are a slight abstraction of the \fBovsdb\fR interface and as such
+they operate at a lower level than other \fBovs\-vsctl\fR commands.
+.PP
+.ST "Identifying Tables, Records, and Columns"
+.PP
+Each of these commands has a \fItable\fR parameter to identify a table
+within the database.  Many of them also take a \fIrecord\fR parameter
+that identifies a particular record within a table.  The \fIrecord\fR
+parameter may be the UUID for a record, and many tables offer
+additional ways to identify records.  Some commands also take
+\fIcolumn\fR parameters that identify a particular field within the
+records in a table.
+.PP
+The following tables are currently defined:
+.IP "\fBOpen_vSwitch\fR"
+Global configuration for an \fBovs\-vswitchd\fR.  This table contains
+exactly one record, identified by specifying \fB.\fR as the record
+name.
+.IP "\fBBridge\fR"
+Configuration for a bridge within an Open vSwitch.  Records may be
+identified by bridge name.
+.IP "\fBPort\fR"
+A bridge port.  Records may be identified by port name.
+.IP "\fBInterface\fR"
+A network device attached to a port.  Records may be identified by
+name.
+.IP "\fBController\fR"
+Configuration for an OpenFlow controller.  A controller attached to a
+particular bridge may be identified by the bridge's name.  The default
+controller controller for an Open vSwitch may be identified by
+specifying \fB.\fR as the record name.
+.IP "\fBMirror\fR"
+A port mirroring configuration attached to a bridge.  Records may be
+identified by mirror name.
+.IP "\fBNetFlow\fR"
+A NetFlow configuration attached to a bridge.  Records may be
+identified by bridge name.
+.PP
+Names of tables, records, and columns are not case-sensitive, and
+\fB--\fR and \fB_\fR are treated interchangeably.  Unique
+abbreviations are acceptable, e.g. \fBnet\fR or \fRn\fR is sufficient
+to identify the \fBNetFlow\fR table.
+.
+.ST "Database Values"
+.PP
+Each column in the database accepts a fixed type of data.  The
+currently defined basic types, and their representations, are:
+.IP "integer"
+A decimal integer in the range \-2**63 to 2**63\-1, inclusive.
+.IP "real"
+A floating-point number.
+.IP "Boolean"
+True or false, written \fBtrue\fR or \fBfalse\fR, respectively.
+.IP "string"
+An arbitrary Unicode string, except that null bytes are not allowed.
+Quotes are optional for most strings that begin with an English letter
+or underscore and consist only of letters, underscores, hyphens, and
+periods.  However, \fBtrue\fR and \fBfalse\fR and strings that match
+the syntax of UUIDs (see below) must be enclosed in double quotes to
+distinguish them from other basic types.  When double quotes are used,
+the syntax is that of strings in JSON, e.g. backslashes may be used to
+escape special characters.  The empty string must be represented as a
+pair of double quotes (\fB""\fR).
+.IP "UUID"
+A universally unique identifier in the style of RFC 4122,
+e.g. \fBf81d4fae-7dec-11d0-a765-00a0c91e6bf6\fR.
+.PP
+Multiple values in a single column may be separated by spaces or a
+single comma.  When multiple values are present, duplicates are not
+allowed, and order is not important.  Conversely, some database
+columns can have an empty set of values, represented as \fB[]\fR, and
+square brackets may optionally enclose other non-empty sets or single
+values as well.
+.PP
+A few database columns are ``maps'' of key-value pairs, where the key
+and the value are each some fixed database type.  These are specified
+in the form \fIkey\fB=\fIvalue\fR, where \fIkey\fR and \fIvalue\fR
+follow the syntax for the column's key type and value type,
+respectively.  When multiple pairs are present (separated by spaces or
+a comma), duplicate keys are not allowed, and again the order is not
+important.  Duplicate values are allowed.  An empty map is represented
+as \fB{}\fR, and curly braces may be optionally enclose non-empty maps
+as well.
+.
+.ST "Database Command Syntax"
+.IP "\fBlist \fItable \fR[\fIrecord\fR]..."
+List the values of all columns of each specified \fIrecord\fR.  If no
+records are specified, lists all the records in \fItable\fR.
+.IP
+The UUIDs shown for rows created in the same \fBovs\-vsctl\fR
+invocation will be wrong.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBget \fItable record column\fR[\fB:\fIkey\fR]..."
+Prints the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR.  For map columns, a \fIkey\fR may
+optionally be specified, in which case the value associated with
+\fIkey\fR in the column is printed, instead of the entire map.
+.IP
+For a map column, without \fB\-\-if\-exists\fR it is an error if
+\fIkey\fR does not exist; with it, a blank line is printed.  If
+\fIcolumn\fR is not a map column or if \fIkey\fR is not specified,
+\fB\-\-if\-exists\fR has no effect.
+.
+.IP "\fBset \fItable record column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Sets the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR to \fIvalue\fR.  For map columns, a
+\fIkey\fR may optionally be specified, in which case the value
+associated with \fIkey\fR in that column is changed (or added, if none
+exists), instead of the entire map.
+.
+.IP "\fBadd \fItable record column \fR[\fIkey\fB=\fR]\fIvalue\fR..."
+Adds the specified value or key-value pair to \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  If \fIcolumn\fR is a map, then \fIkey\fR
+is required, otherwise it is prohibited.  If \fIkey\fR already exists
+in a map column, then the current \fIvalue\fR is not replaced (use the
+\fBset\fR command to replace an existing value).
+.
+.IP "\fBremove \fItable record column \fR\fIvalue\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fB=\fR\fIvalue\fR..."
+Removes the specified values or key-value pairs from \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  The first form applies to columns that
+are not maps: each specified \fIvalue\fR is removed from the column.
+The second and third forms apply to map columns: if only a \fIkey\fR
+is specified, then any key-value pair with the given \fIkey\fR is
+removed, regardless of its value; if a \fIvalue\fR is given then a
+pair is removed only if both key and value match.
+.IP
+It is not an error if the column does not contain the specified key or
+value or pair.
+.
+.IP "\fBclear\fR \fItable record column\fR..."
+Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
+or empty map, as appropriate.  This command applies only to columns
+that are allowed to be empty.
+.
+.IP "create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Creates a new record in \fItable\fR and sets the initial values of
+each \fIcolumn\fR.  Columns not explicitly set will receive their
+default values.  Outputs the UUID of the new row.
+.
+.IP "\fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
+Deletes each specified \fIrecord\fR from \fItable\fR.  Unless
+\fB\-\-if\-exists\fR is specified, each \fIrecord\fRs must exist.
+.SH "EXAMPLES"
+Create a new bridge named br0 and add port eth0 to it:
+.IP
+.B "ovs-vsctl add\-br br0"
+.br
+.B "ovs-vsctl add\-port br0 eth0"
+.PP
+Alternatively, perform both operations in a single atomic transaction:
+.IP 
+.B "ovs-vsctl add\-br br0 \-\- add\-port br0 eth0"
+.PP
+Delete bridge \fBbr0\fR, reporting an error if it does not exist:
+.IP
+.B "ovs\-vsctl del\-br br0"
+.PP
+Delete bridge \fBbr0\fR if it exists (the \fB\-\-\fR is required to
+separate \fBdel\-br\fR's options from the global options):
+.IP
+.B "ovs\-vsctl \-\- \-\-if\-exists del\-br br0"
+.
+.SH "EXIT STATUS"
+.IP "0"
+Successful program execution.
+.IP "1"
+Usage, syntax, or configuration file error.
+.IP "2"
+The \fIbridge\fR argument to \fBbr\-exists\fR specified the name of a
+bridge that does not exist.
+.SH "SEE ALSO"
+.
+.BR ovsdb\-server (1),
+.BR ovs\-vswitchd (8).
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
new file mode 100644 (file)
index 0000000..4ca737e
--- /dev/null
@@ -0,0 +1,2530 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-idl.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "svec.h"
+#include "vswitchd/vswitch-idl.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_vsctl
+
+/* vsctl_fatal() also logs the error, so it is preferred in this file. */
+#define ovs_fatal please_use_vsctl_fatal_instead_of_ovs_fatal
+
+struct vsctl_context;
+
+typedef void vsctl_handler_func(struct vsctl_context *);
+
+struct vsctl_command_syntax {
+    const char *name;
+    int min_args;
+    int max_args;
+    vsctl_handler_func *run;
+    vsctl_handler_func *postprocess;
+    const char *options;
+};
+
+struct vsctl_command {
+    /* Data that remains constant after initialization. */
+    const struct vsctl_command_syntax *syntax;
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Data modified by commands. */
+    struct ds output;
+};
+
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --no-wait: Wait for ovs-vswitchd to reload its configuration? */
+static bool wait_for_reload = true;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout = 5;
+
+/* All supported commands. */
+static const struct vsctl_command_syntax all_commands[];
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by vsctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+
+static void vsctl_exit(int status) NO_RETURN;
+static void vsctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN;
+static char *default_db(void);
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+static struct vsctl_command *parse_commands(int argc, char *argv[],
+                                            size_t *n_commandsp);
+static void parse_command(int argc, char *argv[], struct vsctl_command *);
+static void do_vsctl(const char *args,
+                     struct vsctl_command *, size_t n_commands,
+                     struct ovsdb_idl *);
+
+int
+main(int argc, char *argv[])
+{
+    struct ovsdb_idl *idl;
+    unsigned int seqno;
+    struct vsctl_command *commands;
+    size_t n_commands;
+    char *args;
+    int trials;
+
+    set_program_name(argv[0]);
+    signal(SIGPIPE, SIG_IGN);
+    time_init();
+    vlog_init();
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+    ovsrec_init();
+
+    /* Log our arguments.  This is often valuable for debugging systems. */
+    args = process_escape_args(argv);
+    VLOG_INFO("Called as %s", args);
+
+    /* Parse command line. */
+    parse_options(argc, argv);
+    commands = parse_commands(argc - optind, argv + optind, &n_commands);
+
+    if (timeout) {
+        time_alarm(timeout);
+    }
+
+    /* Now execute the commands. */
+    idl = the_idl = ovsdb_idl_create(db, &ovsrec_idl_class);
+    seqno = ovsdb_idl_get_seqno(idl);
+    trials = 0;
+    for (;;) {
+        unsigned int new_seqno;
+
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            if (++trials > 5) {
+                vsctl_fatal("too many database inconsistency failures");
+            }
+            do_vsctl(args, commands, n_commands, idl);
+            seqno = new_seqno;
+        }
+
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_DB = UCHAR_MAX + 1,
+        OPT_ONELINE,
+        OPT_NO_SYSLOG,
+        OPT_NO_WAIT,
+        OPT_DRY_RUN,
+        VLOG_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"db", required_argument, 0, OPT_DB},
+        {"no-syslog", no_argument, 0, OPT_NO_SYSLOG},
+        {"no-wait", no_argument, 0, OPT_NO_WAIT},
+        {"dry-run", no_argument, 0, OPT_DRY_RUN},
+        {"oneline", no_argument, 0, OPT_ONELINE},
+        {"timeout", required_argument, 0, 't'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        VLOG_LONG_OPTIONS,
+        {0, 0, 0, 0},
+    };
+
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, "+v::hVt:", long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_DB:
+            db = optarg;
+            break;
+
+        case OPT_ONELINE:
+            oneline = true;
+            break;
+
+        case OPT_NO_SYSLOG:
+            vlog_set_levels(VLM_vsctl, VLF_SYSLOG, VLL_WARN);
+            break;
+
+        case OPT_NO_WAIT:
+            wait_for_reload = false;
+            break;
+
+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout < 0) {
+                vsctl_fatal("value %s on -t or --timeout is invalid",
+                            optarg);
+            }
+            break;
+
+        VLOG_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+
+    if (!db) {
+        db = default_db();
+    }
+}
+
+static struct vsctl_command *
+parse_commands(int argc, char *argv[], size_t *n_commandsp)
+{
+    struct vsctl_command *commands;
+    size_t n_commands, allocated_commands;
+    int i, start;
+
+    commands = NULL;
+    n_commands = allocated_commands = 0;
+
+    for (start = i = 0; i <= argc; i++) {
+        if (i == argc || !strcmp(argv[i], "--")) {
+            if (i > start) {
+                if (n_commands >= allocated_commands) {
+                    struct vsctl_command *c;
+
+                    commands = x2nrealloc(commands, &allocated_commands,
+                                          sizeof *commands);
+                    for (c = commands; c < &commands[n_commands]; c++) {
+                        shash_moved(&c->options);
+                    }
+                }
+                parse_command(i - start, &argv[start],
+                              &commands[n_commands++]);
+            }
+            start = i + 1;
+        }
+    }
+    if (!n_commands) {
+        vsctl_fatal("missing command name (use --help for help)");
+    }
+    *n_commandsp = n_commands;
+    return commands;
+}
+
+static void
+parse_command(int argc, char *argv[], struct vsctl_command *command)
+{
+    const struct vsctl_command_syntax *p;
+    int i;
+
+    shash_init(&command->options);
+    for (i = 0; i < argc; i++) {
+        if (argv[i][0] != '-') {
+            break;
+        }
+        if (!shash_add_once(&command->options, argv[i], NULL)) {
+            vsctl_fatal("'%s' option specified multiple times", argv[i]);
+        }
+    }
+    if (i == argc) {
+        vsctl_fatal("missing command name");
+    }
+
+    for (p = all_commands; p->name; p++) {
+        if (!strcmp(p->name, argv[i])) {
+            struct shash_node *node;
+            int n_arg;
+
+            SHASH_FOR_EACH (node, &command->options) {
+                const char *s = strstr(p->options, node->name);
+                int end = s ? s[strlen(node->name)] : EOF;
+                if (end != ',' && end != ' ' && end != '\0') {
+                    vsctl_fatal("'%s' command has no '%s' option",
+                                argv[i], node->name);
+                }
+            }
+
+            n_arg = argc - i - 1;
+            if (n_arg < p->min_args) {
+                vsctl_fatal("'%s' command requires at least %d arguments",
+                            p->name, p->min_args);
+            } else if (n_arg > p->max_args) {
+                int j;
+
+                for (j = i + 1; j < argc; j++) {
+                    if (argv[j][0] == '-') {
+                        vsctl_fatal("'%s' command takes at most %d arguments "
+                                    "(note that options must precede command "
+                                    "names and follow a \"--\" argument)",
+                                    p->name, p->max_args);
+                    }
+                }
+
+                vsctl_fatal("'%s' command takes at most %d arguments",
+                            p->name, p->max_args);
+            } else {
+                command->syntax = p;
+                command->argc = n_arg + 1;
+                command->argv = &argv[i];
+                return;
+            }
+        }
+    }
+
+    vsctl_fatal("unknown command '%s'; use --help for help", argv[i]);
+}
+
+static void
+vsctl_fatal(const char *format, ...)
+{
+    char *message;
+    va_list args;
+
+    va_start(args, format);
+    message = xvasprintf(format, args);
+    va_end(args);
+
+    vlog_set_levels(VLM_vsctl, VLF_CONSOLE, VLL_EMER);
+    VLOG_ERR("%s", message);
+    ovs_error(0, "%s", message);
+    vsctl_exit(EXIT_FAILURE);
+}
+
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case.  That makes
+ * it easier to notice real memory leaks. */
+static void
+vsctl_exit(int status)
+{
+    if (the_idl_txn) {
+        ovsdb_idl_txn_abort(the_idl_txn);
+        ovsdb_idl_txn_destroy(the_idl_txn);
+    }
+    ovsdb_idl_destroy(the_idl);
+    exit(status);
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: ovs-vswitchd management utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+Bridge commands:\n\
+  add-br BRIDGE               create a new bridge named BRIDGE\n\
+  add-br BRIDGE PARENT VLAN   create new fake BRIDGE in PARENT on VLAN\n\
+  del-br BRIDGE               delete BRIDGE and all of its ports\n\
+  list-br                     print the names of all the bridges\n\
+  br-exists BRIDGE            test whether BRIDGE exists\n\
+  br-to-vlan BRIDGE           print the VLAN which BRIDGE is on\n\
+  br-to-parent BRIDGE         print the parent of BRIDGE\n\
+  br-set-external-id BRIDGE KEY VALUE  set KEY on BRIDGE to VALUE\n\
+  br-set-external-id BRIDGE KEY  unset KEY on BRIDGE\n\
+  br-get-external-id BRIDGE KEY  print value of KEY on BRIDGE\n\
+  br-get-external-id BRIDGE  list key-value pairs on BRIDGE\n\
+\n\
+Port commands:\n\
+  list-ports BRIDGE           print the names of all the ports on BRIDGE\n\
+  add-port BRIDGE PORT        add network device PORT to BRIDGE\n\
+  add-bond BRIDGE PORT IFACE...  add bonded port PORT in BRIDGE from IFACES\n\
+  del-port [BRIDGE] PORT      delete PORT (which may be bonded) from BRIDGE\n\
+  port-to-br PORT             print name of bridge that contains PORT\n\
+A bond is considered to be a single port.\n\
+\n\
+Interface commands (a bond consists of multiple interfaces):\n\
+  list-ifaces BRIDGE          print the names of all interfaces on BRIDGE\n\
+  iface-to-br IFACE           print name of bridge that contains IFACE\n\
+\n\
+Controller commands:\n\
+  get-controller [BRIDGE]     print the controller for BRIDGE\n\
+  del-controller [BRIDGE]     delete the controller for BRIDGE\n\
+  set-controller [BRIDGE] TARGET  set the controller for BRIDGE to TARGET\n\
+  get-fail-mode [BRIDGE]      print the fail-mode for BRIDGE\n\
+  del-fail-mode [BRIDGE]      delete the fail-mode for BRIDGE\n\
+  set-fail-mode [BRIDGE] MODE set the fail-mode for BRIDGE to MODE\n\
+\n\
+SSL commands:\n\
+  get-ssl                     print the SSL configuration\n\
+  del-ssl                     delete the SSL configuration\n\
+  set-ssl PRIV-KEY CERT CA-CERT  set the SSL configuration\n\
+\n\
+Database commands:\n\
+  list TBL [REC]              list RECord (or all records) in TBL\n\
+  get TBL REC COL[:KEY]       print values of COLumns in RECORD in TBL\n\
+  set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
+  add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
+  remove TBL REC COL [KEY=]VALUE  remove (KEY=)VALUE from COLumn\n\
+  clear TBL REC COL           clear values from COLumn in RECord in TBL\n\
+  create TBL COL[:KEY]=VALUE  create and initialize new record\n\
+  destroy TBL REC             delete REC from TBL\n\
+Potentially unsafe database commands require --force option.\n\
+\n\
+Options:\n\
+  --db=DATABASE               connect to DATABASE\n\
+                              (default: %s)\n\
+  --oneline                   print exactly one line of output per command\n",
+           program_name, program_name, default_db());
+    vlog_usage();
+    printf("\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+
+static char *
+default_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = xasprintf("unix:%s/ovsdb-server", ovs_rundir);
+    }
+    return def;
+}
+\f
+struct vsctl_context {
+    /* Read-only. */
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Modifiable state. */
+    struct ds output;
+    struct ovsdb_idl *idl;
+    struct ovsdb_idl_txn *txn;
+    const struct ovsrec_open_vswitch *ovs;
+};
+
+struct vsctl_bridge {
+    struct ovsrec_bridge *br_cfg;
+    char *name;
+    struct ovsrec_controller *ctrl;
+    struct vsctl_bridge *parent;
+    int vlan;
+};
+
+struct vsctl_port {
+    struct ovsrec_port *port_cfg;
+    struct vsctl_bridge *bridge;
+};
+
+struct vsctl_iface {
+    struct ovsrec_interface *iface_cfg;
+    struct vsctl_port *port;
+};
+
+struct vsctl_info {
+    struct shash bridges;
+    struct shash ports;
+    struct shash ifaces;
+    struct ovsrec_controller *ctrl;
+};
+
+static char *
+vsctl_context_to_string(const struct vsctl_context *ctx)
+{
+    const struct shash_node *node;
+    struct svec words;
+    char *s;
+    int i;
+
+    svec_init(&words);
+    SHASH_FOR_EACH (node, &ctx->options) {
+        svec_add(&words, node->name);
+    }
+    for (i = 0; i < ctx->argc; i++) {
+        svec_add(&words, ctx->argv[i]);
+    }
+    svec_terminate(&words);
+
+    s = process_escape_args(words.names);
+
+    svec_destroy(&words);
+
+    return s;
+}
+
+static struct vsctl_bridge *
+add_bridge(struct vsctl_info *b,
+           struct ovsrec_bridge *br_cfg, const char *name,
+           struct vsctl_bridge *parent, int vlan)
+{
+    struct vsctl_bridge *br = xmalloc(sizeof *br);
+    br->br_cfg = br_cfg;
+    br->name = xstrdup(name);
+    br->parent = parent;
+    br->vlan = vlan;
+    br->ctrl = parent ? parent->br_cfg->controller : br_cfg->controller;
+    shash_add(&b->bridges, br->name, br);
+    return br;
+}
+
+static bool
+port_is_fake_bridge(const struct ovsrec_port *port_cfg)
+{
+    return (port_cfg->fake_bridge
+            && port_cfg->tag
+            && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095);
+}
+
+static struct vsctl_bridge *
+find_vlan_bridge(struct vsctl_info *info,
+                 struct vsctl_bridge *parent, int vlan)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->bridges) {
+        struct vsctl_bridge *br = node->data;
+        if (br->parent == parent && br->vlan == vlan) {
+            return br;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+free_info(struct vsctl_info *info)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->bridges) {
+        struct vsctl_bridge *bridge = node->data;
+        free(bridge->name);
+        free(bridge);
+    }
+    shash_destroy(&info->bridges);
+
+    SHASH_FOR_EACH (node, &info->ports) {
+        struct vsctl_port *port = node->data;
+        free(port);
+    }
+    shash_destroy(&info->ports);
+
+    SHASH_FOR_EACH (node, &info->ifaces) {
+        struct vsctl_iface *iface = node->data;
+        free(iface);
+    }
+    shash_destroy(&info->ifaces);
+}
+
+static void
+get_info(const struct ovsrec_open_vswitch *ovs, struct vsctl_info *info)
+{
+    struct shash bridges, ports;
+    size_t i;
+
+    shash_init(&info->bridges);
+    shash_init(&info->ports);
+    shash_init(&info->ifaces);
+
+    info->ctrl = ovs->controller;
+
+    shash_init(&bridges);
+    shash_init(&ports);
+    for (i = 0; i < ovs->n_bridges; i++) {
+        struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+        struct vsctl_bridge *br;
+        size_t j;
+
+        if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+            VLOG_WARN("%s: database contains duplicate bridge name",
+                      br_cfg->name);
+            continue;
+        }
+        br = add_bridge(info, br_cfg, br_cfg->name, NULL, 0);
+        if (!br) {
+            continue;
+        }
+
+        for (j = 0; j < br_cfg->n_ports; j++) {
+            struct ovsrec_port *port_cfg = br_cfg->ports[j];
+
+            if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+                VLOG_WARN("%s: database contains duplicate port name",
+                          port_cfg->name);
+                continue;
+            }
+
+            if (port_is_fake_bridge(port_cfg)
+                && shash_add_once(&bridges, port_cfg->name, NULL)) {
+                add_bridge(info, NULL, port_cfg->name, br, *port_cfg->tag);
+            }
+        }
+    }
+    shash_destroy(&bridges);
+    shash_destroy(&ports);
+
+    shash_init(&bridges);
+    shash_init(&ports);
+    for (i = 0; i < ovs->n_bridges; i++) {
+        struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+        struct vsctl_bridge *br;
+        size_t j;
+
+        if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+            continue;
+        }
+        br = shash_find_data(&info->bridges, br_cfg->name);
+        for (j = 0; j < br_cfg->n_ports; j++) {
+            struct ovsrec_port *port_cfg = br_cfg->ports[j];
+            struct vsctl_port *port;
+            size_t k;
+
+            if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+                continue;
+            }
+
+            if (port_is_fake_bridge(port_cfg)
+                && !shash_add_once(&bridges, port_cfg->name, NULL)) {
+                continue;
+            }
+
+            port = xmalloc(sizeof *port);
+            port->port_cfg = port_cfg;
+            if (port_cfg->tag
+                && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095) {
+                port->bridge = find_vlan_bridge(info, br, *port_cfg->tag);
+                if (!port->bridge) {
+                    port->bridge = br;
+                }
+            } else {
+                port->bridge = br;
+            }
+            shash_add(&info->ports, port_cfg->name, port);
+
+            for (k = 0; k < port_cfg->n_interfaces; k++) {
+                struct ovsrec_interface *iface_cfg = port_cfg->interfaces[k];
+                struct vsctl_iface *iface;
+
+                if (shash_find(&info->ifaces, iface_cfg->name)) {
+                    VLOG_WARN("%s: database contains duplicate interface name",
+                              iface_cfg->name);
+                    continue;
+                }
+
+                iface = xmalloc(sizeof *iface);
+                iface->iface_cfg = iface_cfg;
+                iface->port = port;
+                shash_add(&info->ifaces, iface_cfg->name, iface);
+            }
+        }
+    }
+    shash_destroy(&bridges);
+    shash_destroy(&ports);
+}
+
+static void
+check_conflicts(struct vsctl_info *info, const char *name,
+                char *msg)
+{
+    struct vsctl_iface *iface;
+    struct vsctl_port *port;
+
+    if (shash_find(&info->bridges, name)) {
+        vsctl_fatal("%s because a bridge named %s already exists",
+                    msg, name);
+    }
+
+    port = shash_find_data(&info->ports, name);
+    if (port) {
+        vsctl_fatal("%s because a port named %s already exists on "
+                    "bridge %s", msg, name, port->bridge->name);
+    }
+
+    iface = shash_find_data(&info->ifaces, name);
+    if (iface) {
+        vsctl_fatal("%s because an interface named %s already exists "
+                    "on bridge %s", msg, name, iface->port->bridge->name);
+    }
+
+    free(msg);
+}
+
+static struct vsctl_bridge *
+find_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_bridge *br = shash_find_data(&info->bridges, name);
+    if (must_exist && !br) {
+        vsctl_fatal("no bridge named %s", name);
+    }
+    return br;
+}
+
+static struct vsctl_bridge *
+find_real_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_bridge *br = find_bridge(info, name, must_exist);
+    if (br && br->parent) {
+        vsctl_fatal("%s is a fake bridge", name);
+    }
+    return br;
+}
+
+static struct vsctl_port *
+find_port(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_port *port = shash_find_data(&info->ports, name);
+    if (port && !strcmp(name, port->bridge->name)) {
+        port = NULL;
+    }
+    if (must_exist && !port) {
+        vsctl_fatal("no port named %s", name);
+    }
+    return port;
+}
+
+static struct vsctl_iface *
+find_iface(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_iface *iface = shash_find_data(&info->ifaces, name);
+    if (iface && !strcmp(name, iface->port->bridge->name)) {
+        iface = NULL;
+    }
+    if (must_exist && !iface) {
+        vsctl_fatal("no interface named %s", name);
+    }
+    return iface;
+}
+
+static void
+bridge_insert_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+    struct ovsrec_port **ports;
+    size_t i;
+
+    ports = xmalloc(sizeof *br->ports * (br->n_ports + 1));
+    for (i = 0; i < br->n_ports; i++) {
+        ports[i] = br->ports[i];
+    }
+    ports[br->n_ports] = port;
+    ovsrec_bridge_set_ports(br, ports, br->n_ports + 1);
+    free(ports);
+}
+
+static void
+bridge_delete_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+    struct ovsrec_port **ports;
+    size_t i, n;
+
+    ports = xmalloc(sizeof *br->ports * br->n_ports);
+    for (i = n = 0; i < br->n_ports; i++) {
+        if (br->ports[i] != port) {
+            ports[n++] = br->ports[i];
+        }
+    }
+    ovsrec_bridge_set_ports(br, ports, n);
+    free(ports);
+}
+
+static void
+ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i;
+
+    bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1));
+    for (i = 0; i < ovs->n_bridges; i++) {
+        bridges[i] = ovs->bridges[i];
+    }
+    bridges[ovs->n_bridges] = bridge;
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1);
+    free(bridges);
+}
+
+static void
+ovs_delete_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i, n;
+
+    bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges);
+    for (i = n = 0; i < ovs->n_bridges; i++) {
+        if (ovs->bridges[i] != bridge) {
+            bridges[n++] = ovs->bridges[i];
+        }
+    }
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, n);
+    free(bridges);
+}
+
+static void
+cmd_init(struct vsctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
+cmd_add_br(struct vsctl_context *ctx)
+{
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+    const char *br_name, *parent_name;
+    struct vsctl_info info;
+    int vlan;
+
+    br_name = ctx->argv[1];
+    if (ctx->argc == 2) {
+        parent_name = NULL;
+        vlan = 0;
+    } else if (ctx->argc == 4) {
+        parent_name = ctx->argv[2];
+        vlan = atoi(ctx->argv[3]);
+        if (vlan < 1 || vlan > 4095) {
+            vsctl_fatal("%s: vlan must be between 1 and 4095", ctx->argv[0]);
+        }
+    } else {
+        vsctl_fatal("'%s' command takes exactly 1 or 3 arguments",
+                    ctx->argv[0]);
+    }
+
+    get_info(ctx->ovs, &info);
+    if (may_exist) {
+        struct vsctl_bridge *br;
+
+        br = find_bridge(&info, br_name, false);
+        if (br) {
+            if (!parent_name) {
+                if (br->parent) {
+                    vsctl_fatal("\"--may-exist add-br %s\" but %s is "
+                                "a VLAN bridge for VLAN %d",
+                                br_name, br_name, br->vlan);
+                }
+            } else {
+                if (!br->parent) {
+                    vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+                                "is not a VLAN bridge",
+                                br_name, parent_name, vlan, br_name);
+                } else if (strcmp(br->parent->name, parent_name)) {
+                    vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+                                "has the wrong parent %s",
+                                br_name, parent_name, vlan,
+                                br_name, br->parent->name);
+                } else if (br->vlan != vlan) {
+                    vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+                                "is a VLAN bridge for the wrong VLAN %d",
+                                br_name, parent_name, vlan, br_name, br->vlan);
+                }
+            }
+            return;
+        }
+    }
+    check_conflicts(&info, br_name,
+                    xasprintf("cannot create a bridge named %s", br_name));
+
+    if (!parent_name) {
+        struct ovsrec_port *port;
+        struct ovsrec_interface *iface;
+        struct ovsrec_bridge *br;
+
+        iface = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(iface, br_name);
+
+        port = ovsrec_port_insert(ctx->txn);
+        ovsrec_port_set_name(port, br_name);
+        ovsrec_port_set_interfaces(port, &iface, 1);
+
+        br = ovsrec_bridge_insert(ctx->txn);
+        ovsrec_bridge_set_name(br, br_name);
+        ovsrec_bridge_set_ports(br, &port, 1);
+
+        ovs_insert_bridge(ctx->ovs, br);
+    } else {
+        struct vsctl_bridge *parent;
+        struct ovsrec_port *port;
+        struct ovsrec_interface *iface;
+        struct ovsrec_bridge *br;
+        int64_t tag = vlan;
+
+        parent = find_bridge(&info, parent_name, false);
+        if (parent && parent->vlan) {
+            vsctl_fatal("cannot create bridge with fake bridge as parent");
+        }
+        if (!parent) {
+            vsctl_fatal("parent bridge %s does not exist", parent_name);
+        }
+        br = parent->br_cfg;
+
+        iface = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(iface, br_name);
+        ovsrec_interface_set_type(iface, "internal");
+
+        port = ovsrec_port_insert(ctx->txn);
+        ovsrec_port_set_name(port, br_name);
+        ovsrec_port_set_interfaces(port, &iface, 1);
+        ovsrec_port_set_fake_bridge(port, true);
+        ovsrec_port_set_tag(port, &tag, 1);
+
+        bridge_insert_port(br, port);
+    }
+
+    free_info(&info);
+}
+
+static void
+del_port(struct vsctl_info *info, struct vsctl_port *port)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->ifaces) {
+        struct vsctl_iface *iface = node->data;
+        if (iface->port == port) {
+            ovsrec_interface_delete(iface->iface_cfg);
+        }
+    }
+    ovsrec_port_delete(port->port_cfg);
+
+    bridge_delete_port((port->bridge->parent
+                        ? port->bridge->parent->br_cfg
+                        : port->bridge->br_cfg), port->port_cfg);
+}
+
+static void
+cmd_del_br(struct vsctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], must_exist);
+    if (bridge) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &info.ports) {
+            struct vsctl_port *port = node->data;
+            if (port->bridge == bridge || port->bridge->parent == bridge
+                || !strcmp(port->port_cfg->name, bridge->name)) {
+                del_port(&info, port);
+            }
+        }
+        if (bridge->br_cfg) {
+            ovsrec_bridge_delete(bridge->br_cfg);
+            ovs_delete_bridge(ctx->ovs, bridge->br_cfg);
+        }
+    }
+    free_info(&info);
+}
+
+static void
+output_sorted(struct svec *svec, struct ds *output)
+{
+    const char *name;
+    size_t i;
+
+    svec_sort(svec);
+    SVEC_FOR_EACH (i, name, svec) {
+        ds_put_format(output, "%s\n", name);
+    }
+}
+
+static void
+cmd_list_br(struct vsctl_context *ctx)
+{
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec bridges;
+
+    get_info(ctx->ovs, &info);
+
+    svec_init(&bridges);
+    SHASH_FOR_EACH (node, &info.bridges) {
+        struct vsctl_bridge *br = node->data;
+        svec_add(&bridges, br->name);
+    }
+    output_sorted(&bridges, &ctx->output);
+    svec_destroy(&bridges);
+
+    free_info(&info);
+}
+
+static void
+cmd_br_exists(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    if (!find_bridge(&info, ctx->argv[1], false)) {
+        vsctl_exit(2);
+    }
+    free_info(&info);
+}
+
+/* Returns true if 'b_prefix' (of length 'b_prefix_len') concatenated with 'b'
+ * equals 'a', false otherwise. */
+static bool
+key_matches(const char *a,
+            const char *b_prefix, size_t b_prefix_len, const char *b)
+{
+    return !strncmp(a, b_prefix, b_prefix_len) && !strcmp(a + b_prefix_len, b);
+}
+
+static void
+set_external_id(char **old_keys, char **old_values, size_t old_n,
+                char *key, char *value,
+                char ***new_keysp, char ***new_valuesp, size_t *new_np)
+{
+    char **new_keys;
+    char **new_values;
+    size_t new_n;
+    size_t i;
+
+    new_keys = xmalloc(sizeof *new_keys * (old_n + 1));
+    new_values = xmalloc(sizeof *new_values * (old_n + 1));
+    new_n = 0;
+    for (i = 0; i < old_n; i++) {
+        if (strcmp(key, old_keys[i])) {
+            new_keys[new_n] = old_keys[i];
+            new_values[new_n] = old_values[i];
+            new_n++;
+        }
+    }
+    if (value) {
+        new_keys[new_n] = key;
+        new_values[new_n] = value;
+        new_n++;
+    }
+    *new_keysp = new_keys;
+    *new_valuesp = new_values;
+    *new_np = new_n;
+}
+
+static void
+cmd_br_set_external_id(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+    char **keys, **values;
+    size_t n;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->br_cfg) {
+        set_external_id(bridge->br_cfg->key_external_ids,
+                        bridge->br_cfg->value_external_ids,
+                        bridge->br_cfg->n_external_ids,
+                        ctx->argv[2], ctx->argc >= 4 ? ctx->argv[3] : NULL,
+                        &keys, &values, &n);
+        ovsrec_bridge_set_external_ids(bridge->br_cfg, keys, values, n);
+    } else {
+        char *key = xasprintf("fake-bridge-%s", ctx->argv[2]);
+        struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+        set_external_id(port->port_cfg->key_external_ids,
+                        port->port_cfg->value_external_ids,
+                        port->port_cfg->n_external_ids,
+                        key, ctx->argc >= 4 ? ctx->argv[3] : NULL,
+                        &keys, &values, &n);
+        ovsrec_port_set_external_ids(port->port_cfg, keys, values, n);
+        free(key);
+    }
+    free(keys);
+    free(values);
+
+    free_info(&info);
+}
+
+static void
+get_external_id(char **keys, char **values, size_t n,
+                const char *prefix, const char *key,
+                struct ds *output)
+{
+    size_t prefix_len = strlen(prefix);
+    struct svec svec;
+    size_t i;
+
+    svec_init(&svec);
+    for (i = 0; i < n; i++) {
+        if (!key && !strncmp(keys[i], prefix, prefix_len)) {
+            svec_add_nocopy(&svec, xasprintf("%s=%s",
+                                             keys[i] + prefix_len, values[i]));
+        } else if (key_matches(keys[i], prefix, prefix_len, key)) {
+            svec_add(&svec, values[i]);
+            break;
+        }
+    }
+    output_sorted(&svec, output);
+    svec_destroy(&svec);
+}
+
+static void
+cmd_br_get_external_id(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->br_cfg) {
+        get_external_id(bridge->br_cfg->key_external_ids,
+                        bridge->br_cfg->value_external_ids,
+                        bridge->br_cfg->n_external_ids,
+                        "", ctx->argc >= 3 ? ctx->argv[2] : NULL,
+                        &ctx->output);
+    } else {
+        struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+        get_external_id(port->port_cfg->key_external_ids,
+                        port->port_cfg->value_external_ids,
+                        port->port_cfg->n_external_ids,
+                        "fake-bridge-", ctx->argc >= 3 ? ctx->argv[2] : NULL, &ctx->output);
+    }
+    free_info(&info);
+}
+
+
+static void
+cmd_list_ports(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *br;
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec ports;
+
+    get_info(ctx->ovs, &info);
+    br = find_bridge(&info, ctx->argv[1], true);
+
+    svec_init(&ports);
+    SHASH_FOR_EACH (node, &info.ports) {
+        struct vsctl_port *port = node->data;
+
+        if (strcmp(port->port_cfg->name, br->name) && br == port->bridge) {
+            svec_add(&ports, port->port_cfg->name);
+        }
+    }
+    output_sorted(&ports, &ctx->output);
+    svec_destroy(&ports);
+
+    free_info(&info);
+}
+
+static void
+add_port(struct vsctl_context *ctx,
+         const char *br_name, const char *port_name,
+         bool may_exist, bool fake_iface,
+         char *iface_names[], int n_ifaces)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+    struct ovsrec_interface **ifaces;
+    struct ovsrec_port *port;
+    size_t i;
+
+    get_info(ctx->ovs, &info);
+    if (may_exist) {
+        struct vsctl_port *port;
+
+        port = find_port(&info, port_name, false);
+        if (port) {
+            struct svec want_names, have_names;
+            size_t i;
+
+            svec_init(&want_names);
+            for (i = 0; i < n_ifaces; i++) {
+                svec_add(&want_names, iface_names[i]);
+            }
+            svec_sort(&want_names);
+
+            svec_init(&have_names);
+            for (i = 0; i < port->port_cfg->n_interfaces; i++) {
+                svec_add(&have_names, port->port_cfg->interfaces[i]->name);
+            }
+            svec_sort(&have_names);
+
+            if (strcmp(port->bridge->name, br_name)) {
+                char *command = vsctl_context_to_string(ctx);
+                vsctl_fatal("\"%s\" but %s is actually attached to bridge %s",
+                            command, port_name, port->bridge->name);
+            }
+
+            if (!svec_equal(&want_names, &have_names)) {
+                char *have_names_string = svec_join(&have_names, ", ", "");
+                char *command = vsctl_context_to_string(ctx);
+
+                vsctl_fatal("\"%s\" but %s actually has interface(s) %s",
+                            command, port_name, have_names_string);
+            }
+
+            svec_destroy(&want_names);
+            svec_destroy(&have_names);
+
+            return;
+        }
+    }
+    check_conflicts(&info, port_name,
+                    xasprintf("cannot create a port named %s", port_name));
+    for (i = 0; i < n_ifaces; i++) {
+        check_conflicts(&info, iface_names[i],
+                        xasprintf("cannot create an interface named %s",
+                                  iface_names[i]));
+    }
+    bridge = find_bridge(&info, br_name, true);
+
+    ifaces = xmalloc(n_ifaces * sizeof *ifaces);
+    for (i = 0; i < n_ifaces; i++) {
+        ifaces[i] = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(ifaces[i], iface_names[i]);
+    }
+
+    port = ovsrec_port_insert(ctx->txn);
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, ifaces, n_ifaces);
+    ovsrec_port_set_bond_fake_iface(port, fake_iface);
+    free(ifaces);
+
+    if (bridge->vlan) {
+        int64_t tag = bridge->vlan;
+        ovsrec_port_set_tag(port, &tag, 1);
+    }
+
+    bridge_insert_port((bridge->parent ? bridge->parent->br_cfg
+                        : bridge->br_cfg), port);
+
+    free_info(&info);
+}
+
+static void
+cmd_add_port(struct vsctl_context *ctx)
+{
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+
+    add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, false,
+             &ctx->argv[2], 1);
+}
+
+static void
+cmd_add_bond(struct vsctl_context *ctx)
+{
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+    bool fake_iface = shash_find(&ctx->options, "--fake-iface");
+
+    add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, fake_iface,
+             &ctx->argv[3], ctx->argc - 3);
+}
+
+static void
+cmd_del_port(struct vsctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    bool with_iface = shash_find(&ctx->options, "--with-iface") != NULL;
+    struct vsctl_port *port;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    if (!with_iface) {
+        port = find_port(&info, ctx->argv[ctx->argc - 1], must_exist);
+    } else {
+        const char *target = ctx->argv[ctx->argc - 1];
+        struct vsctl_iface *iface;
+
+        port = find_port(&info, target, false);
+        if (!port) {
+            iface = find_iface(&info, target, false);
+            if (iface) {
+                port = iface->port;
+            }
+        }
+        if (must_exist && !port) {
+            vsctl_fatal("no port or interface named %s", target);
+        }
+    }
+
+    if (port) {
+        if (ctx->argc == 3) {
+            struct vsctl_bridge *bridge;
+
+            bridge = find_bridge(&info, ctx->argv[1], true);
+            if (port->bridge != bridge) {
+                if (port->bridge->parent == bridge) {
+                    vsctl_fatal("bridge %s does not have a port %s (although "
+                                "its parent bridge %s does)",
+                                ctx->argv[1], ctx->argv[2],
+                                bridge->parent->name);
+                } else {
+                    vsctl_fatal("bridge %s does not have a port %s",
+                                ctx->argv[1], ctx->argv[2]);
+                }
+            }
+        }
+
+        del_port(&info, port);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_port_to_br(struct vsctl_context *ctx)
+{
+    struct vsctl_port *port;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    port = find_port(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%s\n", port->bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_br_to_vlan(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%d\n", bridge->vlan);
+    free_info(&info);
+}
+
+static void
+cmd_br_to_parent(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->parent) {
+        bridge = bridge->parent;
+    }
+    ds_put_format(&ctx->output, "%s\n", bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_list_ifaces(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *br;
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec ifaces;
+
+    get_info(ctx->ovs, &info);
+    br = find_bridge(&info, ctx->argv[1], true);
+
+    svec_init(&ifaces);
+    SHASH_FOR_EACH (node, &info.ifaces) {
+        struct vsctl_iface *iface = node->data;
+
+        if (strcmp(iface->iface_cfg->name, br->name)
+            && br == iface->port->bridge) {
+            svec_add(&ifaces, iface->iface_cfg->name);
+        }
+    }
+    output_sorted(&ifaces, &ctx->output);
+    svec_destroy(&ifaces);
+
+    free_info(&info);
+}
+
+static void
+cmd_iface_to_br(struct vsctl_context *ctx)
+{
+    struct vsctl_iface *iface;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    iface = find_iface(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%s\n", iface->port->bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_get_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        /* Return the controller from the "Open_vSwitch" table */
+        if (info.ctrl) {
+            ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+        }
+    } else {
+        /* Return the controller for a particular bridge. */
+        struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+        /* If no controller is explicitly defined for the requested
+         * bridge, fallback to the "Open_vSwitch" table's controller. */
+        if (br->ctrl) {
+            ds_put_format(&ctx->output, "%s\n", br->ctrl->target);
+        } else if (info.ctrl) {
+            ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_del_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        if (info.ctrl) {
+            ovsrec_controller_delete(info.ctrl);
+            ovsrec_open_vswitch_set_controller(ctx->ovs, NULL);
+        }
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl) {
+            ovsrec_controller_delete(br->ctrl);
+            ovsrec_bridge_set_controller(br->br_cfg, NULL);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_set_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct ovsrec_controller *ctrl;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 2) {
+        /* Set the controller in the "Open_vSwitch" table. */
+        if (info.ctrl) {
+            ovsrec_controller_delete(info.ctrl);
+        }
+        ctrl = ovsrec_controller_insert(ctx->txn);
+        ovsrec_controller_set_target(ctrl, ctx->argv[1]);
+        ovsrec_open_vswitch_set_controller(ctx->ovs, ctrl);
+    } else {
+        /* Set the controller for a particular bridge. */
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl) {
+            ovsrec_controller_delete(br->ctrl);
+        }
+        ctrl = ovsrec_controller_insert(ctx->txn);
+        ovsrec_controller_set_target(ctrl, ctx->argv[2]);
+        ovsrec_bridge_set_controller(br->br_cfg, ctrl);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_get_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    const char *fail_mode = NULL;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        /* Return the fail-mode from the "Open_vSwitch" table */
+        if (info.ctrl && info.ctrl->fail_mode) {
+            fail_mode = info.ctrl->fail_mode;
+        }
+    } else {
+        /* Return the fail-mode for a particular bridge. */
+        struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+        /* If no controller or fail-mode is explicitly defined for the 
+         * requested bridge, fallback to the "Open_vSwitch" table's 
+         * setting. */
+        if (br->ctrl && br->ctrl->fail_mode) {
+            fail_mode = br->ctrl->fail_mode;
+        } else if (info.ctrl && info.ctrl->fail_mode) {
+            fail_mode = info.ctrl->fail_mode;
+        }
+    }
+
+    if (fail_mode && strlen(fail_mode)) {
+        ds_put_format(&ctx->output, "%s\n", fail_mode);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_del_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        if (info.ctrl && info.ctrl->fail_mode) {
+            ovsrec_controller_set_fail_mode(info.ctrl, NULL);
+        }
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl && br->ctrl->fail_mode) {
+            ovsrec_controller_set_fail_mode(br->ctrl, NULL);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_set_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    const char *fail_mode;
+
+    get_info(ctx->ovs, &info);
+
+    fail_mode = (ctx->argc == 2) ? ctx->argv[1] : ctx->argv[2];
+
+    if (strcmp(fail_mode, "standalone") && strcmp(fail_mode, "secure")) {
+        vsctl_fatal("fail-mode must be \"standalone\" or \"secure\"");
+    }
+
+    if (ctx->argc == 2) {
+        /* Set the fail-mode in the "Open_vSwitch" table. */
+        if (!info.ctrl) {
+            vsctl_fatal("no controller declared");
+        }
+        ovsrec_controller_set_fail_mode(info.ctrl, fail_mode);
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (!br->ctrl) {
+            vsctl_fatal("no controller declared for %s", br->name);
+        }
+        ovsrec_controller_set_fail_mode(br->ctrl, fail_mode);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_get_ssl(struct vsctl_context *ctx)
+{
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
+        ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
+        ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
+        ds_put_format(&ctx->output, "Bootstrap: %s\n",
+                ssl->bootstrap_ca_cert ? "true" : "false");
+    }
+}
+
+static void
+cmd_del_ssl(struct vsctl_context *ctx)
+{
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ovsrec_ssl_delete(ssl);
+        ovsrec_open_vswitch_set_ssl(ctx->ovs, NULL);
+    }
+}
+
+static void
+cmd_set_ssl(struct vsctl_context *ctx)
+{
+    bool bootstrap = shash_find(&ctx->options, "--bootstrap");
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ovsrec_ssl_delete(ssl);
+    }
+    ssl = ovsrec_ssl_insert(ctx->txn);
+
+    ovsrec_ssl_set_private_key(ssl, ctx->argv[1]);
+    ovsrec_ssl_set_certificate(ssl, ctx->argv[2]);
+    ovsrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
+
+    ovsrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
+
+    ovsrec_open_vswitch_set_ssl(ctx->ovs, ssl);
+}
+\f
+/* Parameter commands. */
+
+struct vsctl_row_id {
+    const struct ovsdb_idl_table_class *table;
+    const struct ovsdb_idl_column *name_column;
+    const struct ovsdb_idl_column *uuid_column;
+};
+
+struct vsctl_table_class {
+    struct ovsdb_idl_table_class *class;
+    struct vsctl_row_id row_ids[2];
+};
+
+static const struct vsctl_table_class tables[] = {
+    {&ovsrec_table_bridge,
+     {{&ovsrec_table_bridge, &ovsrec_bridge_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_controller,
+     {{&ovsrec_table_bridge,
+       &ovsrec_bridge_col_name,
+       &ovsrec_bridge_col_controller},
+      {&ovsrec_table_open_vswitch,
+       NULL,
+       &ovsrec_open_vswitch_col_controller}}},
+
+    {&ovsrec_table_interface,
+     {{&ovsrec_table_interface, &ovsrec_interface_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_mirror,
+     {{&ovsrec_table_mirror, &ovsrec_mirror_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_netflow,
+     {{&ovsrec_table_bridge,
+       &ovsrec_bridge_col_name,
+       &ovsrec_bridge_col_netflow},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_open_vswitch,
+     {{&ovsrec_table_open_vswitch, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_port,
+     {{&ovsrec_table_port, &ovsrec_port_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_ssl,
+     {{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}},
+
+    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+static void
+die_if_error(char *error)
+{
+    if (error) {
+        vsctl_fatal("%s", error);
+    }
+}
+
+static int
+to_lower_and_underscores(unsigned c)
+{
+    return c == '-' ? '_' : tolower(c);
+}
+
+static unsigned int
+score_partial_match(const char *name, const char *s)
+{
+    int score;
+
+    if (!strcmp(name, s)) {
+        return UINT_MAX;
+    }
+    for (score = 0; ; score++, name++, s++) {
+        if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) {
+            break;
+        } else if (*name == '\0') {
+            return UINT_MAX - 1;
+        }
+    }
+    return *s == '\0' ? score : 0;
+}
+
+static const struct vsctl_table_class *
+get_table(const char *table_name)
+{
+    const struct vsctl_table_class *table;
+    const struct vsctl_table_class *best_match = NULL;
+    unsigned int best_score = 0;
+
+    for (table = tables; table->class; table++) {
+        unsigned int score = score_partial_match(table->class->name,
+                                                 table_name);
+        if (score > best_score) {
+            best_match = table;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
+        }
+    }
+    if (best_match) {
+        return best_match;
+    } else if (best_score) {
+        vsctl_fatal("multiple table names match \"%s\"", table_name);
+    } else {
+        vsctl_fatal("unknown table \"%s\"", table_name);
+    }
+}
+
+static const struct ovsdb_idl_row *
+get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table,
+              const struct vsctl_row_id *id, const char *record_id)
+{
+    const struct ovsdb_idl_row *referrer, *final;
+
+    if (!id->table) {
+        return NULL;
+    }
+
+    if (!id->name_column) {
+        if (strcmp(record_id, ".")) {
+            return NULL;
+        }
+        referrer = ovsdb_idl_first_row(ctx->idl, id->table);
+        if (!referrer || ovsdb_idl_next_row(referrer)) {
+            return NULL;
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+        unsigned int best_score = 0;
+
+        /* It might make sense to relax this assertion. */
+        assert(id->name_column->type.key.type == OVSDB_TYPE_STRING);
+
+        referrer = NULL;
+        for (row = ovsdb_idl_first_row(ctx->idl, id->table);
+             row != NULL && best_score != UINT_MAX;
+             row = ovsdb_idl_next_row(row))
+        {
+            struct ovsdb_datum name;
+
+            ovsdb_idl_txn_read(row, id->name_column, &name);
+            if (name.n == 1) {
+                unsigned int score = score_partial_match(name.keys[0].string,
+                                                         record_id);
+                if (score > best_score) {
+                    referrer = row;
+                    best_score = score;
+                } else if (score == best_score) {
+                    referrer = NULL;
+                }
+            }
+            ovsdb_datum_destroy(&name, &id->name_column->type);
+        }
+        if (best_score && !referrer) {
+            vsctl_fatal("multiple rows in %s match \"%s\"",
+                        table->class->name, record_id);
+        }
+    }
+    if (!referrer) {
+        return NULL;
+    }
+
+    final = NULL;
+    if (id->uuid_column) {
+        struct ovsdb_datum uuid;
+
+        assert(id->uuid_column->type.key.type == OVSDB_TYPE_UUID);
+        assert(id->uuid_column->type.value.type == OVSDB_TYPE_VOID);
+
+        ovsdb_idl_txn_read(referrer, id->uuid_column, &uuid);
+        if (uuid.n == 1) {
+            final = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class,
+                                               &uuid.keys[0].uuid);
+        }
+        ovsdb_datum_destroy(&uuid, &id->uuid_column->type);
+    } else {
+        final = referrer;
+    }
+
+    return final;
+}
+
+static const struct ovsdb_idl_row *
+get_row(struct vsctl_context *ctx,
+        const struct vsctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row;
+    struct uuid uuid;
+
+    if (uuid_from_string(&uuid, record_id)) {
+        row = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, &uuid);
+    } else {
+        int i;
+
+        for (i = 0; i < ARRAY_SIZE(table->row_ids); i++) {
+            row = get_row_by_id(ctx, table, &table->row_ids[i], record_id);
+            if (row) {
+                break;
+            }
+        }
+    }
+    return row;
+}
+
+static const struct ovsdb_idl_row *
+must_get_row(struct vsctl_context *ctx,
+             const struct vsctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row = get_row(ctx, table, record_id);
+    if (!row) {
+        vsctl_fatal("no row \"%s\" in table %s",
+                    record_id, table->class->name);
+    }
+    return row;
+}
+
+static char *
+get_column(const struct vsctl_table_class *table, const char *column_name,
+           const struct ovsdb_idl_column **columnp)
+{
+    const struct ovsdb_idl_column *best_match = NULL;
+    unsigned int best_score = 0;
+    size_t i;
+
+    for (i = 0; i < table->class->n_columns; i++) {
+        const struct ovsdb_idl_column *column = &table->class->columns[i];
+        unsigned int score = score_partial_match(column->name, column_name);
+        if (score > best_score) {
+            best_match = column;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
+        }
+    }
+
+    *columnp = best_match;
+    if (best_match) {
+        return NULL;
+    } else if (best_score) {
+        return xasprintf("%s contains more than one column whose name "
+                         "matches \"%s\"", table->class->name, column_name);
+    } else {
+        return xasprintf("%s does not contain a column whose name matches "
+                         "\"%s\"", table->class->name, column_name);
+    }
+}
+
+static char * WARN_UNUSED_RESULT
+parse_column_key_value(const char *arg, const struct vsctl_table_class *table,
+                       const struct ovsdb_idl_column **columnp,
+                       char **keyp, char **valuep)
+{
+    const char *p = arg;
+    char *error;
+
+    assert(columnp || keyp);
+    if (keyp) {
+        *keyp = NULL;
+    }
+    if (valuep) {
+        *valuep = NULL;
+    }
+
+    /* Parse column name. */
+    if (columnp) {
+        char *column_name;
+
+        error = ovsdb_token_parse(&p, &column_name);
+        if (error) {
+            goto error;
+        }
+        if (column_name[0] == '\0') {
+            free(column_name);
+            error = xasprintf("%s: missing column name", arg);
+            goto error;
+        }
+        error = get_column(table, column_name, columnp);
+        free(column_name);
+        if (error) {
+            goto error;
+        }
+    }
+
+    /* Parse key string. */
+    if (*p == ':' || !columnp) {
+        if (columnp) {
+            p++;
+        } else if (!keyp) {
+            error = xasprintf("%s: key not accepted here", arg);
+            goto error;
+        }
+        error = ovsdb_token_parse(&p, keyp);
+        if (error) {
+            goto error;
+        }
+    } else if (keyp) {
+        *keyp = NULL;
+    }
+
+    /* Parse value string. */
+    if (*p == '=') {
+        if (!valuep) {
+            error = xasprintf("%s: value not accepted here", arg);
+            goto error;
+        }
+        *valuep = xstrdup(p + 1);
+    } else {
+        if (valuep) {
+            *valuep = NULL;
+        }
+        if (*p != '\0') {
+            error = xasprintf("%s: trailing garbage \"%s\" in argument",
+                              arg, p);
+            goto error;
+        }
+    }
+    return NULL;
+
+error:
+    if (columnp) {
+        *columnp = NULL;
+    }
+    if (keyp) {
+        free(*keyp);
+        *keyp = NULL;
+    }
+    if (valuep) {
+        free(*valuep);
+        *valuep = NULL;
+    }
+    return error;
+}
+
+static void
+cmd_get(struct vsctl_context *ctx)
+{
+    bool if_exists = shash_find(&ctx->options, "--if-exists");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    struct ds *out = &ctx->output;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+        struct ovsdb_datum datum;
+        char *key_string;
+
+        die_if_error(parse_column_key_value(ctx->argv[i], table,
+                                            &column, &key_string, NULL));
+
+        ovsdb_idl_txn_read(row, column, &datum);
+        if (key_string) {
+            union ovsdb_atom key;
+            unsigned int idx;
+
+            if (column->type.value.type == OVSDB_TYPE_VOID) {
+                vsctl_fatal("cannot specify key to get for non-map column %s",
+                            column->name);
+            }
+
+            die_if_error(ovsdb_atom_from_string(&key,
+                                                &column->type.key,
+                                                key_string));
+
+            idx = ovsdb_datum_find_key(&datum, &key,
+                                       column->type.key.type);
+            if (idx == UINT_MAX) {
+                if (!if_exists) {
+                    vsctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
+                                key_string, table->class->name, record_id,
+                                column->name);
+                }
+            } else {
+                ovsdb_atom_to_string(&datum.values[idx],
+                                     column->type.value.type, out);
+            }
+            ovsdb_atom_destroy(&key, column->type.key.type);
+        } else {
+            ovsdb_datum_to_string(&datum, &column->type, out);
+        }
+        ds_put_char(out, '\n');
+        ovsdb_datum_destroy(&datum, &column->type);
+
+        free(key_string);
+    }
+}
+
+static void
+list_record(const struct vsctl_table_class *table,
+            const struct ovsdb_idl_row *row, struct ds *out)
+{
+    size_t i;
+
+    ds_put_format(out, "%-20s: "UUID_FMT"\n", "_uuid",
+                  UUID_ARGS(&row->uuid));
+    for (i = 0; i < table->class->n_columns; i++) {
+        const struct ovsdb_idl_column *column = &table->class->columns[i];
+        struct ovsdb_datum datum;
+
+        ovsdb_idl_txn_read(row, column, &datum);
+
+        ds_put_format(out, "%-20s: ", column->name);
+        ovsdb_datum_to_string(&datum, &column->type, out);
+        ds_put_char(out, '\n');
+
+        ovsdb_datum_destroy(&datum, &column->type);
+    }
+}
+
+static void
+cmd_list(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    struct ds *out = &ctx->output;
+    int i;
+
+    table = get_table(table_name);
+    if (ctx->argc > 2) {
+        for (i = 2; i < ctx->argc; i++) {
+            if (i > 2) {
+                ds_put_char(out, '\n');
+            }
+            list_record(table, must_get_row(ctx, table, ctx->argv[i]), out);
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+        bool first;
+
+        for (row = ovsdb_idl_first_row(ctx->idl, table->class), first = true;
+             row != NULL;
+             row = ovsdb_idl_next_row(row), first = false) {
+            if (!first) {
+                ds_put_char(out, '\n');
+            }
+            list_record(table, row, out);
+        }
+    }
+}
+
+static void
+set_column(const struct vsctl_table_class *table,
+           const struct ovsdb_idl_row *row, const char *arg)
+{
+    const struct ovsdb_idl_column *column;
+    char *key_string, *value_string;
+    char *error;
+
+    error = parse_column_key_value(arg, table, &column, &key_string,
+                                   &value_string);
+    die_if_error(error);
+    if (!value_string) {
+        vsctl_fatal("%s: missing value", arg);
+    }
+
+    if (key_string) {
+        union ovsdb_atom key, value;
+        struct ovsdb_datum old, new;
+
+        if (column->type.value.type == OVSDB_TYPE_VOID) {
+            vsctl_fatal("cannot specify key to set for non-map column %s",
+                        column->name);
+        }
+
+        die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
+                                            key_string));
+        die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
+                                            value_string));
+
+        ovsdb_datum_init_empty(&new);
+        ovsdb_datum_add_unsafe(&new, &key, &value, &column->type);
+
+        ovsdb_atom_destroy(&key, column->type.key.type);
+        ovsdb_atom_destroy(&value, column->type.value.type);
+
+        ovsdb_idl_txn_read(row, column, &old);
+        ovsdb_datum_union(&old, &new, &column->type, true);
+        ovsdb_idl_txn_write(row, column, &old);
+
+        ovsdb_datum_destroy(&new, &column->type);
+    } else {
+        struct ovsdb_datum datum;
+
+        die_if_error(ovsdb_datum_from_string(&datum, &column->type,
+                                             value_string));
+        ovsdb_idl_txn_write(row, column, &datum);
+    }
+
+    free(key_string);
+    free(value_string);
+}
+
+static void
+cmd_set(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i]);
+    }
+}
+
+static void
+cmd_add(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+
+    type = &column->type;
+    ovsdb_idl_txn_read(row, column, &old);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type add_type;
+        struct ovsdb_datum add;
+
+        add_type = *type;
+        add_type.n_min = 1;
+        add_type.n_max = UINT_MAX;
+        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i]));
+        ovsdb_datum_union(&old, &add, type, false);
+        ovsdb_datum_destroy(&add, type);
+    }
+    if (old.n > type->n_max) {
+        vsctl_fatal("\"add\" operation would put %u %s in column %s of "
+                    "table %s but the maximum number is %u",
+                    old.n,
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_max);
+    }
+    ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+cmd_remove(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+
+    type = &column->type;
+    ovsdb_idl_txn_read(row, column, &old);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type rm_type;
+        struct ovsdb_datum rm;
+        char *error;
+
+        rm_type = *type;
+        rm_type.n_min = 1;
+        rm_type.n_max = UINT_MAX;
+        error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]);
+        if (error && ovsdb_type_is_map(&rm_type)) {
+            free(error);
+            rm_type.value.type = OVSDB_TYPE_VOID;
+            die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]));
+        }
+        ovsdb_datum_subtract(&old, type, &rm, &rm_type);
+        ovsdb_datum_destroy(&rm, &rm_type);
+    }
+    if (old.n < type->n_min) {
+        vsctl_fatal("\"remove\" operation would put %u %s in column %s of "
+                    "table %s but the minimun number is %u",
+                    old.n,
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_min);
+    }
+    ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+cmd_clear(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+        const struct ovsdb_type *type;
+        struct ovsdb_datum datum;
+
+        die_if_error(get_column(table, ctx->argv[i], &column));
+
+        type = &column->type;
+        if (type->n_min > 0) {
+            vsctl_fatal("\"clear\" operation cannot be applied to column %s "
+                        "of table %s, which is not allowed to be empty",
+                        column->name, table->class->name);
+        }
+
+        ovsdb_datum_init_empty(&datum);
+        ovsdb_idl_txn_write(row, column, &datum);
+    }
+}
+
+static void
+cmd_create(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = ovsdb_idl_txn_insert(ctx->txn, table->class);
+    for (i = 2; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i]);
+    }
+    ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
+}
+
+/* This function may be used as the 'postprocess' function for commands that
+ * insert new rows into the database.  It expects that the command's 'run'
+ * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's
+ * sole output.  It replaces that output by the row's permanent UUID assigned
+ * by the database server and appends a new-line.
+ *
+ * Currently we use this only for "create", because the higher-level commands
+ * are supposed to be independent of the actual structure of the vswitch
+ * configuration. */
+static void
+post_create(struct vsctl_context *ctx)
+{
+    const struct uuid *real;
+    struct uuid dummy;
+
+    uuid_from_string(&dummy, ds_cstr(&ctx->output));
+    real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy);
+    if (real) {
+        ds_clear(&ctx->output);
+        ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real));
+    }
+    ds_put_char(&ctx->output, '\n');
+}
+
+static void
+cmd_destroy(struct vsctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    int i;
+
+    table = get_table(table_name);
+    for (i = 2; i < ctx->argc; i++) {
+        const struct ovsdb_idl_row *row;
+
+        row = (must_exist ? must_get_row : get_row)(ctx, table, ctx->argv[i]);
+        if (row) {
+            ovsdb_idl_txn_delete(row);
+        }
+    }
+}
+\f
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+    return
+        json_array_create_1(
+            json_array_create_3(
+                json_string_create("_uuid"),
+                json_string_create("=="),
+                json_array_create_2(
+                    json_string_create("uuid"),
+                    json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
+}
+
+static void
+vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command,
+                   struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
+                   const struct ovsrec_open_vswitch *ovs)
+{
+    ctx->argc = command->argc;
+    ctx->argv = command->argv;
+    ctx->options = command->options;
+
+    ds_swap(&ctx->output, &command->output);
+    ctx->idl = idl;
+    ctx->txn = txn;
+    ctx->ovs = ovs;
+
+}
+
+static void
+vsctl_context_done(struct vsctl_context *ctx, struct vsctl_command *command)
+{
+    ds_swap(&ctx->output, &command->output);
+}
+
+static void
+do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
+         struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+    const struct ovsrec_open_vswitch *ovs;
+    enum ovsdb_idl_txn_status status;
+    struct vsctl_command *c;
+    int64_t next_cfg = 0;
+    char *error;
+
+    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+    if (dry_run) {
+        ovsdb_idl_txn_set_dry_run(txn);
+    }
+
+    ovsdb_idl_txn_add_comment(txn, "ovs-vsctl: %s", args);
+
+    ovs = ovsrec_open_vswitch_first(idl);
+    if (!ovs) {
+        /* XXX add verification that table is empty */
+        ovs = ovsrec_open_vswitch_insert(txn);
+    }
+
+    if (wait_for_reload) {
+        struct json *where = where_uuid_equals(&ovs->header_.uuid);
+        ovsdb_idl_txn_increment(txn, "Open_vSwitch", "next_cfg", where);
+        json_destroy(where);
+    }
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct vsctl_context ctx;
+
+        ds_init(&c->output);
+        vsctl_context_init(&ctx, c, idl, txn, ovs);
+        (c->syntax->run)(&ctx);
+        vsctl_context_done(&ctx, c);
+    }
+
+    status = ovsdb_idl_txn_commit_block(txn);
+    if (wait_for_reload && status == TXN_SUCCESS) {
+        next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
+    }
+    for (c = commands; c < &commands[n_commands]; c++) {
+        if (c->syntax->postprocess) {
+            struct vsctl_context ctx;
+
+            vsctl_context_init(&ctx, c, idl, txn, ovs);
+            (c->syntax->postprocess)(&ctx);
+            vsctl_context_done(&ctx, c);
+        }
+    }
+    error = xstrdup(ovsdb_idl_txn_get_error(txn));
+    ovsdb_idl_txn_destroy(txn);
+    the_idl_txn = NULL;
+
+    switch (status) {
+    case TXN_INCOMPLETE:
+        NOT_REACHED();
+
+    case TXN_ABORTED:
+        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+        vsctl_fatal("transaction aborted");
+
+    case TXN_UNCHANGED:
+    case TXN_SUCCESS:
+        break;
+
+    case TXN_TRY_AGAIN:
+        for (c = commands; c < &commands[n_commands]; c++) {
+            ds_destroy(&c->output);
+        }
+        free(error);
+        return;
+
+    case TXN_ERROR:
+        vsctl_fatal("transaction error: %s", error);
+
+    default:
+        NOT_REACHED();
+    }
+    free(error);
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct ds *ds = &c->output;
+        if (oneline) {
+            size_t j;
+
+            ds_chomp(ds, '\n');
+            for (j = 0; j < ds->length; j++) {
+                int c = ds->string[j];
+                switch (c) {
+                case '\n':
+                    fputs("\\n", stdout);
+                    break;
+
+                case '\\':
+                    fputs("\\\\", stdout);
+                    break;
+
+                default:
+                    putchar(c);
+                }
+            }
+            putchar('\n');
+        } else {
+            fputs(ds_cstr(ds), stdout);
+        }
+        ds_destroy(&c->output);
+        shash_destroy(&c->options);
+    }
+    free(commands);
+
+    if (wait_for_reload && status != TXN_UNCHANGED) {
+        for (;;) {
+            const struct ovsrec_open_vswitch *ovs;
+
+            ovsdb_idl_run(idl);
+            OVSREC_OPEN_VSWITCH_FOR_EACH (ovs, idl) {
+                if (ovs->cur_cfg >= next_cfg) {
+                    goto done;
+                }
+            }
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    done: ;
+    }
+    ovsdb_idl_destroy(idl);
+
+    exit(EXIT_SUCCESS);
+}
+
+static const struct vsctl_command_syntax all_commands[] = {
+    /* Open vSwitch commands. */
+    {"init", 0, 0, cmd_init, NULL, ""},
+
+    /* Bridge commands. */
+    {"add-br", 1, 3, cmd_add_br, NULL, "--may-exist"},
+    {"del-br", 1, 1, cmd_del_br, NULL, "--if-exists"},
+    {"list-br", 0, 0, cmd_list_br, NULL, ""},
+    {"br-exists", 1, 1, cmd_br_exists, NULL, ""},
+    {"br-to-vlan", 1, 1, cmd_br_to_vlan, NULL, ""},
+    {"br-to-parent", 1, 1, cmd_br_to_parent, NULL, ""},
+    {"br-set-external-id", 2, 3, cmd_br_set_external_id, NULL, ""},
+    {"br-get-external-id", 1, 2, cmd_br_get_external_id, NULL, ""},
+
+    /* Port commands. */
+    {"list-ports", 1, 1, cmd_list_ports, NULL, ""},
+    {"add-port", 2, 2, cmd_add_port, NULL, "--may-exist"},
+    {"add-bond", 4, INT_MAX, cmd_add_bond, NULL, "--may-exist,--fake-iface"},
+    {"del-port", 1, 2, cmd_del_port, NULL, "--if-exists,--with-iface"},
+    {"port-to-br", 1, 1, cmd_port_to_br, NULL, ""},
+
+    /* Interface commands. */
+    {"list-ifaces", 1, 1, cmd_list_ifaces, NULL, ""},
+    {"iface-to-br", 1, 1, cmd_iface_to_br, NULL, ""},
+
+    /* Controller commands. */
+    {"get-controller", 0, 1, cmd_get_controller, NULL, ""},
+    {"del-controller", 0, 1, cmd_del_controller, NULL, ""},
+    {"set-controller", 1, 2, cmd_set_controller, NULL, ""},
+    {"get-fail-mode", 0, 1, cmd_get_fail_mode, NULL, ""},
+    {"del-fail-mode", 0, 1, cmd_del_fail_mode, NULL, ""},
+    {"set-fail-mode", 1, 2, cmd_set_fail_mode, NULL, ""},
+
+    /* SSL commands. */
+    {"get-ssl", 0, 0, cmd_get_ssl, NULL, ""},
+    {"del-ssl", 0, 0, cmd_del_ssl, NULL, ""},
+    {"set-ssl", 3, 3, cmd_set_ssl, NULL, "--bootstrap"},
+
+    /* Parameter commands. */
+    {"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"},
+    {"list", 1, INT_MAX, cmd_list, NULL, ""},
+    {"set", 3, INT_MAX, cmd_set, NULL, ""},
+    {"add", 4, INT_MAX, cmd_add, NULL, ""},
+    {"remove", 4, INT_MAX, cmd_remove, NULL, ""},
+    {"clear", 3, INT_MAX, cmd_clear, NULL, ""},
+    {"create", 2, INT_MAX, cmd_create, post_create, ""},
+    {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"},
+
+    {NULL, 0, 0, NULL, NULL, NULL},
+};
+
index fc3efb1..fedc65c 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
 #define DEFAULT_INTERVAL  1
 #define DEFAULT_TIMEOUT   30
 
-int fd = -1;
+static int fd = -1;
 
 /* The WDT is automatically enabled when /dev/watchdog is opened.  If we
  * do not send the magic value to the device first before exiting, the 
@@ -134,7 +134,7 @@ int main(int argc, char *argv[])
         {"help", no_argument, NULL, 'h'},
         {"verbose", no_argument, NULL, 'v'},
         {"version", no_argument, NULL, 'V'},
-        {0, 0, 0, 0}
+        {NULL, 0, NULL, 0}
     };
 
     setup_signal();
index 01d57ae..bfc4373 100644 (file)
@@ -4,4 +4,7 @@
 /ovs-brcompatd.8
 /ovs-vswitchd
 /ovs-vswitchd.8
-/ovs-vswitchd.conf.5
+/ovs-vswitchd.conf.db.5
+/vswitch-idl.c
+/vswitch-idl.h
+/vswitch-idl.ovsidl
index 6883731..2cff64b 100644 (file)
@@ -1,40 +1,56 @@
 sbin_PROGRAMS += vswitchd/ovs-vswitchd vswitchd/ovs-brcompatd
 man_MANS += \
-       vswitchd/ovs-vswitchd.conf.5 \
        vswitchd/ovs-vswitchd.8 \
        vswitchd/ovs-brcompatd.8
 DISTCLEANFILES += \
-       vswitchd/ovs-vswitchd.conf.5 \
        vswitchd/ovs-vswitchd.8 \
        vswitchd/ovs-brcompatd.8
 
 vswitchd_ovs_vswitchd_SOURCES = \
        vswitchd/bridge.c \
        vswitchd/bridge.h \
-       vswitchd/mgmt.c \
-       vswitchd/mgmt.h \
-       vswitchd/port.c \
-       vswitchd/port.h \
        vswitchd/proc-net-compat.c \
        vswitchd/proc-net-compat.h \
        vswitchd/ovs-vswitchd.c \
-       vswitchd/ovs-vswitchd.h \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h \
        vswitchd/xenserver.c \
        vswitchd/xenserver.h
 vswitchd_ovs_vswitchd_LDADD = \
-       secchan/libsecchan.a \
+       ofproto/libofproto.a \
+       lib/libsflow.a \
        lib/libopenvswitch.a \
-       $(FAULT_LIBS) \
        $(SSL_LIBS)
 
 vswitchd_ovs_brcompatd_SOURCES = \
-       vswitchd/ovs-brcompatd.c
+       vswitchd/ovs-brcompatd.c \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h
 
-vswitchd_ovs_brcompatd_LDADD = \
-       lib/libopenvswitch.a \
-       $(FAULT_LIBS) 
+vswitchd_ovs_brcompatd_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 EXTRA_DIST += \
-       vswitchd/ovs-vswitchd.conf.5.in \
        vswitchd/ovs-vswitchd.8.in \
        vswitchd/ovs-brcompatd.8.in
+
+# vswitch schema and IDL
+OVSIDL_BUILT += \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h \
+       vswitchd/vswitch-idl.ovsidl
+VSWITCH_IDL_FILES = vswitchd/vswitch.ovsschema vswitchd/vswitch-idl.ann
+EXTRA_DIST += $(VSWITCH_IDL_FILES)
+vswitchd/vswitch-idl.ovsidl: $(VSWITCH_IDL_FILES)
+       $(OVSDB_IDLC) -C $(srcdir) annotate $(VSWITCH_IDL_FILES) > $@.tmp
+       mv $@.tmp $@
+
+# vswitch schema documentation
+EXTRA_DIST += vswitchd/vswitch.xml
+dist_man_MANS += vswitchd/ovs-vswitchd.conf.db.5
+vswitchd/ovs-vswitchd.conf.db.5: \
+       ovsdb/ovsdb-doc.in vswitchd/vswitch.xml vswitchd/vswitch.ovsschema
+       $(OVSDB_DOC) \
+               --title="ovs-vswitchd.conf.db" \
+               $(srcdir)/vswitchd/vswitch.ovsschema \
+               $(srcdir)/vswitchd/vswitch.xml > $@.tmp
+       mv $@.tmp $@
index fb6a141..130e094 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include "bitmap.h"
-#include "cfg.h"
 #include "coverage.h"
 #include "dirs.h"
 #include "dpif.h"
 #include "odp-util.h"
 #include "ofp-print.h"
 #include "ofpbuf.h"
+#include "ofproto/netflow.h"
+#include "ofproto/ofproto.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "port-array.h"
 #include "proc-net-compat.h"
 #include "process.h"
-#include "secchan/netflow.h"
-#include "secchan/ofproto.h"
+#include "sha1.h"
+#include "shash.h"
 #include "socket-util.h"
-#include "stp.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "timeval.h"
 #include "util.h"
 #include "unixctl.h"
 #include "vconn.h"
-#include "vconn-ssl.h"
+#include "vswitchd/vswitch-idl.h"
 #include "xenserver.h"
 #include "xtoxll.h"
+#include "sflow_api.h"
 
 #define THIS_MODULE VLM_bridge
 #include "vlog.h"
@@ -69,20 +71,22 @@ struct dst {
     uint16_t dp_ifidx;
 };
 
-extern uint64_t mgmt_id;
-
 struct iface {
+    /* These members are always valid. */
     struct port *port;          /* Containing port. */
     size_t port_ifidx;          /* Index within containing port. */
-
     char *name;                 /* Host network device name. */
-    int dp_ifidx;               /* Index within kernel datapath. */
-
-    uint8_t mac[ETH_ADDR_LEN];  /* Ethernet address (all zeros if unknowns). */
-
     tag_type tag;               /* Tag associated with this interface. */
-    bool enabled;               /* May be chosen for flows? */
     long long delay_expires;    /* Time after which 'enabled' may change. */
+
+    /* These members are valid only after bridge_reconfigure() causes them to
+     * be initialized.*/
+    int dp_ifidx;               /* Index within kernel datapath. */
+    struct netdev *netdev;      /* Network device. */
+    bool enabled;               /* May be chosen for flows? */
+
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_interface *cfg;
 };
 
 #define BOND_MASK 0xff
@@ -102,8 +106,8 @@ struct mirror {
     char *name;
 
     /* Selection criteria. */
-    struct svec src_ports;
-    struct svec dst_ports;
+    struct shash src_ports;     /* Name is port name; data is always NULL. */
+    struct shash dst_ports;     /* Name is port name; data is always NULL. */
     int *vlans;
     size_t n_vlans;
 
@@ -132,15 +136,15 @@ struct port {
     tag_type no_ifaces_tag;     /* Tag for flows when all ifaces disabled. */
     int updelay, downdelay;     /* Delay before iface goes up/down, in ms. */
     bool bond_compat_is_stale;  /* Need to call port_update_bond_compat()? */
+    bool bond_fake_iface;       /* Fake a bond interface for legacy compat? */
 
     /* Port mirroring info. */
     mirror_mask_t src_mirrors;  /* Mirrors triggered when packet received. */
     mirror_mask_t dst_mirrors;  /* Mirrors triggered when packet sent. */
     bool is_mirror_output_port; /* Does port mirroring send frames here? */
 
-    /* Spanning tree info. */
-    enum stp_state stp_state;   /* Always STP_FORWARDING if STP not in use. */
-    tag_type stp_state_tag;     /* Tag for STP state change. */
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_port *cfg;
 };
 
 #define DP_MAX_PORTS 255
@@ -159,8 +163,15 @@ struct bridge {
     /* OpenFlow switch processing. */
     struct ofproto *ofproto;    /* OpenFlow switch. */
 
+    /* Description strings. */
+    char *mfr_desc;             /* Manufacturer. */
+    char *hw_desc;              /* Hardware. */
+    char *sw_desc;              /* Software version. */
+    char *serial_desc;          /* Serial number. */
+    char *dp_desc;              /* Datapath description. */
+
     /* Kernel datapath information. */
-    struct dpif dpif;           /* Kernel datapath. */
+    struct dpif *dpif;          /* Datapath. */
     struct port_array ifaces;   /* Indexed by kernel datapath port number. */
 
     /* Bridge ports. */
@@ -180,9 +191,8 @@ struct bridge {
     /* Port mirroring. */
     struct mirror *mirrors[MAX_MIRRORS];
 
-    /* Spanning tree. */
-    struct stp *stp;
-    long long int stp_last_tick;
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_bridge *cfg;
 };
 
 /* List of all bridges. */
@@ -191,25 +201,31 @@ static struct list all_bridges = LIST_INITIALIZER(&all_bridges);
 /* Maximum number of datapaths. */
 enum { DP_MAX = 256 };
 
-static struct bridge *bridge_create(const char *name);
+static struct bridge *bridge_create(const struct ovsrec_bridge *br_cfg);
 static void bridge_destroy(struct bridge *);
 static struct bridge *bridge_lookup(const char *name);
-static void bridge_unixctl_dump_flows(struct unixctl_conn *, const char *);
+static unixctl_cb_func bridge_unixctl_dump_flows;
 static int bridge_run_one(struct bridge *);
-static void bridge_reconfigure_one(struct bridge *);
-static void bridge_reconfigure_controller(struct bridge *);
-static void bridge_get_all_ifaces(const struct bridge *, struct svec *ifaces);
+static const struct ovsrec_controller *bridge_get_controller(
+                      const struct ovsrec_open_vswitch *ovs_cfg,
+                      const struct bridge *br);
+static void bridge_reconfigure_one(const struct ovsrec_open_vswitch *,
+                                   struct bridge *);
+static void bridge_reconfigure_controller(const struct ovsrec_open_vswitch *,
+                                          struct bridge *);
+static void bridge_get_all_ifaces(const struct bridge *, struct shash *ifaces);
 static void bridge_fetch_dp_ifaces(struct bridge *);
 static void bridge_flush(struct bridge *);
 static void bridge_pick_local_hw_addr(struct bridge *,
                                       uint8_t ea[ETH_ADDR_LEN],
-                                      const char **devname);
+                                      struct iface **hw_addr_iface);
 static uint64_t bridge_pick_datapath_id(struct bridge *,
                                         const uint8_t bridge_ea[ETH_ADDR_LEN],
-                                        const char *devname);
+                                        struct iface *hw_addr_iface);
+static struct iface *bridge_get_local_iface(struct bridge *);
 static uint64_t dpid_from_hash(const void *, size_t nbytes);
 
-static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args);
+static unixctl_cb_func bridge_unixctl_fdb_show;
 
 static void bond_init(void);
 static void bond_run(struct bridge *);
@@ -218,8 +234,8 @@ static void bond_rebalance_port(struct port *);
 static void bond_send_learning_packets(struct port *);
 static void bond_enable_slave(struct iface *iface, bool enable);
 
-static void port_create(struct bridge *, const char *name);
-static void port_reconfigure(struct port *);
+static struct port *port_create(struct bridge *, const char *name);
+static void port_reconfigure(struct port *, const struct ovsrec_port *);
 static void port_destroy(struct port *);
 static struct port *port_lookup(const struct bridge *, const char *name);
 static struct iface *port_lookup_iface(const struct port *, const char *name);
@@ -227,19 +243,16 @@ static struct port *port_from_dp_ifidx(const struct bridge *,
                                        uint16_t dp_ifidx);
 static void port_update_bond_compat(struct port *);
 static void port_update_vlan_compat(struct port *);
+static void port_update_bonding(struct port *);
 
-static void mirror_create(struct bridge *, const char *name);
+static struct mirror *mirror_create(struct bridge *, const char *name);
 static void mirror_destroy(struct mirror *);
 static void mirror_reconfigure(struct bridge *);
-static void mirror_reconfigure_one(struct mirror *);
+static void mirror_reconfigure_one(struct mirror *, struct ovsrec_mirror *);
 static bool vlan_is_mirrored(const struct mirror *, int vlan);
 
-static void brstp_reconfigure(struct bridge *);
-static void brstp_adjust_timers(struct bridge *);
-static void brstp_run(struct bridge *);
-static void brstp_wait(struct bridge *);
-
-static void iface_create(struct port *, const char *name);
+static struct iface *iface_create(struct port *port, 
+                                  const struct ovsrec_interface *if_cfg);
 static void iface_destroy(struct iface *);
 static struct iface *iface_lookup(const struct bridge *, const char *name);
 static struct iface *iface_from_dp_ifidx(const struct bridge *,
@@ -267,8 +280,8 @@ bridge_get_ifaces(struct svec *svec)
             for (j = 0; j < port->n_ifaces; j++) {
                 struct iface *iface = port->ifaces[j];
                 if (iface->dp_ifidx < 0) {
-                    VLOG_ERR("%s interface not in dp%u, ignoring",
-                             iface->name, dpif_id(&br->dpif));
+                    VLOG_ERR("%s interface not in datapath %s, ignoring",
+                             iface->name, dpif_name(br->dpif));
                 } else {
                     if (iface->dp_ifidx != ODPP_LOCAL) {
                         svec_add(svec, iface->name);
@@ -279,144 +292,291 @@ bridge_get_ifaces(struct svec *svec)
     }
 }
 
-/* The caller must already have called cfg_read(). */
 void
-bridge_init(void)
+bridge_init(const struct ovsrec_open_vswitch *cfg)
 {
-    int retval;
-    int i;
+    struct svec bridge_names;
+    struct svec dpif_names, dpif_types;
+    size_t i;
 
-    bond_init();
+    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show, NULL);
 
-    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show);
+    svec_init(&bridge_names);
+    for (i = 0; i < cfg->n_bridges; i++) {
+        svec_add(&bridge_names, cfg->bridges[i]->name);
+    }
+    svec_sort(&bridge_names);
+
+    svec_init(&dpif_names);
+    svec_init(&dpif_types);
+    dp_enumerate_types(&dpif_types);
+    for (i = 0; i < dpif_types.n; i++) {
+        struct dpif *dpif;
+        int retval;
+        size_t j;
 
-    for (i = 0; i < DP_MAX; i++) {
-        struct dpif dpif;
-        char devname[16];
+        dp_enumerate_names(dpif_types.names[i], &dpif_names);
 
-        sprintf(devname, "dp%d", i);
-        retval = dpif_open(devname, &dpif);
-        if (!retval) {
-            char dpif_name[IF_NAMESIZE];
-            if (dpif_get_name(&dpif, dpif_name, sizeof dpif_name)
-                || !cfg_has("bridge.%s.port", dpif_name)) {
-                dpif_delete(&dpif);
+        for (j = 0; j < dpif_names.n; j++) {
+            retval = dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif);
+            if (!retval) {
+                struct svec all_names;
+                size_t k;
+
+                svec_init(&all_names);
+                dpif_get_all_names(dpif, &all_names);
+                for (k = 0; k < all_names.n; k++) {
+                    if (svec_contains(&bridge_names, all_names.names[k])) {
+                        goto found;
+                    }
+                }
+                dpif_delete(dpif);
+            found:
+                svec_destroy(&all_names);
+                dpif_close(dpif);
             }
-            dpif_close(&dpif);
-        } else if (retval != ENODEV) {
-            VLOG_ERR("failed to delete datapath dp%d: %s",
-                     i, strerror(retval));
         }
     }
+    svec_destroy(&dpif_names);
+    svec_destroy(&dpif_types);
 
-    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows);
+    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows,
+                             NULL);
 
-    bridge_reconfigure();
+    bond_init();
+    bridge_reconfigure(cfg);
 }
 
 #ifdef HAVE_OPENSSL
+static void
+bridge_configure_ssl(const struct ovsrec_ssl *ssl)
+{
+    /* XXX SSL should be configurable on a per-bridge basis. */
+    if (ssl) {
+        stream_ssl_set_private_key_file(ssl->private_key);
+        stream_ssl_set_certificate_file(ssl->certificate);
+        stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
+    }
+}
+#endif
+
+/* Attempt to create the network device 'iface_name' through the netdev
+ * library. */
+static int
+set_up_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface,
+             bool create)
+{
+    struct shash_node *node;
+    struct shash options;
+    int error = 0;
+    size_t i;
+
+    shash_init(&options);
+    for (i = 0; i < iface_cfg->n_options; i++) {
+        shash_add(&options, iface_cfg->key_options[i],
+                  xstrdup(iface_cfg->value_options[i]));
+    }
+
+    if (create) {
+        struct netdev_options netdev_options;
+
+        memset(&netdev_options, 0, sizeof netdev_options);
+        netdev_options.name = iface_cfg->name;
+        if (!strcmp(iface_cfg->type, "internal")) {
+            /* An "internal" config type maps to a netdev "system" type. */
+            netdev_options.type = "system";
+        } else {
+            netdev_options.type = iface_cfg->type;
+        }
+        netdev_options.args = &options;
+        netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
+        netdev_options.may_create = true;
+        if (iface_is_internal(iface->port->bridge, iface_cfg->name)) {
+            netdev_options.may_open = true;
+        }
+
+        error = netdev_open(&netdev_options, &iface->netdev);
+
+        if (iface->netdev) {
+            netdev_get_carrier(iface->netdev, &iface->enabled);
+        }
+    } else if (iface->netdev) {
+        const char *netdev_type = netdev_get_type(iface->netdev);
+        const char *iface_type = iface_cfg->type && strlen(iface_cfg->type)
+                                  ? iface_cfg->type : NULL;
+
+        /* An "internal" config type maps to a netdev "system" type. */
+        if (iface_type && !strcmp(iface_type, "internal")) {
+            iface_type = "system";
+        }
+
+        if (!iface_type || !strcmp(netdev_type, iface_type)) {
+            error = netdev_reconfigure(iface->netdev, &options);
+        } else {
+            VLOG_WARN("%s: attempting change device type from %s to %s",
+                      iface_cfg->name, netdev_type, iface_type);
+            error = EINVAL;
+        }
+    }
+
+    SHASH_FOR_EACH (node, &options) {
+        free(node->data);
+    }
+    shash_destroy(&options);
+
+    return error;
+}
+
+static int
+reconfigure_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface)
+{
+    return set_up_iface(iface_cfg, iface, false);
+}
+
 static bool
-config_string_change(const char *key, char **valuep)
+check_iface_netdev(struct bridge *br OVS_UNUSED, struct iface *iface,
+                   void *aux OVS_UNUSED)
 {
-    const char *value = cfg_get_string(0, "%s", key);
-    if (value && (!*valuep || strcmp(value, *valuep))) {
-        free(*valuep);
-        *valuep = xstrdup(value);
+    if (!iface->netdev) {
+        int error = set_up_iface(iface->cfg, iface, true);
+        if (error) {
+            VLOG_WARN("could not open netdev on %s, dropping: %s", iface->name,
+                                                               strerror(error));
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+check_iface_dp_ifidx(struct bridge *br, struct iface *iface,
+                     void *aux OVS_UNUSED)
+{
+    if (iface->dp_ifidx >= 0) {
+        VLOG_DBG("%s has interface %s on port %d",
+                 dpif_name(br->dpif),
+                 iface->name, iface->dp_ifidx);
         return true;
     } else {
+        VLOG_ERR("%s interface not in %s, dropping",
+                 iface->name, dpif_name(br->dpif));
         return false;
     }
 }
 
-static void
-bridge_configure_ssl(void)
+static bool
+set_iface_properties(struct bridge *br OVS_UNUSED, struct iface *iface,
+                     void *aux OVS_UNUSED)
 {
-    /* XXX SSL should be configurable on a per-bridge basis.
-     * XXX should be possible to de-configure SSL. */
-    static char *private_key_file;
-    static char *certificate_file;
-    static char *cacert_file;
-    struct stat s;
+    /* Set policing attributes. */
+    netdev_set_policing(iface->netdev,
+                        iface->cfg->ingress_policing_rate,
+                        iface->cfg->ingress_policing_burst);
 
-    if (config_string_change("ssl.private-key", &private_key_file)) {
-        vconn_ssl_set_private_key_file(private_key_file);
+    /* Set MAC address of internal interfaces other than the local
+     * interface. */
+    if (iface->dp_ifidx != ODPP_LOCAL
+        && iface_is_internal(br, iface->name)) {
+        iface_set_mac(iface);
     }
 
-    if (config_string_change("ssl.certificate", &certificate_file)) {
-        vconn_ssl_set_certificate_file(certificate_file);
-    }
+    return true;
+}
+
+/* Calls 'cb' for each interfaces in 'br', passing along the 'aux' argument.
+ * Deletes from 'br' all the interfaces for which 'cb' returns false, and then
+ * deletes from 'br' any ports that no longer have any interfaces. */
+static void
+iterate_and_prune_ifaces(struct bridge *br,
+                         bool (*cb)(struct bridge *, struct iface *,
+                                    void *aux),
+                         void *aux)
+{
+    size_t i, j;
 
-    /* We assume that even if the filename hasn't changed, if the CA cert 
-     * file has been removed, that we want to move back into
-     * boot-strapping mode.  This opens a small security hole, because
-     * the old certificate will still be trusted until vSwitch is
-     * restarted.  We may want to address this in vconn's SSL library. */
-    if (config_string_change("ssl.ca-cert", &cacert_file)
-            || (stat(cacert_file, &s) && errno == ENOENT)) {
-        vconn_ssl_set_ca_cert_file(cacert_file,
-                                   cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
+    for (i = 0; i < br->n_ports; ) {
+        struct port *port = br->ports[i];
+        for (j = 0; j < port->n_ifaces; ) {
+            struct iface *iface = port->ifaces[j];
+            if (cb(br, iface, aux)) {
+                j++;
+            } else {
+                iface_destroy(iface);
+            }
+        }
+
+        if (port->n_ifaces) {
+            i++;
+        } else  {
+            VLOG_ERR("%s port has no interfaces, dropping", port->name);
+            port_destroy(port);
+        }
     }
 }
-#endif
 
 void
-bridge_reconfigure(void)
+bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
 {
-    struct svec old_br, new_br, raw_new_br;
+    struct ovsdb_idl_txn *txn;
+    struct shash old_br, new_br;
+    struct shash_node *node;
     struct bridge *br, *next;
-    size_t i, j;
+    size_t i;
+    int sflow_bridge_number;
 
     COVERAGE_INC(bridge_reconfigure);
 
-    /* Collect old bridges. */
-    svec_init(&old_br);
+    txn = ovsdb_idl_txn_create(ovs_cfg->header_.table->idl);
+
+    /* Collect old and new bridges. */
+    shash_init(&old_br);
+    shash_init(&new_br);
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        svec_add(&old_br, br->name);
-    }
-
-    /* Collect new bridges. */
-    svec_init(&raw_new_br);
-    cfg_get_subsections(&raw_new_br, "bridge");
-    svec_init(&new_br);
-    for (i = 0; i < raw_new_br.n; i++) {
-        const char *name = raw_new_br.names[i];
-        if ((!strncmp(name, "dp", 2) && isdigit(name[2])) ||
-            (!strncmp(name, "nl:", 3) && isdigit(name[3]))) {
-            VLOG_ERR("%s is not a valid bridge name (bridges may not be "
-                     "named \"dp\" or \"nl:\" followed by a digit)", name);
-        } else {
-            svec_add(&new_br, name);
+        shash_add(&old_br, br->name, br);
+    }
+    for (i = 0; i < ovs_cfg->n_bridges; i++) {
+        const struct ovsrec_bridge *br_cfg = ovs_cfg->bridges[i];
+        if (!shash_add_once(&new_br, br_cfg->name, br_cfg)) {
+            VLOG_WARN("more than one bridge named %s", br_cfg->name);
         }
     }
-    svec_destroy(&raw_new_br);
 
     /* Get rid of deleted bridges and add new bridges. */
-    svec_sort(&old_br);
-    svec_sort(&new_br);
-    assert(svec_is_unique(&old_br));
-    assert(svec_is_unique(&new_br));
     LIST_FOR_EACH_SAFE (br, next, struct bridge, node, &all_bridges) {
-        if (!svec_contains(&new_br, br->name)) {
+        struct ovsrec_bridge *br_cfg = shash_find_data(&new_br, br->name);
+        if (br_cfg) {
+            br->cfg = br_cfg;
+        } else {
             bridge_destroy(br);
         }
     }
-    for (i = 0; i < new_br.n; i++) {
-        const char *name = new_br.names[i];
-        if (!svec_contains(&old_br, name)) {
-            bridge_create(name);
+    SHASH_FOR_EACH (node, &new_br) {
+        const char *br_name = node->name;
+        const struct ovsrec_bridge *br_cfg = node->data;
+        br = shash_find_data(&old_br, br_name);
+        if (br) {
+            /* If the bridge datapath type has changed, we need to tear it
+             * down and recreate. */
+            if (strcmp(br->cfg->datapath_type, br_cfg->datapath_type)) {
+                bridge_destroy(br);
+                bridge_create(br_cfg);
+            }
+        } else {
+            bridge_create(br_cfg);
         }
     }
-    svec_destroy(&old_br);
-    svec_destroy(&new_br);
+    shash_destroy(&old_br);
+    shash_destroy(&new_br);
 
 #ifdef HAVE_OPENSSL
     /* Configure SSL. */
-    bridge_configure_ssl();
+    bridge_configure_ssl(ovs_cfg->ssl);
 #endif
 
     /* Reconfigure all bridges. */
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        bridge_reconfigure_one(br);
+        bridge_reconfigure_one(ovs_cfg, br);
     }
 
     /* Add and delete ports on all datapaths.
@@ -427,108 +587,94 @@ bridge_reconfigure(void)
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         struct odp_port *dpif_ports;
         size_t n_dpif_ports;
-        struct svec want_ifaces;
+        struct shash want_ifaces;
 
-        dpif_port_list(&br->dpif, &dpif_ports, &n_dpif_ports);
+        dpif_port_list(br->dpif, &dpif_ports, &n_dpif_ports);
         bridge_get_all_ifaces(br, &want_ifaces);
         for (i = 0; i < n_dpif_ports; i++) {
             const struct odp_port *p = &dpif_ports[i];
-            if (!svec_contains(&want_ifaces, p->devname)
+            if (!shash_find(&want_ifaces, p->devname)
                 && strcmp(p->devname, br->name)) {
-                int retval = dpif_port_del(&br->dpif, p->port);
+                int retval = dpif_port_del(br->dpif, p->port);
                 if (retval) {
-                    VLOG_ERR("failed to remove %s interface from dp%u: %s",
-                             p->devname, dpif_id(&br->dpif), strerror(retval));
+                    VLOG_ERR("failed to remove %s interface from %s: %s",
+                             p->devname, dpif_name(br->dpif),
+                             strerror(retval));
                 }
             }
         }
-        svec_destroy(&want_ifaces);
+        shash_destroy(&want_ifaces);
         free(dpif_ports);
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         struct odp_port *dpif_ports;
         size_t n_dpif_ports;
-        struct svec cur_ifaces, want_ifaces, add_ifaces;
-        int next_port_no;
+        struct shash cur_ifaces, want_ifaces;
+        struct shash_node *node;
 
-        dpif_port_list(&br->dpif, &dpif_ports, &n_dpif_ports);
-        svec_init(&cur_ifaces);
+        /* Get the set of interfaces currently in this datapath. */
+        dpif_port_list(br->dpif, &dpif_ports, &n_dpif_ports);
+        shash_init(&cur_ifaces);
         for (i = 0; i < n_dpif_ports; i++) {
-            svec_add(&cur_ifaces, dpif_ports[i].devname);
+            const char *name = dpif_ports[i].devname;
+            if (!shash_find(&cur_ifaces, name)) {
+                shash_add(&cur_ifaces, name, NULL);
+            }
         }
         free(dpif_ports);
-        svec_sort_unique(&cur_ifaces);
+
+        /* Get the set of interfaces we want on this datapath. */
         bridge_get_all_ifaces(br, &want_ifaces);
-        svec_diff(&want_ifaces, &cur_ifaces, &add_ifaces, NULL, NULL);
 
-        next_port_no = 1;
-        for (i = 0; i < add_ifaces.n; i++) {
-            const char *if_name = add_ifaces.names[i];
-            for (;;) {
+        SHASH_FOR_EACH (node, &want_ifaces) {
+            const char *if_name = node->name;
+            struct iface *iface = node->data;
+
+            if (shash_find(&cur_ifaces, if_name)) {
+                /* Already exists, just reconfigure it. */
+                if (iface) {
+                    reconfigure_iface(iface->cfg, iface);
+                }
+            } else {
+                /* Need to add to datapath. */
                 bool internal;
                 int error;
 
                 /* Add to datapath. */
                 internal = iface_is_internal(br, if_name);
-                error = dpif_port_add(&br->dpif, if_name, next_port_no++,
-                                      internal ? ODP_PORT_INTERNAL : 0);
-                if (error != EEXIST) {
-                    if (next_port_no >= 256) {
-                        VLOG_ERR("ran out of valid port numbers on dp%u",
-                                 dpif_id(&br->dpif));
-                        goto out;
-                    }
-                    if (error) {
-                        VLOG_ERR("failed to add %s interface to dp%u: %s",
-                                 if_name, dpif_id(&br->dpif), strerror(error));
-                    }
+                error = dpif_port_add(br->dpif, if_name,
+                                      internal ? ODP_PORT_INTERNAL : 0, NULL);
+                if (error == EFBIG) {
+                    VLOG_ERR("ran out of valid port numbers on %s",
+                             dpif_name(br->dpif));
                     break;
+                } else if (error) {
+                    VLOG_ERR("failed to add %s interface to %s: %s",
+                             if_name, dpif_name(br->dpif), strerror(error));
                 }
             }
         }
-    out:
-        svec_destroy(&cur_ifaces);
-        svec_destroy(&want_ifaces);
-        svec_destroy(&add_ifaces);
+        shash_destroy(&cur_ifaces);
+        shash_destroy(&want_ifaces);
     }
+    sflow_bridge_number = 0;
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         uint8_t ea[8];
         uint64_t dpid;
-        struct iface *local_iface = NULL;
-        const char *devname;
-        struct netflow_options nf_options;
+        struct iface *local_iface;
+        struct iface *hw_addr_iface;
+        char *dpid_string;
 
         bridge_fetch_dp_ifaces(br);
-        for (i = 0; i < br->n_ports; ) {
-            struct port *port = br->ports[i];
 
-            for (j = 0; j < port->n_ifaces; ) {
-                struct iface *iface = port->ifaces[j];
-                if (iface->dp_ifidx < 0) {
-                    VLOG_ERR("%s interface not in dp%u, dropping",
-                             iface->name, dpif_id(&br->dpif));
-                    iface_destroy(iface);
-                } else {
-                    if (iface->dp_ifidx == ODPP_LOCAL) {
-                        local_iface = iface;
-                    }
-                    VLOG_DBG("dp%u has interface %s on port %d",
-                             dpif_id(&br->dpif), iface->name, iface->dp_ifidx);
-                    j++;
-                }
-            }
-            if (!port->n_ifaces) {
-                VLOG_ERR("%s port has no interfaces, dropping", port->name);
-                port_destroy(port);
-                continue;
-            }
-            i++;
-        }
+        iterate_and_prune_ifaces(br, check_iface_netdev, NULL);
+        iterate_and_prune_ifaces(br, check_iface_dp_ifidx, NULL);
 
         /* Pick local port hardware address, datapath ID. */
-        bridge_pick_local_hw_addr(br, ea, &devname);
+        bridge_pick_local_hw_addr(br, ea, &hw_addr_iface);
+        local_iface = bridge_get_local_iface(br);
         if (local_iface) {
-            int error = netdev_nodev_set_etheraddr(local_iface->name, ea);
+            int error = netdev_set_etheraddr(local_iface->netdev, ea);
             if (error) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_ERR_RL(&rl, "bridge %s: failed to set bridge "
@@ -537,50 +683,99 @@ bridge_reconfigure(void)
             }
         }
 
-        dpid = bridge_pick_datapath_id(br, ea, devname);
+        dpid = bridge_pick_datapath_id(br, ea, hw_addr_iface);
         ofproto_set_datapath_id(br->ofproto, dpid);
 
+        dpid_string = xasprintf("%012"PRIx64, dpid);
+        ovsrec_bridge_set_datapath_id(br->cfg, dpid_string);
+        free(dpid_string);
+
         /* Set NetFlow configuration on this bridge. */
-        memset(&nf_options, 0, sizeof nf_options);
-        nf_options.engine_type = br->dpif.minor;
-        nf_options.engine_id = br->dpif.minor;
-        nf_options.active_timeout = -1;
-
-        if (cfg_has("netflow.%s.engine-type", br->name)) {
-            nf_options.engine_type = cfg_get_int(0, "netflow.%s.engine-type", 
-                    br->name);
-        }
-        if (cfg_has("netflow.%s.engine-id", br->name)) {
-            nf_options.engine_id = cfg_get_int(0, "netflow.%s.engine-id",
-                                               br->name);
-        }
-        if (cfg_has("netflow.%s.active-timeout", br->name)) {
-            nf_options.active_timeout = cfg_get_int(0,
-                                                    "netflow.%s.active-timeout",
-                                                    br->name);
-        }
-        if (cfg_has("netflow.%s.add-id-to-iface", br->name)) {
-            nf_options.add_id_to_iface = cfg_get_bool(0,
-                                                   "netflow.%s.add-id-to-iface",
-                                                    br->name);
-        }
-        if (nf_options.add_id_to_iface && nf_options.engine_id > 0x7f) {
-            VLOG_WARN("bridge %s: netflow port mangling may conflict with "
-                    "another vswitch, choose an engine id less than 128", 
-                    br->name);
-        }
-        if (nf_options.add_id_to_iface && br->n_ports > 508) {
-            VLOG_WARN("bridge %s: netflow port mangling will conflict with "
-                    "another port when more than 508 ports are used", 
-                    br->name);
-        }
-        svec_init(&nf_options.collectors);
-        cfg_get_all_keys(&nf_options.collectors, "netflow.%s.host", br->name);
-        if (ofproto_set_netflow(br->ofproto, &nf_options)) {
-            VLOG_ERR("bridge %s: problem setting netflow collectors", 
-                    br->name);
-        }
-        svec_destroy(&nf_options.collectors);
+        if (br->cfg->netflow) {
+            struct ovsrec_netflow *nf_cfg = br->cfg->netflow;
+            struct netflow_options opts;
+
+            memset(&opts, 0, sizeof opts);
+
+            dpif_get_netflow_ids(br->dpif, &opts.engine_type, &opts.engine_id);
+            if (nf_cfg->engine_type) {
+                opts.engine_type = *nf_cfg->engine_type;
+            }
+            if (nf_cfg->engine_id) {
+                opts.engine_id = *nf_cfg->engine_id;
+            }
+
+            opts.active_timeout = nf_cfg->active_timeout;
+            if (!opts.active_timeout) {
+                opts.active_timeout = -1;
+            } else if (opts.active_timeout < 0) {
+                VLOG_WARN("bridge %s: active timeout interval set to negative "
+                          "value, using default instead (%d seconds)", br->name,
+                          NF_ACTIVE_TIMEOUT_DEFAULT);
+                opts.active_timeout = -1;
+            }
+
+            opts.add_id_to_iface = nf_cfg->add_id_to_interface;
+            if (opts.add_id_to_iface) {
+                if (opts.engine_id > 0x7f) {
+                    VLOG_WARN("bridge %s: netflow port mangling may conflict "
+                              "with another vswitch, choose an engine id less "
+                              "than 128", br->name);
+                }
+                if (br->n_ports > 508) {
+                    VLOG_WARN("bridge %s: netflow port mangling will conflict "
+                              "with another port when more than 508 ports are "
+                              "used", br->name);
+                }
+            }
+
+            opts.collectors.n = nf_cfg->n_targets;
+            opts.collectors.names = nf_cfg->targets;
+            if (ofproto_set_netflow(br->ofproto, &opts)) {
+                VLOG_ERR("bridge %s: problem setting netflow collectors", 
+                         br->name);
+            }
+        } else {
+            ofproto_set_netflow(br->ofproto, NULL);
+        }
+
+        /* Set sFlow configuration on this bridge. */
+        if (br->cfg->sflow) {
+            const struct ovsrec_sflow *sflow_cfg = br->cfg->sflow;
+            const struct ovsrec_controller *ctrl;
+            struct ofproto_sflow_options oso;
+
+            memset(&oso, 0, sizeof oso);
+
+            oso.targets.n = sflow_cfg->n_targets;
+            oso.targets.names = sflow_cfg->targets;
+
+            oso.sampling_rate = SFL_DEFAULT_SAMPLING_RATE;
+            if (sflow_cfg->sampling) {
+                oso.sampling_rate = *sflow_cfg->sampling;
+            }
+
+            oso.polling_interval = SFL_DEFAULT_POLLING_INTERVAL;
+            if (sflow_cfg->polling) {
+                oso.polling_interval = *sflow_cfg->polling;
+            }
+
+            oso.header_len = SFL_DEFAULT_HEADER_SIZE;
+            if (sflow_cfg->header) {
+                oso.header_len = *sflow_cfg->header;
+            }
+
+            oso.sub_id = sflow_bridge_number++;
+            oso.agent_device = sflow_cfg->agent;
+
+            ctrl = bridge_get_controller(ovs_cfg, br);
+            oso.control_ip = ctrl ? ctrl->local_ip : NULL;
+            ofproto_set_sflow(br->ofproto, &oso);
+
+            svec_destroy(&oso.targets);
+        } else {
+            ofproto_set_sflow(br->ofproto, NULL);
+        }
 
         /* Update the controller and related settings.  It would be more
          * straightforward to call this from bridge_reconfigure_one(), but we
@@ -591,42 +786,52 @@ bridge_reconfigure(void)
          * yet; when a controller is configured, resetting the datapath ID will
          * immediately disconnect from the controller, so it's better to set
          * the datapath ID before the controller. */
-        bridge_reconfigure_controller(br);
+        bridge_reconfigure_controller(ovs_cfg, br);
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         for (i = 0; i < br->n_ports; i++) {
             struct port *port = br->ports[i];
 
             port_update_vlan_compat(port);
-
-            for (j = 0; j < port->n_ifaces; j++) {
-                struct iface *iface = port->ifaces[j];
-                if (iface->dp_ifidx != ODPP_LOCAL
-                    && iface_is_internal(br, iface->name)) {
-                    iface_set_mac(iface);
-                }
-            }
+            port_update_bonding(port);
         }
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        brstp_reconfigure(br);
+        iterate_and_prune_ifaces(br, set_iface_properties, NULL);
     }
+
+    ovsrec_open_vswitch_set_cur_cfg(ovs_cfg, ovs_cfg->next_cfg);
+
+    ovsdb_idl_txn_commit(txn);
+    ovsdb_idl_txn_destroy(txn); /* XXX */
+}
+
+static const char *
+bridge_get_other_config(const struct ovsrec_bridge *br_cfg, const char *key)
+{
+    size_t i;
+
+    for (i = 0; i < br_cfg->n_other_config; i++) {
+        if (!strcmp(br_cfg->key_other_config[i], key)) {
+            return br_cfg->value_other_config[i];
+        }
+    }
+    return NULL;
 }
 
 static void
 bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
-                          const char **devname)
+                          struct iface **hw_addr_iface)
 {
-    uint64_t requested_ea;
+    const char *hwaddr;
     size_t i, j;
     int error;
 
-    *devname = NULL;
+    *hw_addr_iface = NULL;
 
     /* Did the user request a particular MAC? */
-    requested_ea = cfg_get_mac(0, "bridge.%s.mac", br->name);
-    if (requested_ea) {
-        eth_addr_from_uint64(requested_ea, ea);
+    hwaddr = bridge_get_other_config(br->cfg, "hwaddr");
+    if (hwaddr && eth_addr_from_string(hwaddr, ea)) {
         if (eth_addr_is_multicast(ea)) {
             VLOG_ERR("bridge %s: cannot set MAC address to multicast "
                      "address "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(ea));
@@ -637,14 +842,12 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
         }
     }
 
-    /* Otherwise choose the minimum MAC address among all of the interfaces.
-     * (Xen uses FE:FF:FF:FF:FF:FF for virtual interfaces so this will get the
-     * MAC of the physical interface in such an environment.) */
+    /* Otherwise choose the minimum non-local MAC address among all of the
+     * interfaces. */
     memset(ea, 0xff, sizeof ea);
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
         uint8_t iface_ea[ETH_ADDR_LEN];
-        uint64_t iface_ea_u64;
         struct iface *iface;
 
         /* Mirror output ports don't participate. */
@@ -653,18 +856,14 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
         }
 
         /* Choose the MAC address to represent the port. */
-        iface_ea_u64 = cfg_get_mac(0, "port.%s.mac", port->name);
-        if (iface_ea_u64) {
-            /* User specified explicitly. */
-            eth_addr_from_uint64(iface_ea_u64, iface_ea);
-
+        if (port->cfg->mac && eth_addr_from_string(port->cfg->mac, iface_ea)) {
             /* Find the interface with this Ethernet address (if any) so that
              * we can provide the correct devname to the caller. */
             iface = NULL;
             for (j = 0; j < port->n_ifaces; j++) {
                 struct iface *candidate = port->ifaces[j];
                 uint8_t candidate_ea[ETH_ADDR_LEN];
-                if (!netdev_nodev_get_etheraddr(candidate->name, candidate_ea)
+                if (!netdev_get_etheraddr(candidate->netdev, candidate_ea)
                     && eth_addr_equals(iface_ea, candidate_ea)) {
                     iface = candidate;
                 }
@@ -685,16 +884,13 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
             }
 
             /* The local port doesn't count (since we're trying to choose its
-             * MAC address anyway).  Other internal ports don't count because
-             * we really want a physical MAC if we can get it, and internal
-             * ports typically have randomly generated MACs. */
-            if (iface->dp_ifidx == ODPP_LOCAL
-                || cfg_get_bool(0, "iface.%s.internal", iface->name)) {
+             * MAC address anyway). */
+            if (iface->dp_ifidx == ODPP_LOCAL) {
                 continue;
             }
 
             /* Grab MAC. */
-            error = netdev_nodev_get_etheraddr(iface->name, iface_ea);
+            error = netdev_get_etheraddr(iface->netdev, iface_ea);
             if (error) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_ERR_RL(&rl, "failed to obtain Ethernet address of %s: %s",
@@ -705,17 +901,18 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
 
         /* Compare against our current choice. */
         if (!eth_addr_is_multicast(iface_ea) &&
+            !eth_addr_is_local(iface_ea) &&
             !eth_addr_is_reserved(iface_ea) &&
             !eth_addr_is_zero(iface_ea) &&
             memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0)
         {
             memcpy(ea, iface_ea, ETH_ADDR_LEN);
-            *devname = iface ? iface->name : NULL;
+            *hw_addr_iface = iface;
         }
     }
-    if (eth_addr_is_multicast(ea) || eth_addr_is_vif(ea)) {
+    if (eth_addr_is_multicast(ea)) {
         memcpy(ea, br->default_ea, ETH_ADDR_LEN);
-        *devname = NULL;
+        *hw_addr_iface = NULL;
         VLOG_WARN("bridge %s: using default bridge Ethernet "
                   "address "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(ea));
     } else {
@@ -726,13 +923,13 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
 
 /* Choose and returns the datapath ID for bridge 'br' given that the bridge
  * Ethernet address is 'bridge_ea'.  If 'bridge_ea' is the Ethernet address of
- * a network device, then that network device's name must be passed in as
- * 'devname'; if 'bridge_ea' was derived some other way, then 'devname' must be
- * passed in as a null pointer. */
+ * an interface on 'br', then that interface must be passed in as
+ * 'hw_addr_iface'; if 'bridge_ea' was derived some other way, then
+ * 'hw_addr_iface' must be passed in as a null pointer. */
 static uint64_t
 bridge_pick_datapath_id(struct bridge *br,
                         const uint8_t bridge_ea[ETH_ADDR_LEN],
-                        const char *devname)
+                        struct iface *hw_addr_iface)
 {
     /*
      * The procedure for choosing a bridge MAC address will, in the most
@@ -746,16 +943,17 @@ bridge_pick_datapath_id(struct bridge *br,
      * stable from one run to the next, so that policy set on a datapath
      * "sticks".
      */
+    const char *datapath_id;
     uint64_t dpid;
 
-    dpid = cfg_get_dpid(0, "bridge.%s.datapath-id", br->name);
-    if (dpid) {
+    datapath_id = bridge_get_other_config(br->cfg, "datapath-id");
+    if (datapath_id && dpid_from_string(datapath_id, &dpid)) {
         return dpid;
     }
 
-    if (devname) {
+    if (hw_addr_iface) {
         int vlan;
-        if (!netdev_get_vlan_vid(devname, &vlan)) {
+        if (!netdev_get_vlan_vid(hw_addr_iface->netdev, &vlan)) {
             /*
              * A bridge whose MAC address is taken from a VLAN network device
              * (that is, a network device created with vconfig(8) or similar
@@ -848,7 +1046,6 @@ bridge_wait(void)
 
         mac_learning_wait(br->ml);
         bond_wait(br);
-        brstp_wait(br);
     }
 }
 
@@ -861,10 +1058,31 @@ bridge_flush(struct bridge *br)
     br->flush = true;
     mac_learning_flush(br->ml);
 }
+
+/* Returns the 'br' interface for the ODPP_LOCAL port, or null if 'br' has no
+ * such interface. */
+static struct iface *
+bridge_get_local_iface(struct bridge *br)
+{
+    size_t i, j;
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct port *port = br->ports[i];
+        for (j = 0; j < port->n_ifaces; j++) {
+            struct iface *iface = port->ifaces[j];
+            if (iface->dp_ifidx == ODPP_LOCAL) {
+                return iface;
+            }
+        }
+    }
+
+    return NULL;
+}
 \f
 /* Bridge unixctl user interface functions. */
 static void
-bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_fdb_show(struct unixctl_conn *conn,
+                        const char *args, void *aux OVS_UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -890,45 +1108,39 @@ bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
 }
 \f
 /* Bridge reconfiguration functions. */
-
 static struct bridge *
-bridge_create(const char *name)
+bridge_create(const struct ovsrec_bridge *br_cfg)
 {
     struct bridge *br;
     int error;
 
-    assert(!bridge_lookup(name));
-    br = xcalloc(1, sizeof *br);
+    assert(!bridge_lookup(br_cfg->name));
+    br = xzalloc(sizeof *br);
 
-    error = dpif_create(name, &br->dpif);
-    if (error == EEXIST) {
-        error = dpif_open(name, &br->dpif);
-        if (error) {
-            VLOG_ERR("datapath %s already exists but cannot be opened: %s",
-                     name, strerror(error));
-            free(br);
-            return NULL;
-        }
-        dpif_flow_flush(&br->dpif);
-    } else if (error) {
-        VLOG_ERR("failed to create datapath %s: %s", name, strerror(error));
+    error = dpif_create_and_open(br_cfg->name, br_cfg->datapath_type,
+                                 &br->dpif);
+    if (error) {
         free(br);
         return NULL;
     }
+    dpif_flow_flush(br->dpif);
 
-    error = ofproto_create(name, &bridge_ofhooks, br, &br->ofproto);
+    error = ofproto_create(br_cfg->name, br_cfg->datapath_type, &bridge_ofhooks,
+                           br, &br->ofproto);
     if (error) {
-        VLOG_ERR("failed to create switch %s: %s", name, strerror(error));
-        dpif_delete(&br->dpif);
-        dpif_close(&br->dpif);
+        VLOG_ERR("failed to create switch %s: %s", br_cfg->name,
+                 strerror(error));
+        dpif_delete(br->dpif);
+        dpif_close(br->dpif);
         free(br);
         return NULL;
     }
 
-    br->name = xstrdup(name);
+    br->name = xstrdup(br_cfg->name);
+    br->cfg = br_cfg;
     br->ml = mac_learning_create();
     br->sent_config_request = false;
-    eth_addr_random(br->default_ea);
+    eth_addr_nicira_random(br->default_ea);
 
     port_array_init(&br->ifaces);
 
@@ -937,7 +1149,7 @@ bridge_create(const char *name)
 
     list_push_back(&all_bridges, &br->node);
 
-    VLOG_INFO("created bridge %s on dp%u", br->name, dpif_id(&br->dpif));
+    VLOG_INFO("created bridge %s on %s", br->name, dpif_name(br->dpif));
 
     return br;
 }
@@ -952,12 +1164,12 @@ bridge_destroy(struct bridge *br)
             port_destroy(br->ports[br->n_ports - 1]);
         }
         list_remove(&br->node);
-        error = dpif_delete(&br->dpif);
+        error = dpif_delete(br->dpif);
         if (error && error != ENOENT) {
-            VLOG_ERR("failed to delete dp%u: %s",
-                     dpif_id(&br->dpif), strerror(error));
+            VLOG_ERR("failed to delete %s: %s",
+                     dpif_name(br->dpif), strerror(error));
         }
-        dpif_close(&br->dpif);
+        dpif_close(br->dpif);
         ofproto_destroy(br->ofproto);
         free(br->controller);
         mac_learning_destroy(br->ml);
@@ -997,7 +1209,8 @@ bridge_get_datapathid(const char *name)
 /* Handle requests for a listing of all flows known by the OpenFlow
  * stack, including those normally hidden. */
 static void
-bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_dump_flows(struct unixctl_conn *conn,
+                          const char *args, void *aux OVS_UNUSED)
 {
     struct bridge *br;
     struct ds results;
@@ -1027,7 +1240,6 @@ bridge_run_one(struct bridge *br)
 
     mac_learning_run(br->ml, ofproto_get_revalidate_set(br->ofproto));
     bond_run(br);
-    brstp_run(br);
 
     error = ofproto_run2(br->ofproto, br->flush);
     br->flush = false;
@@ -1035,103 +1247,178 @@ bridge_run_one(struct bridge *br)
     return error;
 }
 
-static const char *
-bridge_get_controller(const struct bridge *br)
+static const struct ovsrec_controller *
+bridge_get_controller(const struct ovsrec_open_vswitch *ovs_cfg,
+                      const struct bridge *br)
+{
+    const struct ovsrec_controller *controller;
+
+    controller = (br->cfg->controller ? br->cfg->controller
+                  : ovs_cfg->controller ? ovs_cfg->controller
+                  : NULL);
+
+    if (controller && !strcmp(controller->target, "none")) {
+        return NULL;
+    }
+
+    return controller;
+}
+
+static bool
+check_duplicate_ifaces(struct bridge *br, struct iface *iface, void *ifaces_)
+{
+    struct svec *ifaces = ifaces_;
+    if (!svec_contains(ifaces, iface->name)) {
+        svec_add(ifaces, iface->name);
+        svec_sort(ifaces);
+        return true;
+    } else {
+        VLOG_ERR("bridge %s: %s interface is on multiple ports, "
+                 "removing from %s",
+                 br->name, iface->name, iface->port->name);
+        return false;
+    }
+}
+
+static void
+bridge_update_desc(struct bridge *br)
 {
-    const char *controller;
+#if 0
+    bool changed = false;
+    const char *desc;
+
+    desc = cfg_get_string(0, "bridge.%s.mfr-desc", br->name);
+    if (desc != br->mfr_desc) {
+        free(br->mfr_desc);
+        if (desc) {
+            br->mfr_desc = xstrdup(desc);
+        } else {
+            br->mfr_desc = xstrdup(DEFAULT_MFR_DESC);
+        }
+        changed = true;
+    }
+
+    desc = cfg_get_string(0, "bridge.%s.hw-desc", br->name);
+    if (desc != br->hw_desc) {
+        free(br->hw_desc);
+        if (desc) {
+            br->hw_desc = xstrdup(desc);
+        } else {
+            br->hw_desc = xstrdup(DEFAULT_HW_DESC);
+        }
+        changed = true;
+    }
+
+    desc = cfg_get_string(0, "bridge.%s.sw-desc", br->name);
+    if (desc != br->sw_desc) {
+        free(br->sw_desc);
+        if (desc) {
+            br->sw_desc = xstrdup(desc);
+        } else {
+            br->sw_desc = xstrdup(DEFAULT_SW_DESC);
+        }
+        changed = true;
+    }
+
+    desc = cfg_get_string(0, "bridge.%s.serial-desc", br->name);
+    if (desc != br->serial_desc) {
+        free(br->serial_desc);
+        if (desc) {
+            br->serial_desc = xstrdup(desc);
+        } else {
+            br->serial_desc = xstrdup(DEFAULT_SERIAL_DESC);
+        }
+        changed = true;
+    }
 
-    controller = cfg_get_string(0, "bridge.%s.controller", br->name);
-    if (!controller) {
-        controller = cfg_get_string(0, "mgmt.controller");
+    desc = cfg_get_string(0, "bridge.%s.dp-desc", br->name);
+    if (desc != br->dp_desc) {
+        free(br->dp_desc);
+        if (desc) {
+            br->dp_desc = xstrdup(desc);
+        } else {
+            br->dp_desc = xstrdup(DEFAULT_DP_DESC);
+        }
+        changed = true;
     }
-    return controller && controller[0] ? controller : NULL;
+
+    if (changed) {
+        ofproto_set_desc(br->ofproto, br->mfr_desc, br->hw_desc,
+                br->sw_desc, br->serial_desc, br->dp_desc);
+    }
+#endif
 }
 
 static void
-bridge_reconfigure_one(struct bridge *br)
+bridge_reconfigure_one(const struct ovsrec_open_vswitch *ovs_cfg,
+                       struct bridge *br)
 {
-    struct svec old_ports, new_ports, ifaces;
+    struct shash old_ports, new_ports;
+    struct svec ifaces;
     struct svec listeners, old_listeners;
     struct svec snoops, old_snoops;
-    size_t i, j;
+    struct shash_node *node;
+    size_t i;
 
     /* Collect old ports. */
-    svec_init(&old_ports);
+    shash_init(&old_ports);
     for (i = 0; i < br->n_ports; i++) {
-        svec_add(&old_ports, br->ports[i]->name);
+        shash_add(&old_ports, br->ports[i]->name, br->ports[i]);
     }
-    svec_sort(&old_ports);
-    assert(svec_is_unique(&old_ports));
 
     /* Collect new ports. */
-    svec_init(&new_ports);
-    cfg_get_all_keys(&new_ports, "bridge.%s.port", br->name);
-    svec_sort(&new_ports);
-    if (bridge_get_controller(br) && !svec_contains(&new_ports, br->name)) {
-        svec_add(&new_ports, br->name);
-        svec_sort(&new_ports);
-    }
-    if (!svec_is_unique(&new_ports)) {
-        VLOG_WARN("bridge %s: %s specified twice as bridge port",
-                  br->name, svec_get_duplicate(&new_ports));
-        svec_unique(&new_ports);
+    shash_init(&new_ports);
+    for (i = 0; i < br->cfg->n_ports; i++) {
+        const char *name = br->cfg->ports[i]->name;
+        if (!shash_add_once(&new_ports, name, br->cfg->ports[i])) {
+            VLOG_WARN("bridge %s: %s specified twice as bridge port",
+                      br->name, name);
+        }
     }
 
-    ofproto_set_mgmt_id(br->ofproto, mgmt_id);
+    /* If we have a controller, then we need a local port.  Complain if the
+     * user didn't specify one.
+     *
+     * XXX perhaps we should synthesize a port ourselves in this case. */
+    if (bridge_get_controller(ovs_cfg, br)) {
+        char local_name[IF_NAMESIZE];
+        int error;
 
-    /* Get rid of deleted ports and add new ports. */
-    for (i = 0; i < br->n_ports; ) {
-        struct port *port = br->ports[i];
-        if (!svec_contains(&new_ports, port->name)) {
-            port_destroy(port);
-        } else {
-            i++;
+        error = dpif_port_get_name(br->dpif, ODPP_LOCAL,
+                                   local_name, sizeof local_name);
+        if (!error && !shash_find(&new_ports, local_name)) {
+            VLOG_WARN("bridge %s: controller specified but no local port "
+                      "(port named %s) defined",
+                      br->name, local_name);
         }
     }
-    for (i = 0; i < new_ports.n; i++) {
-        const char *name = new_ports.names[i];
-        if (!svec_contains(&old_ports, name)) {
-            port_create(br, name);
+
+    /* Get rid of deleted ports and add new ports. */
+    SHASH_FOR_EACH (node, &old_ports) {
+        if (!shash_find(&new_ports, node->name)) {
+            port_destroy(node->data);
         }
     }
-    svec_destroy(&old_ports);
-    svec_destroy(&new_ports);
-
-    /* Reconfigure all ports. */
-    for (i = 0; i < br->n_ports; i++) {
-        port_reconfigure(br->ports[i]);
+    SHASH_FOR_EACH (node, &new_ports) {
+        struct port *port = shash_find_data(&old_ports, node->name);
+        if (!port) {
+            port = port_create(br, node->name);
+        }
+        port_reconfigure(port, node->data);
     }
+    shash_destroy(&old_ports);
+    shash_destroy(&new_ports);
 
     /* Check and delete duplicate interfaces. */
     svec_init(&ifaces);
-    for (i = 0; i < br->n_ports; ) {
-        struct port *port = br->ports[i];
-        for (j = 0; j < port->n_ifaces; ) {
-            struct iface *iface = port->ifaces[j];
-            if (svec_contains(&ifaces, iface->name)) {
-                VLOG_ERR("bridge %s: %s interface is on multiple ports, "
-                         "removing from %s",
-                         br->name, iface->name, port->name);
-                iface_destroy(iface);
-            } else {
-                svec_add(&ifaces, iface->name);
-                svec_sort(&ifaces);
-                j++;
-            }
-        }
-        if (!port->n_ifaces) {
-            VLOG_ERR("%s port has no interfaces, dropping", port->name);
-            port_destroy(port);
-        } else {
-            i++;
-        }
-    }
+    iterate_and_prune_ifaces(br, check_duplicate_ifaces, &ifaces);
     svec_destroy(&ifaces);
 
     /* Delete all flows if we're switching from connected to standalone or vice
      * versa.  (XXX Should we delete all flows if we are switching from one
      * controller to another?) */
 
+#if 0
     /* Configure OpenFlow management listeners. */
     svec_init(&listeners);
     cfg_get_all_strings(&listeners, "bridge.%s.openflow.listeners", br->name);
@@ -1173,135 +1460,115 @@ bridge_reconfigure_one(struct bridge *br)
     }
     svec_destroy(&snoops);
     svec_destroy(&old_snoops);
+#else
+    /* Default listener. */
+    svec_init(&listeners);
+    svec_add_nocopy(&listeners, xasprintf("punix:%s/%s.mgmt",
+                                          ovs_rundir, br->name));
+    svec_init(&old_listeners);
+    ofproto_get_listeners(br->ofproto, &old_listeners);
+    if (!svec_equal(&listeners, &old_listeners)) {
+        ofproto_set_listeners(br->ofproto, &listeners);
+    }
+    svec_destroy(&listeners);
+    svec_destroy(&old_listeners);
+
+    /* Default snoop. */
+    svec_init(&snoops);
+    svec_add_nocopy(&snoops, xasprintf("punix:%s/%s.snoop",
+                                       ovs_rundir, br->name));
+    svec_init(&old_snoops);
+    ofproto_get_snoops(br->ofproto, &old_snoops);
+    if (!svec_equal(&snoops, &old_snoops)) {
+        ofproto_set_snoops(br->ofproto, &snoops);
+    }
+    svec_destroy(&snoops);
+    svec_destroy(&old_snoops);
+#endif
 
     mirror_reconfigure(br);
+
+    bridge_update_desc(br);
 }
 
 static void
-bridge_reconfigure_controller(struct bridge *br)
+bridge_reconfigure_controller(const struct ovsrec_open_vswitch *ovs_cfg,
+                              struct bridge *br)
 {
-    char *pfx = xasprintf("bridge.%s.controller", br->name);
-    const char *controller;
+    const struct ovsrec_controller *c;
 
-    controller = bridge_get_controller(br);
-    if ((br->controller != NULL) != (controller != NULL)) {
+    c = bridge_get_controller(ovs_cfg, br);
+    if ((br->controller != NULL) != (c != NULL)) {
         ofproto_flush_flows(br->ofproto);
     }
     free(br->controller);
-    br->controller = controller ? xstrdup(controller) : NULL;
+    br->controller = c ? xstrdup(c->target) : NULL;
 
-    if (controller) {
-        const char *fail_mode;
+    if (c) {
         int max_backoff, probe;
         int rate_limit, burst_limit;
 
-        if (!strcmp(controller, "discover")) {
-            bool update_resolv_conf = true;
-
-            if (cfg_has("%s.update-resolv.conf", pfx)) {
-                update_resolv_conf = cfg_get_bool(0, "%s.update-resolv.conf",
-                        pfx);
-            }
+        if (!strcmp(c->target, "discover")) {
             ofproto_set_discovery(br->ofproto, true,
-                                  cfg_get_string(0, "%s.accept-regex", pfx),
-                                  update_resolv_conf);
+                                  c->discover_accept_regex,
+                                  c->discover_update_resolv_conf);
         } else {
-            struct netdev *netdev;
+            struct iface *local_iface;
+            struct in_addr ip;
             bool in_band;
-            int error;
 
-            in_band = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED,
-                                     "%s.in-band", pfx)
-                       || cfg_get_bool(0, "%s.in-band", pfx));
+            in_band = (!c->connection_mode
+                       || !strcmp(c->connection_mode, "out-of-band"));
             ofproto_set_discovery(br->ofproto, false, NULL, NULL);
             ofproto_set_in_band(br->ofproto, in_band);
 
-            error = netdev_open(br->name, NETDEV_ETH_TYPE_NONE, &netdev);
-            if (!error) {
-                if (cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) {
-                    struct in_addr ip, mask, gateway;
-                    ip.s_addr = cfg_get_ip(0, "%s.ip", pfx);
-                    mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx);
-                    gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx);
-
-                    netdev_turn_flags_on(netdev, NETDEV_UP, true);
-                    if (!mask.s_addr) {
-                        mask.s_addr = guess_netmask(ip.s_addr);
-                    }
-                    if (!netdev_set_in4(netdev, ip, mask)) {
-                        VLOG_INFO("bridge %s: configured IP address "IP_FMT", "
-                                  "netmask "IP_FMT,
-                                  br->name, IP_ARGS(&ip.s_addr),
-                                  IP_ARGS(&mask.s_addr));
-                    }
+            local_iface = bridge_get_local_iface(br);
+            if (local_iface && c->local_ip && inet_aton(c->local_ip, &ip)) {
+                struct netdev *netdev = local_iface->netdev;
+                struct in_addr mask, gateway;
 
-                    if (gateway.s_addr) {
-                        if (!netdev_add_router(gateway)) {
-                            VLOG_INFO("bridge %s: configured gateway "IP_FMT,
-                                      br->name, IP_ARGS(&gateway.s_addr));
-                        }
+                if (!c->local_netmask || !inet_aton(c->local_netmask, &mask)) {
+                    mask.s_addr = 0;
+                }
+                if (!c->local_gateway
+                    || !inet_aton(c->local_gateway, &gateway)) {
+                    gateway.s_addr = 0;
+                }
+
+                netdev_turn_flags_on(netdev, NETDEV_UP, true);
+                if (!mask.s_addr) {
+                    mask.s_addr = guess_netmask(ip.s_addr);
+                }
+                if (!netdev_set_in4(netdev, ip, mask)) {
+                    VLOG_INFO("bridge %s: configured IP address "IP_FMT", "
+                              "netmask "IP_FMT,
+                              br->name, IP_ARGS(&ip.s_addr),
+                              IP_ARGS(&mask.s_addr));
+                }
+
+                if (gateway.s_addr) {
+                    if (!netdev_add_router(netdev, gateway)) {
+                        VLOG_INFO("bridge %s: configured gateway "IP_FMT,
+                                  br->name, IP_ARGS(&gateway.s_addr));
                     }
                 }
-                netdev_close(netdev);
             }
         }
 
-        fail_mode = cfg_get_string(0, "%s.fail-mode", pfx);
-        if (!fail_mode) {
-            fail_mode = cfg_get_string(0, "mgmt.fail-mode");
-        }
         ofproto_set_failure(br->ofproto,
-                            (!fail_mode
-                             || !strcmp(fail_mode, "standalone")
-                             || !strcmp(fail_mode, "open")));
-
-        probe = cfg_get_int(0, "%s.inactivity-probe", pfx);
-        if (probe < 5) {
-            probe = cfg_get_int(0, "mgmt.inactivity-probe");
-            if (probe < 5) {
-                probe = 5;
-            }
-        }
+                            (!c->fail_mode
+                             || !strcmp(c->fail_mode, "standalone")
+                             || !strcmp(c->fail_mode, "open")));
+
+        probe = c->inactivity_probe ? *c->inactivity_probe / 1000 : 5;
         ofproto_set_probe_interval(br->ofproto, probe);
 
-        max_backoff = cfg_get_int(0, "%s.max-backoff", pfx);
-        if (!max_backoff) {
-            max_backoff = cfg_get_int(0, "mgmt.max-backoff");
-            if (!max_backoff) {
-                max_backoff = 8;
-            }
-        }
+        max_backoff = c->max_backoff ? *c->max_backoff / 1000 : 8;
         ofproto_set_max_backoff(br->ofproto, max_backoff);
 
-        rate_limit = cfg_get_int(0, "%s.rate-limit", pfx);
-        if (!rate_limit) {
-            rate_limit = cfg_get_int(0, "mgmt.rate-limit");
-        }
-        burst_limit = cfg_get_int(0, "%s.burst-limit", pfx);
-        if (!burst_limit) {
-            burst_limit = cfg_get_int(0, "mgmt.burst-limit");
-        }
+        rate_limit = c->controller_rate_limit ? *c->controller_rate_limit : 0;
+        burst_limit = c->controller_burst_limit ? *c->controller_burst_limit : 0;
         ofproto_set_rate_limit(br->ofproto, rate_limit, burst_limit);
-
-        ofproto_set_stp(br->ofproto, cfg_get_bool(0, "%s.stp", pfx));
-
-        if (cfg_has("%s.commands.acl", pfx)) {
-            struct svec command_acls;
-            char *command_acl;
-
-            svec_init(&command_acls);
-            cfg_get_all_strings(&command_acls, "%s.commands.acl", pfx);
-            command_acl = svec_join(&command_acls, ",", "");
-
-            ofproto_set_remote_execution(br->ofproto, command_acl,
-                                         cfg_get_string(0, "%s.commands.dir",
-                                                        pfx));
-
-            svec_destroy(&command_acls);
-            free(command_acl);
-        } else {
-            ofproto_set_remote_execution(br->ofproto, NULL, NULL);
-        }
     } else {
         union ofp_action action;
         flow_t flow;
@@ -1320,31 +1587,27 @@ bridge_reconfigure_controller(struct bridge *br)
         ofproto_set_max_backoff(br->ofproto, 1);
         ofproto_set_probe_interval(br->ofproto, 5);
         ofproto_set_failure(br->ofproto, false);
-        ofproto_set_stp(br->ofproto, false);
     }
-    free(pfx);
 
     ofproto_set_controller(br->ofproto, br->controller);
 }
 
 static void
-bridge_get_all_ifaces(const struct bridge *br, struct svec *ifaces)
+bridge_get_all_ifaces(const struct bridge *br, struct shash *ifaces)
 {
     size_t i, j;
 
-    svec_init(ifaces);
+    shash_init(ifaces);
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
         for (j = 0; j < port->n_ifaces; j++) {
             struct iface *iface = port->ifaces[j];
-            svec_add(ifaces, iface->name);
+            shash_add_once(ifaces, iface->name, iface);
         }
-        if (port->n_ifaces > 1
-            && cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) {
-            svec_add(ifaces, port->name);
+        if (port->n_ifaces > 1 && port->cfg->bond_fake_iface) {
+            shash_add_once(ifaces, port->name, NULL);
         }
     }
-    svec_sort_unique(ifaces);
 }
 
 /* For robustness, in case the administrator moves around datapath ports behind
@@ -1372,21 +1635,28 @@ bridge_fetch_dp_ifaces(struct bridge *br)
     }
     port_array_clear(&br->ifaces);
 
-    dpif_port_list(&br->dpif, &dpif_ports, &n_dpif_ports);
+    dpif_port_list(br->dpif, &dpif_ports, &n_dpif_ports);
     for (i = 0; i < n_dpif_ports; i++) {
         struct odp_port *p = &dpif_ports[i];
         struct iface *iface = iface_lookup(br, p->devname);
         if (iface) {
             if (iface->dp_ifidx >= 0) {
-                VLOG_WARN("dp%u reported interface %s twice",
-                          dpif_id(&br->dpif), p->devname);
+                VLOG_WARN("%s reported interface %s twice",
+                          dpif_name(br->dpif), p->devname);
             } else if (iface_from_dp_ifidx(br, p->port)) {
-                VLOG_WARN("dp%u reported interface %"PRIu16" twice",
-                          dpif_id(&br->dpif), p->port);
+                VLOG_WARN("%s reported interface %"PRIu16" twice",
+                          dpif_name(br->dpif), p->port);
             } else {
                 port_array_set(&br->ifaces, p->port, iface);
                 iface->dp_ifidx = p->port;
             }
+
+            if (iface->cfg) {
+                int64_t ofport = (iface->dp_ifidx >= 0
+                                  ? odp_port_to_ofp_port(iface->dp_ifidx)
+                                  : -1);
+                ovsrec_interface_set_ofport(iface->cfg, &ofport, 1);
+            }
         }
     }
     free(dpif_ports);
@@ -1616,18 +1886,6 @@ set_dst(struct dst *p, const flow_t *flow,
         const struct port *in_port, const struct port *out_port,
         tag_type *tags)
 {
-    /* STP handling.
-     *
-     * XXX This uses too many tags: any broadcast flow will get one tag per
-     * destination port, and thus a broadcast on a switch of any size is likely
-     * to have all tag bits set.  We should figure out a way to be smarter.
-     *
-     * This is OK when STP is disabled, because stp_state_tag is 0 then. */
-    *tags |= out_port->stp_state_tag;
-    if (!(out_port->stp_state & (STP_DISABLED | STP_FORWARDING))) {
-        return false;
-    }
-
     p->vlan = (out_port->vlan >= 0 ? OFP_VLAN_NONE
               : in_port->vlan >= 0 ? in_port->vlan
               : ntohs(flow->dl_vlan));
@@ -1720,7 +1978,6 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
     struct dst *dst = dsts;
     size_t i;
 
-    *tags |= in_port->stp_state_tag;
     if (out_port == FLOOD_PORT) {
         /* XXX use ODP_FLOOD if no vlans or bonding. */
         /* XXX even better, define each VLAN as a datapath port group */
@@ -1789,7 +2046,7 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
     return dst - dsts;
 }
 
-static void UNUSED
+static void OVS_UNUSED
 print_dsts(const struct dst *dsts, size_t n)
 {
     for (; n--; dsts++) {
@@ -1949,13 +2206,6 @@ process_flow(struct bridge *br, const flow_t *flow,
         goto done;
     }
 
-    /* Drop frames for ports that STP wants entirely killed (both for
-     * forwarding and for learning).  Later, after we do learning, we'll drop
-     * the frames that STP wants to do learning but not forwarding on. */
-    if (in_port->stp_state & (STP_LISTENING | STP_BLOCKING)) {
-        goto done;
-    }
-
     /* Drop frames for reserved multicast addresses. */
     if (eth_addr_is_reserved(flow->dl_dst)) {
         goto done;
@@ -2014,9 +2264,8 @@ process_flow(struct bridge *br, const flow_t *flow,
         return false;
     }
 
-    /* Don't send packets out their input ports.  Don't forward frames that STP
-     * wants us to discard. */
-    if (in_port == out_port || in_port->stp_state == STP_LEARNING) {
+    /* Don't send packets out their input ports. */
+    if (in_port == out_port) {
         out_port = NULL;
     }
 
@@ -2056,7 +2305,6 @@ bridge_port_changed_ofhook_cb(enum ofp_port_reason reason,
 
         bridge_flush(br);
     } else {
-        memcpy(iface->mac, opp->hw_addr, ETH_ADDR_LEN);
         if (port->n_ifaces > 1) {
             bool up = !(opp->state & OFPPS_LINK_DOWN);
             bond_link_status_update(iface, up);
@@ -2072,14 +2320,6 @@ bridge_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet,
 {
     struct bridge *br = br_;
 
-#if 0
-    if (flow->dl_type == htons(OFP_DL_TYPE_NOT_ETH_TYPE)
-        && eth_addr_equals(flow->dl_dst, stp_eth_addr)) {
-        brstp_receive(br, flow, payload);
-        return true;
-    }
-#endif
-
     COVERAGE_INC(bridge_process_flow);
     return process_flow(br, flow, packet, actions, tags, nf_output_iface);
 }
@@ -2520,7 +2760,8 @@ bond_send_learning_packets(struct port *port)
 /* Bonding unixctl user interface functions. */
 
 static void
-bond_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+bond_unixctl_list(struct unixctl_conn *conn,
+                  const char *args OVS_UNUSED, void *aux OVS_UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -2570,7 +2811,8 @@ bond_find(const char *name)
 }
 
 static void
-bond_unixctl_show(struct unixctl_conn *conn, const char *args)
+bond_unixctl_show(struct unixctl_conn *conn,
+                  const char *args, void *aux OVS_UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct port *port;
@@ -2635,7 +2877,8 @@ bond_unixctl_show(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_,
+                     void *aux OVS_UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2691,7 +2934,8 @@ bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
 }
 
 static void
-bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_,
+                              void *aux OVS_UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2771,45 +3015,68 @@ enable_slave(struct unixctl_conn *conn, const char *args_, bool enable)
 }
 
 static void
-bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args,
+                          void *aux OVS_UNUSED)
 {
     enable_slave(conn, args, true);
 }
 
 static void
-bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args,
+                           void *aux OVS_UNUSED)
 {
     enable_slave(conn, args, false);
 }
 
+static void
+bond_unixctl_hash(struct unixctl_conn *conn, const char *args,
+                  void *aux OVS_UNUSED)
+{
+       uint8_t mac[ETH_ADDR_LEN];
+       uint8_t hash;
+       char *hash_cstr;
+
+       if (sscanf(args, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))
+           == ETH_ADDR_SCAN_COUNT) {
+               hash = bond_hash(mac);
+
+               hash_cstr = xasprintf("%u", hash);
+               unixctl_command_reply(conn, 200, hash_cstr);
+               free(hash_cstr);
+       } else {
+               unixctl_command_reply(conn, 501, "invalid mac");
+       }
+}
+
 static void
 bond_init(void)
 {
-    unixctl_command_register("bond/list", bond_unixctl_list);
-    unixctl_command_register("bond/show", bond_unixctl_show);
-    unixctl_command_register("bond/migrate", bond_unixctl_migrate);
+    unixctl_command_register("bond/list", bond_unixctl_list, NULL);
+    unixctl_command_register("bond/show", bond_unixctl_show, NULL);
+    unixctl_command_register("bond/migrate", bond_unixctl_migrate, NULL);
     unixctl_command_register("bond/set-active-slave",
-                             bond_unixctl_set_active_slave);
-    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave);
-    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave);
+                             bond_unixctl_set_active_slave, NULL);
+    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave,
+                             NULL);
+    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave,
+                             NULL);
+    unixctl_command_register("bond/hash", bond_unixctl_hash, NULL);
 }
 \f
 /* Port functions. */
 
-static void
+static struct port *
 port_create(struct bridge *br, const char *name)
 {
     struct port *port;
 
-    port = xcalloc(1, sizeof *port);
+    port = xzalloc(sizeof *port);
     port->bridge = br;
     port->port_idx = br->n_ports;
     port->vlan = -1;
     port->trunks = NULL;
     port->name = xstrdup(name);
     port->active_iface = -1;
-    port->stp_state = STP_DISABLED;
-    port->stp_state_tag = 0;
 
     if (br->n_ports >= br->allocated_ports) {
         br->ports = x2nrealloc(br->ports, &br->allocated_ports,
@@ -2819,70 +3086,70 @@ port_create(struct bridge *br, const char *name)
 
     VLOG_INFO("created port %s on bridge %s", port->name, br->name);
     bridge_flush(br);
+
+    return port;
 }
 
 static void
-port_reconfigure(struct port *port)
+port_reconfigure(struct port *port, const struct ovsrec_port *cfg)
 {
-    bool bonded = cfg_has_section("bonding.%s", port->name);
-    struct svec old_ifaces, new_ifaces;
+    struct shash old_ifaces, new_ifaces;
+    struct shash_node *node;
     unsigned long *trunks;
     int vlan;
     size_t i;
 
+    port->cfg = cfg;
+
     /* Collect old and new interfaces. */
-    svec_init(&old_ifaces);
-    svec_init(&new_ifaces);
+    shash_init(&old_ifaces);
+    shash_init(&new_ifaces);
     for (i = 0; i < port->n_ifaces; i++) {
-        svec_add(&old_ifaces, port->ifaces[i]->name);
+        shash_add(&old_ifaces, port->ifaces[i]->name, port->ifaces[i]);
     }
-    svec_sort(&old_ifaces);
-    if (bonded) {
-        cfg_get_all_keys(&new_ifaces, "bonding.%s.slave", port->name);
-        if (!new_ifaces.n) {
-            VLOG_ERR("port %s: no interfaces specified for bonded port",
-                     port->name);
-        } else if (new_ifaces.n == 1) {
-            VLOG_WARN("port %s: only 1 interface specified for bonded port",
-                      port->name);
+    for (i = 0; i < cfg->n_interfaces; i++) {
+        const char *name = cfg->interfaces[i]->name;
+        if (!shash_add_once(&new_ifaces, name, cfg->interfaces[i])) {
+            VLOG_WARN("port %s: %s specified twice as port interface",
+                      port->name, name);
         }
-
-        port->updelay = cfg_get_int(0, "bonding.%s.updelay", port->name);
-        if (port->updelay < 0) {
-            port->updelay = 0;
-        }
-        port->downdelay = cfg_get_int(0, "bonding.%s.downdelay", port->name);
-        if (port->downdelay < 0) {
-            port->downdelay = 0;
-        }
-    } else {
-        svec_init(&new_ifaces);
-        svec_add(&new_ifaces, port->name);
+    }
+    port->updelay = cfg->bond_updelay;
+    if (port->updelay < 0) {
+        port->updelay = 0;
+    }
+    port->updelay = cfg->bond_downdelay;
+    if (port->downdelay < 0) {
+        port->downdelay = 0;
     }
 
     /* Get rid of deleted interfaces and add new interfaces. */
-    for (i = 0; i < port->n_ifaces; i++) {
-        struct iface *iface = port->ifaces[i];
-        if (!svec_contains(&new_ifaces, iface->name)) {
-            iface_destroy(iface);
-        } else {
-            i++;
+    SHASH_FOR_EACH (node, &old_ifaces) {
+        if (!shash_find(&new_ifaces, node->name)) {
+            iface_destroy(node->data);
         }
     }
-    for (i = 0; i < new_ifaces.n; i++) {
-        const char *name = new_ifaces.names[i];
-        if (!svec_contains(&old_ifaces, name)) {
-            iface_create(port, name);
+    SHASH_FOR_EACH (node, &new_ifaces) {
+        const struct ovsrec_interface *if_cfg = node->data;
+        struct iface *iface;
+
+        iface = shash_find_data(&old_ifaces, if_cfg->name);
+        if (!iface) {
+            iface_create(port, if_cfg);
+        } else {
+            iface->cfg = if_cfg;
         }
     }
 
     /* Get VLAN tag. */
     vlan = -1;
-    if (cfg_has("vlan.%s.tag", port->name)) {
-        if (!bonded) {
-            vlan = cfg_get_vlan(0, "vlan.%s.tag", port->name);
+    if (cfg->tag) {
+        if (port->n_ifaces < 2) {
+            vlan = *cfg->tag;
             if (vlan >= 0 && vlan <= 4095) {
                 VLOG_DBG("port %s: assigning VLAN tag %d", port->name, vlan);
+            } else {
+                vlan = -1;
             }
         } else {
             /* It's possible that bonded, VLAN-tagged ports make sense.  Maybe
@@ -2899,14 +3166,13 @@ port_reconfigure(struct port *port)
     /* Get trunked VLANs. */
     trunks = NULL;
     if (vlan < 0) {
-        size_t n_trunks, n_errors;
+        size_t n_errors;
         size_t i;
 
         trunks = bitmap_allocate(4096);
-        n_trunks = cfg_count("vlan.%s.trunks", port->name);
         n_errors = 0;
-        for (i = 0; i < n_trunks; i++) {
-            int trunk = cfg_get_vlan(i, "vlan.%s.trunks", port->name);
+        for (i = 0; i < cfg->n_trunks; i++) {
+            int trunk = cfg->trunks[i];
             if (trunk >= 0) {
                 bitmap_set1(trunks, trunk);
             } else {
@@ -2915,9 +3181,9 @@ port_reconfigure(struct port *port)
         }
         if (n_errors) {
             VLOG_ERR("port %s: invalid values for %zu trunk VLANs",
-                     port->name, n_trunks);
+                     port->name, cfg->n_trunks);
         }
-        if (n_errors == n_trunks) {
+        if (n_errors == cfg->n_trunks) {
             if (n_errors) {
                 VLOG_ERR("port %s: no valid trunks, trunking all VLANs",
                          port->name);
@@ -2925,9 +3191,9 @@ port_reconfigure(struct port *port)
             bitmap_set_multiple(trunks, 0, 4096, 1);
         }
     } else {
-        if (cfg_has("vlan.%s.trunks", port->name)) {
-            VLOG_ERR("ignoring vlan.%s.trunks in favor of vlan.%s.vlan",
-                     port->name, port->name);
+        if (cfg->n_trunks) {
+            VLOG_ERR("port %s: ignoring trunks in favor of implicit vlan",
+                     port->name);
         }
     }
     if (trunks == NULL
@@ -2938,8 +3204,8 @@ port_reconfigure(struct port *port)
     bitmap_free(port->trunks);
     port->trunks = trunks;
 
-    svec_destroy(&old_ifaces);
-    svec_destroy(&new_ifaces);
+    shash_destroy(&old_ifaces);
+    shash_destroy(&new_ifaces);
 }
 
 static void
@@ -2948,7 +3214,7 @@ port_destroy(struct port *port)
     if (port) {
         struct bridge *br = port->bridge;
         struct port *del;
-        size_t i;
+        int i;
 
         proc_net_compat_update_vlan(port->name, NULL, 0);
         proc_net_compat_update_bond(port->name, NULL);
@@ -3019,6 +3285,7 @@ port_update_bonding(struct port *port)
             free(port->bond_hash);
             port->bond_hash = NULL;
             port->bond_compat_is_stale = true;
+            port->bond_fake_iface = false;
         }
     } else {
         if (!port->bond_hash) {
@@ -3034,6 +3301,7 @@ port_update_bonding(struct port *port)
             bond_choose_active_iface(port);
         }
         port->bond_compat_is_stale = true;
+        port->bond_fake_iface = port->cfg->bond_fake_iface;
     }
 }
 
@@ -3090,13 +3358,13 @@ port_update_bond_compat(struct port *port)
         if (slave->up) {
             bond.up = true;
         }
-        memcpy(slave->mac, iface->mac, ETH_ADDR_LEN);
+        netdev_get_etheraddr(iface->netdev, slave->mac);
     }
 
-    if (cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) {
+    if (port->bond_fake_iface) {
         struct netdev *bond_netdev;
 
-        if (!netdev_open(port->name, NETDEV_ETH_TYPE_NONE, &bond_netdev)) {
+        if (!netdev_open_default(port->name, &bond_netdev)) {
             if (bond.up) {
                 netdev_turn_flags_on(bond_netdev, NETDEV_UP, true);
             } else {
@@ -3134,7 +3402,8 @@ port_update_vlan_compat(struct port *port)
                 && p->n_ifaces
                 && (!vlandev_name || strcmp(p->name, vlandev_name) <= 0))
             {
-                const uint8_t *ea = p->ifaces[0]->mac;
+                uint8_t ea[ETH_ADDR_LEN];
+                netdev_get_etheraddr(p->ifaces[0]->netdev, ea);
                 if (!eth_addr_is_multicast(ea) &&
                     !eth_addr_is_reserved(ea) &&
                     !eth_addr_is_zero(ea)) {
@@ -3148,30 +3417,22 @@ port_update_vlan_compat(struct port *port)
 \f
 /* Interface functions. */
 
-static void
-iface_create(struct port *port, const char *name)
+static struct iface *
+iface_create(struct port *port, const struct ovsrec_interface *if_cfg)
 {
     struct iface *iface;
+    char *name = if_cfg->name;
+    int error;
 
-    iface = xcalloc(1, sizeof *iface);
+    iface = xzalloc(sizeof *iface);
     iface->port = port;
     iface->port_ifidx = port->n_ifaces;
     iface->name = xstrdup(name);
     iface->dp_ifidx = -1;
     iface->tag = tag_create_random();
     iface->delay_expires = LLONG_MAX;
-
-    if (!cfg_get_bool(0, "iface.%s.internal", iface->name)) {
-        netdev_nodev_get_etheraddr(name, iface->mac);
-        netdev_nodev_get_carrier(name, &iface->enabled);
-    } else {
-        /* Internal interfaces are created later by the call to dpif_port_add()
-         * in bridge_reconfigure().  Until then, we can't obtain any
-         * information about them.  (There's no real value in doing so, anyway,
-         * because the 'mac' and 'enabled' values are only used for interfaces
-         * that are bond slaves, and it doesn't normally make sense to bond an
-         * internal interface.) */
-    }
+    iface->netdev = NULL;
+    iface->cfg = if_cfg;
 
     if (port->n_ifaces >= port->allocated_ifaces) {
         port->ifaces = x2nrealloc(port->ifaces, &port->allocated_ifaces,
@@ -3182,10 +3443,21 @@ iface_create(struct port *port, const char *name)
         port->bridge->has_bonded_ports = true;
     }
 
+    /* Attempt to create the network interface in case it
+     * doesn't exist yet. */
+    if (!iface_is_internal(port->bridge, iface->name)) {
+        error = set_up_iface(if_cfg, iface, true);
+        if (error) {
+            VLOG_WARN("could not create iface %s: %s", iface->name,
+                    strerror(error));
+        }
+    }
+
     VLOG_DBG("attached network device %s to port %s", iface->name, port->name);
 
-    port_update_bonding(port);
     bridge_flush(port->bridge);
+
+    return iface;
 }
 
 static void
@@ -3204,8 +3476,7 @@ iface_destroy(struct iface *iface)
         del = port->ifaces[iface->port_ifidx] = port->ifaces[--port->n_ifaces];
         del->port_ifidx = iface->port_ifidx;
 
-        free(iface->name);
-        free(iface);
+        netdev_close(iface->netdev);
 
         if (del_active) {
             ofproto_revalidate(port->bridge->ofproto, port->active_iface_tag);
@@ -3213,7 +3484,9 @@ iface_destroy(struct iface *iface)
             bond_send_learning_packets(port);
         }
 
-        port_update_bonding(port);
+        free(iface->name);
+        free(iface);
+
         bridge_flush(port->bridge);
     }
 }
@@ -3252,20 +3525,25 @@ iface_from_dp_ifidx(const struct bridge *br, uint16_t dp_ifidx)
  * reason why this function takes a name instead of a struct iface: the fake
  * interfaces created this way do not have a struct iface. */
 static bool
-iface_is_internal(const struct bridge *br, const char *iface)
+iface_is_internal(const struct bridge *br, const char *if_name)
 {
-    if (!strcmp(iface, br->name)
-        || cfg_get_bool(0, "iface.%s.internal", iface)) {
+    /* XXX wastes time */
+    struct iface *iface;
+    struct port *port;
+
+    if (!strcmp(if_name, br->name)) {
         return true;
     }
 
-    if (cfg_get_bool(0, "bonding.%s.fake-iface", iface)) {
-        struct port *port = port_lookup(br, iface);
-        if (port && port->n_ifaces > 1) {
-            return true;
-        }
+    iface = iface_lookup(br, if_name);
+    if (iface && !strcmp(iface->cfg->type, "internal")) {
+        return true;
     }
 
+    port = port_lookup(br, if_name);
+    if (port && port->n_ifaces > 1 && port->cfg->bond_fake_iface) {
+        return true;
+    }
     return false;
 }
 
@@ -3274,11 +3552,9 @@ iface_is_internal(const struct bridge *br, const char *iface)
 static void
 iface_set_mac(struct iface *iface)
 {
-    uint64_t mac = cfg_get_mac(0, "iface.%s.mac", iface->name);
-    if (mac) {
-        static uint8_t ea[ETH_ADDR_LEN];
+    uint8_t ea[ETH_ADDR_LEN];
 
-        eth_addr_from_uint64(mac, ea);
+    if (iface->cfg->mac && eth_addr_from_string(iface->cfg->mac, ea)) {
         if (eth_addr_is_multicast(ea)) {
             VLOG_ERR("interface %s: cannot set MAC to multicast address",
                      iface->name);
@@ -3286,7 +3562,7 @@ iface_set_mac(struct iface *iface)
             VLOG_ERR("ignoring iface.%s.mac; use bridge.%s.mac instead",
                      iface->name, iface->name);
         } else {
-            int error = netdev_nodev_set_etheraddr(iface->name, ea);
+            int error = netdev_set_etheraddr(iface->netdev, ea);
             if (error) {
                 VLOG_ERR("interface %s: setting MAC failed (%s)",
                          iface->name, strerror(error));
@@ -3300,46 +3576,47 @@ iface_set_mac(struct iface *iface)
 static void
 mirror_reconfigure(struct bridge *br)
 {
-    struct svec old_mirrors, new_mirrors;
-    size_t i, n_rspan_vlans;
+    struct shash old_mirrors, new_mirrors;
+    struct shash_node *node;
     unsigned long *rspan_vlans;
+    int i;
 
-    /* Collect old and new mirrors. */
-    svec_init(&old_mirrors);
-    svec_init(&new_mirrors);
-    cfg_get_subsections(&new_mirrors, "mirror.%s", br->name);
+    /* Collect old mirrors. */
+    shash_init(&old_mirrors);
     for (i = 0; i < MAX_MIRRORS; i++) {
         if (br->mirrors[i]) {
-            svec_add(&old_mirrors, br->mirrors[i]->name);
+            shash_add(&old_mirrors, br->mirrors[i]->name, br->mirrors[i]);
         }
     }
 
-    /* Get rid of deleted mirrors and add new mirrors. */
-    svec_sort(&old_mirrors);
-    assert(svec_is_unique(&old_mirrors));
-    svec_sort(&new_mirrors);
-    assert(svec_is_unique(&new_mirrors));
-    for (i = 0; i < MAX_MIRRORS; i++) {
-        struct mirror *m = br->mirrors[i];
-        if (m && !svec_contains(&new_mirrors, m->name)) {
-            mirror_destroy(m);
+    /* Collect new mirrors. */
+    shash_init(&new_mirrors);
+    for (i = 0; i < br->cfg->n_mirrors; i++) {
+        struct ovsrec_mirror *cfg = br->cfg->mirrors[i];
+        if (!shash_add_once(&new_mirrors, cfg->name, cfg)) {
+            VLOG_WARN("bridge %s: %s specified twice as mirror",
+                      br->name, cfg->name);
         }
     }
-    for (i = 0; i < new_mirrors.n; i++) {
-        const char *name = new_mirrors.names[i];
-        if (!svec_contains(&old_mirrors, name)) {
-            mirror_create(br, name);
+
+    /* Get rid of deleted mirrors and add new mirrors. */
+    SHASH_FOR_EACH (node, &old_mirrors) {
+        if (!shash_find(&new_mirrors, node->name)) {
+            mirror_destroy(node->data);
         }
     }
-    svec_destroy(&old_mirrors);
-    svec_destroy(&new_mirrors);
-
-    /* Reconfigure all mirrors. */
-    for (i = 0; i < MAX_MIRRORS; i++) {
-        if (br->mirrors[i]) {
-            mirror_reconfigure_one(br->mirrors[i]);
+    SHASH_FOR_EACH (node, &new_mirrors) {
+        struct mirror *mirror = shash_find_data(&old_mirrors, node->name);
+        if (!mirror) {
+            mirror = mirror_create(br, node->name);
+            if (!mirror) {
+                break;
+            }
         }
+        mirror_reconfigure_one(mirror, node->data);
     }
+    shash_destroy(&old_mirrors);
+    shash_destroy(&new_mirrors);
 
     /* Update port reserved status. */
     for (i = 0; i < br->n_ports; i++) {
@@ -3352,31 +3629,29 @@ mirror_reconfigure(struct bridge *br)
         }
     }
 
-    /* Update learning disabled vlans (for RSPAN). */
+    /* Update flooded vlans (for RSPAN). */
     rspan_vlans = NULL;
-    n_rspan_vlans = cfg_count("vlan.%s.disable-learning", br->name);
-    if (n_rspan_vlans) {
+    if (br->cfg->n_flood_vlans) {
         rspan_vlans = bitmap_allocate(4096);
 
-        for (i = 0; i < n_rspan_vlans; i++) {
-            int vlan = cfg_get_vlan(i, "vlan.%s.disable-learning", br->name);
-            if (vlan >= 0) {
+        for (i = 0; i < br->cfg->n_flood_vlans; i++) {
+            int64_t vlan = br->cfg->flood_vlans[i];
+            if (vlan >= 0 && vlan < 4096) {
                 bitmap_set1(rspan_vlans, vlan);
-                VLOG_INFO("bridge %s: disabling learning on vlan %d\n",
+                VLOG_INFO("bridge %s: disabling learning on vlan %"PRId64,
                           br->name, vlan);
             } else {
-                VLOG_ERR("bridge %s: invalid value '%s' for learning disabled "
-                         "VLAN", br->name,
-                       cfg_get_string(i, "vlan.%s.disable-learning", br->name));
+                VLOG_ERR("bridge %s: invalid value %"PRId64 "for flood VLAN",
+                         br->name, vlan);
             }
         }
     }
-    if (mac_learning_set_disabled_vlans(br->ml, rspan_vlans)) {
+    if (mac_learning_set_flood_vlans(br->ml, rspan_vlans)) {
         bridge_flush(br);
     }
 }
 
-static void
+static struct mirror *
 mirror_create(struct bridge *br, const char *name)
 {
     struct mirror *m;
@@ -3386,7 +3661,7 @@ mirror_create(struct bridge *br, const char *name)
         if (i >= MAX_MIRRORS) {
             VLOG_WARN("bridge %s: maximum of %d port mirrors reached, "
                       "cannot create %s", br->name, MAX_MIRRORS, name);
-            return;
+            return NULL;
         }
         if (!br->mirrors[i]) {
             break;
@@ -3396,16 +3671,18 @@ mirror_create(struct bridge *br, const char *name)
     VLOG_INFO("created port mirror %s on bridge %s", name, br->name);
     bridge_flush(br);
 
-    br->mirrors[i] = m = xcalloc(1, sizeof *m);
+    br->mirrors[i] = m = xzalloc(sizeof *m);
     m->bridge = br;
     m->idx = i;
     m->name = xstrdup(name);
-    svec_init(&m->src_ports);
-    svec_init(&m->dst_ports);
+    shash_init(&m->src_ports);
+    shash_init(&m->dst_ports);
     m->vlans = NULL;
     m->n_vlans = 0;
     m->out_vlan = -1;
     m->out_port = NULL;
+
+    return m;
 }
 
 static void
@@ -3420,8 +3697,8 @@ mirror_destroy(struct mirror *m)
             br->ports[i]->dst_mirrors &= ~(MIRROR_MASK_C(1) << m->idx);
         }
 
-        svec_destroy(&m->src_ports);
-        svec_destroy(&m->dst_ports);
+        shash_destroy(&m->src_ports);
+        shash_destroy(&m->dst_ports);
         free(m->vlans);
 
         m->bridge->mirrors[m->idx] = NULL;
@@ -3432,45 +3709,36 @@ mirror_destroy(struct mirror *m)
 }
 
 static void
-prune_ports(struct mirror *m, struct svec *ports)
+mirror_collect_ports(struct mirror *m, struct ovsrec_port **ports, int n_ports,
+                     struct shash *names)
 {
-    struct svec tmp;
     size_t i;
 
-    svec_sort_unique(ports);
-
-    svec_init(&tmp);
-    for (i = 0; i < ports->n; i++) {
-        const char *name = ports->names[i];
+    for (i = 0; i < n_ports; i++) {
+        const char *name = ports[i]->name;
         if (port_lookup(m->bridge, name)) {
-            svec_add(&tmp, name);
+            shash_add_once(names, name, NULL);
         } else {
-            VLOG_WARN("mirror.%s.%s: cannot match on nonexistent port %s",
-                      m->bridge->name, m->name, name);
+            VLOG_WARN("bridge %s: mirror %s cannot match on nonexistent "
+                      "port %s", m->bridge->name, m->name, name);
         }
     }
-    svec_swap(ports, &tmp);
-    svec_destroy(&tmp);
 }
 
 static size_t
-prune_vlans(struct mirror *m, struct svec *vlan_strings, int **vlans)
+mirror_collect_vlans(struct mirror *m, const struct ovsrec_mirror *cfg,
+                     int **vlans)
 {
-    size_t n_vlans, i;
-
-    /* This isn't perfect: it won't combine "0" and "00", and the textual sort
-     * order won't give us numeric sort order.  But that's good enough for what
-     * we need right now. */
-    svec_sort_unique(vlan_strings);
+    size_t n_vlans;
+    size_t i;
 
-    *vlans = xmalloc(sizeof *vlans * vlan_strings->n);
+    *vlans = xmalloc(sizeof **vlans * cfg->n_select_vlan);
     n_vlans = 0;
-    for (i = 0; i < vlan_strings->n; i++) {
-        const char *name = vlan_strings->names[i];
-        int vlan;
-        if (!str_to_int(name, 10, &vlan) || vlan < 0 || vlan > 4095) {
-            VLOG_WARN("mirror.%s.%s.select.vlan: ignoring invalid VLAN %s",
-                      m->bridge->name, m->name, name);
+    for (i = 0; i < cfg->n_select_vlan; i++) {
+        int64_t vlan = cfg->select_vlan[i];
+        if (vlan < 0 || vlan > 4095) {
+            VLOG_WARN("bridge %s: mirror %s selects invalid VLAN %"PRId64,
+                      m->bridge->name, m->name, vlan);
         } else {
             (*vlans)[n_vlans++] = vlan;
         }
@@ -3505,104 +3773,86 @@ port_trunks_any_mirrored_vlan(const struct mirror *m, const struct port *p)
 }
 
 static void
-mirror_reconfigure_one(struct mirror *m)
+mirror_reconfigure_one(struct mirror *m, struct ovsrec_mirror *cfg)
 {
-    char *pfx = xasprintf("mirror.%s.%s", m->bridge->name, m->name);
-    struct svec src_ports, dst_ports, ports;
-    struct svec vlan_strings;
+    struct shash src_ports, dst_ports;
     mirror_mask_t mirror_bit;
-    const char *out_port_name;
     struct port *out_port;
     int out_vlan;
     size_t n_vlans;
     int *vlans;
     size_t i;
-    bool mirror_all_ports;
-    bool any_ports_specified;
 
     /* Get output port. */
-    out_port_name = cfg_get_key(0, "mirror.%s.%s.output.port",
-                                m->bridge->name, m->name);
-    if (out_port_name) {
-        out_port = port_lookup(m->bridge, out_port_name);
+    if (cfg->output_port) {
+        out_port = port_lookup(m->bridge, cfg->output_port->name);
         if (!out_port) {
-            VLOG_ERR("%s.output.port: bridge %s does not have a port "
-                      "named %s", pfx, m->bridge->name, out_port_name);
+            VLOG_ERR("bridge %s: mirror %s outputs to port not on bridge",
+                     m->bridge->name, m->name);
             mirror_destroy(m);
-            free(pfx);
             return;
         }
         out_vlan = -1;
 
-        if (cfg_has("%s.output.vlan", pfx)) {
-            VLOG_ERR("%s.output.port and %s.output.vlan both specified; "
-                     "ignoring %s.output.vlan", pfx, pfx, pfx);
+        if (cfg->output_vlan) {
+            VLOG_ERR("bridge %s: mirror %s specifies both output port and "
+                     "output vlan; ignoring output vlan",
+                     m->bridge->name, m->name);
         }
-    } else if (cfg_has("%s.output.vlan", pfx)) {
+    } else if (cfg->output_vlan) {
         out_port = NULL;
-        out_vlan = cfg_get_vlan(0, "%s.output.vlan", pfx);
+        out_vlan = *cfg->output_vlan;
     } else {
-        VLOG_ERR("%s: neither %s.output.port nor %s.output.vlan specified, "
-                 "but exactly one is required; disabling port mirror %s",
-                 pfx, pfx, pfx, pfx);
+        VLOG_ERR("bridge %s: mirror %s does not specify output; ignoring",
+                 m->bridge->name, m->name);
         mirror_destroy(m);
-        free(pfx);
         return;
     }
 
-    /* Get all the ports, and drop duplicates and ports that don't exist. */
-    svec_init(&src_ports);
-    svec_init(&dst_ports);
-    svec_init(&ports);
-    cfg_get_all_keys(&src_ports, "%s.select.src-port", pfx);
-    cfg_get_all_keys(&dst_ports, "%s.select.dst-port", pfx);
-    cfg_get_all_keys(&ports, "%s.select.port", pfx);
-    any_ports_specified = src_ports.n || dst_ports.n || ports.n;
-    svec_append(&src_ports, &ports);
-    svec_append(&dst_ports, &ports);
-    svec_destroy(&ports);
-    prune_ports(m, &src_ports);
-    prune_ports(m, &dst_ports);
-    if (any_ports_specified && !src_ports.n && !dst_ports.n) {
-        VLOG_ERR("%s: none of the specified ports exist; "
-                 "disabling port mirror %s", pfx, pfx);
-        mirror_destroy(m);
-        goto exit;
-    }
+    shash_init(&src_ports);
+    shash_init(&dst_ports);
+    if (cfg->select_all) {
+        for (i = 0; i < m->bridge->n_ports; i++) {
+            const char *name = m->bridge->ports[i]->name;
+            shash_add_once(&src_ports, name, NULL);
+            shash_add_once(&dst_ports, name, NULL);
+        }
+        vlans = NULL;
+        n_vlans = 0;
+    } else {
+        /* Get ports, and drop duplicates and ports that don't exist. */
+        mirror_collect_ports(m, cfg->select_src_port, cfg->n_select_src_port,
+                             &src_ports);
+        mirror_collect_ports(m, cfg->select_dst_port, cfg->n_select_dst_port,
+                             &dst_ports);
 
-    /* Get all the vlans, and drop duplicate and invalid vlans. */
-    svec_init(&vlan_strings);
-    cfg_get_all_keys(&vlan_strings, "%s.select.vlan", pfx);
-    n_vlans = prune_vlans(m, &vlan_strings, &vlans);
-    svec_destroy(&vlan_strings);
+        /* Get all the vlans, and drop duplicate and invalid vlans. */
+        n_vlans = mirror_collect_vlans(m, cfg, &vlans);
+    }
 
     /* Update mirror data. */
-    if (!svec_equal(&m->src_ports, &src_ports)
-        || !svec_equal(&m->dst_ports, &dst_ports)
+    if (!shash_equal_keys(&m->src_ports, &src_ports)
+        || !shash_equal_keys(&m->dst_ports, &dst_ports)
         || m->n_vlans != n_vlans
         || memcmp(m->vlans, vlans, sizeof *vlans * n_vlans)
         || m->out_port != out_port
         || m->out_vlan != out_vlan) {
         bridge_flush(m->bridge);
     }
-    svec_swap(&m->src_ports, &src_ports);
-    svec_swap(&m->dst_ports, &dst_ports);
+    shash_swap(&m->src_ports, &src_ports);
+    shash_swap(&m->dst_ports, &dst_ports);
     free(m->vlans);
     m->vlans = vlans;
     m->n_vlans = n_vlans;
     m->out_port = out_port;
     m->out_vlan = out_vlan;
 
-    /* If no selection criteria have been given, mirror for all ports. */
-    mirror_all_ports = (!m->src_ports.n) && (!m->dst_ports.n) && (!m->n_vlans);
-
     /* Update ports. */
     mirror_bit = MIRROR_MASK_C(1) << m->idx;
     for (i = 0; i < m->bridge->n_ports; i++) {
         struct port *port = m->bridge->ports[i];
 
-        if (mirror_all_ports
-            || svec_contains(&m->src_ports, port->name)
+        if (shash_find(&m->src_ports, port->name)
             || (m->n_vlans
                 && (!port->vlan
                     ? port_trunks_any_mirrored_vlan(m, port)
@@ -3612,7 +3862,7 @@ mirror_reconfigure_one(struct mirror *m)
             port->src_mirrors &= ~mirror_bit;
         }
 
-        if (mirror_all_ports || svec_contains(&m->dst_ports, port->name)) {
+        if (shash_find(&m->dst_ports, port->name)) {
             port->dst_mirrors |= mirror_bit;
         } else {
             port->dst_mirrors &= ~mirror_bit;
@@ -3620,215 +3870,6 @@ mirror_reconfigure_one(struct mirror *m)
     }
 
     /* Clean up. */
-exit:
-    svec_destroy(&src_ports);
-    svec_destroy(&dst_ports);
-    free(pfx);
-}
-\f
-/* Spanning tree protocol. */
-
-static void brstp_update_port_state(struct port *);
-
-static void
-brstp_send_bpdu(struct ofpbuf *pkt, int port_no, void *br_)
-{
-    struct bridge *br = br_;
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    struct iface *iface = iface_from_dp_ifidx(br, port_no);
-    if (!iface) {
-        VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d",
-                     br->name, port_no);
-    } else if (eth_addr_is_zero(iface->mac)) {
-        VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d with unknown MAC",
-                     br->name, port_no);
-    } else {
-        union ofp_action action;
-        struct eth_header *eth = pkt->l2;
-        flow_t flow;
-
-        memcpy(eth->eth_src, iface->mac, ETH_ADDR_LEN);
-
-        memset(&action, 0, sizeof action);
-        action.type = htons(OFPAT_OUTPUT);
-        action.output.len = htons(sizeof action);
-        action.output.port = htons(port_no);
-
-        flow_extract(pkt, ODPP_NONE, &flow);
-        ofproto_send_packet(br->ofproto, &flow, &action, 1, pkt);
-    }
-    ofpbuf_delete(pkt);
-}
-
-static void
-brstp_reconfigure(struct bridge *br)
-{
-    size_t i;
-
-    if (!cfg_get_bool(0, "stp.%s.enabled", br->name)) {
-        if (br->stp) {
-            stp_destroy(br->stp);
-            br->stp = NULL;
-
-            bridge_flush(br);
-        }
-    } else {
-        uint64_t bridge_address, bridge_id;
-        int bridge_priority;
-
-        bridge_address = cfg_get_mac(0, "stp.%s.address", br->name);
-        if (!bridge_address) {
-            if (br->stp) {
-                bridge_address = (stp_get_bridge_id(br->stp)
-                                  & ((UINT64_C(1) << 48) - 1));
-            } else {
-                uint8_t mac[ETH_ADDR_LEN];
-                eth_addr_random(mac);
-                bridge_address = eth_addr_to_uint64(mac);
-            }
-        }
-
-        if (cfg_is_valid(CFG_INT | CFG_REQUIRED, "stp.%s.priority",
-                         br->name)) {
-            bridge_priority = cfg_get_int(0, "stp.%s.priority", br->name);
-        } else {
-            bridge_priority = STP_DEFAULT_BRIDGE_PRIORITY;
-        }
-
-        bridge_id = bridge_address | ((uint64_t) bridge_priority << 48);
-        if (!br->stp) {
-            br->stp = stp_create(br->name, bridge_id, brstp_send_bpdu, br);
-            br->stp_last_tick = time_msec();
-            bridge_flush(br);
-        } else {
-            if (bridge_id != stp_get_bridge_id(br->stp)) {
-                stp_set_bridge_id(br->stp, bridge_id);
-                bridge_flush(br);
-            }
-        }
-
-        for (i = 0; i < br->n_ports; i++) {
-            struct port *p = br->ports[i];
-            int dp_ifidx;
-            struct stp_port *sp;
-            int path_cost, priority;
-            bool enable;
-
-            if (!p->n_ifaces) {
-                continue;
-            }
-            dp_ifidx = p->ifaces[0]->dp_ifidx;
-            if (dp_ifidx < 0 || dp_ifidx >= STP_MAX_PORTS) {
-                continue;
-            }
-
-            sp = stp_get_port(br->stp, dp_ifidx);
-            enable = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED,
-                                    "stp.%s.port.%s.enabled",
-                                    br->name, p->name)
-                      || cfg_get_bool(0, "stp.%s.port.%s.enabled",
-                                      br->name, p->name));
-            if (p->is_mirror_output_port) {
-                enable = false;
-            }
-            if (enable != (stp_port_get_state(sp) != STP_DISABLED)) {
-                bridge_flush(br); /* Might not be necessary. */
-                if (enable) {
-                    stp_port_enable(sp);
-                } else {
-                    stp_port_disable(sp);
-                }
-            }
-
-            path_cost = cfg_get_int(0, "stp.%s.port.%s.path-cost",
-                                    br->name, p->name);
-            stp_port_set_path_cost(sp, path_cost ? path_cost : 19 /* XXX */);
-
-            priority = (cfg_is_valid(CFG_INT | CFG_REQUIRED,
-                                     "stp.%s.port.%s.priority",
-                                     br->name, p->name)
-                        ? cfg_get_int(0, "stp.%s.port.%s.priority",
-                                      br->name, p->name)
-                        : STP_DEFAULT_PORT_PRIORITY);
-            stp_port_set_priority(sp, priority);
-        }
-
-        brstp_adjust_timers(br);
-    }
-    for (i = 0; i < br->n_ports; i++) {
-        brstp_update_port_state(br->ports[i]);
-    }
-}
-
-static void
-brstp_update_port_state(struct port *p)
-{
-    struct bridge *br = p->bridge;
-    enum stp_state state;
-
-    /* Figure out new state. */
-    state = STP_DISABLED;
-    if (br->stp && p->n_ifaces > 0) {
-        int dp_ifidx = p->ifaces[0]->dp_ifidx;
-        if (dp_ifidx >= 0 && dp_ifidx < STP_MAX_PORTS) {
-            state = stp_port_get_state(stp_get_port(br->stp, dp_ifidx));
-        }
-    }
-
-    /* Update state. */
-    if (p->stp_state != state) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
-        VLOG_INFO_RL(&rl, "port %s: STP state changed from %s to %s",
-                     p->name, stp_state_name(p->stp_state),
-                     stp_state_name(state));
-        if (p->stp_state == STP_DISABLED) {
-            bridge_flush(br);
-        } else {
-            ofproto_revalidate(p->bridge->ofproto, p->stp_state_tag);
-        }
-        p->stp_state = state;
-        p->stp_state_tag = (p->stp_state == STP_DISABLED ? 0
-                            : tag_create_random());
-    }
-}
-
-static void
-brstp_adjust_timers(struct bridge *br)
-{
-    int hello_time = cfg_get_int(0, "stp.%s.hello-time", br->name);
-    int max_age = cfg_get_int(0, "stp.%s.max-age", br->name);
-    int forward_delay = cfg_get_int(0, "stp.%s.forward-delay", br->name);
-
-    stp_set_hello_time(br->stp, hello_time ? hello_time : 2000);
-    stp_set_max_age(br->stp, max_age ? max_age : 20000);
-    stp_set_forward_delay(br->stp, forward_delay ? forward_delay : 15000);
-}
-
-static void
-brstp_run(struct bridge *br)
-{
-    if (br->stp) {
-        long long int now = time_msec();
-        long long int elapsed = now - br->stp_last_tick;
-        struct stp_port *sp;
-
-        if (elapsed > 0) {
-            stp_tick(br->stp, MIN(INT_MAX, elapsed));
-            br->stp_last_tick = now;
-        }
-        while (stp_get_changed_port(br->stp, &sp)) {
-            struct port *p = port_from_dp_ifidx(br, stp_port_no(sp));
-            if (p) {
-                brstp_update_port_state(p);
-            }
-        }
-    }
-}
-
-static void
-brstp_wait(struct bridge *br)
-{
-    if (br->stp) {
-        poll_timer_wait(1000);
-    }
+    shash_destroy(&src_ports);
+    shash_destroy(&dst_ports);
 }
index a7250b2..3d3770d 100644 (file)
 #include <stddef.h>
 #include "list.h"
 
+struct ovsrec_open_vswitch;
 struct svec;
 
-void bridge_init(void);
-void bridge_reconfigure(void);
+void bridge_init(const struct ovsrec_open_vswitch *);
+void bridge_reconfigure(const struct ovsrec_open_vswitch *);
 int bridge_run(void);
 void bridge_wait(void);
 bool bridge_exists(const char *);
diff --git a/vswitchd/mgmt.c b/vswitchd/mgmt.c
deleted file mode 100644 (file)
index 8da640f..0000000
+++ /dev/null
@@ -1,909 +0,0 @@
-/* Copyright (c) 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-
-#include <arpa/inet.h>
-#include <assert.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include "bridge.h"
-#include "cfg.h"
-#include "coverage.h"
-#include "list.h"
-#include "mgmt.h"
-#include "openflow/nicira-ext.h"
-#include "openflow/openflow.h"
-#include "openflow/openflow-mgmt.h"
-#include "ofpbuf.h"
-#include "ovs-vswitchd.h"
-#include "packets.h"
-#include "rconn.h"
-#include "svec.h"
-#include "vconn.h"
-#include "vconn-ssl.h"
-#include "xenserver.h"
-#include "xtoxll.h"
-
-#define THIS_MODULE VLM_mgmt
-#include "vlog.h"
-
-#define MAX_BACKOFF_DEFAULT 15
-#define INACTIVITY_PROBE_DEFAULT 15
-
-static struct svec mgmt_cfg;
-static uint8_t cfg_cookie[CFG_COOKIE_LEN];
-static bool need_reconfigure = false;
-static struct rconn *mgmt_rconn;
-static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
-static struct svec capabilities;
-static struct ofpbuf ext_data_buffer;
-static uint32_t ext_data_xid = UINT32_MAX;
-uint64_t mgmt_id;
-
-
-#define TXQ_LIMIT 128         /* Max number of packets to queue for tx. */
-struct rconn_packet_counter *txqlen; /* # pkts queued for tx on mgmt_rconn. */
-
-static uint64_t pick_fallback_mgmt_id(void);
-static void send_config_update(uint32_t xid, bool use_xid);
-static void send_resources_update(uint32_t xid, bool use_xid);
-static int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len);
-
-void
-mgmt_init(void)
-{
-    txqlen = rconn_packet_counter_create();
-
-    svec_init(&mgmt_cfg);
-    svec_init(&capabilities);
-    svec_add_nocopy(&capabilities, 
-            xasprintf("com.nicira.mgmt.manager=true\n"));
-
-    mgmt_id = cfg_get_dpid(0, "mgmt.id");
-    if (!mgmt_id) {
-        /* Randomly generate a mgmt id */
-        mgmt_id = pick_fallback_mgmt_id();
-    }
-
-    ofpbuf_init(&ext_data_buffer, 0);
-}
-
-#ifdef HAVE_OPENSSL
-static bool
-config_string_change(const char *key, char **valuep)
-{
-    const char *value = cfg_get_string(0, "%s", key);
-    if (value && (!*valuep || strcmp(value, *valuep))) {
-        free(*valuep);
-        *valuep = xstrdup(value);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-static void
-mgmt_configure_ssl(void)
-{
-    static char *private_key_file;
-    static char *certificate_file;
-    static char *cacert_file;
-    struct stat s;
-
-    /* XXX SSL should be configurable separate from the bridges.
-     * XXX should be possible to de-configure SSL. */
-    if (config_string_change("ssl.private-key", &private_key_file)) {
-        vconn_ssl_set_private_key_file(private_key_file);
-    }
-
-    if (config_string_change("ssl.certificate", &certificate_file)) {
-        vconn_ssl_set_certificate_file(certificate_file);
-    }
-
-    /* We assume that even if the filename hasn't changed, if the CA cert 
-     * file has been removed, that we want to move back into
-     * boot-strapping mode.  This opens a small security hole, because
-     * the old certificate will still be trusted until vSwitch is
-     * restarted.  We may want to address this in vconn's SSL library. */
-    if (config_string_change("ssl.ca-cert", &cacert_file) 
-            || (stat(cacert_file, &s) && errno == ENOENT)) {
-        vconn_ssl_set_ca_cert_file(cacert_file,
-                cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
-    }
-}
-#endif
-
-void
-mgmt_reconfigure(void)
-{
-    struct svec new_cfg;
-    uint8_t new_cookie[CFG_COOKIE_LEN];
-    bool cfg_updated = false;
-    const char *controller_name;
-    int max_backoff;
-    int inactivity_probe;
-    int retval;
-
-    if (!cfg_has_section("mgmt")) {
-        svec_clear(&mgmt_cfg);
-        if (mgmt_rconn) {
-            rconn_destroy(mgmt_rconn);
-            mgmt_rconn = NULL;
-        }
-        return;
-    }
-
-    /* If this is an established connection, send a resources update. */
-    /* xxx This is wasteful if there were no resource changes!!! */
-    if (mgmt_rconn) {
-        send_resources_update(0, false);
-    }
-
-    cfg_get_cookie(new_cookie);
-    if (memcmp(cfg_cookie, new_cookie, sizeof(cfg_cookie))) {
-        memcpy(cfg_cookie, new_cookie, sizeof(cfg_cookie));
-        cfg_updated = true;
-    }
-
-    svec_init(&new_cfg);
-    cfg_get_section(&new_cfg, "mgmt");
-    if (svec_equal(&mgmt_cfg, &new_cfg)) {
-        /* Reconnecting to the controller causes the config file to be
-         * resent automatically.  If we're not reconnecting and the
-         * config file has changed, we need to notify the controller of
-         * changes. */
-        if (cfg_updated && mgmt_rconn) {
-            send_config_update(0, false);
-        }
-        svec_destroy(&new_cfg);
-        return;
-    }
-
-    controller_name = cfg_get_string(0, "mgmt.controller");
-    if (!controller_name) {
-        VLOG_ERR("no controller specified for managment");
-        svec_destroy(&new_cfg);
-        return;
-    }
-
-    max_backoff = cfg_get_int(0, "mgmt.max-backoff");
-    if (max_backoff < 1) {
-        max_backoff = MAX_BACKOFF_DEFAULT;
-    } else if (max_backoff > 3600) {
-        max_backoff = 3600;
-    }
-
-    inactivity_probe = cfg_get_int(0, "mgmt.inactivity-probe");
-    if (inactivity_probe < 5) {
-        inactivity_probe = INACTIVITY_PROBE_DEFAULT;
-    }
-
-    /* xxx If this changes, we need to restart bridges to use new id,
-     * xxx but they need the id before the connect to controller, but we
-     * xxx need their dpids. */
-    /* Check if a different mgmt id has been assigned. */
-    if (cfg_has("mgmt.id")) {
-        uint64_t cfg_mgmt_id = cfg_get_dpid(0, "mgmt.id");
-        if (cfg_mgmt_id != mgmt_id) {
-            mgmt_id = cfg_mgmt_id;
-        }
-    }
-
-    svec_swap(&new_cfg, &mgmt_cfg);
-    svec_destroy(&new_cfg);
-
-#ifdef HAVE_OPENSSL
-    /* Configure SSL. */
-    mgmt_configure_ssl();
-#endif
-
-    if (mgmt_rconn) {
-        rconn_destroy(mgmt_rconn);
-        mgmt_rconn = NULL;
-    }
-    mgmt_rconn = rconn_create(inactivity_probe, max_backoff);
-    retval = rconn_connect(mgmt_rconn, controller_name);
-    if (retval == EAFNOSUPPORT) {
-        VLOG_ERR("no support for %s vconn", controller_name);
-    }
-
-    /* Reset the extended message buffer when we create a new
-     * management connection. */
-    ofpbuf_clear(&ext_data_buffer);
-}
-
-static void *
-make_ofmp_xid(size_t ofmp_len, uint16_t type, uint32_t xid,
-        struct ofpbuf **bufferp)
-{
-    struct ofmp_header *oh;
-
-    oh = make_openflow_xid(ofmp_len, OFPT_VENDOR, xid, bufferp);
-    oh->header.vendor = htonl(NX_VENDOR_ID);
-    oh->header.subtype = htonl(NXT_MGMT);
-    oh->type = htons(type);
-
-    return oh;
-}
-
-static void *
-make_ofmp(size_t ofmp_len, uint16_t type, struct ofpbuf **bufferp)
-{
-    struct ofmp_header *oh;
-
-    oh = make_openflow(ofmp_len, OFPT_VENDOR, bufferp);
-    oh->header.vendor = htonl(NX_VENDOR_ID);
-    oh->header.subtype = htonl(NXT_MGMT);
-    oh->type = htons(type);
-
-    return oh;
-}
-
-static int
-send_openflow_buffer(struct ofpbuf *buffer)
-{               
-    int retval;
-
-    if (!mgmt_rconn) {
-        VLOG_ERR("attempt to send openflow packet with no rconn\n");
-        return EINVAL;
-    }
-
-    /* Make sure there's room to transmit the data.  We don't want to
-     * fail part way through a send. */
-    if (rconn_packet_counter_read(txqlen) >= TXQ_LIMIT) {
-        return EAGAIN;
-    }
-
-    /* OpenFlow messages use a 16-bit length field, so messages over 64K
-     * must be broken into multiple pieces. 
-     */
-    if (buffer->size <= 65535) {
-        update_openflow_length(buffer);
-        retval = rconn_send(mgmt_rconn, buffer, txqlen);
-        if (retval) {
-            VLOG_WARN_RL(&rl, "send to %s failed: %s",
-                         rconn_get_name(mgmt_rconn), strerror(retval));
-        }   
-        return retval;
-    } else {
-        struct ofmp_header *header = (struct ofmp_header *)buffer->data;
-        uint32_t xid = header->header.header.xid;
-        size_t remain = buffer->size;
-        uint8_t *ptr = buffer->data;
-        
-        /* Mark the OpenFlow header with a zero length to indicate some
-         * funkiness. 
-         */
-        header->header.header.length = 0;
-
-        while (remain > 0) {
-            struct ofpbuf *new_buffer;
-            struct ofmp_extended_data *oed;
-            size_t new_len = MIN(65535 - sizeof *oed, remain);
-
-            oed = make_ofmp_xid(sizeof *oed, OFMPT_EXTENDED_DATA, xid, 
-                    &new_buffer);
-            oed->type = header->type;
-
-            if (remain > new_len) {
-                oed->flags |= OFMPEDF_MORE_DATA;
-            }
-
-            /* Copy the entire original message, including the OpenFlow
-             * header, since management protocol structure definitions
-             * include these headers.
-             */
-            ofpbuf_put(new_buffer, ptr, new_len);
-
-            update_openflow_length(new_buffer);
-            retval = rconn_send(mgmt_rconn, new_buffer, txqlen);
-            if (retval) {
-                VLOG_WARN_RL(&rl, "send to %s failed: %s",
-                             rconn_get_name(mgmt_rconn), strerror(retval));
-                ofpbuf_delete(buffer);
-                return retval;
-            }   
-
-            remain -= new_len;
-            ptr += new_len;
-        }
-
-        ofpbuf_delete(buffer);
-        return 0;
-    }
-}   
-    
-static void
-send_features_reply(uint32_t xid)
-{
-    struct ofpbuf *buffer;
-    struct ofp_switch_features *ofr;
-
-    ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer);
-    ofr->datapath_id  = 0;
-    ofr->n_tables     = 0;
-    ofr->n_buffers    = 0;
-    ofr->capabilities = 0;
-    ofr->actions      = 0;
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_capability_reply(uint32_t xid)
-{
-    int i;
-    struct ofpbuf *buffer;
-    struct ofmp_capability_reply *ofmpcr;
-
-    ofmpcr = make_ofmp_xid(sizeof *ofmpcr, OFMPT_CAPABILITY_REPLY, 
-            xid, &buffer);
-    ofmpcr->format = htonl(OFMPCOF_SIMPLE);
-    ofmpcr->mgmt_id = htonll(mgmt_id);
-    for (i=0; i<capabilities.n; i++) {
-        ofpbuf_put(buffer, capabilities.names[i], 
-                strlen(capabilities.names[i]));
-    }
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_resources_update(uint32_t xid, bool use_xid)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_resources_update *ofmpru;
-    struct ofmp_tlv *tlv;
-    struct svec br_list;
-    struct svec port_list;
-    const char *host_uuid;
-    int i;
-
-    if (use_xid) {
-        ofmpru = make_ofmp_xid(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, 
-                xid, &buffer);
-    } else {
-        ofmpru = make_ofmp(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, &buffer);
-    }
-
-    /* On XenServer systems, each host has its own UUID, which we provide
-     * to the controller. 
-     */ 
-    host_uuid = xenserver_get_host_uuid();
-    if (host_uuid) {
-        struct ofmptsr_mgmt_uuid *mgmt_uuid_tlv;
-
-        mgmt_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*mgmt_uuid_tlv));
-        mgmt_uuid_tlv->type = htons(OFMPTSR_MGMT_UUID);
-        mgmt_uuid_tlv->len = htons(sizeof(*mgmt_uuid_tlv));
-        mgmt_uuid_tlv->mgmt_id = htonll(mgmt_id);
-        memcpy(mgmt_uuid_tlv->uuid, host_uuid, OFMP_UUID_LEN);
-    }
-
-    svec_init(&br_list);
-    cfg_get_subsections(&br_list, "bridge");
-    for (i=0; i < br_list.n; i++) {
-        struct ofmptsr_dp *dp_tlv;
-        uint64_t dp_id;
-        int n_uuid;
-
-        dp_id = bridge_get_datapathid(br_list.names[i]);
-        if (!dp_id) {
-            VLOG_WARN_RL(&rl, "bridge %s doesn't seem to exist", 
-                    br_list.names[i]);
-            continue;
-        }
-        dp_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_tlv));
-        dp_tlv->type = htons(OFMPTSR_DP);
-        dp_tlv->len = htons(sizeof(*dp_tlv));
-
-        dp_tlv->dp_id = htonll(dp_id);
-        memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1);
-
-        /* On XenServer systems, each network has one or more UUIDs
-         * associated with it, which we provide to the controller. 
-         */
-        n_uuid = cfg_count("bridge.%s.xs-network-uuids", br_list.names[i]);
-        if (n_uuid) {
-            struct ofmptsr_dp_uuid *dp_uuid_tlv;
-            size_t tlv_len = sizeof(*dp_uuid_tlv) + n_uuid * OFMP_UUID_LEN;
-            int j;
-
-            dp_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_uuid_tlv));
-            dp_uuid_tlv->type = htons(OFMPTSR_DP_UUID);
-            dp_uuid_tlv->len = htons(tlv_len);
-            dp_uuid_tlv->dp_id = htonll(dp_id);
-
-            for (j=0; j<n_uuid; j++) {
-                const char *dp_uuid = cfg_get_string(j, 
-                        "bridge.%s.xs-network-uuids", br_list.names[i]);
-
-                /* The UUID list could change underneath us, so just
-                 * fill with zeros in that case.  Another update will be
-                 * initiated shortly, which should contain corrected data.
-                 */
-                if (dp_uuid) {
-                    ofpbuf_put(buffer, dp_uuid, OFMP_UUID_LEN);
-                } else {
-                    ofpbuf_put_zeros(buffer, OFMP_UUID_LEN);
-                }
-            }
-        }
-    }
-    svec_destroy(&br_list);
-
-    /* On XenServer systems, extended information about virtual interfaces 
-     * (VIFs) is available, which is needed by the controller. 
-     */ 
-    svec_init(&port_list);
-    bridge_get_ifaces(&port_list);
-    for (i=0; i < port_list.n; i++) {
-        const char *vif_uuid, *vm_uuid, *net_uuid;
-        uint64_t vif_mac;
-        struct ofmptsr_vif *vif_tlv;
-
-        vif_uuid = cfg_get_string(0, "port.%s.vif-uuid", port_list.names[i]);
-        if (!vif_uuid) {
-            continue;
-        }
-
-        vif_tlv = ofpbuf_put_zeros(buffer, sizeof(*vif_tlv));
-        vif_tlv->type = htons(OFMPTSR_VIF);
-        vif_tlv->len = htons(sizeof(*vif_tlv));
-
-        memcpy(vif_tlv->name, port_list.names[i], strlen(port_list.names[i])+1);
-        memcpy(vif_tlv->vif_uuid, vif_uuid, sizeof(vif_tlv->vif_uuid));
-
-        vm_uuid = cfg_get_string(0, "port.%s.vm-uuid", port_list.names[i]);
-        if (vm_uuid) {
-            memcpy(vif_tlv->vm_uuid, vm_uuid, sizeof(vif_tlv->vm_uuid));
-        } else {
-            /* In case the vif disappeared underneath us. */
-            memset(vif_tlv->vm_uuid, '\0', sizeof(vif_tlv->vm_uuid));
-        }
-
-        net_uuid = cfg_get_string(0, "port.%s.net-uuid", port_list.names[i]);
-        if (net_uuid) {
-            memcpy(vif_tlv->net_uuid, net_uuid, sizeof(vif_tlv->net_uuid));
-        } else {
-            /* In case the vif disappeared underneath us. */
-            memset(vif_tlv->net_uuid, '\0', sizeof(vif_tlv->net_uuid));
-        }
-
-        vif_mac = cfg_get_mac(0, "port.%s.vif-mac", port_list.names[i]);
-        vif_tlv->vif_mac = htonll(vif_mac);
-    }
-    svec_destroy(&port_list);
-
-    /* Put end marker. */
-    tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv));
-    tlv->type = htons(OFMPTSR_END);
-    tlv->len = htons(sizeof(*tlv));
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_config_update(uint32_t xid, bool use_xid)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_config_update *ofmpcu;
-
-    if (use_xid) {
-        ofmpcu = make_ofmp_xid(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, 
-                xid, &buffer);
-    } else {
-        ofmpcu = make_ofmp(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, &buffer);
-    }
-
-    ofmpcu->format = htonl(OFMPCOF_SIMPLE);
-    memcpy(ofmpcu->cookie, cfg_cookie, sizeof(ofmpcu->cookie));
-    cfg_buf_put(buffer);
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_config_update_ack(uint32_t xid, bool success)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_config_update_ack *ofmpcua;
-
-    ofmpcua = make_ofmp_xid(sizeof *ofmpcua, OFMPT_CONFIG_UPDATE_ACK, 
-            xid, &buffer);
-
-    ofmpcua->format = htonl(OFMPCOF_SIMPLE);
-    if (success) {
-        ofmpcua->flags = htonl(OFMPCUAF_SUCCESS);
-    }
-    cfg_get_cookie(ofmpcua->cookie);
-    send_openflow_buffer(buffer);
-}
-
-static void
-send_error_msg(uint32_t xid, uint16_t type, uint16_t code, 
-            const void *data, size_t len)
-{
-    struct ofpbuf *buffer;
-    struct ofp_error_msg *oem;
-
-    oem = make_openflow_xid(sizeof(*oem)+len, OFPT_ERROR, xid, &buffer);
-    oem->type = htons(type);
-    oem->code = htons(code);
-    memcpy(oem->data, data, len);
-    send_openflow_buffer(buffer);
-}
-
-static int
-recv_echo_request(uint32_t xid UNUSED, const void *msg)
-{
-    const struct ofp_header *rq = msg;
-    send_openflow_buffer(make_echo_reply(rq));
-    return 0;
-}
-
-static int
-recv_features_request(uint32_t xid, const void *msg UNUSED)
-{
-    send_features_reply(xid);
-    return 0;
-}
-
-static int
-recv_set_config(uint32_t xid UNUSED, const void *msg UNUSED)
-{
-    /* Nothing to configure! */
-    return 0;
-}
-
-static int
-recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    struct ofmp_capability_request *ofmpcr;
-
-    if (len != sizeof(*ofmpcr)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    ofmpcr = (struct ofmp_capability_request *)ofmph;
-    if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    send_capability_reply(xid);
-
-    return 0;
-}
-
-static int
-recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED, 
-        size_t len UNUSED)
-{
-    send_resources_update(xid, true);
-    return 0;
-}
-
-static int
-recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph, 
-        size_t len)
-{
-    struct ofmp_config_request *ofmpcr;
-
-    if (len != sizeof(*ofmpcr)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    ofmpcr = (struct ofmp_config_request *)ofmph;
-    if (ofmpcr->format != htonl(OFMPCOF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    send_config_update(xid, true);
-
-    return 0;
-}
-
-static int
-recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    struct ofmp_config_update *ofmpcu;
-    int data_len;
-
-    data_len = len - sizeof(*ofmpcu);
-    if (data_len <= sizeof(*ofmpcu)) {
-        /* xxx Send error. */
-        return -EINVAL;
-    }
-
-    ofmpcu = (struct ofmp_config_update *)ofmph;
-    if (ofmpcu->format != htonl(OFMPCOF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    /* Check if the supplied cookie matches our current understanding of
-     * it.  If they don't match, tell the controller and let it sort
-     * things out. */
-    if (cfg_lock(ofmpcu->cookie, 0)) {  
-        /* xxx cfg_lock can fail for other reasons, such as being
-         * xxx locked... */
-        VLOG_WARN_RL(&rl, "config update failed due to bad cookie\n");
-
-        /* Check if our local view matches the controller, in which
-         * case, it is likely that there were local modifications
-         * without our being told to reread the config file. */
-        if (!memcmp(cfg_cookie, ofmpcu->cookie, sizeof cfg_cookie)) {
-            VLOG_WARN_RL(&rl, "config appears to have been locally modified "
-                              "without having told ovs-vswitchd to reload");
-        }
-        send_config_update_ack(xid, false);
-        return 0;
-    }
-
-    /* xxx We should probably do more sanity checking than this. */
-
-    cfg_write_data(ofmpcu->data, data_len);
-    cfg_unlock();
-
-    /* Send the ACK before running reconfigure, since our management
-     * connection settings may have changed. */
-    send_config_update_ack(xid, true);
-
-    need_reconfigure = true;
-
-    return 0;
-}
-
-static int
-recv_ofmp_extended_data(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    int data_len;
-    struct ofmp_extended_data *ofmped;
-
-    if (len <= sizeof(*ofmped)) {
-        /* xxx Send error. */
-        return -EINVAL;
-    }
-
-    ext_data_xid = xid;
-    ofmped = (struct ofmp_extended_data *)ofmph;
-
-    data_len = len - sizeof(*ofmped);
-    ofpbuf_put(&ext_data_buffer, ofmped->data, data_len);
-
-    if (!(ofmped->flags & OFMPEDF_MORE_DATA)) {
-        struct ofmp_header *new_oh;
-        int error;
-
-        /* An embedded message must be greater than the size of an
-         * OpenFlow message. */
-        new_oh = ofpbuf_at(&ext_data_buffer, 0, 65536);
-        if (!new_oh) {
-            VLOG_WARN_RL(&rl, "received short embedded message: %zu\n",
-                    ext_data_buffer.size);
-            return -EINVAL;
-        }
-
-        /* Make sure that this is a management message and that there's
-         * not an embedded extended data message. */
-        if ((new_oh->header.vendor != htonl(NX_VENDOR_ID))
-                || (new_oh->header.subtype != htonl(NXT_MGMT))
-                || (new_oh->type == htonl(OFMPT_EXTENDED_DATA))) {
-            VLOG_WARN_RL(&rl, "received bad embedded message\n");
-            return -EINVAL;
-        }
-        new_oh->header.header.xid = ext_data_xid;
-        new_oh->header.header.length = 0;
-
-        error = recv_ofmp(xid, ext_data_buffer.data, ext_data_buffer.size);
-        ofpbuf_clear(&ext_data_buffer);
-
-        return error;
-    }
-
-    return 0;
-}
-
-/* Handles receiving a management message.  Generally, this function
- * will be called 'len' set to zero, and the length will be derived by
- * the OpenFlow header.  With the extended data message, management
- * messages are not constrained by OpenFlow's 64K message length limit.  
- * The extended data handler calls this function with the 'len' set to
- * the total message length and the OpenFlow header's length field is 
- * ignored.
- */
-static
-int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len)
-{
-    if (!len) {
-        len = ntohs(ofmph->header.header.length);
-    }
-
-    /* Reset the extended data buffer if this isn't a continuation of an 
-     * existing extended data message. */
-    if (ext_data_xid != xid) {
-        ofpbuf_clear(&ext_data_buffer);
-    }
-
-    /* xxx Should sanity-check for min/max length */
-    switch (ntohs(ofmph->type)) 
-    {
-        case OFMPT_CAPABILITY_REQUEST:
-            return recv_ofmp_capability_request(xid, ofmph, len);
-        case OFMPT_RESOURCES_REQUEST:
-            return recv_ofmp_resources_request(xid, ofmph, len);
-        case OFMPT_CONFIG_REQUEST:
-            return recv_ofmp_config_request(xid, ofmph, len);
-        case OFMPT_CONFIG_UPDATE:
-            return recv_ofmp_config_update(xid, ofmph, len);
-        case OFMPT_EXTENDED_DATA:
-            return recv_ofmp_extended_data(xid, ofmph, len);
-        default:
-            VLOG_WARN_RL(&rl, "unknown mgmt message: %d", 
-                    ntohs(ofmph->type));
-            return -EINVAL;
-    }
-}
-
-static int 
-recv_nx_msg(uint32_t xid, const void *oh)
-{
-    const struct nicira_header *nh = oh;
-
-    switch (ntohl(nh->subtype)) {
-
-    case NXT_MGMT:
-        return recv_ofmp(xid, (struct ofmp_header *)oh, 0);
-
-    default:
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE, 
-                oh, ntohs(nh->header.length));
-        return -EINVAL;
-    }
-}
-
-static int
-recv_vendor(uint32_t xid, const void *oh)
-{
-    const struct ofp_vendor_header *ovh = oh;
-
-    switch (ntohl(ovh->vendor))
-    {
-    case NX_VENDOR_ID:
-        return recv_nx_msg(xid, oh);
-
-    default:
-        VLOG_WARN_RL(&rl, "unknown vendor: 0x%x", ntohl(ovh->vendor));
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR, 
-                oh, ntohs(ovh->header.length));
-        return -EINVAL; 
-    }
-}
-
-static int
-handle_msg(uint32_t xid, const void *msg, size_t length)
-{
-    int (*handler)(uint32_t, const void *);
-    struct ofp_header *oh;
-    size_t min_size;
-
-    COVERAGE_INC(mgmt_received);
-
-    /* Check encapsulated length. */
-    oh = (struct ofp_header *) msg;
-    if (ntohs(oh->length) > length) {
-        return -EINVAL;
-    }
-    assert(oh->version == OFP_VERSION);
-
-    /* Figure out how to handle it. */
-    switch (oh->type) {
-    case OFPT_ECHO_REQUEST:
-        min_size = sizeof(struct ofp_header);
-        handler = recv_echo_request;
-        break;
-    case OFPT_ECHO_REPLY:
-        return 0;
-    case OFPT_FEATURES_REQUEST:
-        min_size = sizeof(struct ofp_header);
-        handler = recv_features_request;
-        break;
-    case OFPT_SET_CONFIG:
-        min_size = sizeof(struct ofp_switch_config);
-        handler = recv_set_config;
-        break;
-    case OFPT_VENDOR:
-        min_size = sizeof(struct ofp_vendor_header);
-        handler = recv_vendor;
-        break;
-    default:
-        VLOG_WARN_RL(&rl, "unknown openflow type: %d", oh->type);
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE,
-                msg, length);
-        return -EINVAL;
-    }
-
-    /* Handle it. */
-    if (length < min_size) {
-        return -EFAULT;
-    }
-    return handler(xid, msg);
-}
-
-bool 
-mgmt_run(void)
-{
-    int i;
-
-    if (!mgmt_rconn) {
-        return false;
-    }
-
-    need_reconfigure = false;
-    rconn_run(mgmt_rconn);
-
-    /* Do some processing, but cap it at a reasonable amount so that
-     * other processing doesn't starve. */
-    for (i=0; i<50; i++) {
-        struct ofpbuf *buffer;
-        struct ofp_header *oh;
-
-        buffer = rconn_recv(mgmt_rconn);
-        if (!buffer) {
-            break;
-        }
-
-        if (buffer->size >= sizeof *oh) {
-            oh = buffer->data;
-            handle_msg(oh->xid, buffer->data, buffer->size);
-            ofpbuf_delete(buffer);
-        } else {
-            VLOG_WARN_RL(&rl, "received too-short OpenFlow message");
-        }
-    }
-
-    return need_reconfigure;
-}
-
-void
-mgmt_wait(void)
-{
-    if (!mgmt_rconn) {
-        return;
-    }
-
-    rconn_run_wait(mgmt_rconn);
-    rconn_recv_wait(mgmt_rconn);
-}
-
-static uint64_t
-pick_fallback_mgmt_id(void)
-{
-    uint8_t ea[ETH_ADDR_LEN];
-    eth_addr_random(ea);
-    ea[0] = 0x00;               /* Set Nicira OUI. */
-    ea[1] = 0x23;
-    ea[2] = 0x20;
-    return eth_addr_to_uint64(ea);
-}
index 68965ca..f5dd62a 100644 (file)
@@ -6,14 +6,18 @@ ovs\-brcompatd \- Bridge compatibility front-end for ovs\-vswitchd
 .
 .SH SYNOPSIS
 .B ovs\-brcompatd
-[\fIoptions\fR] \fIconfig\fR
+[\fIoptions\fR] \fIdatabase\fR
 .
 .SH DESCRIPTION
 A daemon that provides a legacy bridge front-end for \fBovs\-vswitchd\fR.  It 
 does this by listening for bridge ioctl commands (e.g., those generated by 
 the \fBbrctl\fR program) to add or remove datapaths and the interfaces 
-that attach to them.  It modifies \fIconfig\fR and forces
-\fBovs\-vswitchd\fR to reload its configuration file.
+that attach to them.  
+.PP
+The mandatory \fIdatabase\fR argument specifies the
+\fBovsdb\-server\fR from which \fBovs\-vswitchd\fR's configuration is
+retrieved.  It should take the form \fBunix:\fIfile\fR, to connect to
+the Unix domain server socket named \fIfile\fR.
 .PP
 .SH OPTIONS
 .IP "\fB--appctl-command=\fIcommand\fR"
@@ -29,10 +33,8 @@ replaced by a single \fB%\fR.  The \fB%\fR character may not otherwise
 appear in \fIcommand\fR.
 .IP
 The commands that are substituted into \fIcommand\fR are those that
-can be listed by passing \fB-e help\fR to \fBovs\-appctl\fR with
-\fBovs\-vswitchd\fR as target.  The command that is substituted may
-include white space-separated arguments, so \fIcommand\fR should include
-shell quotes around \fB%s\fR.
+can be listed by passing \fBhelp\fR to \fBovs\-appctl\fR with
+\fBovs\-vswitchd\fR as target.
 .IP
 \fIcommand\fR must not redirect \fBovs\-appctl\fR's standard output or
 standard error streams, because \fBovs\-brcompatd\fR expects to read
@@ -41,14 +43,9 @@ both of these streams separately.
 \fB--prune-timeout=\fIsecs\fR
 .
 Sets the maximum time between pruning port entries to \fIsecs\fR seconds.
-Pruning ports is the process of removing port entries from \fIconfig\fR 
+Pruning ports is the process of removing port entries from \fIdatabase\fR 
 that no longer exist.  If \fIsecs\fR is zero, then entries are never
 pruned.  The default prune timeout is 5 seconds.
-.TP
-\fB--lock-timeout=\fImsecs\fR
-.
-Sets the maximum time to wait for \fIconfig\fR to become unlocked to 
-\fImsecs\fR milliseconds.  The default lock timeout is 500 milliseconds.
 .
 .so lib/daemon.man
 .so lib/vlog.man
@@ -61,5 +58,5 @@ loaded.
 .SH "SEE ALSO"
 .BR ovs\-appctl (8),
 .BR ovs\-vswitchd (8),
-.BR ovs\-vswitchd.conf (5),
-\fBINSTALL\fR in the Open vSwitch distribution.
+.BR ovsdb\-server (1),
+\fBINSTALL.bridge\fR in the Open vSwitch distribution.
index 13bb843..b503705 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <fcntl.h>
 #include <unistd.h>
 
-#include "cfg.h"
 #include "command-line.h"
 #include "coverage.h"
 #include "daemon.h"
 #include "dirs.h"
-#include "dpif.h"
 #include "dynamic-string.h"
 #include "fatal-signal.h"
-#include "fault.h"
+#include "json.h"
 #include "leak-checker.h"
 #include "netdev.h"
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "openvswitch/brcompat-netlink.h"
+#include "ovsdb-idl.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "process.h"
@@ -55,6 +54,7 @@
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
+#include "vswitchd/vswitch-idl.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_brcompatd
@@ -70,22 +70,15 @@ enum bmc_action {
     BMC_DEL_PORT
 };
 
-static void parse_options(int argc, char *argv[]);
+static const char *parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 60);
 
-/* Maximum number of milliseconds to wait for the config file to be
- * unlocked.  If set to zero, no waiting will occur. */
-static int lock_timeout = 500;
-
 /* Maximum number of milliseconds to wait before pruning port entries that 
  * no longer exist.  If set to zero, ports are never pruned. */
 static int prune_timeout = 5000;
 
-/* Config file shared with ovs-vswitchd (usually ovs-vswitchd.conf). */
-static char *config_file;
-
 /* Shell command to execute (via popen()) to send a control command to the
  * running ovs-vswitchd process.  The string must contain one instance of %s,
  * which is replaced by the control command. */
@@ -174,10 +167,18 @@ static const struct nl_policy brc_dp_policy[] = {
     [BRC_GENL_A_DP_NAME] = { .type = NL_A_STRING },
 };
 
-static bool
-bridge_exists(const char *name)
+static struct ovsrec_bridge *
+find_bridge(const struct ovsrec_open_vswitch *ovs, const char *br_name)
 {
-    return cfg_has_section("bridge.%s", name);
+    size_t i;
+
+    for (i = 0; i < ovs->n_bridges; i++) {
+        if (!strcmp(br_name, ovs->bridges[i]->name)) {
+            return ovs->bridges[i];
+        }
+    }
+
+    return NULL;
 }
 
 static int
@@ -222,52 +223,31 @@ execute_appctl_command(const char *unixctl_command, char **output)
     return error;
 }
 
-static int
-rewrite_and_reload_config(void)
-{
-    if (cfg_is_dirty()) {
-        int error1 = cfg_write();
-        int error2 = cfg_read();
-        long long int reload_start = time_msec();
-        int error3 = execute_appctl_command("vswitchd/reload", NULL);
-        long long int elapsed = time_msec() - reload_start;
-        COVERAGE_INC(brcompatd_reload);
-        if (elapsed > 0) {
-            VLOG_INFO("reload command executed in %lld ms", elapsed);
-        }
-        return error1 ? error1 : error2 ? error2 : error3;
-    }
-    return 0;
-}
-
 static void
-do_get_bridge_parts(const char *bridge, struct svec *parts, int vlan,
-                    bool break_down_bonds)
+do_get_bridge_parts(const struct ovsrec_bridge *br, struct svec *parts, 
+                    int vlan, bool break_down_bonds)
 {
     struct svec ports;
-    int i;
+    size_t i, j;
 
     svec_init(&ports);
-    cfg_get_all_keys(&ports, "bridge.%s.port", bridge);
-    for (i = 0; i < ports.n; i++) {
-        const char *port_name = ports.names[i];
+    for (i = 0; i < br->n_ports; i++) {
+        const struct ovsrec_port *port = br->ports[i];
+
+        svec_add(&ports, port->name);
         if (vlan >= 0) {
-            int port_vlan = cfg_get_vlan(0, "vlan.%s.tag", port_name);
-            if (port_vlan < 0) {
-                port_vlan = 0;
-            }
+            int port_vlan = port->n_tag ? *port->tag : 0;
             if (vlan != port_vlan) {
                 continue;
             }
         }
-        if (break_down_bonds && cfg_has_section("bonding.%s", port_name)) {
-            struct svec slaves;
-            svec_init(&slaves);
-            cfg_get_all_keys(&slaves, "bonding.%s.slave", port_name);
-            svec_append(parts, &slaves);
-            svec_destroy(&slaves);
+        if (break_down_bonds) {
+            for (j = 0; j < port->n_interfaces; j++) {
+                const struct ovsrec_interface *iface = port->interfaces[j];
+                svec_add(parts, iface->name);
+            }
         } else {
-            svec_add(parts, port_name);
+            svec_add(parts, port->name);
         }
     }
     svec_destroy(&ports);
@@ -281,9 +261,10 @@ do_get_bridge_parts(const char *bridge, struct svec *parts, int vlan,
  * reported.  If 'vlan' > 0, only interfaces with implicit VLAN 'vlan' are
  * reported.  */
 static void
-get_bridge_ifaces(const char *bridge, struct svec *ifaces, int vlan)
+get_bridge_ifaces(const struct ovsrec_bridge *br, struct svec *ifaces, 
+                  int vlan)
 {
-    do_get_bridge_parts(bridge, ifaces, vlan, true);
+    do_get_bridge_parts(br, ifaces, vlan, true);
 }
 
 /* Add all the ports for 'bridge' to 'ports'.  Bonded ports are reported under
@@ -293,18 +274,19 @@ get_bridge_ifaces(const char *bridge, struct svec *ifaces, int vlan)
  * only trunk ports or ports with implicit VLAN 0 are reported.  If 'vlan' > 0,
  * only port with implicit VLAN 'vlan' are reported.  */
 static void
-get_bridge_ports(const char *bridge, struct svec *ports, int vlan)
+get_bridge_ports(const struct ovsrec_bridge *br, struct svec *ports, 
+                 int vlan)
 {
-    do_get_bridge_parts(bridge, ports, vlan, false);
+    do_get_bridge_parts(br, ports, vlan, false);
 }
 
+#if 0
 /* Go through the configuration file and remove any ports that no longer
  * exist associated with a bridge. */
 static void
 prune_ports(void)
 {
     int i, j;
-    int error;
     struct svec bridges, delete;
 
     if (cfg_lock(NULL, 0)) {
@@ -324,7 +306,6 @@ prune_ports(void)
         get_bridge_ifaces(br_name, &ifaces, -1);
         for (j = 0; j < ifaces.n; j++) {
             const char *iface_name = ifaces.names[j];
-            enum netdev_flags flags;
 
             /* The local port and internal ports are created and destroyed by
              * ovs-vswitchd itself, so don't bother checking for them at all.
@@ -335,14 +316,10 @@ prune_ports(void)
                 continue;
             }
 
-            error = netdev_nodev_get_flags(iface_name, &flags);
-            if (error == ENODEV) {
+            if (!netdev_exists(iface_name)) {
                 VLOG_INFO_RL(&rl, "removing dead interface %s from %s",
                              iface_name, br_name);
                 svec_add(&delete, iface_name);
-            } else if (error) {
-                VLOG_INFO_RL(&rl, "unknown error %d on interface %s from %s",
-                             error, iface_name, br_name);
             }
         }
         svec_destroy(&ifaces);
@@ -356,74 +333,352 @@ prune_ports(void)
             cfg_del_match("bridge.*.port=%s", delete.names[i]);
             cfg_del_match("bonding.*.slave=%s", delete.names[i]);
         }
-        rewrite_and_reload_config();
+        reload_config();
         cfg_unlock();
     } else {
         cfg_unlock();
     }
     svec_destroy(&delete);
 }
+#endif
+
+static struct ovsdb_idl_txn *
+txn_from_openvswitch(const struct ovsrec_open_vswitch *ovs)
+{
+    return ovsdb_idl_txn_get(&ovs->header_);
+}
 
-/* Checks whether a network device named 'name' exists and returns true if so,
- * false otherwise.
- *
- * XXX it is possible that this doesn't entirely accomplish what we want in
- * context, since ovs-vswitchd.conf may cause vswitchd to create or destroy
- * network devices based on iface.*.internal settings.
- *
- * XXX may want to move this to lib/netdev.
- *
- * XXX why not just use netdev_nodev_get_flags() or similar function? */
 static bool
-netdev_exists(const char *name)
+port_is_fake_bridge(const struct ovsrec_port *port)
 {
-    struct stat s;
-    char *filename;
-    int error;
+    return (port->fake_bridge
+            && port->tag
+            && *port->tag >= 1 && *port->tag <= 4095);
+}
+
+static void
+ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i;     
+
+    bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1));
+    for (i = 0; i < ovs->n_bridges; i++) {
+        bridges[i] = ovs->bridges[i];
+    }
+    bridges[ovs->n_bridges] = bridge;
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1);
+    free(bridges);
+}   
 
-    filename = xasprintf("/sys/class/net/%s", name);
-    error = stat(filename, &s);
-    free(filename);
-    return !error;
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+    return
+        json_array_create_1(
+            json_array_create_3(
+                json_string_create("_uuid"),
+                json_string_create("=="),
+                json_array_create_2(
+                    json_string_create("uuid"),
+                    json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
 }
 
+/* Commits 'txn'.  If 'wait_for_reload' is true, also waits for Open vSwitch to
+   reload the configuration before returning.
+
+   Returns EAGAIN if the caller should try the operation again, 0 on success,
+   otherwise a positive errno value. */
 static int
-add_bridge(const char *br_name)
+commit_txn(struct ovsdb_idl_txn *txn, bool wait_for_reload)
 {
-    if (bridge_exists(br_name)) {
+    struct ovsdb_idl *idl = ovsdb_idl_txn_get_idl (txn);
+    enum ovsdb_idl_txn_status status;
+    int64_t next_cfg = 0;
+
+    if (wait_for_reload) {
+        const struct ovsrec_open_vswitch *ovs = ovsrec_open_vswitch_first(idl);
+        struct json *where = where_uuid_equals(&ovs->header_.uuid);
+        ovsdb_idl_txn_increment(txn, "Open_vSwitch", "next_cfg", where);
+        json_destroy(where);
+    }
+    status = ovsdb_idl_txn_commit_block(txn);
+    if (wait_for_reload && status == TXN_SUCCESS) {
+        next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
+    }
+    ovsdb_idl_txn_destroy(txn);
+
+    switch (status) {
+    case TXN_INCOMPLETE:
+        NOT_REACHED();
+
+    case TXN_ABORTED:
+        VLOG_ERR_RL(&rl, "OVSDB transaction unexpectedly aborted");
+        return ECONNABORTED;
+
+    case TXN_UNCHANGED:
+        return 0;
+
+    case TXN_SUCCESS:
+        if (wait_for_reload) {
+            for (;;) {
+                /* We can't use 'ovs' any longer because ovsdb_idl_run() can
+                 * destroy it. */
+                const struct ovsrec_open_vswitch *ovs2;
+
+                ovsdb_idl_run(idl);
+                OVSREC_OPEN_VSWITCH_FOR_EACH (ovs2, idl) {
+                    if (ovs2->cur_cfg >= next_cfg) {
+                        goto done;
+                    }
+                }
+                ovsdb_idl_wait(idl);
+                poll_block();
+            }
+        done: ;
+        }
+        return 0;
+
+    case TXN_TRY_AGAIN:
+        VLOG_ERR_RL(&rl, "OVSDB transaction needs retry");
+        return EAGAIN;
+
+    case TXN_ERROR:
+        VLOG_ERR_RL(&rl, "OVSDB transaction failed: %s",
+                    ovsdb_idl_txn_get_error(txn));
+        return EBUSY;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static int
+add_bridge(struct ovsdb_idl *idl, const struct ovsrec_open_vswitch *ovs,
+           const char *br_name)
+{
+    struct ovsrec_bridge *br;
+    struct ovsrec_port *port;
+    struct ovsrec_interface *iface;
+    struct ovsdb_idl_txn *txn;
+
+    if (find_bridge(ovs, br_name)) {
         VLOG_WARN("addbr %s: bridge %s exists", br_name, br_name);
         return EEXIST;
     } else if (netdev_exists(br_name)) {
-        if (cfg_get_bool(0, "iface.%s.fake-bridge", br_name)) {
-            VLOG_WARN("addbr %s: %s exists as a fake bridge",
-                      br_name, br_name);
-            return 0;
-        } else {
-            VLOG_WARN("addbr %s: cannot create bridge %s because a network "
-                      "device named %s already exists",
-                      br_name, br_name, br_name);
-            return EEXIST;
+        size_t i;
+
+        for (i = 0; i < ovs->n_bridges; i++) {
+            size_t j;
+            struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+
+            for (j = 0; j < br_cfg->n_ports; j++) {
+                if (port_is_fake_bridge(br_cfg->ports[j])) {
+                    VLOG_WARN("addbr %s: %s exists as a fake bridge",
+                              br_name, br_name);
+                    return 0;
+                }
+            }
         }
+
+        VLOG_WARN("addbr %s: cannot create bridge %s because a network "
+                  "device named %s already exists",
+                  br_name, br_name, br_name);
+        return EEXIST;
     }
 
-    cfg_add_entry("bridge.%s.port=%s", br_name, br_name);
-    VLOG_INFO("addbr %s: success", br_name);
+    txn = ovsdb_idl_txn_create(idl);
 
-    return 0;
+    ovsdb_idl_txn_add_comment(txn, "ovs-brcompatd: addbr %s", br_name);
+
+    iface = ovsrec_interface_insert(txn_from_openvswitch(ovs));
+    ovsrec_interface_set_name(iface, br_name);
+
+    port = ovsrec_port_insert(txn_from_openvswitch(ovs));
+    ovsrec_port_set_name(port, br_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+    
+    br = ovsrec_bridge_insert(txn_from_openvswitch(ovs));
+    ovsrec_bridge_set_name(br, br_name);
+    ovsrec_bridge_set_ports(br, &port, 1);
+    
+    ovs_insert_bridge(ovs, br);
+
+    return commit_txn(txn, true);
+}
+
+static void
+add_port(const struct ovsrec_open_vswitch *ovs, 
+         const struct ovsrec_bridge *br, const char *port_name)
+{
+    struct ovsrec_interface *iface;
+    struct ovsrec_port *port;
+    struct ovsrec_port **ports;
+    size_t i;
+
+    /* xxx Check conflicts? */
+    iface = ovsrec_interface_insert(txn_from_openvswitch(ovs));
+    ovsrec_interface_set_name(iface, port_name);
+
+    port = ovsrec_port_insert(txn_from_openvswitch(ovs));
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+
+    ports = xmalloc(sizeof *br->ports * (br->n_ports + 1));
+    for (i = 0; i < br->n_ports; i++) {
+        ports[i] = br->ports[i];
+    }
+    ports[br->n_ports] = port;
+    ovsrec_bridge_set_ports(br, ports, br->n_ports + 1);
+    free(ports);
+}
+
+/* Deletes 'port' from 'br'.
+ *
+ * After calling this function, 'port' must not be referenced again. */
+static void
+del_port(const struct ovsrec_bridge *br, const struct ovsrec_port *port)
+{
+    struct ovsrec_port **ports;
+    size_t i, n;
+
+    /* Remove 'port' from the bridge's list of ports. */
+    ports = xmalloc(sizeof *br->ports * br->n_ports);
+    for (i = n = 0; i < br->n_ports; i++) {
+        if (br->ports[i] != port) {
+            ports[n++] = br->ports[i];
+        }
+    }
+    ovsrec_bridge_set_ports(br, ports, n);
+    free(ports);
+
+    /* Delete all of the port's interfaces. */
+    for (i = 0; i < port->n_interfaces; i++) {
+        ovsrec_interface_delete(port->interfaces[i]);
+    }
+
+    /* Delete the port itself. */
+    ovsrec_port_delete(port);
+}
+
+/* Delete 'iface' from 'port' (which must be within 'br').  If 'iface' was
+ * 'port''s only interface, delete 'port' from 'br' also.
+ *
+ * After calling this function, 'iface' must not be referenced again. */
+static void
+del_interface(const struct ovsrec_bridge *br,
+              const struct ovsrec_port *port,
+              const struct ovsrec_interface *iface)
+{
+    if (port->n_interfaces == 1) {
+        del_port(br, port);
+    } else {
+        struct ovsrec_interface **ifaces;
+        size_t i, n;
+
+        ifaces = xmalloc(sizeof *port->interfaces * port->n_interfaces);
+        for (i = n = 0; i < port->n_interfaces; i++) {
+            if (port->interfaces[i] != iface) {
+                ifaces[n++] = port->interfaces[i];
+            }
+        }
+        ovsrec_port_set_interfaces(port, ifaces, n);
+        free(ifaces);
+        ovsrec_interface_delete(iface);
+    }
+}
+
+/* Find and return a port within 'br' named 'port_name'. */
+static const struct ovsrec_port *
+find_port(const struct ovsrec_bridge *br, const char *port_name)
+{
+    size_t i;
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct ovsrec_port *port = br->ports[i];
+        if (!strcmp(port_name, port->name)) {
+            return port;
+        }
+    }
+    return NULL;
+}
+
+/* Find and return an interface within 'br' named 'iface_name'. */
+static const struct ovsrec_interface *
+find_interface(const struct ovsrec_bridge *br, const char *iface_name,
+               struct ovsrec_port **portp)
+{
+    size_t i;
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct ovsrec_port *port = br->ports[i];
+        size_t j;
+
+        for (j = 0; j < port->n_interfaces; j++) {
+            struct ovsrec_interface *iface = port->interfaces[j];
+            if (!strcmp(iface->name, iface_name)) {
+                *portp = port;
+                return iface;
+            }
+        }
+    }
+
+    *portp = NULL;
+    return NULL;
 }
 
-static int 
-del_bridge(const char *br_name)
+static int
+del_bridge(struct ovsdb_idl *idl,
+           const struct ovsrec_open_vswitch *ovs, const char *br_name)
 {
-    if (!bridge_exists(br_name)) {
+    struct ovsrec_bridge *br = find_bridge(ovs, br_name);
+    struct ovsrec_bridge **bridges;
+    struct ovsdb_idl_txn *txn;
+    size_t i, n;
+
+    if (!br) {
         VLOG_WARN("delbr %s: no bridge named %s", br_name, br_name);
         return ENXIO;
     }
 
-    cfg_del_section("bridge.%s", br_name);
-    VLOG_INFO("delbr %s: success", br_name);
+    txn = ovsdb_idl_txn_create(idl);
 
-    return 0;
+    ovsdb_idl_txn_add_comment(txn, "ovs-brcompatd: delbr %s", br_name);
+
+    /* Delete everything that the bridge points to, then delete the bridge
+     * itself. */
+    while (br->n_ports > 0) {
+        del_port(br, br->ports[0]);
+    }
+    for (i = 0; i < br->n_mirrors; i++) {
+        ovsrec_mirror_delete(br->mirrors[i]);
+    }
+    if (br->netflow) {
+        ovsrec_netflow_delete(br->netflow);
+    }
+    if (br->sflow) {
+        ovsrec_sflow_delete(br->sflow);
+    }
+    if (br->controller) {
+        ovsrec_controller_delete(br->controller);
+    }
+
+    /* Remove 'br' from the vswitch's list of bridges. */
+    bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges);
+    for (i = n = 0; i < ovs->n_bridges; i++) {
+        if (ovs->bridges[i] != br) {
+            bridges[n++] = ovs->bridges[i];
+        }
+    }
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, n);
+    free(bridges);
+
+    /* Delete the bridge itself. */
+    ovsrec_bridge_delete(br);
+
+    return commit_txn(txn, true);
 }
 
 static int
@@ -498,7 +753,9 @@ send_simple_reply(uint32_t seq, int error)
 }
 
 static int
-handle_bridge_cmd(struct ofpbuf *buffer, bool add)
+handle_bridge_cmd(struct ovsdb_idl *idl,
+                  const struct ovsrec_open_vswitch *ovs, 
+                  struct ofpbuf *buffer, bool add)
 {
     const char *br_name;
     uint32_t seq;
@@ -506,10 +763,14 @@ handle_bridge_cmd(struct ofpbuf *buffer, bool add)
 
     error = parse_command(buffer, &seq, &br_name, NULL, NULL, NULL);
     if (!error) {
-        error = add ? add_bridge(br_name) : del_bridge(br_name);
-        if (!error) {
-            error = rewrite_and_reload_config();
-        }
+        int retval;
+
+        do {
+            retval = (add ? add_bridge : del_bridge)(idl, ovs, br_name);
+            VLOG_INFO_RL(&rl, "%sbr %s: %s",
+                         add ? "add" : "del", br_name, strerror(retval));
+        } while (retval == EAGAIN);
+
         send_simple_reply(seq, error);
     }
     return error;
@@ -520,16 +781,10 @@ static const struct nl_policy brc_port_policy[] = {
     [BRC_GENL_A_PORT_NAME] = { .type = NL_A_STRING },
 };
 
-static void
-del_port(const char *br_name, const char *port_name)
-{
-    cfg_del_entry("bridge.%s.port=%s", br_name, port_name);
-    cfg_del_match("bonding.*.slave=%s", port_name);
-    cfg_del_match("vlan.%s.[!0-9]*", port_name);
-}
-
 static int
-handle_port_cmd(struct ofpbuf *buffer, bool add)
+handle_port_cmd(struct ovsdb_idl *idl,
+                const struct ovsrec_open_vswitch *ovs,
+                struct ofpbuf *buffer, bool add)
 {
     const char *cmd_name = add ? "add-if" : "del-if";
     const char *br_name, *port_name;
@@ -538,7 +793,9 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
 
     error = parse_command(buffer, &seq, &br_name, &port_name, NULL, NULL);
     if (!error) {
-        if (!bridge_exists(br_name)) {
+        struct ovsrec_bridge *br = find_bridge(ovs, br_name);
+
+        if (!br) {
             VLOG_WARN("%s %s %s: no bridge named %s",
                       cmd_name, br_name, port_name, br_name);
             error = EINVAL;
@@ -547,13 +804,27 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
                       cmd_name, br_name, port_name, port_name);
             error = EINVAL;
         } else {
-            if (add) {
-                cfg_add_entry("bridge.%s.port=%s", br_name, port_name);
-            } else {
-                del_port(br_name, port_name);
-            }
-            VLOG_INFO("%s %s %s: success", cmd_name, br_name, port_name);
-            error = rewrite_and_reload_config();
+            do {
+                struct ovsdb_idl_txn *txn = ovsdb_idl_txn_create(idl);
+
+                if (add) {
+                    ovsdb_idl_txn_add_comment(txn, "ovs-brcompatd: add-if %s",
+                                              port_name);
+                    add_port(ovs, br, port_name);
+                } else {
+                    const struct ovsrec_port *port = find_port(br, port_name);
+                    if (port) {
+                        ovsdb_idl_txn_add_comment(txn,
+                                                  "ovs-brcompatd: del-if %s",
+                                                  port_name);
+                        del_port(br, port);
+                    }
+                }
+
+                error = commit_txn(txn, true);
+                VLOG_INFO_RL(&rl, "%s %s %s: %s",
+                             cmd_name, br_name, port_name, strerror(error));
+            } while (error == EAGAIN);
         }
         send_simple_reply(seq, error);
     }
@@ -561,56 +832,47 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
     return error;
 }
 
-/* Returns the name of the bridge that contains a port named 'port_name', as a
- * malloc'd string that the caller must free, or a null pointer if no bridge
- * contains a port named 'port_name'. */
-static char *
-get_bridge_containing_port(const char *port_name)
-{
-    struct svec matches;
-    const char *start, *end;
-
-    svec_init(&matches);
-    cfg_get_matches(&matches, "bridge.*.port=%s", port_name);
-    if (!matches.n) {
-        return 0;
-    }
-
-    start = matches.names[0] + strlen("bridge.");
-    end = strstr(start, ".port=");
-    assert(end);
-    return xmemdup0(start, end - start);
-}
-
+/* The caller is responsible for freeing '*ovs_name' if the call is
+ * successful. */
 static int
-linux_bridge_to_ovs_bridge(const char *linux_bridge,
-                           char **ovs_bridge, int *br_vlan)
+linux_bridge_to_ovs_bridge(const struct ovsrec_open_vswitch *ovs,
+                           const char *linux_name,
+                           const struct ovsrec_bridge **ovs_bridge,
+                           int *br_vlan)
 {
-    if (bridge_exists(linux_bridge)) {
+    *ovs_bridge = find_bridge(ovs, linux_name);
+    if (*ovs_bridge) {
         /* Bridge name is the same.  We are interested in VLAN 0. */
-        *ovs_bridge = xstrdup(linux_bridge);
         *br_vlan = 0;
         return 0;
     } else {
-        /* No such Open vSwitch bridge 'linux_bridge', but there might be an
-         * internal port named 'linux_bridge' on some other bridge
+        /* No such Open vSwitch bridge 'linux_name', but there might be an
+         * internal port named 'linux_name' on some other bridge
          * 'ovs_bridge'.  If so then we are interested in the VLAN assigned to
-         * port 'linux_bridge' on the bridge named 'ovs_bridge'. */
-        const char *port_name = linux_bridge;
+         * port 'linux_name' on the bridge named 'ovs_bridge'. */
+        size_t i, j;
+
+        for (i = 0; i < ovs->n_bridges; i++) {
+            const struct ovsrec_bridge *br = ovs->bridges[i];
+
+            for (j = 0; j < br->n_ports; j++) {
+                const struct ovsrec_port *port = br->ports[j];
+
+                if (!strcmp(port->name, linux_name)) {
+                    *ovs_bridge = br;
+                    *br_vlan = port->n_tag ? *port->tag : -1;
+                    return 0;
+                }
+            }
 
-        *ovs_bridge = get_bridge_containing_port(port_name);
-        *br_vlan = cfg_get_vlan(0, "vlan.%s.tag", port_name);
-        if (*ovs_bridge && *br_vlan >= 0) {
-            return 0;
-        } else {
-            free(*ovs_bridge);
-            return ENODEV;
         }
+        return ENODEV;
     }
 }
 
 static int
-handle_fdb_query_cmd(struct ofpbuf *buffer)
+handle_fdb_query_cmd(const struct ovsrec_open_vswitch *ovs,
+                     struct ofpbuf *buffer)
 {
     /* This structure is copied directly from the Linux 2.6.30 header files.
      * It would be more straightforward to #include <linux/if_bridge.h>, but
@@ -638,8 +900,8 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
      * vswitchd can deal with all the VLANs on a single bridge.  We have to
      * pretend that the former is the case even though the latter is the
      * implementation. */
-    const char *linux_bridge;   /* Name used by brctl. */
-    char *ovs_bridge;           /* Name used by ovs-vswitchd. */
+    const char *linux_name;   /* Name used by brctl. */
+    const struct ovsrec_bridge *ovs_bridge;  /* Bridge used by ovs-vswitchd. */
     int br_vlan;                /* VLAN tag. */
     struct svec ifaces;
 
@@ -653,25 +915,24 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
     int error;
 
     /* Parse the command received from brcompat_mod. */
-    error = parse_command(buffer, &seq, &linux_bridge, NULL, &count, &skip);
+    error = parse_command(buffer, &seq, &linux_name, NULL, &count, &skip);
     if (error) {
         return error;
     }
 
     /* Figure out vswitchd bridge and VLAN. */
-    cfg_read();
-    error = linux_bridge_to_ovs_bridge(linux_bridge, &ovs_bridge, &br_vlan);
+    error = linux_bridge_to_ovs_bridge(ovs, linux_name, 
+                                       &ovs_bridge, &br_vlan);
     if (error) {
         send_simple_reply(seq, error);
         return error;
     }
 
     /* Fetch the forwarding database using ovs-appctl. */
-    unixctl_command = xasprintf("fdb/show %s", ovs_bridge);
+    unixctl_command = xasprintf("fdb/show %s", ovs_bridge->name);
     error = execute_appctl_command(unixctl_command, &output);
     free(unixctl_command);
     if (error) {
-        free(ovs_bridge);
         send_simple_reply(seq, error);
         return error;
     }
@@ -685,8 +946,14 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
     for (i = 0; i < ifaces.n; i++) {
         const char *iface_name = ifaces.names[i];
         struct mac *mac = &local_macs[n_local_macs];
-        if (!netdev_nodev_get_etheraddr(iface_name, mac->addr)) {
-            n_local_macs++;
+        struct netdev *netdev;
+
+        error = netdev_open_default(iface_name, &netdev);
+        if (!error) {
+            if (!netdev_get_etheraddr(netdev, mac->addr)) {
+                n_local_macs++;
+            }
+            netdev_close(netdev);
         }
     }
     svec_destroy(&ifaces);
@@ -754,7 +1021,6 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
 
     /* Free memory. */
     ofpbuf_uninit(&query_data);
-    free(ovs_bridge);
 
     return 0;
 }
@@ -793,11 +1059,11 @@ send_ifindex_reply(uint32_t seq, struct svec *ifaces)
 }
 
 static int
-handle_get_bridges_cmd(struct ofpbuf *buffer)
+handle_get_bridges_cmd(const struct ovsrec_open_vswitch *ovs,
+                       struct ofpbuf *buffer)
 {
     struct svec bridges;
-    const char *br_name;
-    size_t i;
+    size_t i, j;
 
     uint32_t seq;
 
@@ -813,22 +1079,18 @@ handle_get_bridges_cmd(struct ofpbuf *buffer)
     }
 
     /* Get all the real bridges and all the fake ones. */
-    cfg_read();
     svec_init(&bridges);
-    cfg_get_subsections(&bridges, "bridge");
-    SVEC_FOR_EACH (i, br_name, &bridges) {
-        const char *iface_name;
-        struct svec ifaces;
-        size_t j;
+    for (i = 0; i < ovs->n_bridges; i++) {
+        const struct ovsrec_bridge *br = ovs->bridges[i];
 
-        svec_init(&ifaces);
-        get_bridge_ifaces(br_name, &ifaces, -1);
-        SVEC_FOR_EACH (j, iface_name, &ifaces) {
-            if (cfg_get_bool(0, "iface.%s.fake-bridge", iface_name)) {
-                svec_add(&bridges, iface_name);
+        svec_add(&bridges, br->name);
+        for (j = 0; j < br->n_ports; j++) {
+            const struct ovsrec_port *port = br->ports[j];
+
+            if (port->fake_bridge) {
+                svec_add(&bridges, port->name);
             }
         }
-        svec_destroy(&ifaces);
     }
 
     send_ifindex_reply(seq, &bridges);
@@ -838,12 +1100,13 @@ handle_get_bridges_cmd(struct ofpbuf *buffer)
 }
 
 static int
-handle_get_ports_cmd(struct ofpbuf *buffer)
+handle_get_ports_cmd(const struct ovsrec_open_vswitch *ovs,
+                     struct ofpbuf *buffer)
 {
     uint32_t seq;
 
-    const char *linux_bridge;
-    char *ovs_bridge;
+    const char *linux_name;
+    const struct ovsrec_bridge *ovs_bridge;
     int br_vlan;
 
     struct svec ports;
@@ -851,13 +1114,13 @@ handle_get_ports_cmd(struct ofpbuf *buffer)
     int error;
 
     /* Parse Netlink command. */
-    error = parse_command(buffer, &seq, &linux_bridge, NULL, NULL, NULL);
+    error = parse_command(buffer, &seq, &linux_name, NULL, NULL, NULL);
     if (error) {
         return error;
     }
 
-    cfg_read();
-    error = linux_bridge_to_ovs_bridge(linux_bridge, &ovs_bridge, &br_vlan);
+    error = linux_bridge_to_ovs_bridge(ovs, linux_name, 
+                                       &ovs_bridge, &br_vlan);
     if (error) {
         send_simple_reply(seq, error);
         return error;
@@ -866,22 +1129,20 @@ handle_get_ports_cmd(struct ofpbuf *buffer)
     svec_init(&ports);
     get_bridge_ports(ovs_bridge, &ports, br_vlan);
     svec_sort(&ports);
-    svec_del(&ports, linux_bridge);
+    svec_del(&ports, linux_name);
     send_ifindex_reply(seq, &ports); /* XXX bonds won't show up */
     svec_destroy(&ports);
 
-    free(ovs_bridge);
-
     return 0;
 }
 
-static int
-brc_recv_update(void)
+static void
+brc_recv_update(struct ovsdb_idl *idl)
 {
     int retval;
     struct ofpbuf *buffer;
     struct genlmsghdr *genlmsghdr;
-
+    const struct ovsrec_open_vswitch *ovs;
 
     buffer = NULL;
     do {
@@ -895,7 +1156,7 @@ brc_recv_update(void)
         if (retval != EAGAIN) {
             VLOG_WARN_RL(&rl, "brc_recv_update: %s", strerror(retval));
         }
-        return retval;
+        return;
     }
 
     genlmsghdr = nl_msg_genlmsghdr(buffer);
@@ -910,55 +1171,60 @@ brc_recv_update(void)
         goto error;
     }
 
-    if (cfg_lock(NULL, lock_timeout)) {
-        /* Couldn't lock config file. */
-        retval = EAGAIN;
+    /* Get the Open vSwitch configuration.  Just drop the request on the floor
+     * if a valid configuration doesn't exist.  (We could check this earlier,
+     * but we want to drain pending Netlink messages even when there is no Open
+     * vSwitch configuration.) */
+    ovs = ovsrec_open_vswitch_first(idl);
+    if (!ovs) {
+        VLOG_WARN_RL(&rl, "could not find valid configuration to update");
         goto error;
     }
 
     switch (genlmsghdr->cmd) {
     case BRC_GENL_C_DP_ADD:
-        retval = handle_bridge_cmd(buffer, true);
+        handle_bridge_cmd(idl, ovs, buffer, true);
         break;
 
     case BRC_GENL_C_DP_DEL:
-        retval = handle_bridge_cmd(buffer, false);
+        handle_bridge_cmd(idl, ovs, buffer, false);
         break;
 
     case BRC_GENL_C_PORT_ADD:
-        retval = handle_port_cmd(buffer, true);
+        handle_port_cmd(idl, ovs, buffer, true);
         break;
 
     case BRC_GENL_C_PORT_DEL:
-        retval = handle_port_cmd(buffer, false);
+        handle_port_cmd(idl, ovs, buffer, false);
         break;
 
     case BRC_GENL_C_FDB_QUERY:
-        retval = handle_fdb_query_cmd(buffer);
+        handle_fdb_query_cmd(ovs, buffer);
         break;
 
     case BRC_GENL_C_GET_BRIDGES:
-        retval = handle_get_bridges_cmd(buffer);
+        handle_get_bridges_cmd(ovs, buffer);
         break;
 
     case BRC_GENL_C_GET_PORTS:
-        retval = handle_get_ports_cmd(buffer);
+        handle_get_ports_cmd(ovs, buffer);
         break;
 
     default:
-        retval = EPROTO;
+        VLOG_WARN_RL(&rl, "received unknown brc netlink command: %d\n",
+                     genlmsghdr->cmd);
+        break;
     }
 
-    cfg_unlock();
-
 error:
     ofpbuf_delete(buffer);
-    return retval;
+    return;
 }
 
 /* Check for interface configuration changes announced through RTNL. */
 static void
-rtnl_recv_update(void)
+rtnl_recv_update(struct ovsdb_idl *idl,
+                 const struct ovsrec_open_vswitch *ovs)
 {
     struct ofpbuf *buf;
 
@@ -994,35 +1260,41 @@ rtnl_recv_update(void)
             const char *port_name = nl_attr_get_string(attrs[IFLA_IFNAME]);
             char br_name[IFNAMSIZ];
             uint32_t br_idx = nl_attr_get_u32(attrs[IFLA_MASTER]);
-            enum netdev_flags flags;
 
             if (!if_indextoname(br_idx, br_name)) {
                 ofpbuf_delete(buf);
                 return;
             }
 
-            if (cfg_lock(NULL, lock_timeout)) {
-                /* Couldn't lock config file. */
-                /* xxx this should try again and print error msg. */
-                ofpbuf_delete(buf);
-                return;
-            }
-
-            if (netdev_nodev_get_flags(port_name, &flags) == ENODEV) {
+            if (!netdev_exists(port_name)) {
                 /* Network device is really gone. */
-                struct svec ports;
+                struct ovsdb_idl_txn *txn;
+                const struct ovsrec_interface *iface;
+                struct ovsrec_port *port;
+                struct ovsrec_bridge *br;
 
                 VLOG_INFO("network device %s destroyed, "
                           "removing from bridge %s", port_name, br_name);
 
-                svec_init(&ports);
-                cfg_get_all_keys(&ports, "bridge.%s.port", br_name);
-                svec_sort(&ports);
-                if (svec_contains(&ports, port_name)) {
-                    del_port(br_name, port_name);
-                    rewrite_and_reload_config();
+                br = find_bridge(ovs, br_name);
+                if (!br) {
+                    VLOG_WARN("no bridge named %s from which to remove %s", 
+                            br_name, port_name);
+                    ofpbuf_delete(buf);
+                    return;
                 }
-                svec_destroy(&ports);
+
+                txn = ovsdb_idl_txn_create(idl);
+
+                iface = find_interface(br, port_name, &port);
+                if (iface) {
+                    del_interface(br, port, iface);
+                    ovsdb_idl_txn_add_comment(txn,
+                                              "ovs-brcompatd: destroy port %s",
+                                              port_name);
+                }
+
+                commit_txn(txn, false);
             } else {
                 /* A network device by that name exists even though the kernel
                  * told us it had disappeared.  Probably, what happened was
@@ -1069,7 +1341,6 @@ rtnl_recv_update(void)
                           "a device by that name exists (XS Tools 5.0.0?)",
                           port_name);
             }
-            cfg_unlock();
         }
         ofpbuf_delete(buf);
     }
@@ -1079,22 +1350,28 @@ int
 main(int argc, char *argv[])
 {
     struct unixctl_server *unixctl;
+    const char *remote;
+    struct ovsdb_idl *idl;
     int retval;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
-    parse_options(argc, argv);
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+
+    remote = parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
     process_init();
+    ovsrec_init();
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "could not listen for vlog connections");
+        exit(EXIT_FAILURE);
     }
 
     if (brc_open(&brc_sock)) {
@@ -1108,18 +1385,29 @@ main(int argc, char *argv[])
         }
     }
 
-    retval = cfg_read();
-    if (retval) {
-        ovs_fatal(retval, "could not read config file");
-    }
+    daemonize_complete();
+
+    idl = ovsdb_idl_create(remote, &ovsrec_idl_class);
 
     for (;;) {
+        const struct ovsrec_open_vswitch *ovs;
+
+        ovsdb_idl_run(idl);
+
         unixctl_server_run(unixctl);
-        brc_recv_update();
+        brc_recv_update(idl);
+
+        ovs = ovsrec_open_vswitch_first(idl);
+        if (!ovs && ovsdb_idl_has_ever_connected(idl)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "%s: database does not contain any Open vSwitch "
+                         "configuration", remote);
+        }
+        netdev_run();
 
         /* If 'prune_timeout' is non-zero, we actively prune from the
-         * config file any 'bridge.<br_name>.port' entries that are no 
-         * longer valid.  We use two methods: 
+         * configuration of port entries that are no longer valid.  We 
+         * use two methods: 
          *
          *   1) The kernel explicitly notifies us of removed ports
          *      through the RTNL messages.
@@ -1127,19 +1415,26 @@ main(int argc, char *argv[])
          *   2) We periodically check all ports associated with bridges
          *      to see if they no longer exist.
          */
-        if (prune_timeout) {
-            rtnl_recv_update();
+        if (ovs && prune_timeout) {
+            rtnl_recv_update(idl, ovs);
+#if 0
             prune_ports();
+#endif
 
             nl_sock_wait(rtnl_sock, POLLIN);
             poll_timer_wait(prune_timeout);
         }
 
+
         nl_sock_wait(brc_sock, POLLIN);
+        ovsdb_idl_wait(idl);
         unixctl_server_wait(unixctl);
+        netdev_wait();
         poll_block();
     }
 
+    ovsdb_idl_destroy(idl);
+
     return 0;
 }
 
@@ -1164,11 +1459,10 @@ validate_appctl_command(void)
     }
 }
 
-static void
+static const char *
 parse_options(int argc, char *argv[])
 {
     enum {
-        OPT_LOCK_TIMEOUT = UCHAR_MAX + 1,
         OPT_PRUNE_TIMEOUT,
         OPT_APPCTL_COMMAND,
         VLOG_OPTION_ENUMS,
@@ -1177,7 +1471,6 @@ parse_options(int argc, char *argv[])
     static struct option long_options[] = {
         {"help",             no_argument, 0, 'h'},
         {"version",          no_argument, 0, 'V'},
-        {"lock-timeout",     required_argument, 0, OPT_LOCK_TIMEOUT},
         {"prune-timeout",    required_argument, 0, OPT_PRUNE_TIMEOUT},
         {"appctl-command",   required_argument, 0, OPT_APPCTL_COMMAND},
         DAEMON_LONG_OPTIONS,
@@ -1186,12 +1479,8 @@ parse_options(int argc, char *argv[])
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
-    int error;
 
-    appctl_command = xasprintf("%s/ovs-appctl -t "
-                               "%s/ovs-vswitchd.`cat %s/ovs-vswitchd.pid`.ctl "
-                               "-e '%%s'",
-                               ovs_bindir, ovs_rundir, ovs_rundir);
+    appctl_command = xasprintf("%s/ovs-appctl %%s", ovs_bindir);
     for (;;) {
         int c;
 
@@ -1209,10 +1498,6 @@ parse_options(int argc, char *argv[])
             OVS_PRINT_VERSION(0, 0);
             exit(EXIT_SUCCESS);
 
-        case OPT_LOCK_TIMEOUT:
-            lock_timeout = atoi(optarg);
-            break;
-
         case OPT_PRUNE_TIMEOUT:
             prune_timeout = atoi(optarg) * 1000;
             break;
@@ -1240,17 +1525,11 @@ parse_options(int argc, char *argv[])
     argv += optind;
 
     if (argc != 1) {
-        ovs_fatal(0, "exactly one non-option argument required; "
+        ovs_fatal(0, "database socket is non-option argument; "
                 "use --help for usage");
     }
 
-    cfg_init();
-    config_file = argv[0];
-    error = cfg_set_file(config_file);
-    if (error) {
-        ovs_fatal(error, "failed to add configuration file \"%s\"", 
-                config_file);
-    }
+    return argv[0];
 }
 
 static void
@@ -1263,7 +1542,6 @@ usage(void)
     printf("\nConfiguration options:\n"
            "  --appctl-command=COMMAND  shell command to run ovs-appctl\n"
            "  --prune-timeout=SECS    wait at most SECS before pruning ports\n"
-           "  --lock-timeout=MSECS    wait at most MSECS for CONFIG to unlock\n"
           );
     daemon_usage();
     vlog_usage();
index e9c11f4..0ec1ecd 100644 (file)
@@ -4,32 +4,36 @@
 .  ns
 .  IP "\\$1"
 ..
-.TH ovs\-vswitchd 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.TH ovs\-vswitchd 8 "June 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-vswitchd
 .
 .SH NAME
-ovs\-vswitchd \- virtual switch daemon
+ovs\-vswitchd \- Open vSwitch daemon
 .
 .SH SYNOPSIS
 .B ovs\-vswitchd
-\fIconfig\fR
+\fIdatabase\fR
 .
 .SH DESCRIPTION
-A daemon that manages and controls any number of virtual switches on
-the local machine.
+A daemon that manages and controls any number of Open vSwitch switches 
+on the local machine.
 .PP
-The mandatory \fIconfig\fR argument specifies a configuration file.
-For a description of \fBovs\-vswitchd\fR configuration syntax, see
-\fBovs\-vswitchd.conf\fR(5).
+The mandatory \fIdatabase\fR argument specifies the
+\fBovsdb\-server\fR from which \fBovs\-vswitchd\fR's configuration
+should be retrieved.  It takes one of the following forms:
+.so ovsdb/remote-active.man
 .PP
-At startup or upon receipt of a \fBSIGHUP\fR signal, \fBovs\-vswitchd\fR
-reads the configuration file.  It sets up Open vSwitch datapaths and then 
-operates switching across each bridge described in its configuration 
-files.  If a logfile was specified on the command line it will also 
-be opened or reopened.
+\fBovs\-vswitchd\fR retrieves its configuration from \fIdatabase\fR at
+startup.  It sets up Open vSwitch datapaths and then operates
+switching across each bridge described in its configuration files.  As
+the database changes, \fBovs\-vswitchd\fR automatically updates its
+configuration to match.
 .PP
-\fBovs\-vswitchd\fR virtual switches may be configured with any of the
-following features:
+Upon receipt of a SIGHUP signal, \fBovs\-vswitchd\fR reopens its log
+file, if one was specified on the command line.
+.PP
+\fBovs\-vswitchd\fR switches may be configured with any of the following 
+features:
 .
 .IP \(bu
 L2 switching with MAC learning.
@@ -48,11 +52,14 @@ Port mirroring, with optional VLAN tagging.
 NetFlow v5 flow logging.
 .
 .IP \(bu
+sFlow(R) monitoring.
+.
+.IP \(bu
 Connectivity to an external OpenFlow controller, such as NOX.
 .
 .PP
 Only a single instance of \fBovs\-vswitchd\fR is intended to run at a time.
-A single \fBovs\-vswitchd\fR can manage any number of virtual switches, up
+A single \fBovs\-vswitchd\fR can manage any number of switch instances, up
 to the maximum number of supported Open vSwitch datapaths.
 .PP
 \fBovs\-vswitchd\fR does all the necessary management of Open vSwitch datapaths
@@ -62,7 +69,7 @@ to modify datapaths when \fBovs\-vswitchd\fR is running can interfere with
 its operation.  (\fBovs\-dpctl\fR may still be useful for diagnostics.)
 .PP
 An Open vSwitch datapath kernel module must be loaded for \fBovs\-vswitchd\fR
-to be useful.  Please refer to the \fBINSTALL\fR file included in the
+to be useful.  Please refer to the \fBINSTALL.Linux\fR file included in the
 Open vSwitch distribution for instructions on how to build and load
 the Open vSwitch kernel module.
 .PP
@@ -86,6 +93,9 @@ actually in use.  It requires the \fBbrcompat_mod.ko\fR kernel module
 to be loaded.
 .
 .so lib/daemon.man
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
 .so lib/vlog.man
 .so lib/common.man
 .so lib/leak-checker.man
@@ -94,16 +104,7 @@ to be loaded.
 \fBovs\-appctl\fR(8) can send commands to a running
 \fBovs\-vswitchd\fR process.  The currently supported commands are
 described below.  The command descriptions assume an understanding of
-how to configure Open vSwitch, as described in
-\fBovs-vswitchd.conf\fR(5).
-.SS "OVS\-VSWITCHD COMMANDS"
-These commands manage the \fBovs-vswitchd\fR process.
-.IP "\fBvswitchd/reload\fR"
-Reloads the \fBovs\-vswitchd\fR configuration file, as if a
-\fBSIGHUP\fR signal were received.  The command completes only after
-reloading is finished, in particular after all datapaths have been
-created and destroyed and ports added and removed as specified by the
-new configuration.
+how to configure Open vSwitch.
 .SS "BRIDGE COMMANDS"
 These commands manage bridges.
 .IP "\fBfdb/show\fR \fIbridge\fR"
@@ -159,10 +160,12 @@ updelay (or downdelay).
 .IP
 This setting is not permanent: it persists only until the carrier
 status of \fIslave\fR changes.
+.IP "\fBbond/hash\fR \fImac\fR"
+Returns the hash value which would be used for \fImac\fR.
 .
 .so lib/vlog-unixctl.man
 .SH "SEE ALSO"
 .BR ovs\-appctl (8),
-.BR ovs\-vswitchd.conf (5),
 .BR ovs\-brcompatd (8),
-\fBINSTALL\fR in the Open vSwitch distribution.
+.BR ovsdb\-server (1),
+\fBINSTALL.Linux\fR in the Open vSwitch distribution.
index 3309c08..c1acfc4 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #endif
 
 #include "bridge.h"
-#include "cfg.h"
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
-#include "fault.h"
+#include "dpif.h"
 #include "leak-checker.h"
-#include "mgmt.h"
-#include "ovs-vswitchd.h"
+#include "netdev.h"
+#include "ovsdb-idl.h"
 #include "poll-loop.h"
-#include "port.h"
 #include "proc-net-compat.h"
 #include "process.h"
 #include "signals.h"
+#include "stream-ssl.h"
+#include "stream.h"
 #include "svec.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
+#include "vswitchd/vswitch-idl.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_vswitchd
 
-static void parse_options(int argc, char *argv[]);
+static const char *parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
-static void reload(struct unixctl_conn *, const char *args);
-
-static bool need_reconfigure;
-static struct unixctl_conn **conns;
-static size_t n_conns;
 
 int
 main(int argc, char *argv[])
 {
     struct unixctl_server *unixctl;
     struct signal *sighup;
+    struct ovsdb_idl *idl;
+    const char *remote;
+    bool need_reconfigure;
+    bool inited;
+    unsigned int idl_seqno;
     int retval;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
-    parse_options(argc, argv);
+    remote = parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
     sighup = signal_register(SIGHUP);
     process_init();
+    ovsrec_init();
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "could not listen for control connections");
+        exit(EXIT_FAILURE);
     }
-    unixctl_command_register("vswitchd/reload", reload);
 
-    retval = cfg_read();
-    if (retval) {
-        ovs_fatal(retval, "could not read config file");
-    }
-    mgmt_init();
-    bridge_init();
-    port_init();
-    mgmt_reconfigure();
+    daemonize_complete();
+
+    idl = ovsdb_idl_create(remote, &ovsrec_idl_class);
+    idl_seqno = ovsdb_idl_get_seqno(idl);
 
     need_reconfigure = false;
+    inited = false;
     for (;;) {
-        if (need_reconfigure || signal_poll(sighup)) {
-            need_reconfigure = false;
+        if (signal_poll(sighup)) {
             vlog_reopen_log_file();
-            reconfigure();
         }
-        if (mgmt_run()) {
+        if (inited && bridge_run()) {
             need_reconfigure = true;
         }
-        if (bridge_run()) {
+        ovsdb_idl_run(idl);
+        if (idl_seqno != ovsdb_idl_get_seqno(idl)) {
+            idl_seqno = ovsdb_idl_get_seqno(idl);
             need_reconfigure = true;
         }
-        unixctl_server_run(unixctl);
-
         if (need_reconfigure) {
-            poll_immediate_wake();
+            const struct ovsrec_open_vswitch *cfg;
+
+            need_reconfigure = false;
+            cfg = ovsrec_open_vswitch_first(idl);
+            if (cfg) {
+                if (inited) {
+                    bridge_reconfigure(cfg);
+                } else {
+                    bridge_init(cfg);
+                    inited = true;
+                }
+            }
         }
+        unixctl_server_run(unixctl);
+        dp_run();
+        netdev_run();
+
         signal_wait(sighup);
-        mgmt_wait();
-        bridge_wait();
+        if (inited) {
+            bridge_wait();
+        }
+        ovsdb_idl_wait(idl);
         unixctl_server_wait(unixctl);
+        dp_wait();
+        netdev_wait();
         poll_block();
     }
 
     return 0;
 }
 
-static void
-reload(struct unixctl_conn *conn, const char *args UNUSED)
-{
-    need_reconfigure = true;
-    conns = xrealloc(conns, sizeof *conns * (n_conns + 1));
-    conns[n_conns++] = conn;
-}
-
-void
-reconfigure(void)
-{
-    size_t i;
-
-    cfg_read();
-    bridge_reconfigure();
-    mgmt_reconfigure();
-    port_reconfigure();
-
-    for (i = 0; i < n_conns; i++) {
-        unixctl_command_reply(conns[i], 202, NULL);
-    }
-    free(conns);
-    conns = NULL;
-    n_conns = 0;
-}
-
-static void
+static const char *
 parse_options(int argc, char *argv[])
 {
     enum {
@@ -154,7 +142,8 @@ parse_options(int argc, char *argv[])
         OPT_MLOCKALL,
         OPT_FAKE_PROC_NET,
         VLOG_OPTION_ENUMS,
-        LEAK_CHECKER_OPTION_ENUMS
+        LEAK_CHECKER_OPTION_ENUMS,
+        OPT_BOOTSTRAP_CA_CERT
     };
     static struct option long_options[] = {
         {"help",        no_argument, 0, 'h'},
@@ -165,13 +154,13 @@ parse_options(int argc, char *argv[])
         VLOG_LONG_OPTIONS,
         LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
 #endif
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
-    const char *config_file;
     int error;
 
     for (;;) {
@@ -211,12 +200,17 @@ parse_options(int argc, char *argv[])
 
         VLOG_OPTION_HANDLERS
         DAEMON_OPTION_HANDLERS
-        VCONN_SSL_OPTION_HANDLERS
         LEAK_CHECKER_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
         case OPT_PEER_CA_CERT:
-            vconn_ssl_set_peer_ca_cert_file(optarg);
+            stream_ssl_set_peer_ca_cert_file(optarg);
+            break;
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
             break;
 #endif
 
@@ -233,26 +227,21 @@ parse_options(int argc, char *argv[])
     argv += optind;
 
     if (argc != 1) {
-        ovs_fatal(0, "config file is only non-option argument; "
+        ovs_fatal(0, "database socket is only non-option argument; "
                 "use --help for usage");
     }
 
-    cfg_init();
-    config_file = argv[0];
-    error = cfg_set_file(config_file);
-    if (error) {
-       ovs_fatal(error, "failed to add configuration file \"%s\"", 
-                config_file);
-    }
+    return argv[0];
 }
 
 static void
 usage(void)
 {
-    printf("%s: virtual switch daemon\n"
-           "usage: %s [OPTIONS] CONFIG\n"
-           "CONFIG is a configuration file in ovs-vswitchd.conf(5) format.\n",
+    printf("%s: Open vSwitch daemon\n"
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a socket on which ovsdb-server is listening.\n",
            program_name, program_name);
+    stream_usage("DATABASE", true, false, true);
     daemon_usage();
     vlog_usage();
     printf("\nLegacy compatibility options:\n"
diff --git a/vswitchd/ovs-vswitchd.conf.5.in b/vswitchd/ovs-vswitchd.conf.5.in
deleted file mode 100644 (file)
index ef9d759..0000000
+++ /dev/null
@@ -1,767 +0,0 @@
-.\" -*- nroff -*-
-.de TQ
-.  br
-.  ns
-.  TP "\\$1"
-..
-.de IQ
-.  br
-.  ns
-.  IP "\\$1"
-..
-.de ST
-.  PP
-.  RS -0.15in
-.  I "\\$1"
-.  RE
-.  PP
-..
-.TH ovs\-vswitchd.conf 5 "April 2009" "Open vSwitch" "Open vSwitch Manual"
-.
-.SH NAME
-ovs\-vswitchd.conf \- configuration file for \fBovs\-vswitchd\fR
-.
-.SH DESCRIPTION
-This manual page explains how to configure \fBovs\-vswitchd\fR, the
-Open vSwitch virtual switch daemon.  Refer to \fBovs\-vswitchd\fR(8)
-for instructions on how to start, stop, and control the virtual switch
-daemon and for an overview of its features.
-.SS "Overview"
-\fBovs\-vswitchd\fR configuration is hierarchical.
-.ST "Global Configuration"
-A few aspects of configuration apply to the entire \fBovs\-vswitchd\fR
-process:
-.IP \(bu
-Remote management (see \fBRemote Management\fR below).
-.IP \(bu
-SSL key and certificate configuration (see \fBSSL Configuration\fR
-below).
-.ST "Bridge Configuration"
-\fBovs\-vswitchd\fR manages one or more ``bridges.''  A bridge is,
-conceptually, an Ethernet switch.  Properties configurable at the
-bridge level include:
-.
-.IP \(bu
-The set of bridge ports (see \fBBridge Configuration\fR below).
-.IP \(bu
-Mirroring of packets across ports and VLANs (see \fBPort mirroring
-(SPAN and RSPAN)\fR below).
-.IP \(bu
-Flow logging via NetFlow (see \fBNetFlow v5 Flow Logging\fR below).
-.IP \(bu
-Connectivity to an OpenFlow controller (see \fBOpenFlow Controller
-Connectivity\fR below).
-.IP \(bu
-Addresses on which to listen for OpenFlow management connections (see
-\fBOpenFlow Management Connections\fR below) or for snooping on the
-connection to the primary OpenFlow controller (see \fBOpenFlow
-Controller Connection Snooping\fR below).
-.PP
-.ST "Port Configuration"
-Each bridge has one or more ``ports.''  The main configurable property
-of a port is its 802.1Q VLAN configuration (see \fB802.1Q VLAN
-support\fR below).
-.PP
-Most commonly, a port has exactly one ``interface.''  Such a port
-logically corresponds to a port on a physical Ethernet switch.
-.PP
-A port that has more than one interface is a ``bonded port.''  Bonding
-allows for load balancing and fail-over (see \fBNetwork Device
-Bonding\fR below).
-.ST "Interface Configuration"
-There are two different kinds of interfaces:
-.IP "``external interfaces''"
-These interfaces are ordinary network devices, e.g. \fBeth0\fR on
-Linux.
-.IP "``internal interfaces''"
-These interfaces are simulated network device that sent and receive
-traffic.  Every bridge has one internal interface called the ``local
-interface'' and may also have additional internal interfaces.  It does
-not make sense to bond an internal interface, so the terms ``port''
-and ``interface'' are often used imprecisely for internal interfaces.
-.PP
-Interfaces have a few configurable properties of their own:
-.IP \(bu
-Ingress rate-limiting (see \fBInterface Rate-Limiting\fR below).
-.IP \(bu
-Ethernet address (internal interfaces only, see \fBBridge
-Configuration\fR below).
-.SS "Configuration File Syntax"
-The \fBovs\-vswitchd\fR configuration file syntax is based on
-key-value pairs, which are given
-one per line in the form \fIkey\fB=\fIvalue\fR.  Each \fIkey\fR
-consists of one or more parts separated by dots,
-e.g. \fIpart1\fB.\fIpart2\fB.\fIpart3\fR.  Each \fIpart\fR may consist
-only of the English letters, digits, and the special characters
-\fB_-@$:+\fR.  White space within \fIvalue\fR  and at the beginning of a
-line is significant, but is otherwise ignored.
-.PP
-If a single key is specified more than once, that key has multiple
-values, one value for each time the key is specified.  The ordering of
-key-value pairs, and the ordering of multiple values for a single key,
-within a configuration file is not significant.
-.PP
-Blank lines, lines that consist only of white space, and lines that
-begin with \fB#\fR (optionally preceded by white space) are ignored.
-Keep in mind that programs that modify the configuration file, such as 
-\fBovs\-brcompatd\fR and \fBovs-cfg-mod\fR, may alter the order of
-elements and 
-strip comments and blank lines.
-.PP
-The following subsections describe how key-value pairs are used to
-configure \fBovs\-vswitchd\fR.
-.SS "Bridge Configuration"
-A bridge (switch) with a given \fIname\fR is configured by specifying
-the names of its network devices as values for key
-\fBbridge.\fIname\fB.port\fR.  (The specified \fIname\fR may not begin
-with \fBdp\fR or \fBnl:\fR followed by a digit.)
-.PP
-To designate network device \fInetdev\fR as an internal port, add
-\fBiface.\fInetdev\fB.internal=true\fR to the configuration file,
-which causes \fBovs\-vswitchd\fR to automatically creates
-\fInetdev\fR, which may then be configured using the usual system
-tools (e.g. \fBifconfig\fR, \fBip\fR).  An internal interface by
-default has a random Ethernet address, but you may configure a
-specific address by setting \fBiface.\fInetdev\fB.mac\fR to a MAC
-address in the format
-\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR, where each
-\fIx\fR is a hex digit.
-.PP
-A bridge with a given \fIname\fR always has an internal port with the
-same \fIname\fR, called the ``local port.''  This network device may
-be included
-in the bridge, by specifying it as one of the values for key
-\fBbridge.\fIname\fB.port\fR, or it may be omitted.  If it is
-included, then its MAC address is by default the lowest-numbered MAC
-address among the other bridge ports, ignoring other internal ports
-and bridge ports that are
-used as port mirroring destinations (see \fBPort Mirroring\fR, below).
-For this purpose, the MAC of a bonded port (see \fBNetwork Device
-Bonding\fR, below) is by default the MAC of its slave whose name is first in
-alphabetical order.
-There are two ways to modify this algorithm for selecting the MAC
-address of the local port:
-.IP \(bu
-To use a specific MAC address for the local port, set
-\fBbridge.\fIname\fB.mac\fR to a MAC address in the format
-\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR, where each
-\fIx\fR is a hex digit.
-.IP \(bu
-To override the MAC of a port for the purpose of this algorithm, set
-\fBport.\fIport\fB.mac\fR to a MAC address in the format described
-above.
-.PP
-If no valid MAC address can be determined
-either of these ways, then a MAC address is randomly generated.
-.PP
-The following syntax defines a bridge named \fBmybr\fR, configured
-with network devices \fBeth0\fR, \fBeth1\fR, and \fBeth2\fR:
-.RS
-.nf
-
-bridge.mybr.port=eth0
-bridge.mybr.port=eth1
-bridge.mybr.port=eth2
-
-.fi
-.RE
-.SS "802.1Q VLAN support"
-A bridge port may be configured either as a trunk port or as belonging
-to a single, untagged VLAN.  These two options are mutually exclusive,
-and a port must be configured in one way or the other.
-.ST "Trunk Ports"
-By default, bridge ports are trunk ports that carry all VLANs.  To
-limit the VLANs that a trunk port carries, define
-\fBvlan.\fIport\fB.trunks\fR to one or more integers between 0 and
-4095 designating VLANs.  Only frames that have an 802.1Q header with
-one of the listed VLANs are accepted on a trunk port.  If 0 is
-included in the list, then frames without an 802.1Q header are also
-accepted.  Other frames are discarded.
-.PP
-The following syntax makes network device \fBeth0\fR a trunk port that
-carries VLANs 1, 2, and 3:
-.PP
-.RS
-.nf
-
-vlan.eth0.trunks=1
-vlan.eth0.trunks=2
-vlan.eth0.trunks=3
-        
-.fi
-.RE
-.ST "Untagged VLAN Ports"
-A bridge port may be configured with an implicit, untagged VLAN.  
-Define key
-\fBvlan.\fIport\fB.tag\fR to an integer value \fIvid\fR between 0 and
-4095, inclusive, to designate the named \fIport\fR as a member
-of 802.1Q VLAN \fIvid\fR.  When \fIport\fR is assigned a VLAN tag this
-way, frames arriving on trunk ports will be forwarded to \fIport\fR
-only if they are tagged with VLAN \fIvid\fR, and frames arriving on
-other VLAN ports will be forwarded to \fIport\fR only if their
-\fIvid\fR values are equal.  Frames forwarded to \fIport\fR will not
-have an 802.1Q header.
-.PP
-When \fIvid\fR is 0, frames arriving on trunk ports without an 802.1Q
-VLAN header will also be forwarded to \fIport\fR.
-.PP
-When a frame with a 802.1Q header that indicates a nonzero VLAN is
-received on an implicit VLAN port, it is discarded.
-.PP
-The following syntax makes network device \fBeth0\fR a member of VLAN
-101:
-.PP
-.RS
-.nf
-
-vlan.eth0.tag=101
-        
-.fi
-.RE
-.SS "Network Device Bonding"
-Bonding allows multiple ``slave'' network devices to be treated as if
-they were a single virtual ``bonded'' network device.  It is useful for
-load balancing and fail-over.
-.PP
-\fBovs\-vswitchd\fR supports ``source load balancing'' (SLB) bonding, which
-assigns flows to slaves based on source MAC address, with periodic
-rebalancing as traffic patterns change.  This form of bonding does not
-require 802.3ad or other special support from the upstream switch to
-which the slave devices are connected.
-.PP
-To configure bonding, create a virtual bonding device by specifying
-the slave network device names as values for
-\fBbonding.\fIname\fB.slave\fR, then specify \fIname\fR as a bridge
-port.  The chosen \fIname\fR should not be the name of any real
-network device on the host system.
-.PP
-By default, bonding interfaces are enabled or disabled immediately
-when a carrier is detected or dropped on the underlying network
-device.  To insert a delay when carrier comes up or goes down before
-enabling or disabling an interface, set the value of
-\fBbonding.\fIname\fB.updelay\fR or
-\fBbonding.\fIname\fB.downdelay\fR, respectively, to a positive
-integer, interpreted in milliseconds.
-The \fBupdelay\fR setting is honored only when at least one bonded
-interface is already enabled.  When no interfaces are enabled, then
-the first bond interface to come up is enabled immediately.  The
-\fBdowndelay\fR setting is always honored.
-.PP
-The following syntax bonds \fBeth0\fR and \fBeth1\fR into a bonding
-device named \fBbond0\fR, which is added to bridge \fBmybr\fR along
-with physical network devices \fBeth2\fR and \fBeth3\fR:
-.PP
-.RS
-.nf
-
-bridge.mybr.port=bond0
-bridge.mybr.port=eth2
-bridge.mybr.port=eth3
-
-bonding.bond0.slave=eth0
-bonding.bond0.slave=eth1
-        
-.fi
-.RE
-.SS "Port Mirroring (SPAN and RSPAN)"
-\fBovs\-vswitchd\fR may be configured to send selected frames to special
-``mirrored'' ports, in addition to their normal destinations.  Mirroring
-traffic may also be referred to as SPAN or RSPAN, depending on the
-mechanism used for delivery.
-.PP
-Up to 32 instances of port mirroring may be configured on a given
-bridge.  Each must be given a name that is unique within the bridge.
-The keys associated with port mirroring instance \fIpmname\fR for
-bridge \fIbrname\fR begin with \fBmirror.\fIbrname\fB.\fIpmname\fR.
-.PP
-The selection of the frames to mirror and the form in which they
-should be output is configured separately for each port mirroring
-instances, through a subsection of
-\fBmirror.\fIbrname\fB.\fIpmname\fR, named \fBselect\fR, and
-\fBoutput\fR, respectively.
-.ST "Selecting Frames to Mirror"
-The values for the following keys, if specified, limit the frames that
-are chosen for mirroring.  If none of these keys is specified, then
-all frames received by the bridge are mirrored.  If more than one of
-these keys is specified, then a frame must meet all specified criteria
-to be mirrored.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.src-port=\fIport\fR
-.TQ
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.dst-port=\fIport\fR
-.TQ
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.port=\fIport\fR
-Frame received on \fIport\fR, output to \fIport\fR, or either received
-on or output to \fIport\fR, respectively.  \fIport\fR must be part of
-the bridge \fIbrname\fR; that is, it must be listed on
-\fBbridge.\fIbrname\fB.port\fR.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.vlan=\fIvid\fR
-.
-\fIvid\fR must be an integer between 0 and 4095, inclusive.  A nonzero
-\fIvid\fR selects frames that belong to VLAN \fIvid\fR, that is,
-frames that arrived on a trunk port tagged with VLAN \fIvid\fR or on a
-port that is configured as part of VLAN \fIvid\fR (see \fB802.1Q VLAN
-tagging\fR, above).  A \fIvid\fR of zero selects frames that do not
-belong to a VLAN, that is, frames that arrived on a trunk port without
-a VLAN tag or tagged with VLAN 0.
-.ST "Mirror Output"
-The values of the following keys determine how frames selected for
-mirroring are output.  Only one of the keys may be specified.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.output.port=\fIport\fR
-.
-Causes the selected frames to be sent out \fIport\fR, which must be
-part of the bridge \fIbrname\fR; that is, it must be listed on
-\fBbridge.\fIbrname\fB.port\fR.
-.IP
-Specifying a \fIport\fR in this way reserves that port exclusively for
-mirroring.  No frames other than those selected for mirroring will be
-forwarded to \fIport\fR, and any frames received on \fIport\fR will be
-discarded.  This type of mirroring may be referred to as SPAN.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.output.vlan=\fIvid\fR
-.
-Causes the selected frames to be sent on the VLAN numbered \fIvid\fR,
-which must be an integer between 0 and 4095, inclusive.  The frames
-will be sent out all ports that trunk VLAN \fIvid\fR, as well as any
-ports with implicit VLAN \fIvid\fR.  When a mirrored frame is sent out
-a trunk port, the frame's VLAN tag will be set to \fIvid\fR, replacing
-any existing tag; when it is sent out an implicit VLAN port, the frame
-will not be tagged.  This type of mirroring may be referred to as
-RSPAN.
-.IP
-Please note that mirroring to a VLAN can disrupt a network that
-contains unmanaged switches.  Consider an unmanaged physical switch
-with two ports: port 1, connected to an end host, and port 2,
-connected to an Open vSwitch configured to mirror received packets
-into VLAN 123 on port 2.  Suppose that the end host sends a packet on
-port 1 that the physical switch forwards to port 2.  The Open vSwitch
-forwards this packet to its destination and then reflects it back on
-port 2 in VLAN 123.  This reflected packet causes the unmanaged
-physical switch to replace the MAC learning table entry, which
-correctly pointed to port 1, with one that incorrectly points to port
-2.  Afterward, the physical switch will direct packets destined for
-the end host to the Open vSwitch on port 2, instead of to the end host
-on port 1, disrupting connectivity.  If mirroring to a VLAN is desired
-in this scenario, then the physical switch must be replaced by one
-that learns Ethernet addresses on a per-VLAN basis.  In addition,
-learning should be disabled on the VLAN containing mirrored traffic.
-If this is not done then intermediate switches will learn the MAC
-address of each end host from the mirrored traffic.  If packets being
-sent to that end host are also mirrored, then they will be dropped
-since the switch will attempt to send them out the input port.
-Disabling learning for the VLAN will cause the switch to correctly
-send the packet out all ports configured for that VLAN.  If Open
-vSwitch is being used as an intermediate switch learning can be disabled
-by setting the key \fBvlan.\fIbrname\fB.learning-disable=\fIvid\fR
-to the mirrored VLAN.
-.ST "Example"
-The following \fBovs\-vswitchd\fR configuration copies all frames received
-on \fBeth1\fR or \fBeth2\fR to \fBeth3\fR.
-.PP
-.RS
-.nf
-
-bridge.mybr.port=eth1
-bridge.mybr.port=eth2
-bridge.mybr.port=eth3
-
-mirror.mybr.a.select.src-port=eth1
-mirror.mybr.a.select.src-port=eth2
-mirror.mybr.a.output.port=eth3
-        
-.fi
-.RE
-.SS "Interface Rate-Limiting"
-Traffic policing and shaping are configured on interfaces.  Policing
-defines a hard limit at which traffic that exceeds the specified rate is
-dropped.  Shaping uses queues to delay packets so that egress traffic
-leaves at the specified rate.
-
-.ST "Ingress Policing"
-The rate at which traffic is allowed to enter through a interface may be 
-configured with ingress policing.  Note that "ingress" is from the 
-perspective of \fBovs\-vswitchd\fR.  If configured on a physical interface, 
-then it limits the rate at which traffic is allowed into the system from 
-the outside.  If configured on a virtual interface that is connected to 
-a virtual machine, then it limits the rate at which the guest is able to 
-transmit.
-
-The rate is specified in kilobits (1000 bits) per second with a maximum 
-burst size specified in kilobits (1000 bits).  The burst size should be at 
-least the size of the interface's MTU.  
-
-An interface may be configured to enforce ingress policing by defining the
-key \fBport.\fIname\fB.ingress.policing-rate\fR with an integer
-indicating the rate.  The interface \fIname\fR will only allow traffic to be
-received at the rate specified in kilobits per second.  If the rate is zero 
-or the key is not defined, then ingress policing is disabled.
-
-If ingress policing is enabled, then the burst rate may be set by defining 
-the key \fBport.\fIname\fB.ingress.policing-burst\fR with an integer 
-indicating the burst rate in kilobits.  If the key is not supplied or is 
-zero, then the default burst is 10 kilobits.
-
-.PP
-The following syntax limits interface \fBeth1\fR to receiving traffic at
-\fB512\fR kilobits per second with a burst of \fB20\fR kilobits:
-.PP
-.RS
-.nf
-
-port.eth1.ingress.policing-rate=512
-port.eth1.ingress.policing-burst=20
-
-.fi
-.SS "NetFlow v5 Flow Logging"
-NetFlow is a protocol that exports a number of details about terminating
-IP flows, such as the principals involved and duration.  A bridge may be
-configured to send NetFlow v5 records to NetFlow collectors when flows
-end.  To enable, define the key \fBnetflow.\fIbridge\fB.host\fR for each
-collector in the form \fIip\fB:\fIport\fR.  Records from \fIbridge\fR
-will be sent to each \fIip\fR on UDP \fIport\fR.  The \fIip\fR must
-be specified numerically, not as a DNS name.
-
-In addition to terminating flows, NetFlow can also send records at a set
-interval for flows that are still active.  This interval can be configured
-by defining the key \fBnetflow.\fIbridge\fB\.active-timeout\fR.  The value
-is in seconds.  An active timeout of 0 will disable this functionality.  By
-default there is timeout value of 600 seconds.
-
-The NetFlow messages will use the datapath index for the engine type and id.
-This can be overridden with the \fBnetflow.\fIbridge\fB.engine-type\fR and
-\fBnetflow.\fIbridge\fB.engine-id\fR, respectively.  Each takes a value
-between 0 and 255, inclusive.
-
-Many NetFlow collectors do not expect multiple virtual switches to be
-sending messages from the same host, and they do not store the engine
-information which could be used to disambiguate the traffic.  To prevent
-flows from multiple switches appearing as if they came on the interface,
-add \fBnetflow.\fIbridge\fB.add-id-to-iface=true\fR to the configuration
-file.  This will place the least significant 7 bits of the engine id
-into the most significant bits of the ingress and egress interface fields
-of flow records.  When this option is enabled, a maximum of 508 ports are
-supported.  By default, this behavior is disabled.
-
-The egress interface field normally contains the OpenFlow port number,
-however, certain port values have special meaning: 0xFFFF indicates
-flooding, 0xFFFE is multiple controller-specified output interfaces, and
-0xFFFD means that packets from the flow were dropped.  If add-id-to-iface
-is enabled then these values become 0x1FF, 0x1FE, and 0x1FD respectively.
-
-The following syntax sends NetFlow records for \fBmybr\fR to the NetFlow
-collector \fBnflow.example.com\fR on UDP port \fB9995\fR:
-.PP
-.RS
-.nf
-
-netflow.mybr.host=nflow.example.com:9995
-
-.fi
-.RE
-.SS "Remote Management"
-A \fBovs\-vswitchd\fR instance may be remotely managed by a controller that
-supports the OpenFlow Management Protocol, such as NOX.  This
-functionality is enabled by setting the key \fBmgmt.controller\fR to one 
-of the following values:
-.
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-SSL must be configured when this form is used (see \fBSSL
-Configuration\fR, below).
-.
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-.PP
-The maximum time between attempts to connect to the controller may be
-specified in integral seconds with the \fBmgmt.max-backoff\fR key.  The
-default maximum backoff is 8 seconds, and the minimum value is 1
-second.
-
-An inactivity probe may be configured with the \fBmgmt.inactivity-probe\fR
-key.  If \fBovs\-vswitchd\fR does not communicate with the controller for the
-specified number of seconds, it will send a probe.  If a response is not
-received for an additional amount of that time, \fBovs\-vswitchd\fR assumes
-the connection has been broken and attempts to reconnect.  The default
-and minimum values are both 5 seconds.
-
-A management id may be specified with the \fBmgmt.id\fR key.  It takes
-an id in the form of exactly 12 hexadecimal digits.  If one is not
-specified, a random id is generated each time \fBovs\-vswitchd\fR is started.
-.fi
-.RE
-.SS "OpenFlow Controller Connectivity"
-\fBovs\-vswitchd\fR can perform all configured bridging and switching
-locally, or it can be configured to connect a given bridge to an
-external OpenFlow controller, such as NOX.  Its behavior depends on
-the \fBbridge.\fIname\fB.controller\fR setting:
-.
-.TP
-\fI\[la]unset\[ra]\fR
-When the key is not set, the behavior depends on whether remote 
-management is configured.  If management is configured, then the switch 
-will connect to the controller specified on \fBmgmt.controller\fR.  If 
-management is not configured, the switch will perform all configured 
-bridging and switching locally.
-.
-.TP
-\fI\[la]empty\[ra]\fR
-Setting an empty string value disables controller connectivity.  The
-switch will perform all configured bridging and switching locally.
-.
-.TP
-\fBdiscover\fR
-Use controller discovery to find the local OpenFlow controller.
-Refer to \fBsecchan\fR(8) for information on how to configure a DHCP
-server to support controller discovery.  The following additional
-options control the discovery process:
-.
-.RS
-.TP
-\fBbridge.\fIname\fB.controller.accept-regex=\fIregex\fR
-A POSIX extended regular expression against which the discovered
-controller location is validated.  Only controllers whose names match
-the regular expression will be accepted.
-.IP
-The default regular expression is \fBssl:.*\fR, meaning that only SSL
-controller connections will be accepted, when SSL is configured (see
-\fBSSL Configuration\fR), and \fB.*\fR otherwise, meaning that any
-controller will be accepted.
-.IP
-The regular expression is implicitly anchored at the beginning of the
-controller location string, as if it begins with \fB^\fR.
-.TP
-\fBbridge.\fIname\fB.controller.update-resolv.conf=\fBtrue\fR|\fBfalse\fR
-By default, or if this is set to \fBtrue\fR, \fBovs\-vswitchd\fR overwrites
-the system's \fB/etc/resolv.conf\fR with domain information and DNS
-servers obtained via DHCP.  If this setting is \fBfalse\fR,
-\fBovs\-vswitchd\fR will not modify \fB/etc/resolv.conf\fR.
-.IP
-\fBovs\-vswitchd\fR will only modify \fBresolv.conf\fR if the DHCP response
-that it receives specifies one or more DNS servers.
-.RE
-.
-.TP
-\fBssl:\fIip\fR[\fB:\fIport\fR]
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-SSL must be configured when this form is used (see \fBSSL
-Configuration\fR, below).
-.
-.TP
-\fBtcp:\fIip\fR[\fB:\fIport\fR]
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-.
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
-.PP
-The datapath ID used by the bridge to identify itself to the remote
-controller may be specified as \fBbridge.\fIname\fB.datapath-id\fR,
-in the form of exactly 12 hexadecimal digits.  If the datapath ID
-is not specified, then it defaults to the bridge's MAC address (see
-\fBBridge Configuration\fR, above, for information on how the bridge's
-MAC address is chosen).
-.ST "Local Port Network Configuration"
-When an external controller is configured, but controller discovery is
-not in use, the following additional settings are honored:
-.TP
-\fBbridge.\fIname\fB.controller.in-band=\fBtrue\fR|\fBfalse\fR
-By default, or if this is set to \fBtrue\fR, \fBovs\-vswitchd\fR connects
-to the controller in-band.  If this is set to \fBfalse\fR,
-\fBovs\-vswitchd\fR connects to the controller out-of-band.  Refer to
-\fBsecchan\fR(8) for a description of in-band and out-of-band control.
-.IP "\fBbridge.\fIname\fB.controller.ip=\fIip\fR"
-If specified, the IP address to configure on the bridge's local port.
-.IP "\fBbridge.\fIname\fB.controller.netmask=\fInetmask\fR"
-When an IP is specified, the corresponding netmask.  The default is
-255.255.255.0 for a Class C IP address, 255.255.0.0 for Class B, and
-255.0.0.0 for Class A.
-.IP "\fBbridge.\fIname\fB.controller.gateway=\fIip\fR"
-When an IP is specified, the corresponding IP gateway.  There is no
-default gateway.
-.ST "Controller Failure Settings"
-The following additional settings take effect when any remote
-controller is configured:
-.IP "\fBbridge.\fIname\fB.controller.inactivity-probe=\fIsecs\fR"
-This optional setting may be set to \fIsecs\fR, a number of seconds.
-The minimum value of \fIsecs\fR is 5 seconds.  The default is taken
-from \fBmgmt.inactivity-probe\fR (see above).
-.IP
-When the virtual switch is connected to the controller, it waits for a
-message to be received from the controller for \fIsecs\fR seconds
-before it sends a inactivity probe to the controller.  After sending
-the inactivity probe, if no response is received for an additional
-\fIsecs\fR seconds, the secure channel assumes that the connection has
-been broken and attempts to reconnect.
-.IP
-Changing the inactivity probe interval also changes the interval
-before entering standalone mode (see below).
-.IP "\fBbridge.\fIname\fB.controller.fail-mode=\fBstandalone\fR|\fBsecure\fR"
-.IQ "\fBmgmt.fail-mode=standalone\fR|\fBsecure\fR"
-When a controller is configured, it is, ordinarily, responsible for
-setting up all flows on the virtual switch.  Thus, if the connection to
-the controller fails, no new network connections can be set up.  If
-the connection to the controller stays down long enough, no packets
-can pass through the switch at all.
-.IP
-The first of these that is set takes effect.
-If the value is \fBstandalone\fR, or if neither of these settings
-is set, \fBovs\-vswitchd\fR will take over
-responsibility for setting up
-flows when no message has been received from the controller for three
-times the inactivity probe interval (see above).  In this mode,
-\fBovs\-vswitchd\fR causes the datapath to act like an ordinary
-MAC-learning switch.  \fBovs\-vswitchd\fR will continue to retry connecting
-to the controller in the background and, when the connection succeeds,
-it discontinues its standalone behavior.
-.IP
-If this option is set to \fBsecure\fR, \fBovs\-vswitchd\fR will not
-set up flows on its own when the controller connection fails.
-.IP "\fBbridge.\fIname\fB.controller.max-backoff=\fIsecs\fR"
-Sets the maximum time between attempts to connect to the controller to
-\fIsecs\fR, which must be at least 1.  The actual interval between
-connection attempts starts at 1 second and doubles on each failing
-attempt until it reaches the maximum.  The default maximum backoff
-time is taken from \fBmgmt.max-backoff\fR.
-.ST "Controller Rate-Limiting"
-These settings configure how the virtual switch applies a ``token
-bucket'' to limit the rate at which packets in unknown flows are
-forwarded to the OpenFlow controller for flow-setup processing.  This
-feature prevents a single bridge from overwhelming a controller.
-.IP "\fBbridge.\fIname\fB.controller.rate-limit=\fIrate\fR"
-.IQ "\fBmgmt.rate-limit=\fIrate\fR"
-Limits the maximum rate at which packets will be forwarded to the
-OpenFlow controller to \fIrate\fR packets per second.  A rate specified
-explicitly for \fIname\fR overrides a value configured using the
-\fBmgmt.rate-limit\fR key.
-.IP
-If neither one of these settings is set, then the bridge does not
-limit the rate at which packets are forwarded to the controller.
-.IP "\fBbridge.\fIname\fB.controller.burst-limit=\fIburst\fR"
-.IQ "\fBmgmt.burst-limit=\fIburst\fR"
-Sets the maximum number of unused packet credits that the bridge will
-allow to accumulate during the time in which no packets are being
-forwarded to the OpenFlow controller to \fIburst\fR (measured in
-packets).  The default \fIburst\fR is one-quarter of the \fIrate\fR
-specified in the rate-limit setting.
-.IP
-A burst specified explicitly for \fIname\fR overrides a value configured 
-using the \fBmgmt.burst-limit\fR key.  This option takes effect only 
-when a rate-limit is specified.
-.ST "Remote Command Execution Settings"
-These settings configure the commands that remote OpenFlow connections
-are allowed to invoke using (e.g.) \fBovs\-ofctl execute\fR.  To be
-permitted, a command name must be whitelisted and must not be
-blacklisted.  When the whitelist and blacklist permit a command name,
-\fBovs\-vswitchd\fR looks for a program with the same name as the command
-in the commands directory (see below).  Other directories are not
-searched.
-.IP "\fBbridge.\fIname\fB.controller.commands.acl=\fIglob\fR"
-Whitelists commands whose names match shell glob pattern \fIglob\fR,
-allowing those commands to be invoked by the remote controller.
-.IP
-By default, no commands are whitelisted, so this setting is mandatory
-if any remote command execution is to be allowed.
-.IP "\fBbridge.\fIname\fB.controller.commands.acl=\fB!\fR\fIglob\fR"
-Blacklists commands whose names match shell glob pattern \fIglob\fR,
-prohibiting those commands from being invoked by the remote
-controller.  Command names that include characters other than upper-
-and lower-case English letters, digits, and the underscore and hyphen
-characters are blacklisted unconditionally.
-.IP "\fBbridge.\fIname\fB.controller.commands.dir=\fIdirectory\fR"
-Sets the directory searched for remote command execution to
-\fIdirectory\fR.  The default directory is
-\fB@pkgdatadir@/commands\fR.
-.SS "SSL Configuration"
-When \fBovs\-vswitchd\fR is configured to connect over SSL for management or
-for controller connectivity, the following settings are required:
-.TP
-\fBssl.private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the virtual
-switch's identity for SSL connections to the controller.
-.TP
-\fBssl.certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-certificate authority (CA) used by the controller and manager, that
-certifies the virtual switch's private key, identifying a trustworthy
-switch.
-.TP
-\fBssl.ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the virtual switch is connected to a trustworthy controller.
-.PP
-These files are read only once, at \fBovs\-vswitchd\fR startup time.  If
-their contents change, \fBovs\-vswitchd\fR must be killed and restarted.
-.PP
-These SSL settings apply to all SSL connections made by the virtual
-switch.
-.ST "CA Certificate Bootstrap"
-Ordinarily, all of the files named in the SSL configuration must exist
-when \fBovs\-vswitchd\fR starts.  However, if \fBssl.bootstrap-ca-cert\fR
-is set to \fBtrue\fR, then \fBovs\-vswitchd\fR will attempt to obtain the
-CA certificate from the controller on its first SSL connection and
-save it to the named PEM file.  If it is successful, it will
-immediately drop the connection and reconnect, and from then on all
-SSL connections must be authenticated by a certificate signed by the
-CA certificate thus obtained.
-.PP
-\fBThis option exposes the SSL connection to a man-in-the-middle
-attack obtaining the initial CA certificate\fR, but it may be useful
-for bootstrapping.
-.PP
-This option is only useful if the controller sends its CA certificate
-as part of the SSL certificate chain.  The SSL protocol does not
-require the controller to send the CA certificate, but
-\fBcontroller\fR(8) can be configured to do so with the
-\fB--peer-ca-cert\fR option.
-.SS "OpenFlow Management Connections"
-By default, each bridge \fIname\fR listens for OpenFlow management
-connections on a Unix domain socket named
-\fB@RUNDIR@/\fIname\fB.mgmt\fR.  This socket can be used to perform
-local OpenFlow monitoring and administration, e.g., \fBovs\-ofctl dump-flows
-unix:@RUNDIR@/\fIname\fB.mgmt\fR to display the flows currently set up
-in bridge \fIname\fR.
-.PP
-If \fBbridge.\fIname\fB.openflow.listeners\fR is set to one or more
-values, \fBovs\-vswitchd\fR instead listens on the specified connection
-methods.  Acceptable connection methods include:
-.RS
-.IP "\fBpunix:\fIfile\fR"
-Listens for connections on the Unix domain server socket named \fIfile\fR.
-.IP "\fBpssl:\fR[\fIport\fR]"
-Listens for SSL connections on \fIport\fR (default: 6633).  SSL must
-be configured when this form is used (see \fBSSL Configuration\fR,
-above).
-.IP "\fBptcp:\fR[\fIport\fR]"
-Listens for TCP connections on \fIport\fR (default: 6633).
-.RE
-To entirely disable listening for management connections, set
-\fBbridge.\fIname\fB.openflow.listeners\fR to the single value
-\fBnone\fR.
-
-.SS "OpenFlow Controller Connection Snooping"
-By default, each bridge \fIname\fR listens for OpenFlow controller
-connection snooping connections on a Unix domain socket named
-\fB@RUNDIR@/\fIname\fB.snoop\fR.  A client that connects to this
-socket, e.g., \fBovs\-ofctl monitor unix:@RUNDIR@/\fIname\fB.snoop\fR, will
-receive a copy of every OpenFlow message sent by the switch to the
-controller, or vice versa, on the primary OpenFlow controller
-connection.
-.PP
-If \fBbridge.\fIname\fB.openflow.snoops\fR is set to one or more
-values, \fBovs\-vswitchd\fR instead listens on the specified connection
-methods.  The acceptable connection methods are the same as for
-OpenFlow management connections (see above).
-.PP
-To entirely disable controller connection snooping, set
-\fBbridge.\fIname\fB.openflow.snoops\fR to the single value
-\fBnone\fR.
-.SH "SEE ALSO"
-.BR ovs\-brcompatd (8),
-.BR ovs\-cfg\-mod (8),
-.BR ovs\-vswitchd (8)
diff --git a/vswitchd/port.c b/vswitchd/port.c
deleted file mode 100644 (file)
index 147b9d4..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright (c) 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-
-#include "bridge.h"
-#include "cfg.h"
-#include "netdev.h"
-#include "ovs-vswitchd.h"
-#include "port.h"
-#include "svec.h"
-
-#define THIS_MODULE VLM_port
-#include "vlog.h"
-
-static int
-set_ingress_policing(const char *port_name) 
-{
-    int kbits_rate = cfg_get_int(0, "port.%s.ingress.policing-rate", 
-            port_name);
-    int kbits_burst = cfg_get_int(0, "port.%s.ingress.policing-burst", 
-            port_name);
-
-    return netdev_nodev_set_policing(port_name, kbits_rate, kbits_burst);
-}
-
-void
-port_init(void)
-{
-    port_reconfigure();
-}
-
-void
-port_reconfigure(void)
-{
-    struct svec ports;
-    int i;
-
-    svec_init(&ports);
-    bridge_get_ifaces(&ports);
-    for (i=0; i<ports.n; i++) {
-        set_ingress_policing(ports.names[i]);
-    }
-}
index 7a59526..68ae1ac 100644 (file)
@@ -256,7 +256,7 @@ proc_net_compat_update_vlan(const char *tagged_dev, const char *trunk_dev,
         }
         if (!vlan) {
             /* Create a new compat_vlan for (trunk_dev,vid). */
-            vlan = xcalloc(1, sizeof *vlan);
+            vlan = xzalloc(sizeof *vlan);
             vlan->trunk_dev = xstrdup(trunk_dev);
             vlan->vid = vid;
             vlan->vlan_dev = xasprintf("%s.%d", trunk_dev, vid);
diff --git a/vswitchd/vswitch-idl.ann b/vswitchd/vswitch-idl.ann
new file mode 100644 (file)
index 0000000..7a5cc31
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates vswitch.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "ovsrec_"
+s["idlHeader"] = "\"vswitchd/vswitch-idl.h\""
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
new file mode 100644 (file)
index 0000000..8661875
--- /dev/null
@@ -0,0 +1,233 @@
+{"name": "Open_vSwitch",
+ "tables": {
+   "Open_vSwitch": {
+     "columns": {
+       "bridges": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Bridge"},
+                  "min": 0, "max": "unlimited"}},
+       "controller": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Controller"},
+                   "min": 0, "max": 1}},
+       "managers": {
+         "type": {"key": "string", "min": 0, "max": "unlimited"}},
+       "ssl": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "SSL"},
+                  "min": 0, "max": 1}},
+       "external_ids": {
+         "type": {"key": "string", "value": "string",
+                  "min": 0, "max": "unlimited"}},
+       "next_cfg": {
+         "type": "integer"},
+       "cur_cfg": {
+         "type": "integer"}},
+     "maxRows": 1},
+   "Bridge": {
+     "columns": {
+       "name": {
+         "type": "string"},
+       "datapath_type": {
+         "type": "string"},
+       "datapath_id": {
+         "type": {"key": "string", "min": 0, "max": 1},
+         "ephemeral": true},
+       "ports": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port"},
+                  "min": 0, "max": "unlimited"}},
+       "mirrors": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Mirror"},
+                  "min": 0, "max": "unlimited"}},
+       "netflow": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "NetFlow"},
+                  "min": 0, "max": 1}},
+       "sflow": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "sFlow"},
+                  "min": 0, "max": 1}},
+       "controller": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Controller"},
+                  "min": 0, "max": 1}},
+       "other_config": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "external_ids": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "flood_vlans": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0, 
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}}}},
+   "Port": {
+     "columns": {
+       "name": {
+         "type": "string"},
+       "interfaces": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Interface"},
+                  "min": 1, "max": "unlimited"}},
+       "trunks": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}},
+       "tag": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 1}},
+       "mac": {
+         "type": {"key": {"type": "string"},
+                  "min": 0, "max": 1}},
+       "bond_updelay": {
+         "type": "integer"},
+       "bond_downdelay": {
+         "type": "integer"},
+       "bond_fake_iface": {
+         "type": "boolean"},
+       "fake_bridge": {
+         "type": "boolean"},
+       "other_config": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "external_ids": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}},
+   "Interface": {
+     "columns": {
+       "name": {
+         "type": "string"},
+       "type": {
+         "type": "string"},
+       "options": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "ingress_policing_rate": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0}}},
+       "ingress_policing_burst": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0}}},
+       "mac": {
+         "type": {"key": {"type": "string"},
+                  "min": 0, "max": 1}},
+       "external_ids": {
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "ofport": {
+         "type": {"key": "integer", "min": 0, "max": 1},
+         "ephemeral": true}}},
+   "Mirror": {
+     "columns": {
+       "name": {
+         "type": "string"},
+       "select_all": {
+         "type": "boolean"
+       },
+       "select_src_port": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port",
+                          "refType": "weak"},
+                   "min": 0, "max": "unlimited"}},
+       "select_dst_port": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port",
+                          "refType": "weak"},
+                   "min": 0, "max": "unlimited"}},
+       "select_vlan": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}},
+       "output_port": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port",
+                          "refType": "weak"},
+                  "min": 0, "max": 1}},
+       "output_vlan": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 1,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 1}}}},
+   "NetFlow": {
+     "columns": {
+       "targets": {
+         "type": {"key": {"type": "string"},
+                  "min": 1, "max": "unlimited"}},
+       "engine_type": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 255},
+                  "min": 0, "max": 1}},
+       "engine_id": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 255},
+                  "min": 0, "max": 1}},
+       "add_id_to_interface": {
+         "type": "boolean"},
+       "active_timeout": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": -1}}}}},
+   "sFlow": {
+     "columns": {
+       "targets": {
+         "type": {"key": "string", "min": 1, "max": "unlimited"}},
+       "sampling": {
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "polling": {
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "header": {
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "agent": {
+         "type": {"key": "string", "min": 0, "max": 1}}}},
+   "Controller": {
+     "columns": {
+       "target": {
+         "type": "string"},
+       "max_backoff": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 1000},
+                  "min": 0, "max": 1}},
+       "inactivity_probe": {
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "fail_mode": {
+         "type": {"key": {"type": "string",
+                          "enum": ["set", ["standalone", "secure"]]},
+                  "min": 0, "max": 1}},
+       "discover_accept_regex": {
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_update_resolv_conf": {
+         "type": {"key": "boolean", "min": 0, "max": 1}},
+       "connection_mode": {
+         "type": {"key": {"type": "string",
+                  "enum": ["set", ["in-band", "out-of-band"]]},
+                  "min": 0, "max": 1}},
+       "local_ip": {
+         "type": {"key": {"type": "string"},
+                  "min": 0, "max": 1}},
+       "local_netmask": {
+         "type": {"key": {"type": "string"},
+                  "min": 0, "max": 1}},
+       "local_gateway": {
+         "type": {"key": {"type": "string"},
+                  "min": 0, "max": 1}},
+       "controller_rate_limit": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 100},
+                  "min": 0, "max": 1}},
+       "controller_burst_limit": {
+         "type": {"key": {"type": "integer", 
+                          "minInteger": 25},
+                  "min": 0, "max": 1}}}},
+   "SSL": {
+     "columns": {
+       "private_key": {
+         "type": "string"},
+       "certificate": {
+         "type": "string"},
+       "ca_cert": {
+         "type": "string"},
+       "bootstrap_ca_cert": {
+         "type": "boolean"}},
+     "maxRows": 1}}}
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
new file mode 100644 (file)
index 0000000..e6ad387
--- /dev/null
@@ -0,0 +1,749 @@
+<database title="Open vSwitch Configuration Database">
+  <p>A database with this schema holds the configuration for one Open
+    vSwitch daemon.  The root of the configuration for the daemon is
+    the <ref table="Open_vSwitch"/> table, which must have exactly one
+    record.  Records in other tables are significant only when they
+    can be reached directly or indirectly from the
+    <ref table="Open_vSwitch"/> table.</p>
+
+  <table name="Open_vSwitch" title="Open vSwitch configuration.">
+    Configuration for an Open vSwitch daemon.  There must be exactly one record
+    in the <ref table="Open_vSwitch"/> table.
+
+    <group title="Configuration">
+      <column name="bridges">
+        Set of bridges managed by the daemon.
+      </column>
+
+      <column name="controller">
+        Default <ref table="Controller"/> used by bridges.  May be
+        overridden on a per-bridge basis by the <ref table="Bridge"
+        column="controller"/> column in <ref table="Bridge"/>.
+      </column>
+
+      <column name="managers">
+        Remote database clients to which the Open vSwitch's database server
+        should connect or to which it should listen.
+      </column>
+
+      <column name="ssl">
+        SSL used globally by the daemon.
+      </column>
+
+      <column name="external_ids">
+        Key-value pairs that identify this Open vSwitch's role in
+        external systems.  The currently defined key-value pairs are:
+        <dl>
+          <dt><code>system-uuid</code></dt>
+          <dd>A universally unique identifier for the Open vSwitch's
+            physical host.  The form of the identifier depends on the
+            type of the host.  On a Citrix XenServer, this is the host
+            UUID displayed by, e.g., <code>xe host-list</code>.</dd>
+        </dl>
+      </column>
+    </group>
+
+    <group title="Status">
+      <column name="next_cfg">
+        Sequence number for client to increment.  When a client modifies
+        any part of the database configuration and wishes to wait for
+        Open vSwitch to finish applying the changes, it may increment
+        this sequence number.
+      </column>
+
+      <column name="cur_cfg">
+        Sequence number that Open vSwitch sets to the current value of
+        <ref column="next_cfg"/> after it finishes applying a set of
+        configuration changes.
+      </column>
+    </group>
+  </table>
+
+  <table name="Bridge">
+    <p>
+      Configuration for a bridge within an
+      <ref table="Open_vSwitch"/>.
+    </p>
+    <p>
+      A <ref table="Bridge"/> record represents an Ethernet switch with one or
+      more ``ports,'' which are the <ref table="Port"/> records pointed to by
+      the <ref table="Bridge"/>'s <ref column="ports"/> column.
+    </p>
+
+    <group title="Core Features">
+      <column name="name">
+        Bridge identifier.  Should be alphanumeric and no more than about 8
+        bytes long.  Must be unique among the names of ports, interfaces, and
+        bridges on a host.
+      </column>
+
+      <column name="ports">
+        Ports included in the bridge.
+      </column>
+
+      <column name="mirrors">
+        Port mirroring configuration.
+      </column>
+
+      <column name="netflow">
+        NetFlow configuration.
+      </column>
+
+      <column name="sflow">
+        sFlow configuration.
+      </column>
+
+      <column name="flood_vlans">
+        VLAN IDs of VLANs on which MAC address learning should be disabled, so
+        that packets are flooded instead of being sent to specific ports that
+        are believed to contain packets' destination MACs.  This should
+        ordinarily be used to disable MAC learning on VLANs used for mirroring
+        (RSPAN VLANs).  It may also be useful for debugging.
+      </column>
+    </group>
+
+    <group title="OpenFlow Configuration">
+      <column name="controller">
+        OpenFlow controller.  If unset, defaults to that specified by
+        <ref column="controller" table="Open_vSwitch"/> in the
+        <ref table="Open_vSwitch"/> table.  If the default is also unset, then
+        no OpenFlow controller will be used.
+      </column>
+
+      <column name="datapath_id">
+        Reports the OpenFlow datapath ID in use.  Exactly 16 hex digits.
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="datapath_type">
+        Name of datapath provider.  The kernel datapath has
+        type <code>system</code>.  The userspace datapath has
+        type <code>netdev</code>.
+      </column>
+
+      <column name="external_ids">
+        Key-value pairs that identify this bridge's role in external systems.
+        The currently defined key-value pairs are:
+        <dl>
+          <dt><code>network-uuids</code></dt>
+          <dd>Space-delimited set of universally unique identifier(s) for the
+            network with which this bridge is associated.  The form of the
+            identifier(s) depends on the type of the host.  On a Citrix
+            XenServer host, the network identifiers are RFC 4122 UUIDs as
+            displayed by, e.g., <code>xe network-list</code>.</dd>
+        </dl>
+      </column>
+
+      <column name="other_config">
+        Key-value pairs for configuring rarely used bridge
+        features.  The currently defined key-value pairs are:
+        <dl>
+          <dt><code>datapath-id</code></dt>
+          <dd>Exactly 16 hex
+            digits to set the OpenFlow datapath ID to a specific
+            value.</dd>
+          <dt><code>hwaddr</code></dt>
+          <dd>An Ethernet address in the form
+            <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>
+            to set the hardware address of the local port and influence the
+            datapath ID.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Port" table="Port or bond configuration.">
+    <p>A port within a <ref table="Bridge"/>.</p>
+    <p>Most commonly, a port has exactly one ``interface,'' pointed to by its
+      <ref column="interfaces"/> column.  Such a port logically
+      corresponds to a port on a physical Ethernet switch.  A port
+      with more than one interface is a ``bonded port'' (see
+      <ref group="Bonding Configuration"/>).</p>
+    <p>Some properties that one might think as belonging to a port are actually
+      part of the port's <ref table="Interface"/> members.</p>
+
+    <column name="name">
+      Port name.  Should be alphanumeric and no more than about 8
+      bytes long.  May be the same as the interface name, for
+      non-bonded ports.  Must otherwise be unique among the names of
+      ports, interfaces, and bridges on a host.
+    </column>
+
+    <column name="interfaces">
+      The port's interfaces.  If there is more than one, this is a
+      bonded Port.
+    </column>
+
+    <group title="VLAN Configuration">
+      <p>A bridge port must be configured for VLANs in one of two
+        mutually exclusive ways:
+        <ul>
+          <li>A ``trunk port'' has an empty value for
+            <ref column="tag"/> and a possibly non-empty
+            <ref column="trunks"/> value.</li>
+          <li>An ``implicitly tagged VLAN port'' or ``access port''
+            has an nonempty value for <ref column="tag"/> and an empty
+            <ref column="trunks"/> value.</li>
+        </ul>
+        If <ref column="trunks"/> and <ref column="tag"/> are both
+        nonempty, the configuration is ill-formed.
+      </p>
+
+      <column name="tag">
+        <p>If nonempty, this port's implicitly tagged VLAN.  Frames
+          arriving on trunk ports will be forwarded to this port only
+          if they are tagged with the given VLAN.  Frames arriving on
+          other VLAN ports will be forwarded to this port only if they
+          have the same <ref column="tag"/> value.  Frames forwarded
+          to this port will not have an 802.1Q header.</p>
+        <p>When a frame with a 802.1Q header that indicates a nonzero VLAN is
+          received on an implicit VLAN port, it is discarded.</p>
+        <p>Must be empty if this is a trunk port.</p>
+      </column>
+
+      <column name="trunks">
+        <p>The 802.1Q VLAN(s) that this port trunks.  If the column is
+          empty, then the port trunks all VLANs as well as packets that
+          have no VLAN header.  Otherwise, only frames that have an
+          802.1Q header with one of the specified VLANs are accepted.
+          If <code>0</code> is included, then frames without an 802.1Q
+          header are also accepted.</p>
+        <p>Must be empty unless this is a trunk port.</p>
+      </column>
+    </group>
+
+    <group title="Bonding Configuration">
+      <p>A port that has more than one interface is a ``bonded port.''
+        Bonding allows for load balancing and fail-over.  Open vSwitch
+        supports ``source load balancing'' (SLB) bonding, which
+        assigns flows to slaves based on source MAC address, with
+        periodic rebalancing as traffic patterns change.  This form of
+        bonding does not require 802.3ad or other special support from
+        the upstream switch to which the slave devices are
+        connected.</p>
+
+      <p>These columns apply only to bonded ports.  Their values are
+        otherwise ignored.</p>
+
+      <column name="bond_updelay">
+        <p>For a bonded port, the number of milliseconds for which carrier must
+          stay up on an interface before the interface is considered to be up.
+          Specify <code>0</code> to enable the interface immediately.</p>
+        <p>This setting is honored only when at least one bonded interface is
+          already enabled.  When no interfaces are enabled, then the first bond
+          interface to come up is enabled immediately.</p>
+      </column>
+
+      <column name="bond_downdelay">
+        For a bonded port, the number of milliseconds for which carrier must
+        stay down on an interface before the interface is considered to be
+        down.  Specify <code>0</code> to disable the interface immediately.
+      </column>
+
+      <column name="bond_fake_iface">
+        For a bonded port, whether to create a fake internal interface with the
+        name of the port.  Use only for compatibility with legacy software that
+        requires this.
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="mac">
+        The MAC address to use for this port for the purpose of choosing the
+        bridge's MAC address.  This column does not necessarily reflect the
+        port's actual MAC address, nor will setting it change the port's actual
+        MAC address.
+      </column>
+
+      <column name="fake_bridge">
+        Does this port represent a sub-bridge for its tagged VLAN within the
+        Bridge?  See ovs-vsctl(8) for more information.
+      </column>
+
+      <column name="external_ids">
+        Key-value pairs that identify this port's role in external systems.  No
+        key-value pairs native to <ref table="Port"/> are currently defined.
+        For fake bridges (see the <ref column="fake_bridge"/> column), external
+        IDs for the fake bridge are defined here by prefixing a
+        <ref table="Bridge"/> <ref table="Bridge" column="external_ids"/> key
+        with <code>fake-bridge-</code>,
+        e.g. <code>fake-bridge-network-uuids</code>.
+      </column>
+
+      <column name="other_config">
+        Key-value pairs for configuring rarely used port features.  The
+        currently defined key-value pairs are:
+        <dl>
+          <dt><code>hwaddr</code></dt>
+          <dd>An Ethernet address in the form
+            <code><var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var></code>.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Interface" title="One physical network device in a Port.">
+    An interface within a <ref table="Port"/>.
+
+    <group title="Core Features">
+      <column name="name">
+        Interface name.  Should be alphanumeric and no more than about 8 bytes
+        long.  May be the same as the port name, for non-bonded ports.  Must
+        otherwise be unique among the names of ports, interfaces, and bridges
+        on a host.
+      </column>
+
+      <column name="mac">
+        <p>Ethernet address to set for this interface.  If unset then the
+          default MAC address is used:</p>
+        <ul>
+          <li>For the local interface, the default is the lowest-numbered MAC
+            address among the other bridge ports, either the value of the
+            <ref table="Port" column="mac"/> in its <ref table="Port"/> record,
+            if set, or its actual MAC (for bonded ports, the MAC of its slave
+            whose name is first in alphabetical order).  Internal ports and
+            bridge ports that are used as port mirroring destinations (see the
+            <ref table="Mirror"/> table) are ignored.</li>
+          <li>For other internal interfaces, the default MAC is randomly
+            generated.</li>
+          <li>External interfaces typically have a MAC address associated with
+            their hardware.</li>
+        </ul>
+        <p>Some interfaces may not have a software-controllable MAC
+        address.</p>
+      </column>
+
+      <column name="ofport">
+        <p>OpenFlow port number for this interface.  Unlike most columns, this
+          column's value should be set only by Open vSwitch itself.  Other
+          clients should set this column to an empty set (the default) when
+          creating an <ref table="Interface"/>.</p>
+        <p>Open vSwitch populates this column when the port number becomes
+          known.  If the interface is successfully added,
+          <ref column="ofport"/> will be set to a number between 1 and 65535
+          (generally either in the range 1 to 65280, exclusive, or 65534, the
+          port number for the OpenFlow ``local port'').  If the interface
+          cannot be added then Open vSwitch sets this column
+          to -1.</p>
+      </column>
+    </group>
+
+    <group title="System-Specific Details">
+      <column name="type">
+        The interface type, one of:
+        <dl>
+          <dt><code>system</code></dt>
+          <dd>An ordinary network device, e.g. <code>eth0</code> on Linux.
+            Sometimes referred to as ``external interfaces'' since they are
+            generally connected to hardware external to that on which the Open
+            vSwitch is running.  The empty string is a synonym for
+            <code>system</code>.</dd>
+          <dt><code>internal</code></dt>
+          <dd>A simulated network device that sends and receives traffic.  An
+            internal interface whose <ref column="name"/> is the same as its
+            bridge's <ref table="Open_vSwitch" column="name"/> is called the
+            ``local interface.''  It does not make sense to bond an internal
+            interface, so the terms ``port'' and ``interface'' are often used
+            imprecisely for internal interfaces.</dd>
+          <dt><code>tap</code></dt>
+          <dd>A TUN/TAP device managed by Open vSwitch.</dd>
+          <dt><code>gre</code></dt>
+          <dd>A GRE tunnel device managed by Open vSwitch.</dd>
+        </dl>
+      </column>
+
+      <column name="options">
+        Configuration options whose interpretation varies based on
+        <ref column="type"/>.
+      </column>
+    </group>
+
+    <group title="Ingress Policing">
+      <column name="ingress_policing_burst">
+        <p>Maximum burst size for data received on this interface, in kb.  The
+          default burst size if set to <code>0</code> is 1000 kb.  This value
+          has no effect if <ref column="ingress_policing_rate"/>
+          is <code>0</code>.</p>
+        <p>The burst size should be at least the size of the interface's
+          MTU.</p>
+      </column>
+
+      <column name="ingress_policing_rate">
+        <p>Maximum rate for data received on this interface, in kbps.  Data
+          received faster than this rate is dropped.  Set to <code>0</code> to
+          disable policing.</p>
+        <p>The meaning of ``ingress'' is from Open vSwitch's perspective.  If
+          configured on a physical interface, then it limits the rate at which
+          traffic is allowed into the system from the outside.  If configured
+          on a virtual interface that is connected to a virtual machine, then
+          it limits the rate at which the guest is able to transmit.</p>
+      </column>
+    </group>
+
+    <group title="Other Features">
+      <column name="external_ids">
+        <p>Key-value pairs that identify this interface's role in external
+          systems.  All of the currently defined key-value pairs specifically
+          apply to an interface that represents a virtual Ethernet interface
+          connected to a virtual machine.  These key-value pairs should not be
+          present for other types of interfaces.  Keys whose names end
+          in <code>-uuid</code> have values that uniquely identify the entity
+          in question.  For a Citrix XenServer hypervisor, these values are
+          UUIDs in RFC 4122 format.  Other hypervisors may use other
+          formats.</p>
+        <p>The currently defined key-value pairs are:</p>
+        <dl>
+          <dt><code>vif-uuid</code></dt>
+          <dd>The virtual interface associated with this interface.</dd>
+          <dt><code>network-uuid</code></dt>
+          <dd>The virtual network to which this interface is attached.</dd>
+          <dt><code>vm-uuid</code></dt>
+          <dd>The VM to which this interface belongs.</dd>
+          <dt><code>vif-mac</code></dt>
+          <dd>The MAC address programmed into the "virtual hardware" for this
+              interface, in the
+              form <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>.
+              For Citrix XenServer, this is the value of the <code>MAC</code>
+              field in the VIF record for this interface.</dd>
+        </dl>
+      </column>
+    </group>
+  </table>
+
+  <table name="Mirror" title="Port mirroring (SPAN/RSPAN).">
+    <p>A port mirror within a <ref table="Bridge"/>.</p>
+    <p>A port mirror configures a bridge to send selected frames to special
+      ``mirrored'' ports, in addition to their normal destinations.  Mirroring
+      traffic may also be referred to as SPAN or RSPAN, depending on the
+      mechanism used for delivery.</p>
+
+    <column name="name">
+      Arbitrary identifier for the <ref table="Mirror"/>.
+    </column>
+
+    <group title="Selecting Packets for Mirroring">
+      <column name="select_all">
+        If true, every packet arriving or departing on any port is
+        selected for mirroring.
+      </column>
+
+      <column name="select_dst_port">
+        Ports on which departing packets are selected for mirroring.
+      </column>
+
+      <column name="select_src_port">
+        Ports on which arriving packets are selected for mirroring.
+      </column>
+
+      <column name="select_vlan">
+        VLANs on which packets are selected for mirroring.  An empty set
+        selects packets on all VLANs.
+      </column>
+    </group>
+
+    <group title="Mirroring Destination Configuration">
+      <column name="output_port">
+        <p>Output port for selected packets, if nonempty.  Mutually exclusive
+          with <ref column="output_vlan"/>.</p>
+        <p>Specifying a port for mirror output reserves that port exclusively
+          for mirroring.  No frames other than those selected for mirroring
+          will be forwarded to the port, and any frames received on the port
+          will be discarded.</p>
+        <p>This type of mirroring is sometimes called SPAN.</p>
+      </column>
+
+      <column name="output_vlan">
+        <p>Output VLAN for selected packets, if nonempty.  Mutually exclusive
+          with <ref column="output_port"/>.</p>
+        <p>The frames will be sent out all ports that trunk
+          <ref column="output_vlan"/>, as well as any ports with implicit VLAN
+          <ref column="output_vlan"/>.  When a mirrored frame is sent out a
+          trunk port, the frame's VLAN tag will be set to
+          <ref column="output_vlan"/>, replacing any existing tag; when it is
+          sent out an implicit VLAN port, the frame will not be tagged.  This
+          type of mirroring is sometimes called RSPAN.</p>
+        <p><em>Please note:</em> Mirroring to a VLAN can disrupt a network that
+          contains unmanaged switches.  Consider an unmanaged physical switch
+          with two ports: port 1, connected to an end host, and port 2,
+          connected to an Open vSwitch configured to mirror received packets
+          into VLAN 123 on port 2.  Suppose that the end host sends a packet on
+          port 1 that the physical switch forwards to port 2.  The Open vSwitch
+          forwards this packet to its destination and then reflects it back on
+          port 2 in VLAN 123.  This reflected packet causes the unmanaged
+          physical switch to replace the MAC learning table entry, which
+          correctly pointed to port 1, with one that incorrectly points to port
+          2.  Afterward, the physical switch will direct packets destined for
+          the end host to the Open vSwitch on port 2, instead of to the end
+          host on port 1, disrupting connectivity.  If mirroring to a VLAN is
+          desired in this scenario, then the physical switch must be replaced
+          by one that learns Ethernet addresses on a per-VLAN basis.  In
+          addition, learning should be disabled on the VLAN containing mirrored
+          traffic. If this is not done then intermediate switches will learn
+          the MAC address of each end host from the mirrored traffic.  If
+          packets being sent to that end host are also mirrored, then they will
+          be dropped since the switch will attempt to send them out the input
+          port. Disabling learning for the VLAN will cause the switch to
+          correctly send the packet out all ports configured for that VLAN.  If
+          Open vSwitch is being used as an intermediate switch, learning can be
+          disabled by adding the mirrored VLAN to <ref column="flood_vlans"/>
+          in the appropriate <ref table="Bridge"/> table or tables.</p>
+      </column>
+    </group>
+  </table>
+
+  <table name="Controller" title="OpenFlow controller configuration.">
+    An OpenFlow controller.
+
+    <group title="Core Features">
+      <column name="target">
+        Connection method for controller.
+        The following connection methods are currently
+        supported:
+        <dl>
+          <dt><code>ssl:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            <p>The specified SSL <var>port</var> (default: 6633) on the host at
+              the given <var>ip</var>, which must be expressed as an IP address
+              (not a DNS name).  The <ref table="Open_vSwitch" column="ssl"/>
+              column in the <ref table="Open_vSwitch"/> must point to a valid
+              SSL configuration when this form is used.</p>
+            <p>SSL support is an optional feature that is not always built as
+              part of Open vSwitch.</p>
+          </dd>
+          <dt><code>tcp:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>The specified TCP <var>port</var> (default: 6633) on the host at
+            the given <var>ip</var>, which must be expressed as an IP address
+            (not a DNS name).</dd>
+          <dt><code>discover</code></dt>
+          <dd>Enables controller discovery.</dd>
+          <dt><code>none</code></dt>
+          <dd>Disables the controller.</dd>
+        </dl>
+      </column>
+
+      <column name="connection_mode">
+        Either <code>in-band</code> or <code>out-of-band</code>.  If not
+        specified, the default is implementation-specific.
+      </column>
+    </group>
+
+    <group title="Controller Failure Detection and Handling">
+      <column name="max_backoff">
+        Maximum number of milliseconds to wait between connection attempts.
+        Default is implementation-specific.
+      </column>
+
+      <column name="inactivity_probe">
+        Maximum number of milliseconds of idle time on connection to
+        controller before sending an inactivity probe message.  If Open
+        vSwitch does not communicate with the controller for the specified
+        number of seconds, it will send a probe.  If a response is not
+        received for the same additional amount of time, Open vSwitch
+        assumes the connection has been broken and attempts to reconnect.
+        Default is implementation-specific.
+      </column>
+
+      <column name="fail_mode">
+        <p>When a controller is configured, it is, ordinarily, responsible
+          for setting up all flows on the switch.  Thus, if the connection to
+          the controller fails, no new network connections can be set up.
+          If the connection to the controller stays down long enough,
+          no packets can pass through the switch at all.  This setting
+          determines the switch's response to such a situation.  It may be set
+          to one of the following:
+          <dl>
+            <dt><code>standalone</code></dt>
+            <dd>If no message is received from the controller for three
+              times the inactivity probe interval
+              (see <ref column="inactivity_probe"/>), then Open vSwitch
+              will take over responsibility for setting up flows.  In
+              this mode, Open vSwitch causes the datapath to act like an
+              ordinary MAC-learning switch.  Open vSwitch will continue
+              to retry connecting to the controller in the background
+              and, when the connection succeeds, it will discontinue its
+              standalone behavior.</dd>
+            <dt><code>secure</code></dt>
+            <dd>Open vSwitch will not set up flows on its own when the
+              controller connection fails.  It will continue retry
+              connecting to the controller forever.</dd>
+          </dl>
+        </p>
+        <p>If this value is unset, the default is
+        implementation-specific.</p>
+      </column>
+    </group>
+
+    <group title="OpenFlow Rate Limiting">
+        <column name="controller_burst_limit">
+          In conjunction with <ref column="controller_rate_limit"/>,
+          the maximum number of unused packet credits that the bridge will
+          allow to accumulate, in packets.  If not specified, the default
+          is implementation-specific.
+        </column>
+
+        <column name="controller_rate_limit">
+          <p>The maximum rate at which packets in unknown flows will be
+            forwarded to the OpenFlow controller, in packets per second.  This
+            feature prevents a single bridge from overwhelming the controller.
+            If not specified, the default is implementation-specific.</p>
+          <p>In addition, when a high rate triggers rate-limiting, Open
+            vSwitch queues controller packets for each port and transmits
+            them to the controller at the configured rate.  The number of
+            queued packets is limited by
+            the <ref column="controller_burst_limit"/> value.  The packet
+            queue is shared fairly among the ports on a bridge.</p><p>Open
+            vSwitch maintains two such packet rate-limiters per bridge.
+            One of these applies to packets sent up to the controller
+            because they do not correspond to any flow.  The other applies
+            to packets sent up to the controller by request through flow
+            actions. When both rate-limiters are filled with packets, the
+            actual rate that packets are sent to the controller is up to
+            twice the specified rate.</p>
+        </column>
+    </group>
+
+    <group title="Additional Configuration for Discovery">
+      <column name="discover_accept_regex">
+        If <ref column="target"/> is <code>discover</code>, a POSIX
+        extended regular expression against which the discovered controller
+        location is validated.  The regular expression is implicitly
+        anchored at the beginning of the controller location string, as
+        if it begins with <code>^</code>.  If not specified, the default
+        is implementation-specific.
+      </column>
+
+      <column name="discover_update_resolv_conf">
+        If <ref column="target"/> is <code>discover</code>,
+        whether to update <code>/etc/resolv.conf</code> when the
+        controller is discovered.  If not specified, the default
+        is implementation-specific.  Open vSwitch will only modify
+        <code>/etc/resolv.conf</code> if the DHCP response that it receives
+        specifies one or more DNS servers.
+      </column>
+    </group>
+
+    <group title="Additional Configuration without Discovery">
+      <column name="local_gateway">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        address of the gateway to configure on the local port.
+      </column>
+
+      <column name="local_ip">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        address to configure on the local port.
+      </column>
+
+      <column name="local_netmask">
+        If <ref column="target"/> is not <code>discover</code>, the IP
+        netmask to configure on the local port.
+      </column>
+    </group>
+  </table>
+
+  <table name="NetFlow">
+    A NetFlow target.  NetFlow is a protocol that exports a number of
+    details about terminating IP flows, such as the principals involved
+    and duration.
+
+    <column name="targets">
+      NetFlow targets in the form
+      <code><var>ip</var>:<var>port</var></code>.  The <var>ip</var>
+      must be specified numerically, not as a DNS name.
+    </column>
+
+    <column name="engine_id">
+      Engine ID to use in NetFlow messages.  Defaults to datapath index
+      if not specified.
+    </column>
+
+    <column name="engine_type">
+      Engine type to use in NetFlow messages.  Defaults to datapath
+      index if not specified.
+    </column>
+
+    <column name="active_timeout">
+      The interval at which NetFlow records are sent for flows that are
+      still active, in seconds.  A value of <code>0</code> requests the
+      default timeout (currently 600 seconds); a value of <code>-1</code>
+      disables active timeouts.
+    </column>
+
+    <column name="add_id_to_interface">
+      <p>If this column's value is <code>false</code>, the ingress and egress
+        interface fields of NetFlow flow records are derived from OpenFlow port
+        numbers.  When it is <code>true</code>, the 7 most significant bits of
+        these fields will be replaced by the least significant 7 bits of the
+        engine id.  This is useful because many NetFlow collectors do not
+        expect multiple switches to be sending messages from the same host, so
+        they do not store the engine information which could be used to
+        disambiguate the traffic.</p>
+      <p>When this option is enabled, a maximum of 508 ports are supported.</p>
+    </column>
+  </table>
+
+  <table name="SSL">
+    SSL configuration for an Open_vSwitch.
+
+    <column name="private_key">
+      Name of a PEM file containing the private key used as the switch's
+      identity for SSL connections to the controller.
+    </column>
+
+    <column name="certificate">
+      Name of a PEM file containing a certificate, signed by the
+      certificate authority (CA) used by the controller and manager,
+      that certifies the switch's private key, identifying a trustworthy
+      switch.
+    </column>
+
+    <column name="ca_cert">
+      Name of a PEM file containing the CA certificate used to verify
+      that the switch is connected to a trustworthy controller.
+    </column>
+
+    <column name="bootstrap_ca_cert">
+      If set to <code>true</code>, then Open vSwitch will attempt to
+      obtain the CA certificate from the controller on its first SSL
+      connection and save it to the named PEM file. If it is successful,
+      it will immediately drop the connection and reconnect, and from then
+      on all SSL connections must be authenticated by a certificate signed
+      by the CA certificate thus obtained.  <em>This option exposes the
+        SSL connection to a man-in-the-middle attack obtaining the initial
+        CA certificate.</em>  It may still be useful for bootstrapping.
+    </column>
+  </table>
+
+  <table name="sFlow">
+    <p>An sFlow(R) target.  sFlow is a protocol for remote monitoring
+      of switches.</p>
+
+    <column name="agent">
+      IP address to report as ``agent address'' to collectors.  If not
+      specified, defaults to the <ref table="Controller" column="local_ip"/> in
+      the collector's <ref table="Controller"/>.  If neither is specified,
+      sFlow is disabled.
+    </column>
+
+    <column name="header">
+      Number of bytes of a sampled packet to send to the collector.
+      If not specified, the default is 128 bytes.
+    </column>
+
+    <column name="polling">
+      Polling rate in seconds to send port statistics to the collector.
+      If not specified, defaults to 30 seconds.
+    </column>
+
+    <column name="sampling">
+      Rate at which packets should be sampled and sent to the collector.
+      If not specified, defaults to 400, which means one out of 400
+      packets, on average, will be sent to the collector.
+    </column>
+
+    <column name="targets">
+      sFlow targets in the form
+      <code><var>ip</var>:<var>port</var></code>.
+    </column>
+  </table>
+</database>
index 5407aac..6dafdb4 100644 (file)
@@ -44,15 +44,9 @@ files are:
 
         vswitch-aware replacement for Citrix script of the same name.
 
-    root_vswitch_scripts_dump-vif-details
+    usr_share_vswitch_scripts_refresh-network-uuids
 
-        Script to retrieve extended information about VIFs that are
-        needed by the controller.  This is called by the "vif" script,
-        which is run when virtual interfaces are added and removed.
-
-    root_vswitch_scripts_refresh-xs-network-uuids
-
-        Script to refresh bridge.<bridge>.xs-network-uuids keys, which
+        Script to refresh bridge.<bridge>.network-uuids keys, which
         can get out-of-sync following a pool join.  Running this script
         is an alternative to rebooting the host.
 
index 4c9c0a9..dde4105 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -6,18 +6,22 @@
 # without warranty of any kind.
 
 EXTRA_DIST += \
+       xenserver/LICENSE \
        xenserver/README \
+       xenserver/automake.mk \
        xenserver/etc_init.d_vswitch \
        xenserver/etc_init.d_vswitch-xapi-update \
        xenserver/etc_logrotate.d_vswitch \
        xenserver/etc_profile.d_vswitch.sh \
        xenserver/etc_xapi.d_plugins_vswitch-cfg-update \
        xenserver/etc_xensource_scripts_vif \
+       xenserver/opt_xensource_libexec_InterfaceReconfigure.py \
+       xenserver/opt_xensource_libexec_InterfaceReconfigureBridge.py \
+       xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py \
        xenserver/opt_xensource_libexec_interface-reconfigure \
-       xenserver/root_vswitch_scripts_sysconfig.template \
-       xenserver/root_vswitch_scripts_dump-vif-details \
-       xenserver/root_vswitch_scripts_refresh-xs-network-uuids \
        xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py \
        xenserver/usr_sbin_brctl \
        xenserver/usr_sbin_xen-bugtool \
+       xenserver/usr_share_vswitch_scripts_refresh-network-uuids \
+       xenserver/usr_share_vswitch_scripts_sysconfig.template \
        xenserver/vswitch-xen.spec
index e8e04ad..9b05879 100755 (executable)
@@ -5,7 +5,7 @@
 # chkconfig: 2345 09 91
 # description: Manage vswitch kernel modules and user-space daemon
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch
 
 # General config variables in /etc/sysconfig/vswitch
-VSWITCH_BASE="${VSWITCH_BASE:-/root/vswitch}"
-ENABLE_BRCOMPAT="${ENABLE_BRCOMPAT:-y}"
-ENABLE_FAKE_PROC_NET="${ENABLE_FAKE_PROC_NET:-y}"
-FORCE_COREFILES="${FORCE_COREFILES:-y}"
+: ${ENABLE_BRCOMPAT:=y}
+: ${ENABLE_FAKE_PROC_NET:=y}
+: ${ENABLE_MONITOR:=y}
+: ${FORCE_COREFILES:=y}
+
+# Config variables specific to ovsdb-server
+: ${OVSDB_SERVER_REMOTES:=punix:/var/run/ovsdb-server db:Open_vSwitch,managers}
+: ${OVSDB_SERVER_DB:=/etc/ovs-vswitchd.conf.db}
+: ${OVSDB_SERVER_PIDFILE:=/var/run/ovsdb-server.pid}
+: ${OVSDB_SERVER_RUN_DIR:=/var/xen/vswitch}
+: ${OVSDB_SERVER_PRIORITY:=-10}
+: ${OVSDB_SERVER_LOGFILE:=/var/log/ovsdb-server.log}
+: ${OVSDB_SERVER_FILE_LOGLEVEL:=INFO}
+: ${OVSDB_SERVER_SYSLOG_LOGLEVEL:=ERR}
+: ${OVSDB_SERVER_MEMLEAK_LOGFILE:=}
+: ${OVSDB_SERVER_STRACE_LOG:=}
+: ${OVSDB_SERVER_STRACE_OPT:=}
+: ${OVSDB_SERVER_VALGRIND_LOG:=}
+: ${OVSDB_SERVER_VALGRIND_OPT:=}
 
 # Config variables specific to ovs-vswitchd
-VSWITCHD_CONF="${VSWITCHD_CONF:-/etc/ovs-vswitchd.conf}"
-VSWITCHD_PIDFILE="${VSWITCHD_PIDFILE:-/var/run/ovs-vswitchd.pid}"
-VSWITCHD_RUN_DIR="${VSWITCHD_RUN_DIR:-/var/xen/vswitch}"
-VSWITCHD_PRIORITY="${VSWITCHD_PRIORITY:--10}"
-VSWITCHD_MLOCKALL="${VSWITCHD_MLOCKALL:-yes}"
-VSWITCHD_LOGFILE="${VSWITCHD_LOGFILE:-/var/log/ovs-vswitchd.log}"
-VSWITCHD_FILE_LOGLEVEL="${VSWITCHD_FILE_LOGLEVEL:-INFO}"
-VSWITCHD_SYSLOG_LOGLEVEL="${VSWITCHD_SYSLOG_LOGLEVEL:-ERR}"
-VSWITCHD_MEMLEAK_LOGFILE="${VSWITCHD_MEMLEAK_LOGFILE:-}"
-VSWITCHD_STRACE_LOG="${VSWITCHD_STRACE_LOG:-}"
-VSWITCHD_STRACE_OPT="${VSWITCHD_STRACE_OPT:-}"
-VSWITCHD_VALGRIND_LOG="${VSWITCHD_VALGRIND_LOG:-}"
-VSWITCHD_VALGRIND_OPT="${VSWITCHD_VALGRIND_OPT:-}"
+: ${VSWITCHD_OVSDB_SERVER:=unix:/var/run/ovsdb-server}
+: ${VSWITCHD_OVSDB_SCHEMA:=/usr/share/vswitch/vswitch.ovsschema}
+: ${VSWITCHD_PIDFILE:=/var/run/ovs-vswitchd.pid}
+: ${VSWITCHD_RUN_DIR:=/var/xen/vswitch}
+: ${VSWITCHD_PRIORITY:=-10}
+: ${VSWITCHD_MLOCKALL:=yes}
+: ${VSWITCHD_LOGFILE:=/var/log/ovs-vswitchd.log}
+: ${VSWITCHD_FILE_LOGLEVEL:=INFO}
+: ${VSWITCHD_SYSLOG_LOGLEVEL:=ERR}
+: ${VSWITCHD_MEMLEAK_LOGFILE:=}
+: ${VSWITCHD_STRACE_LOG:=}
+: ${VSWITCHD_STRACE_OPT:=}
+: ${VSWITCHD_VALGRIND_LOG:=}
+: ${VSWITCHD_VALGRIND_OPT:=}
 
 # Config variables specific to ovs-brcompatd
-BRCOMPATD_PIDFILE="${BRCOMPATD_PIDFILE:-/var/run/ovs-brcompatd.pid}"
-BRCOMPATD_RUN_DIR="${BRCOMPATD_RUN_DIR:-/var/xen/vswitch}"
-BRCOMPATD_PRIORITY="${BRCOMPATD_PRIORITY:--10}"
-BRCOMPATD_LOGFILE="${BRCOMPATD_LOGFILE:-/var/log/ovs-brcompatd.log}"
-BRCOMPATD_FILE_LOGLEVEL="${BRCOMPATD_FILE_LOGLEVEL:-INFO}"
-BRCOMPATD_SYSLOG_LOGLEVEL="${BRCOMPATD_SYSLOG_LOGLEVEL:-ERR}"
-BRCOMPATD_MEMLEAK_LOGFILE="${BRCOMPATD_MEMLEAK_LOGFILE:-}"
-BRCOMPATD_STRACE_LOG="${BRCOMPATD_STRACE_LOG:-}"
-BRCOMPATD_STRACE_OPT="${BRCOMPATD_STRACE_OPT:-}"
-BRCOMPATD_VALGRIND_LOG="${BRCOMPATD_VALGRIND_LOG:-}"
-BRCOMPATD_VALGRIND_OPT="${BRCOMPATD_VALGRIND_OPT:-}"
-
-
-
+: ${BRCOMPATD_PIDFILE:=/var/run/ovs-brcompatd.pid}
+: ${BRCOMPATD_RUN_DIR:=/var/xen/vswitch}
+: ${BRCOMPATD_PRIORITY:=-10}
+: ${BRCOMPATD_LOGFILE:=/var/log/ovs-brcompatd.log}
+: ${BRCOMPATD_FILE_LOGLEVEL:=INFO}
+: ${BRCOMPATD_SYSLOG_LOGLEVEL:=ERR}
+: ${BRCOMPATD_MEMLEAK_LOGFILE:=}
+: ${BRCOMPATD_STRACE_LOG:=}
+: ${BRCOMPATD_STRACE_OPT:=}
+: ${BRCOMPATD_VALGRIND_LOG:=}
+: ${BRCOMPATD_VALGRIND_OPT:=}
 
 # Full paths to executables & modules
-vswitchd="$VSWITCH_BASE/sbin/ovs-vswitchd"
-brcompatd="$VSWITCH_BASE/sbin/ovs-brcompatd"
-dpctl="$VSWITCH_BASE/bin/ovs-dpctl"
-appctl="$VSWITCH_BASE/bin/ovs-appctl"
-ofctl="$VSWITCH_BASE/bin/ovs-ofctl"
-
+ovsdb_server="/usr/sbin/ovsdb-server"
+ovsdb_tool="/usr/bin/ovsdb-tool"
+vswitchd="/usr/sbin/ovs-vswitchd"
+brcompatd="/usr/sbin/ovs-brcompatd"
+dpctl="/usr/bin/ovs-dpctl"
+appctl="/usr/bin/ovs-appctl"
+ofctl="/usr/bin/ovs-ofctl"
+vsctl="/usr/bin/ovs-vsctl"
 
 if [ "$ENABLE_FAKE_PROC_NET" = "y" ]; then
     if [ "$ENABLE_BRCOMPAT" != "y" ]; then
@@ -75,6 +90,12 @@ if [ "$ENABLE_FAKE_PROC_NET" = "y" ]; then
     fi
 fi
 
+if test "$ENABLE_MONITOR" = "y"; then
+    monitor_opt="--monitor"
+else
+    monitor_opt=
+fi
+
 function dp_list {
     "$dpctl" show | grep '^dp[0-9]\+:' | cut -d':' -f 1
 }
@@ -92,10 +113,13 @@ function remove_all_dp {
 function insert_modules_if_required {
     if ! lsmod | grep -q "openvswitch_mod"; then
         action "Inserting llc module" modprobe llc
-        action "Inserting openvswitch module" insmod $VSWITCH_BASE/kernel_modules/openvswitch_mod.ko
+        action "Inserting openvswitch module" modprobe openvswitch_mod
     fi
     if [ -n "$BRCOMPATD_PIDFILE" ] && ! lsmod | grep -q "brcompat_mod"; then
-        action "Inserting brcompat module" insmod $VSWITCH_BASE/kernel_modules/brcompat_mod.ko
+        action "Inserting brcompat module" modprobe brcompat_mod
+    fi
+    if [ -f "/lib/modules/`uname -r`/kernel/net/vswitch/ip_gre_mod.ko" ] && ! lsmod | grep -q "ip_gre_mod"; then
+        action "Inserting ip_gre module" modprobe ip_gre_mod
     fi
 }
 
@@ -106,20 +130,55 @@ function remove_modules {
     if lsmod | grep -q "openvswitch_mod"; then
         action "Removing openvswitch module" rmmod openvswitch_mod.ko
     fi
-}
-
-function reload_vswitchd {
-    if [ -f "$VSWITCHD_PIDFILE" ]; then
-        "$appctl" \
-            --target=ovs-vswitchd.$(cat "$VSWITCHD_PIDFILE").ctl \
-            --execute=vswitchd/reload
+    if lsmod | grep -q "ip_gre_mod"; then
+        action "Removing ip_gre module" rmmod ip_gre_mod.ko
     fi
 }
 
-function reload_brcompatd {
-    if [ -f "$BRCOMPATD_PIDFILE" ]; then
-        "$appctl" \
-            --target=ovs-brcompatd.$(cat "$BRCOMPATD_PIDFILE").ctl --reopen
+function start_ovsdb_server {
+    local syslog_opt="-vANY:SYSLOG:${OVSDB_SERVER_SYSLOG_LOGLEVEL}"
+    local logfile_file_opt=""
+    local logfile_level_opt=""
+    if [ ! -d "$OVSDB_SERVER_RUN_DIR" ]; then
+        mkdir -p "$OVSDB_SERVER_RUN_DIR"
+    fi
+    cd "$OVSDB_SERVER_RUN_DIR"
+    local remotes=
+    for remote in $OVSDB_SERVER_REMOTES; do
+        remotes="$remotes --remote=$remote"
+    done
+    if [ -n "$OVSDB_SERVER_FILE_LOGLEVEL" ]; then
+        logfile_level_opt="-vANY:FILE:${OVSDB_SERVER_FILE_LOGLEVEL}"
+        logfile_file_opt="--log-file=$OVSDB_SERVER_LOGFILE"
+    fi
+    local leak_opt=""
+    if [ -n "$OVSDB_SERVER_MEMLEAK_LOGFILE" ]; then
+        leak_opt="--check-leaks=$OVSDB_SERVER_MEMLEAK_LOGFILE"
+        if [ -e "$OVSDB_SERVER_MEMLEAK_LOGFILE" ]; then
+            mv "$OVSDB_SERVER_MEMLEAK_LOGFILE" "$OVSDB_SERVER_MEMLEAK_LOGFILE.prev"
+        fi
+    fi
+    local strace_opt=""
+    local daemonize="y"
+    if [ -n "$OVSDB_SERVER_STRACE_LOG" ] && [ -n "$OVSDB_SERVER_VALGRIND_LOG" ]; then
+        printf "Can not start with both VALGRIND and STRACE\n"
+        exit 1
+    fi
+    if [ -n "$OVSDB_SERVER_STRACE_LOG" ]; then
+        strace_opt="strace -o $OVSDB_SERVER_STRACE_LOG $OVSDB_SERVER_STRACE_OPT"
+        daemonize="n"
+    fi
+    if [ -n "$OVSDB_SERVER_VALGRIND_LOG" ]; then
+        valgrind_opt="valgrind --log-file=$OVSDB_SERVER_VALGRIND_LOG $OVSDB_SERVER_VALGRIND_OPT"
+        daemonize="n"
+    fi
+    ssl_opts="--private-key=db:SSL,private_key --certificate=db:SSL,certificate --bootstrap-ca-cert=db:SSL,ca_cert"
+    if [ "$daemonize" != "y" ]; then
+        # Start in background and force a "success" message
+        action "Starting ovsdb_server ($strace_opt$valgrind_opt)" true
+        (nice -n "$OVSDB_SERVER_PRIORITY" $strace_opt $valgrind_opt "$ovsdb_server" "$OVSDB_SERVER_DB" --pidfile="$OVSDB_SERVER_PIDFILE" --detach $monitor_opt --no-chdir -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $remotes $ssl_opts) &
+    else
+        action "Starting ovsdb-server" nice -n "$OVSDB_SERVER_PRIORITY" "$ovsdb_server" "$OVSDB_SERVER_DB" --pidfile="$OVSDB_SERVER_PIDFILE" --detach $monitor_opt --no-chdir -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $remotes $ssl_opts
     fi
 }
 
@@ -166,9 +225,9 @@ function start_vswitchd {
     if [ "$daemonize" != "y" ]; then
         # Start in background and force a "success" message
         action "Starting ovs-vswitchd ($strace_opt$valgrind_opt)" true
-        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $mlockall_opt "$VSWITCHD_CONF") &
+        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $monitor_opt --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $mlockall_opt "$VSWITCHD_OVSDB_SERVER") &
     else
-        action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $mlockall_opt "$VSWITCHD_CONF"
+        action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $monitor_opt --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $mlockall_opt "$VSWITCHD_OVSDB_SERVER"
     fi
 }
 
@@ -185,7 +244,7 @@ function start_brcompatd {
         logfile_file_opt="--log-file=$BRCOMPATD_LOGFILE"
     fi
     local leak_opt=""
-    if [ -n "$BRCOMPATD_MEMLEAK_LOG" ]; then
+    if [ -n "$BRCOMPATD_MEMLEAK_LOGFILE" ]; then
         leak_opt="--check-leaks=$BRCOMPATD_MEMLEAK_LOGFILE"
         if [ -e "$BRCOMPATD_MEMLEAK_LOGFILE" ]; then
             mv "$BRCOMPATD_MEMLEAK_LOGFILE" "$BRCOMPATD_MEMLEAK_LOGFILE.prev"
@@ -205,13 +264,21 @@ function start_brcompatd {
         valgrind_opt="valgrind --log-file=$BRCOMPATD_VALGRIND_LOG $BRCOMPATD_VALGRIND_OPT"
         daemonize="n"
     fi
-    appctl_cmd="$appctl -t /var/run/ovs-vswitchd.\`cat $VSWITCHD_PIDFILE\`.ctl -e '%s'"
+    appctl_cmd="$appctl --target=/var/run/ovs-vswitchd.\`cat $VSWITCHD_PIDFILE\`.ctl %s"
     if [ "$daemonize" != "y" ]; then
         # Start in background and force a "success" message
         action "Starting ovs-brcompatd ($strace_opt$valgrind_opt)" true
-        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd"--no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") &
+        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd"--no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER") &
     else
-        action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF"
+        action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach $monitor_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER"
+    fi
+}
+
+function stop_ovsdb_server {
+    if [ -f "$OVSDB_SERVER_PIDFILE" ]; then
+        local pid=$(cat "$OVSDB_SERVER_PIDFILE")
+        action "Killing ovsdb-server ($pid)" kill -TERM $pid
+        rm -f "$OVSDB_SERVER_PIDFILE"
     fi
 }
 
@@ -242,8 +309,6 @@ WARNING!!!
 
 Restarting vswitch on a live server is not guaranteed to work.  It is
 provided as a convenience for those situations in which it does work.
-If you just want to reload the configuration file, use "reload"
-instead of restart.
 
 EOF
     read -s -r -n 1 -p "Countinue with restart (y/N): " response
@@ -258,6 +323,16 @@ EOF
     esac
 }
 
+function set_system_uuid {
+    system_uuid=$(. /etc/xensource-inventory && echo $INSTALLATION_UUID)
+    if test -n "$system_uuid"; then
+        action "Configuring Open vSwitch system UUID" true
+        $vsctl --no-wait set Open_vSwitch . external-ids:system-uuid="$system_uuid"
+    else
+        action "Configuring Open vSwitch system UUID" false
+    fi
+}
+
 function start {
     if [ "$FORCE_COREFILES" = "y" ]; then
         turn_on_corefiles
@@ -269,28 +344,40 @@ function start {
     # ovs-vswitchd needs a few per bridge
     ulimit -n 4096
 
-    if [ ! -e "$VSWITCHD_CONF" ]; then
-        warning "$VSWITCHD_CONF does not exist"
-        action "Creating empty $VSWITCHD_CONF" touch "$VSWITCHD_CONF"
-    elif [ ! -e /var/run/vswitch.booted ]; then
+    # Allow GRE traffic.
+    iptables -I INPUT -p gre -j ACCEPT
+
+    if [ ! -e "$OVSDB_SERVER_DB" ]; then
+        warning "$OVSDB_SERVER_DB does not exist"
+
+        action "Creating empty database $OVSDB_SERVER_DB" true
+        $ovsdb_tool -vANY:console:emer create "$OVSDB_SERVER_DB" "$VSWITCHD_OVSDB_SCHEMA"
+    else
+        # Upgrade or downgrade schema and compact database.
+        $ovsdb_tool -vANY:console:emer convert "$OVSDB_SERVER_DB" "$VSWITCHD_OVSDB_SCHEMA"
+    fi
+
+    start_ovsdb_server
+    $vsctl --no-wait init
+    if [ ! -e /var/run/vswitch.booted ]; then
         touch /var/run/vswitch.booted
-        /usr/bin/ovs-cfg-mod '-vANY:console:emer' -F "$VSWITCHD_CONF" \
-            '--del-match=bridge.*' \
-            '--del-match=port.*' \
-            '--del-match=bonding.*' \
-            '--del-match=iface.*' \
-            '--del-match=vlan.*.trunks=*' \
-            '--del-match=vlan.*.tag=*'
+        for bridge in $($vsctl list-br); do
+            $vsctl --no-wait del-br $bridge
+        done
     fi
 
+    set_system_uuid
+
     start_vswitchd
     start_brcompatd
-    reload_vswitchd  # ensures ovs-vswitchd has fully read config file.
+    touch /var/lock/subsys/vswitch
 }
 
 function stop {
     stop_brcompatd
     stop_vswitchd
+    stop_ovsdb_server
+    rm -f /var/lock/subsys/vswitch
 }
 
 function restart {
@@ -310,10 +397,6 @@ case "$1" in
     restart)
         restart
         ;;
-    reload)
-        reload_vswitchd
-        reload_brcompatd
-        ;;
     strace-vswitchd)
         shift
         strace -p $(cat "$VSWITCHD_PIDFILE") "$@"
@@ -323,15 +406,17 @@ case "$1" in
         strace -p $(cat "$BRCOMPATD_PIDFILE") "$@"
         ;;
     status)
-        status -p ovs-vswitchd.pid ovs-vswitchd
-        status -p ovs-brcompatd.pid ovs-brcompatd
+        status -p "$OVSDB_SERVER_PIDFILE" ovsdb-server
+        status -p "$VSWITCHD_PIDFILE" ovs-vswitchd
+        status -p "$BRCOMPATD_PIDFILE" ovs-brcompatd
         ;;
     version)
-        "$VSWITCH_BASE"/sbin/ovs-vswitchd -V
-        "$VSWITCH_BASE"/sbin/ovs-brcompatd -V
+        /usr/sbin/ovsdb-server -V
+        /usr/sbin/ovs-vswitchd -V
+        /usr/sbin/ovs-brcompatd -V
         ;;
     help)
-        printf "vswitch [start|stop|restart|reload|unload|status|version]\n"
+        printf "vswitch [start|stop|restart|unload|status|version]\n"
         ;;
     *)
         printf "Unknown command: $1\n"
index 5c9e099..1c5f079 100755 (executable)
 
 . /etc/init.d/functions
 
-test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch
-VSWITCH_BASE="${VSWITCH_BASE:-/root/vswitch}"
-VSWITCHD_CONF="${VSWITCHD_CONF:-/etc/ovs-vswitchd.conf}"
-VSWITCHD_PIDFILE="${VSWITCHD_PIDFILE:-/var/run/ovs-vswitchd.pid}"
-VSWITCHD_PRIORITY="${VSWITCHD_PRIORITY:--5}"
-VSWITCHD_LOGFILE="${VSWITCHD_LOGFILE:-/var/log/ovs-vswitchd.log}"
-VSWITCHD_FILE_LOGLEVEL="${VSWITCHD_FILE_LOGLEVEL:-}"
-VSWITCHD_SYSLOG_LOGLEVEL="${VSWITCHD_SYSLOG_LOGLEVEL:-WARN}"
-VSWITCHD_MEMLEAK_LOGFILE="${VSWITCHD_MEMLEAK_LOGFILE:-}"
-BRCOMPATD_PIDFILE="${BRCOMPATD_PIDFILE:-/var/run/ovs-brcompatd.pid}"
-BRCOMPATD_PRIORITY="${BRCOMPATD_PRIORITY:--5}"
-BRCOMPATD_LOGFILE="${BRCOMPATD_LOGFILE:-/var/log/ovs-brcompatd.log}"
-BRCOMPATD_FILE_LOGLEVEL="${BRCOMPATD_FILE_LOGLEVEL:-}"
-BRCOMPATD_SYSLOG_LOGLEVEL="${BRCOMPATD_SYSLOG_LOGLEVEL:-WARN}"
-BRCOMPATD_MEMLEAK_LOGFILE="${BRCOMPATD_MEMLEAK_LOGFILE:-}"
-
 function do_host_call {
     xe host-call-plugin host-uuid="$INSTALLATION_UUID" plugin="vswitch-cfg-update" fn="update" >/dev/null
 }
 
 function start {
     if [ ! -f /etc/xensource-inventory ]; then
-        printf "vxwitch-xapi-update ERROR: XenSource inventory not present in /etc/xensource-inventory\n"
+        printf "vswitch-xapi-update ERROR: XenSource inventory not present in /etc/xensource-inventory\n"
         exit 1
     fi
     source /etc/xensource-inventory
@@ -61,7 +45,7 @@ case "$1" in
         start
         ;;
     help)
-        printf "vswitch [start|stop|restart]\n"
+        printf "vswitch-xapi-update [start|stop|restart]\n"
         ;;
     *)
         printf "Unknown command: $1\n"
index 6366c67..d6f71af 100644 (file)
@@ -9,6 +9,8 @@
        sharedscripts
        postrotate
        # Tell ovs-vswitchd and ovs-brcompatd to reopen their log files
-       /sbin/service vswitch reload
+       /usr/bin/ovs-appctl -t ovs-vswitchd vlog/reopen
+       /usr/bin/ovs-appctl -t ovs-brcompatd vlog/reopen
+       /usr/bin/ovs-appctl -t ovsdb-server vlog/reopen
        endscript
 }
index 9092754..a001e21 100644 (file)
@@ -1,21 +1,12 @@
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
 # notice and this notice are preserved.  This file is offered as-is,
 # without warranty of any kind.
 
-PATH=/root/vswitch/bin:$PATH
-export PATH
-MANPATH=/root/vswitch/share/man:$MANPATH
-export MANPATH
-
 alias vswitch='service vswitch'
 
-function watchconf {
-    watch cat /etc/ovs-vswitchd.conf
-}
-
 function watchdp {
        watch ovs-dpctl show "$@"
 }
index ce407ad..4cf2d88 100755 (executable)
@@ -4,7 +4,7 @@
 # ovs-vswitchd configuration file that are managed in the xapi database
 # when integrated with Citrix management tools.
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # TBD: - error handling needs to be improved.  Currently this can leave
 # TBD:   the system in a bad state if anything goes wrong.
 
-import logging
-log = logging.getLogger("vswitch-cfg-update")
-logging.basicConfig(filename="/var/log/vswitch-cfg-update.log", level=logging.DEBUG)
-
 import XenAPIPlugin
 import XenAPI
 import os
 import subprocess
 
-cfg_mod="/root/vswitch/bin/ovs-cfg-mod"
-vswitchd_cfg_filename="/etc/ovs-vswitchd.conf"
+vsctl="/usr/bin/ovs-vsctl"
 cacert_filename="/etc/ovs-vswitchd.cacert"
 
 # Delete the CA certificate, so that we go back to boot-strapping mode
@@ -46,10 +41,8 @@ def update(session, args):
     pools = session.xenapi.pool.get_all()
     # We assume there is only ever one pool...
     if len(pools) == 0:
-        log.error("No pool for host.")
         raise XenAPIPlugin.Failure("NO_POOL_FOR_HOST", [])
     if len(pools) > 1:
-        log.error("More than one pool for host.")
         raise XenAPIPlugin.Failure("MORE_THAN_ONE_POOL_FOR_HOST", [])
     pool = session.xenapi.pool.get_record(pools[0])
     try:
@@ -58,53 +51,42 @@ def update(session, args):
         controller = ""
     currentController = vswitchCurrentController()
     if controller == "" and currentController != "":
-        log.debug("Removing controller configuration.")
         delete_cacert()
         removeControllerCfg()
         return "Successfully removed controller config"
     elif controller != currentController:
-        if len(controller) == 0:
-            log.debug("Setting controller to: %s" % (controller))
-        else:
-            log.debug("Changing controller from %s to %s" % (currentController, controller))
         delete_cacert()
         setControllerCfg(controller)
         return "Successfully set controller to " + controller
     else:
-        log.debug("No change to controller configuration required.")
-    return "No change to configuration"
-        
+        return "No change to configuration"
+
 def vswitchCurrentController():
-    controller = vswitchCfgQuery("mgmt.controller")
+    controller = vswitchCfgQuery(["get", "Open_vSwitch", 
+                                  ".", "managers"]).strip('[]"')
     if controller == "":
         return controller
     if len(controller) < 4 or controller[0:4] != "ssl:":
-        log.warning("Controller does not specify ssl connection type, returning entire string.")
         return controller
     else:
-        return controller[4:]
+        return controller.split(':')[1]
 
 def removeControllerCfg():
-    vswitchCfgMod(["--del-match", "mgmt.controller=*",
-                   "--del-match", "ssl.bootstrap-ca-cert=*",
-                   "--del-match", "ssl.ca-cert=*",
-                   "--del-match", "ssl.private-key=*",
-                   "--del-match", "ssl.certificate=*"])
-                                       
+    vswitchCfgMod(["--", "clear", "Open_vSwitch", ".", "managers",
+                   "--", "del-ssl"])
+
 def setControllerCfg(controller):
-    vswitchCfgMod(["--del-match", "mgmt.controller=*",
-                   "--del-match", "ssl.bootstrap-ca-cert=*",
-                   "--del-match", "ssl.ca-cert=*",
-                   "--del-match", "ssl.private-key=*",
-                   "--del-match", "ssl.certificate=*",
-                   "-a", "mgmt.controller=ssl:" + controller,
-                   "-a", "ssl.bootstrap-ca-cert=true",
-                   "-a", "ssl.ca-cert=/etc/ovs-vswitchd.cacert",
-                   "-a", "ssl.private-key=/etc/xensource/xapi-ssl.pem",
-                   "-a", "ssl.certificate=/etc/xensource/xapi-ssl.pem"])
+    vswitchCfgMod(["--", "clear", "Open_vSwitch", ".", "managers",
+                   "--", "del-ssl",
+                   "--", "--bootstrap", "set-ssl",
+                   "/etc/xensource/xapi-ssl.pem",
+                   "/etc/xensource/xapi-ssl.pem",
+                   "/etc/ovs-vswitchd.cacert",
+                   "--", "set", "Open_vSwitch", ".",
+                   'managers="ssl:' + controller + ':6632"'])
 
-def vswitchCfgQuery(key):
-    cmd = [cfg_mod, "--config-file=" + vswitchd_cfg_filename, "-q", key]
+def vswitchCfgQuery(action_args):
+    cmd = [vsctl, "-vANY:console:emer"] + action_args
     output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
     if len(output) == 0 or output[0] == None:
         output = ""
@@ -113,22 +95,11 @@ def vswitchCfgQuery(key):
     return output
 
 def vswitchCfgMod(action_args):
-    cmd = [cfg_mod, "-vANY:console:emer",
-           "--config-file=" + vswitchd_cfg_filename] + action_args
+    cmd = [vsctl, "-vANY:console:emer"] + action_args
     exitcode = subprocess.call(cmd)
     if exitcode != 0:
-        log.error("ovs-cfg-mod failed with exit code "
-                  + str(exitcode) + " for " + repr(action_args))
         raise XenAPIPlugin.Failure("VSWITCH_CONFIG_MOD_FAILURE",
                                    [ str(exitcode) , str(action_args) ])
-    vswitchReload()
     
-def vswitchReload():
-    exitcode = subprocess.call(["/sbin/service", "vswitch", "reload"])
-    if exitcode != 0:
-        log.error("vswitch reload failed with exit code " + str(exitcode))
-        raise XenAPIPlugin.Failure("VSWITCH_CFG_RELOAD_FAILURE", [ str(exitcode) ])
-    
-
 if __name__ == "__main__":
     XenAPIPlugin.dispatch({"update": update})
index 9803f80..56c710d 100755 (executable)
@@ -1,7 +1,6 @@
 #!/bin/sh
 
 # Copyright (C) 2008,2009 Citrix Systems, Inc.
-# Copyright (C) 2009 Nicira Networks, Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published
 
 # Keep other-config/ keys in sync with device.ml:vif_udev_keys
 
-cfg_mod="/root/vswitch/bin/ovs-cfg-mod"
-dump_vif_details="/root/vswitch/scripts/dump-vif-details"
-service="/sbin/service"
+BRCTL="/usr/sbin/brctl"
+IP="/sbin/ip"
 
-TYPE=`echo ${XENBUS_PATH} | cut -f 2 -d '/'`
-DOMID=`echo ${XENBUS_PATH} | cut -f 3 -d '/'`
-DEVID=`echo ${XENBUS_PATH} | cut -f 4 -d '/'`
-
-XAPI=/xapi/${DOMID}/hotplug/${TYPE}/${DEVID}
-HOTPLUG=/xapi/${DOMID}/hotplug/${TYPE}/${DEVID}
-PRIVATE=/xapi/${DOMID}/private/${TYPE}/${DEVID}
-BRCTL=/usr/sbin/brctl
-IP=/sbin/ip
+vsctl="/usr/bin/ovs-vsctl"
 
+# XAPI before build 29381 (approximately) did not provide some of the
+# data in XenStore that we rely on.
+. /etc/xensource-inventory
+if test "$PRODUCT_VERSION" = "5.5.0" || test "${BUILD_NUMBER%p}" -le 26131
+then
+    xs550=true
+else
+    xs550=false
+fi
 
 handle_promiscuous()
 {
-    local arg=$(xenstore-read "${PRIVATE}/other-config/promiscuous")
+    local arg=$(xenstore-read "${PRIVATE}/other-config/promiscuous" 2>/dev/null)
     if [ $? -eq 0 -a -n "${arg}" ] ; then
-        case "${arg}" in 
-            true|on) logger -t script-vif "${vif}: Promiscuous ports are not supported via vSwitch." ;;
-            *) ;;
-        esac
+       case $NETWORK_MODE in
+           bridge)
+               case "${arg}" in 
+                   true|on) echo 1 > /sys/class/net/${dev}/brport/promisc ;;
+                   *) echo 0 > /sys/class/net/${dev}/brport/promisc ;;
+               esac
+               ;;
+           vswitch)
+               logger -t script-vif "${dev}: Promiscuous ports are not supported via vSwitch."
+               ;;
+       esac
     fi
 }
 
 handle_ethtool()
 {
     local opt=$1
-    local arg=$(xenstore-read "${PRIVATE}/other-config/ethtool-${opt}")
+    local arg=$(xenstore-read "${PRIVATE}/other-config/ethtool-${opt}" 2>/dev/null)
     if [ $? -eq 0 -a -n "${arg}" ] ; then
         case "${arg}" in
-            true|on)   /sbin/ethtool -K "${vif}" "${opt}" on ;;
-            false|off) /sbin/ethtool -K "${vif}" "${opt}" off ;;
-            *) logger -t scripts-vif "Unknown ethtool argument ${opt}=${arg} on ${vif}/${VIFUUID}" ;;
+            true|on)   /sbin/ethtool -K "${dev}" "${opt}" on ;;
+            false|off) /sbin/ethtool -K "${dev}" "${opt}" off ;;
+            *) logger -t scripts-vif "Unknown ethtool argument ${opt}=${arg} on ${dev}/${VIFUUID}" ;;
         esac
     fi
 }
 
 handle_mtu()
 {
-    local mtu=$(xenstore-read "${PRIVATE}/MTU")
+    local mtu=$(xenstore-read "${PRIVATE}/MTU" 2>/dev/null)
     if [ $? -eq 0 -a -n "${mtu}" ]; then
-       echo "${mtu}" > /sys/class/net/${vif}/mtu
+       logger -t scripts-vif "Setting ${dev} MTU ${mtu}"
+       ${IP} link set "${dev}" mtu ${mtu} || logger -t scripts-vif "Failed to ip link set ${dev} mtu ${mtu}. Error code $?"
+    fi
+}
+
+set_vif_external_id()
+{
+    local key=$1
+    local value=$2
+
+    logger -t scripts-vif "vif${DOMID}.${DEVID} external-ids:\"${key}\"=\"${value}\""
+
+    echo "-- set interface vif${DOMID}.${DEVID} external-ids:\"${key}\"=\"${value}\""
+}
+
+handle_vswitch_vif_details()
+{
+    local vm=$(xenstore-read "/local/domain/$DOMID/vm" 2>/dev/null)
+    if [ $? -eq 0 -a -n "${vm}" ] ; then
+       local vm_uuid=$(xenstore-read "$vm/uuid" 2>/dev/null)
+    fi
+    if [ -n "${vm_uuid}" ] ; then
+       set_vif_external_id "vm-uuid" "${vm_uuid}"
+    fi
+
+    local vif_uuid=$(xenstore-read "${PRIVATE}/vif-uuid" 2>/dev/null)
+    if $xs550 && [ -z "${vif_uuid}" ] && [ -n "${vm_uuid}" ]; then
+        vif_uuid=$(xe vif-list --minimal vm-uuid="${vm_uuid}" device=$DEVID)
+    fi
+    if [ -n "${vif_uuid}" ] ; then
+       set_vif_external_id "vif-uuid" "${vif_uuid}"
+    fi
+
+    local vif_details=
+    local net_uuid=$(xenstore-read "${PRIVATE}/network-uuid" 2>/dev/null)
+    if $xs550 && [ -z "${net_uuid}" ] && [ -n "${vif_uuid}" ]; then
+        net_uuid=$(xe vif-param-get uuid="${vif_uuid}" param-name=network-uuid)
+    fi
+    if [ -n "${net_uuid}" ] ; then
+       set_vif_external_id "network-uuid" "${net_uuid}"
+    fi
+
+    local address=$(xenstore-read "/local/domain/$DOMID/device/vif/$DEVID/mac" 2>/dev/null)
+    if [ -n "${address}" ] ; then
+       set_vif_external_id "vif-mac" "${address}"
+    fi
+}
+
+xs550_set_internal_network_uuid()
+{
+    if $xs550; then
+        # vNetManager needs to know the network UUID(s) associated with each
+        # datapath.  Normally interface-reconfigure adds them, but XAPI does
+        # not use interface-reconfigure for internal networks. Instead, XAPI
+        # calls the addbr ioctl internally, so we have to do it here instead
+        # for internal networks.  This is only acceptable because xapi is lazy
+        # about creating internal networks: it only creates one just before it
+        # adds the first vif to it.  There may still be a brief delay between
+        # the initial ovs-vswitchd connection to vNetManager and setting this
+        # configuration variable, but vNetManager can tolerate that.
+        local bridge=$1
+        local net_uuid=$(xenstore-read "${PRIVATE}/network-uuid" 2>/dev/null)
+        if [ -n "${net_uuid}" ] ; then
+            logger -t scripts-vif "${bridge} network-uuids ${net_uuid}"
+            echo "-- br-set-external-id $bridge network-uuids ${net_uuid}"
+        fi
     fi
 }
 
@@ -73,75 +144,126 @@ add_to_bridge()
     local address=$(xenstore-read "${PRIVATE}/bridge-MAC")
     if [ $? -ne 0 -o -z "${address}" ]; then
        logger -t scripts-vif "Failed to read ${PRIVATE}/bridge-MAC from xenstore"
+       exit 1
     fi
     local bridge=$(xenstore-read "${PRIVATE}/bridge")
     if [ $? -ne 0 -o -z "${bridge}" ]; then
        logger -t scripts-vif "Failed to read ${PRIVATE}/bridge from xenstore"
+       exit 1
     fi
-    logger -t scripts-vif "Adding ${vif} to ${bridge} with address ${address}"
-
-    vid=
-    if [ -e "/var/lib/openvswitch/br-$bridge" ]; then
-       . "/var/lib/openvswitch/br-$bridge"
-       if [ -n "$VLAN_SLAVE" -a -n "$VLAN_VID" ]; then
-           bridge=$VLAN_SLAVE
-           vid="--add=vlan.$vif.tag=$VLAN_VID"
-       fi
-    fi
+    logger -t scripts-vif "Adding ${dev} to ${bridge} with address ${address}"
 
-    ${IP} link set "${vif}" down                        || logger -t scripts-vif "Failed to ip link set ${vif} down"
-    ${IP} link set "${vif}" arp off                     || logger -t scripts-vif "Failed to ip link set ${vif} arp off"
-    ${IP} link set "${vif}" multicast off               || logger -t scripts-vif "Failed to ip link set ${vif} multicast off"
-    ${IP} link set "${vif}" address "${address}"        || logger -t scripts-vif "Failed to ip link set ${vif} address ${address}"
-    ${IP} addr flush "${vif}"                           || logger -t scripts-vif "Failed to ip addr flush ${vif}"
+    ${IP} link set "${dev}" down                        || logger -t scripts-vif "Failed to ip link set ${dev} down"
+    ${IP} link set "${dev}" arp off                     || logger -t scripts-vif "Failed to ip link set ${dev} arp off"
+    ${IP} link set "${dev}" multicast off               || logger -t scripts-vif "Failed to ip link set ${dev} multicast off"
+    ${IP} link set "${dev}" address "${address}"        || logger -t scripts-vif "Failed to ip link set ${dev} address ${address}"
+    ${IP} addr flush "${dev}"                           || logger -t scripts-vif "Failed to ip addr flush ${dev}"
 
-    local vif_details=$($dump_vif_details $DOMID $DEVID)
-    if [ $? -ne 0 -o -z "${vif_details}" ]; then
-           logger -t scripts-vif "Failed to retrieve vif details for vswitch"
-    fi
+    case $NETWORK_MODE in
+       bridge)
+           ${BRCTL} setfd "${bridge}" 0                        || logger -t scripts-vif "Failed to brctl setfd ${bridge} 0"
+           ${BRCTL} addif "${bridge}" "${dev}"                 || logger -t scripts-vif "Failed to brctl addif ${bridge} ${dev}"
+           ;;
+       vswitch)
+           if [ "$TYPE" = "vif" ] ; then
+               local vif_details=$(handle_vswitch_vif_details)
+           fi
 
-    $cfg_mod -F /etc/ovs-vswitchd.conf \
-        --del-match="bridge.*.port=$vif" \
-        --del-match="vlan.$vif.trunks=*" \
-        --del-match="vlan.$vif.tag=*" \
-        --del-match="port.$vif.[!0-9]*" \
-        --add="bridge.$bridge.port=$vif" \
-        $vid $vif_details -c 
-    $service vswitch reload
+           $vsctl -- --if-exists del-port $dev -- add-port $bridge $dev $vif_details $(xs550_set_internal_network_uuid $bridge)
+           ;;
+    esac
+           
+    ${IP} link set "${dev}" up                          || logger -t scripts-vif "Failed to ip link set ${dev} up"
+}
 
-    ${IP} link set "${vif}" up                          || logger -t scripts-vif "Failed to ip link set ${vif} up"
+remove_from_bridge()
+{
+    case $NETWORK_MODE in
+       bridge)
+           # Nothing to do
+           ;;
+       vswitch)
+            # If ovs-brcompatd is running, it might already have deleted the
+            # port.  Use --if-exists to suppress the error that would otherwise
+            # arise in that case.
+           $vsctl -- --if-exists del-port $dev
+           ;;
+    esac
 }
 
-echo Called as "$@" "$TYPE" "$DOMID" "$DEVID" | logger -t scripts-vif
-case "$1" in
+NETWORK_MODE=$(cat /etc/xensource/network.conf)
+ACTION=$1
+
+# Older versions of XenServer do not pass in the type as an argument
+if [[ $# -lt 2 ]]; then
+    TYPE=vif
+else
+    TYPE=$2
+fi
+
+case $NETWORK_MODE in
+    bridge|vswitch) ;;
+    *)
+       logger -t scripts-vif "Unknown network mode $NETWORK_MODE"
+       exit 1
+       ;;
+esac
+
+case ${TYPE} in
+    vif)
+       DOMID=`echo ${XENBUS_PATH} | cut -f 3 -d '/'`
+       DEVID=`echo ${XENBUS_PATH} | cut -f 4 -d '/'`
+       dev=vif${DOMID}.${DEVID}
+       ;;
+    tap)
+       dev=$INTERFACE
+       DOMID=`echo ${dev#tap} | cut -f 1 -d '.'`
+       DEVID=`echo ${dev#tap} | cut -f 2 -d '.'`
+       ;;
+    *)  
+       logger -t scripts-vif "unknown interface type ${TYPE}"
+       exit 1
+       ;;
+esac
+
+XAPI=/xapi/${DOMID}/hotplug/vif/${DEVID}
+HOTPLUG=/xapi/${DOMID}/hotplug/vif/${DEVID}
+PRIVATE=/xapi/${DOMID}/private/vif/${DEVID}
+
+logger -t scripts-vif "Called as \"$@\" domid:$DOMID devid:$DEVID mode:$NETWORK_MODE"
+case "${ACTION}" in
 online)
-       handle_ethtool rx
-       handle_ethtool tx
-       handle_ethtool sg
-       handle_ethtool tso
-       handle_ethtool ufo
-       handle_ethtool gso
+       if [ "${TYPE}" = "vif" ] ; then
+           handle_ethtool rx
+           handle_ethtool tx
+           handle_ethtool sg
+           handle_ethtool tso
+           handle_ethtool ufo
+           handle_ethtool gso
 
-       handle_mtu
-       add_to_bridge
-       handle_promiscuous
+           handle_mtu
+           add_to_bridge
+           handle_promiscuous
 
-       xenstore-write "${HOTPLUG}/vif" "${vif}"
-       xenstore-write "${HOTPLUG}/hotplug" "online"
+           xenstore-write "${HOTPLUG}/vif" "${dev}"
+           xenstore-write "${HOTPLUG}/hotplug" "online"
 
-       # xs-xen.pq.hq:91e986b8e49f netback-wait-for-hotplug
-       xenstore-write "/local/domain/0/backend/vif/${DOMID}/${DEVID}/hotplug-status" "connected"
+           # xs-xen.pq.hq:91e986b8e49f netback-wait-for-hotplug
+           xenstore-write "/local/domain/0/backend/vif/${DOMID}/${DEVID}/hotplug-status" "connected"
+       fi
+       ;;
 
+add)
+       if [ "${TYPE}" = "tap" ] ; then
+           add_to_bridge
+       fi
        ;;
+
 remove)
-       xenstore-rm "${HOTPLUG}/hotplug"
-       vif=vif${DOMID}.${DEVID}
-       logger -t scripts-vif "${vif} has been removed"
-       $cfg_mod -vANY:console:emer -F /etc/ovs-vswitchd.conf \
-           --del-match="bridge.*.port=${vif}" \
-           --del-match="vlan.${vif}.trunks=*" \
-           --del-match="vlan.${vif}.tag=*" \
-           --del-match="port.${vif}.[!0-9]*" -c
-       $service vswitch reload
+       if [ "${TYPE}" = "vif" ] ;then
+           xenstore-rm "${HOTPLUG}/hotplug"
+       fi
+       logger -t scripts-vif "${dev} has been removed"
+       remove_from_bridge
        ;;
 esac
diff --git a/xenserver/opt_xensource_libexec_InterfaceReconfigure.py b/xenserver/opt_xensource_libexec_InterfaceReconfigure.py
new file mode 100644 (file)
index 0000000..570ebcc
--- /dev/null
@@ -0,0 +1,870 @@
+# Copyright (c) 2008,2009 Citrix Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; version 2.1 only. with the special
+# exception on linking described in file LICENSE.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+import sys
+import syslog
+import os
+
+from xml.dom.minidom import getDOMImplementation
+from xml.dom.minidom import parse as parseXML
+
+the_root_prefix = ""
+def root_prefix():
+    """Returns a string to prefix to all file name references, which
+    is useful for testing."""
+    return the_root_prefix
+def set_root_prefix(prefix):
+    global the_root_prefix
+    the_root_prefix = prefix
+
+log_destination = "syslog"
+def get_log_destination():
+    """Returns the current log destination.
+    'syslog' means "log to syslog".
+    'stderr' means "log to stderr"."""
+    return log_destination
+def set_log_destination(dest):
+    global log_destination
+    log_destination = dest
+
+#
+# Logging.
+#
+
+def log(s):
+    if get_log_destination() == 'syslog':
+        syslog.syslog(s)
+    else:
+        print >>sys.stderr, s
+
+#
+# Exceptions.
+#
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+#
+# Run external utilities
+#
+
+def run_command(command):
+    log("Running command: " + ' '.join(command))
+    rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
+    if rc != 0:
+        log("Command failed %d: " % rc + ' '.join(command))
+        return False
+    return True
+
+#
+# Configuration File Handling.
+#
+
+class ConfigurationFile(object):
+    """Write a file, tracking old and new versions.
+
+    Supports writing a new version of a file and applying and
+    reverting those changes.
+    """
+
+    __STATE = {"OPEN":"OPEN",
+               "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
+               "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
+
+    def __init__(self, path):
+        dirname,basename = os.path.split(path)
+
+        self.__state = self.__STATE['OPEN']
+        self.__children = []
+
+        self.__path    = os.path.join(dirname, basename)
+        self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
+        self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
+
+        self.__f = open(self.__newpath, "w")
+
+    def attach_child(self, child):
+        self.__children.append(child)
+
+    def path(self):
+        return self.__path
+
+    def readlines(self):
+        try:
+            return open(self.path()).readlines()
+        except:
+            return ""
+
+    def write(self, args):
+        if self.__state != self.__STATE['OPEN']:
+            raise Error("Attempt to write to file in state %s" % self.__state)
+        self.__f.write(args)
+
+    def close(self):
+        if self.__state != self.__STATE['OPEN']:
+            raise Error("Attempt to close file in state %s" % self.__state)
+
+        self.__f.close()
+        self.__state = self.__STATE['NOT-APPLIED']
+
+    def changed(self):
+        if self.__state != self.__STATE['NOT-APPLIED']:
+            raise Error("Attempt to compare file in state %s" % self.__state)
+
+        return True
+
+    def apply(self):
+        if self.__state != self.__STATE['NOT-APPLIED']:
+            raise Error("Attempt to apply configuration from state %s" % self.__state)
+
+        for child in self.__children:
+            child.apply()
+
+        log("Applying changes to %s configuration" % self.__path)
+
+        # Remove previous backup.
+        if os.access(self.__oldpath, os.F_OK):
+            os.unlink(self.__oldpath)
+
+        # Save current configuration.
+        if os.access(self.__path, os.F_OK):
+            os.link(self.__path, self.__oldpath)
+            os.unlink(self.__path)
+
+        # Apply new configuration.
+        assert(os.path.exists(self.__newpath))
+        os.link(self.__newpath, self.__path)
+
+        # Remove temporary file.
+        os.unlink(self.__newpath)
+
+        self.__state = self.__STATE['APPLIED']
+
+    def revert(self):
+        if self.__state != self.__STATE['APPLIED']:
+            raise Error("Attempt to revert configuration from state %s" % self.__state)
+
+        for child in self.__children:
+            child.revert()
+
+        log("Reverting changes to %s configuration" % self.__path)
+
+        # Remove existing new configuration
+        if os.access(self.__newpath, os.F_OK):
+            os.unlink(self.__newpath)
+
+        # Revert new configuration.
+        if os.access(self.__path, os.F_OK):
+            os.link(self.__path, self.__newpath)
+            os.unlink(self.__path)
+
+        # Revert to old configuration.
+        if os.access(self.__oldpath, os.F_OK):
+            os.link(self.__oldpath, self.__path)
+            os.unlink(self.__oldpath)
+
+        # Leave .*.xapi-new as an aid to debugging.
+
+        self.__state = self.__STATE['REVERTED']
+
+    def commit(self):
+        if self.__state != self.__STATE['APPLIED']:
+            raise Error("Attempt to commit configuration from state %s" % self.__state)
+
+        for child in self.__children:
+            child.commit()
+
+        log("Committing changes to %s configuration" % self.__path)
+
+        if os.access(self.__oldpath, os.F_OK):
+            os.unlink(self.__oldpath)
+        if os.access(self.__newpath, os.F_OK):
+            os.unlink(self.__newpath)
+
+        self.__state = self.__STATE['COMMITTED']
+
+#
+# Helper functions for encoding/decoding database attributes to/from XML.
+#
+
+def _str_to_xml(xml, parent, tag, val):
+    e = xml.createElement(tag)
+    parent.appendChild(e)
+    v = xml.createTextNode(val)
+    e.appendChild(v)
+def _str_from_xml(n):
+    def getText(nodelist):
+        rc = ""
+        for node in nodelist:
+            if node.nodeType == node.TEXT_NODE:
+                rc = rc + node.data
+        return rc
+    return getText(n.childNodes).strip()
+
+def _bool_to_xml(xml, parent, tag, val):
+    if val:
+        _str_to_xml(xml, parent, tag, "True")
+    else:
+        _str_to_xml(xml, parent, tag, "False")
+def _bool_from_xml(n):
+    s = _str_from_xml(n)
+    if s == "True":
+        return True
+    elif s == "False":
+        return False
+    else:
+        raise Error("Unknown boolean value %s" % s)
+
+def _strlist_to_xml(xml, parent, ltag, itag, val):
+    e = xml.createElement(ltag)
+    parent.appendChild(e)
+    for v in val:
+        c = xml.createElement(itag)
+        e.appendChild(c)
+        cv = xml.createTextNode(v)
+        c.appendChild(cv)
+def _strlist_from_xml(n, ltag, itag):
+    ret = []
+    for n in n.childNodes:
+        if n.nodeName == itag:
+            ret.append(_str_from_xml(n))
+    return ret
+
+def _otherconfig_to_xml(xml, parent, val, attrs):
+    otherconfig = xml.createElement("other_config")
+    parent.appendChild(otherconfig)
+    for n,v in val.items():
+        if not n in attrs:
+            raise Error("Unknown other-config attribute: %s" % n)
+        _str_to_xml(xml, otherconfig, n, v)
+def _otherconfig_from_xml(n, attrs):
+    ret = {}
+    for n in n.childNodes:
+        if n.nodeName in attrs:
+            ret[n.nodeName] = _str_from_xml(n)
+    return ret
+
+#
+# Definitions of the database objects (and their attributes) used by interface-reconfigure.
+#
+# Each object is defined by a dictionary mapping an attribute name in
+# the xapi database to a tuple containing two items:
+#  - a function which takes this attribute and encodes it as XML.
+#  - a function which takes XML and decocdes it into a value.
+#
+# other-config attributes are specified as a simple array of strings
+
+_PIF_XML_TAG = "pif"
+_VLAN_XML_TAG = "vlan"
+_BOND_XML_TAG = "bond"
+_NETWORK_XML_TAG = "network"
+
+_ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
+
+_PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
+                        [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
+                        _ETHTOOL_OTHERCONFIG_ATTRS
+
+_PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
+               'management': (_bool_to_xml,_bool_from_xml),
+               'network': (_str_to_xml,_str_from_xml),
+               'device': (_str_to_xml,_str_from_xml),
+               'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
+                                  lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
+               'bond_slave_of': (_str_to_xml,_str_from_xml),
+               'VLAN': (_str_to_xml,_str_from_xml),
+               'VLAN_master_of': (_str_to_xml,_str_from_xml),
+               'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
+                                 lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
+               'ip_configuration_mode': (_str_to_xml,_str_from_xml),
+               'IP': (_str_to_xml,_str_from_xml),
+               'netmask': (_str_to_xml,_str_from_xml),
+               'gateway': (_str_to_xml,_str_from_xml),
+               'DNS': (_str_to_xml,_str_from_xml),
+               'MAC': (_str_to_xml,_str_from_xml),
+               'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
+                                lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
+
+               # Special case: We write the current value
+               # PIF.currently-attached to the cache but since it will
+               # not be valid when we come to use the cache later
+               # (i.e. after a reboot) we always read it as False.
+               'currently_attached': (_bool_to_xml, lambda n: False),
+             }
+
+_VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
+                'tagged_PIF': (_str_to_xml,_str_from_xml),
+                'untagged_PIF': (_str_to_xml,_str_from_xml),
+              }
+
+_BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
+               'master': (_str_to_xml,_str_from_xml),
+               'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
+                          lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
+              }
+
+_NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
+
+_NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
+                   'bridge': (_str_to_xml,_str_from_xml),
+                   'MTU': (_str_to_xml,_str_from_xml),
+                   'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
+                            lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
+                   'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
+                                    lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
+                 }
+
+#
+# Database Cache object
+#
+
+_db = None
+
+def db():
+    assert(_db is not None)
+    return _db
+
+def db_init_from_cache(cache):
+    global _db
+    assert(_db is None)
+    _db = DatabaseCache(cache_file=cache)
+    
+def db_init_from_xenapi(session):
+    global _db 
+    assert(_db is None)
+    _db  = DatabaseCache(session_ref=session)
+    
+class DatabaseCache(object):
+    def __read_xensource_inventory(self):
+        filename = root_prefix() + "/etc/xensource-inventory"
+        f = open(filename, "r")
+        lines = [x.strip("\n") for x in f.readlines()]
+        f.close()
+
+        defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
+        defs = [ (a, b.strip("'")) for (a,b) in defs ]
+
+        return dict(defs)
+
+    def __pif_on_host(self,pif):
+        return self.__pifs.has_key(pif)
+
+    def __get_pif_records_from_xapi(self, session, host):
+        self.__pifs = {}
+        for (p,rec) in session.xenapi.PIF.get_all_records().items():
+            if rec['host'] != host:
+                continue
+            self.__pifs[p] = {}
+            for f in _PIF_ATTRS:
+                self.__pifs[p][f] = rec[f]
+            self.__pifs[p]['other_config'] = {}
+            for f in _PIF_OTHERCONFIG_ATTRS:
+                if not rec['other_config'].has_key(f): continue
+                self.__pifs[p]['other_config'][f] = rec['other_config'][f]
+
+    def __get_vlan_records_from_xapi(self, session):
+        self.__vlans = {}
+        for v in session.xenapi.VLAN.get_all():
+            rec = session.xenapi.VLAN.get_record(v)
+            if not self.__pif_on_host(rec['untagged_PIF']):
+                continue
+            self.__vlans[v] = {}
+            for f in _VLAN_ATTRS:
+                self.__vlans[v][f] = rec[f]
+
+    def __get_bond_records_from_xapi(self, session):
+        self.__bonds = {}
+        for b in session.xenapi.Bond.get_all():
+            rec = session.xenapi.Bond.get_record(b)
+            if not self.__pif_on_host(rec['master']):
+                continue
+            self.__bonds[b] = {}
+            for f in _BOND_ATTRS:
+                self.__bonds[b][f] = rec[f]
+
+    def __get_network_records_from_xapi(self, session):
+        self.__networks = {}
+        for n in session.xenapi.network.get_all():
+            rec = session.xenapi.network.get_record(n)
+            self.__networks[n] = {}
+            for f in _NETWORK_ATTRS:
+                if f == "PIFs":
+                    # drop PIFs on other hosts
+                    self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
+                elif f == "MTU" and f not in rec:
+                    # XenServer 5.5 network records did not have an
+                    # MTU field, so allow this to be missing.
+                    pass
+                else:
+                    self.__networks[n][f] = rec[f]
+            self.__networks[n]['other_config'] = {}
+            for f in _NETWORK_OTHERCONFIG_ATTRS:
+                if not rec['other_config'].has_key(f): continue
+                self.__networks[n]['other_config'][f] = rec['other_config'][f]
+
+    def __to_xml(self, xml, parent, key, ref, rec, attrs):
+        """Encode a database object as XML"""
+        e = xml.createElement(key)
+        parent.appendChild(e)
+        if ref:
+            e.setAttribute('ref', ref)
+
+        for n,v in rec.items():
+            if attrs.has_key(n):
+                h,_ = attrs[n]
+                h(xml, e, n, v)
+            else:
+                raise Error("Unknown attribute %s" % n)
+    def __from_xml(self, e, attrs):
+        """Decode a database object from XML"""
+        ref = e.attributes['ref'].value
+        rec = {}
+        for n in e.childNodes:
+            if n.nodeName in attrs:
+                _,h = attrs[n.nodeName]
+                rec[n.nodeName] = h(n)
+        return (ref,rec)
+
+    def __init__(self, session_ref=None, cache_file=None):
+        if session_ref and cache_file:
+            raise Error("can't specify session reference and cache file")
+        if cache_file == None:
+            import XenAPI
+            session = XenAPI.xapi_local()
+
+            if not session_ref:
+                log("No session ref given on command line, logging in.")
+                session.xenapi.login_with_password("root", "")
+            else:
+                session._session = session_ref
+
+            try:
+
+                inventory = self.__read_xensource_inventory()
+                assert(inventory.has_key('INSTALLATION_UUID'))
+                log("host uuid is %s" % inventory['INSTALLATION_UUID'])
+
+                host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
+
+                self.__get_pif_records_from_xapi(session, host)
+
+                self.__get_vlan_records_from_xapi(session)
+                self.__get_bond_records_from_xapi(session)
+                self.__get_network_records_from_xapi(session)
+            finally:
+                if not session_ref:
+                    session.xenapi.session.logout()
+        else:
+            log("Loading xapi database cache from %s" % cache_file)
+
+            xml = parseXML(root_prefix() + cache_file)
+
+            self.__pifs = {}
+            self.__bonds = {}
+            self.__vlans = {}
+            self.__networks = {}
+
+            assert(len(xml.childNodes) == 1)
+            toplevel = xml.childNodes[0]
+
+            assert(toplevel.nodeName == "xenserver-network-configuration")
+
+            for n in toplevel.childNodes:
+                if n.nodeName == "#text":
+                    pass
+                elif n.nodeName == _PIF_XML_TAG:
+                    (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
+                    self.__pifs[ref] = rec
+                elif n.nodeName == _BOND_XML_TAG:
+                    (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
+                    self.__bonds[ref] = rec
+                elif n.nodeName == _VLAN_XML_TAG:
+                    (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
+                    self.__vlans[ref] = rec
+                elif n.nodeName == _NETWORK_XML_TAG:
+                    (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
+                    self.__networks[ref] = rec
+                else:
+                    raise Error("Unknown XML element %s" % n.nodeName)
+
+    def save(self, cache_file):
+
+        xml = getDOMImplementation().createDocument(
+            None, "xenserver-network-configuration", None)
+        for (ref,rec) in self.__pifs.items():
+            self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
+        for (ref,rec) in self.__bonds.items():
+            self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
+        for (ref,rec) in self.__vlans.items():
+            self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
+        for (ref,rec) in self.__networks.items():
+            self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
+                          _NETWORK_ATTRS)
+
+        f = open(cache_file, 'w')
+        f.write(xml.toprettyxml())
+        f.close()
+
+    def get_pif_by_uuid(self, uuid):
+        pifs = map(lambda (ref,rec): ref,
+                  filter(lambda (ref,rec): uuid == rec['uuid'],
+                         self.__pifs.items()))
+        if len(pifs) == 0:
+            raise Error("Unknown PIF \"%s\"" % uuid)
+        elif len(pifs) > 1:
+            raise Error("Non-unique PIF \"%s\"" % uuid)
+
+        return pifs[0]
+
+    def get_pifs_by_device(self, device):
+        return map(lambda (ref,rec): ref,
+                   filter(lambda (ref,rec): rec['device'] == device,
+                          self.__pifs.items()))
+
+    def get_pif_by_bridge(self, bridge):
+        networks = map(lambda (ref,rec): ref,
+                       filter(lambda (ref,rec): rec['bridge'] == bridge,
+                              self.__networks.items()))
+        if len(networks) == 0:
+            raise Error("No matching network \"%s\"" % bridge)
+
+        answer = None
+        for network in networks:
+            nwrec = self.get_network_record(network)
+            for pif in nwrec['PIFs']:
+                pifrec = self.get_pif_record(pif)
+                if answer:
+                    raise Error("Multiple PIFs on host for network %s" % (bridge))
+                answer = pif
+        if not answer:
+            raise Error("No PIF on host for network %s" % (bridge))
+        return answer
+
+    def get_pif_record(self, pif):
+        if self.__pifs.has_key(pif):
+            return self.__pifs[pif]
+        raise Error("Unknown PIF \"%s\"" % pif)
+    def get_all_pifs(self):
+        return self.__pifs
+    def pif_exists(self, pif):
+        return self.__pifs.has_key(pif)
+
+    def get_management_pif(self):
+        """ Returns the management pif on host
+        """
+        all = self.get_all_pifs()
+        for pif in all:
+            pifrec = self.get_pif_record(pif)
+            if pifrec['management']: return pif
+        return None
+
+    def get_network_record(self, network):
+        if self.__networks.has_key(network):
+            return self.__networks[network]
+        raise Error("Unknown network \"%s\"" % network)
+
+    def get_bond_record(self, bond):
+        if self.__bonds.has_key(bond):
+            return self.__bonds[bond]
+        else:
+            return None
+
+    def get_vlan_record(self, vlan):
+        if self.__vlans.has_key(vlan):
+            return self.__vlans[vlan]
+        else:
+            return None
+
+#
+#
+#
+
+def ethtool_settings(oc):
+    settings = []
+    if oc.has_key('ethtool-speed'):
+        val = oc['ethtool-speed']
+        if val in ["10", "100", "1000"]:
+            settings += ['speed', val]
+        else:
+            log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
+    if oc.has_key('ethtool-duplex'):
+        val = oc['ethtool-duplex']
+        if val in ["10", "100", "1000"]:
+            settings += ['duplex', 'val']
+        else:
+            log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
+    if oc.has_key('ethtool-autoneg'):
+        val = oc['ethtool-autoneg']
+        if val in ["true", "on"]:
+            settings += ['autoneg', 'on']
+        elif val in ["false", "off"]:
+            settings += ['autoneg', 'off']
+        else:
+            log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
+    offload = []
+    for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
+        if oc.has_key("ethtool-" + opt):
+            val = oc["ethtool-" + opt]
+            if val in ["true", "on"]:
+                offload += [opt, 'on']
+            elif val in ["false", "off"]:
+                offload += [opt, 'off']
+            else:
+                log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
+    return settings,offload
+
+# By default the MTU is taken from the Network.MTU setting for VIF,
+# PIF and Bridge. However it is possible to override this by using
+# {VIF,PIF,Network}.other-config:mtu.
+#
+# type parameter is a string describing the object that the oc parameter
+# is from. e.g. "PIF", "Network" 
+def mtu_setting(nw, type, oc):
+    mtu = None
+
+    nwrec = db().get_network_record(nw)
+    if nwrec.has_key('MTU'):
+        mtu = nwrec['MTU']
+    else:
+        mtu = "1500"
+        
+    if oc.has_key('mtu'):
+        log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
+            (nwrec['bridge'], type, mtu))
+        mtu = oc['mtu']
+
+    if mtu is not None:
+        try:
+            int(mtu)      # Check that the value is an integer
+            return mtu
+        except ValueError, x:
+            log("Invalid value for mtu = %s" % mtu)
+
+    return None
+
+#
+# IP Network Devices -- network devices with IP configuration
+#
+def pif_ipdev_name(pif):
+    """Return the ipdev name associated with pif"""
+    pifrec = db().get_pif_record(pif)
+    nwrec = db().get_network_record(pifrec['network'])
+
+    if nwrec['bridge']:
+        # TODO: sanity check that nwrec['bridgeless'] != 'true'
+        return nwrec['bridge']
+    else:
+        # TODO: sanity check that nwrec['bridgeless'] == 'true'
+        return pif_netdev_name(pif)
+
+#
+# Bare Network Devices -- network devices without IP configuration
+#
+
+def netdev_exists(netdev):
+    return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
+
+def pif_netdev_name(pif):
+    """Get the netdev name for a PIF."""
+
+    pifrec = db().get_pif_record(pif)
+
+    if pif_is_vlan(pif):
+        return "%(device)s.%(VLAN)s" % pifrec
+    else:
+        return pifrec['device']
+
+#
+# Bridges
+#
+
+def pif_is_bridged(pif):
+    pifrec = db().get_pif_record(pif)
+    nwrec = db().get_network_record(pifrec['network'])
+
+    if nwrec['bridge']:
+        # TODO: sanity check that nwrec['bridgeless'] != 'true'
+        return True
+    else:
+        # TODO: sanity check that nwrec['bridgeless'] == 'true'
+        return False
+
+def pif_bridge_name(pif):
+    """Return the bridge name of a pif.
+
+    PIF must be a bridged PIF."""
+    pifrec = db().get_pif_record(pif)
+
+    nwrec = db().get_network_record(pifrec['network'])
+
+    if nwrec['bridge']:
+        return nwrec['bridge']
+    else:
+        raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
+
+#
+# Bonded PIFs
+#
+def pif_is_bond(pif):
+    pifrec = db().get_pif_record(pif)
+
+    return len(pifrec['bond_master_of']) > 0
+
+def pif_get_bond_masters(pif):
+    """Returns a list of PIFs which are bond masters of this PIF"""
+
+    pifrec = db().get_pif_record(pif)
+
+    bso = pifrec['bond_slave_of']
+
+    # bond-slave-of is currently a single reference but in principle a
+    # PIF could be a member of several bonds which are not
+    # concurrently attached. Be robust to this possibility.
+    if not bso or bso == "OpaqueRef:NULL":
+        bso = []
+    elif not type(bso) == list:
+        bso = [bso]
+
+    bondrecs = [db().get_bond_record(bond) for bond in bso]
+    bondrecs = [rec for rec in bondrecs if rec]
+
+    return [bond['master'] for bond in bondrecs]
+
+def pif_get_bond_slaves(pif):
+    """Returns a list of PIFs which make up the given bonded pif."""
+
+    pifrec = db().get_pif_record(pif)
+
+    bmo = pifrec['bond_master_of']
+    if len(bmo) > 1:
+        raise Error("Bond-master-of contains too many elements")
+
+    if len(bmo) == 0:
+        return []
+
+    bondrec = db().get_bond_record(bmo[0])
+    if not bondrec:
+        raise Error("No bond record for bond master PIF")
+
+    return bondrec['slaves']
+
+#
+# VLAN PIFs
+#
+
+def pif_is_vlan(pif):
+    return db().get_pif_record(pif)['VLAN'] != '-1'
+
+def pif_get_vlan_slave(pif):
+    """Find the PIF which is the VLAN slave of pif.
+
+Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
+
+    pifrec = db().get_pif_record(pif)
+
+    vlan = pifrec['VLAN_master_of']
+    if not vlan or vlan == "OpaqueRef:NULL":
+        raise Error("PIF is not a VLAN master")
+
+    vlanrec = db().get_vlan_record(vlan)
+    if not vlanrec:
+        raise Error("No VLAN record found for PIF")
+
+    return vlanrec['tagged_PIF']
+
+def pif_get_vlan_masters(pif):
+    """Returns a list of PIFs which are VLANs on top of the given pif."""
+
+    pifrec = db().get_pif_record(pif)
+    vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
+    return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
+
+#
+# Datapath base class
+#
+
+class Datapath(object):
+    """Object encapsulating the actions necessary to (de)configure the
+       datapath for a given PIF. Does not include configuration of the
+       IP address on the ipdev.
+    """
+    
+    def __init__(self, pif):
+        self._pif = pif
+
+    def configure_ipdev(self, cfg):
+        """Write ifcfg TYPE field for an IPdev, plus any type specific
+           fields to cfg
+        """
+        raise NotImplementedError        
+
+    def preconfigure(self, parent):
+        """Prepare datapath configuration for PIF, but do not actually
+           apply any changes.
+
+           Any configuration files should be attached to parent.
+        """
+        raise NotImplementedError
+    
+    def bring_down_existing(self):
+        """Tear down any existing network device configuration which
+           needs to be undone in order to bring this PIF up.
+        """
+        raise NotImplementedError
+
+    def configure(self):
+        """Apply the configuration prepared in the preconfigure stage.
+
+           Should assume any configuration files changed attached in
+           the preconfigure stage are applied and bring up the
+           necesary devices to provide the datapath for the
+           PIF.
+
+           Should not bring up the IPdev.
+        """
+        raise NotImplementedError
+    
+    def post(self):
+        """Called after the IPdev has been brought up.
+
+           Should do any final setup, including reinstating any
+           devices which were taken down in the bring_down_existing
+           hook.
+        """
+        raise NotImplementedError
+
+    def bring_down(self):
+        """Tear down and deconfigure the datapath. Should assume the
+           IPdev has already been brought down.
+        """
+        raise NotImplementedError
+        
+def DatapathFactory(pif):
+    # XXX Need a datapath object for bridgeless PIFs
+
+    try:
+        network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
+        network_backend = network_conf.readline().strip()
+        network_conf.close()                
+    except Exception, e:
+        raise Error("failed to determine network backend:" + e)
+    
+    if network_backend == "bridge":
+        from InterfaceReconfigureBridge import DatapathBridge
+        return DatapathBridge(pif)
+    elif network_backend == "vswitch":
+        from InterfaceReconfigureVswitch import DatapathVswitch
+        return DatapathVswitch(pif)
+    else:
+        raise Error("unknown network backend %s" % network_backend)
diff --git a/xenserver/opt_xensource_libexec_InterfaceReconfigureBridge.py b/xenserver/opt_xensource_libexec_InterfaceReconfigureBridge.py
new file mode 100644 (file)
index 0000000..0438f5c
--- /dev/null
@@ -0,0 +1,471 @@
+# Copyright (c) 2008,2009 Citrix Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; version 2.1 only. with the special
+# exception on linking described in file LICENSE.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+from InterfaceReconfigure import *
+
+import sys
+import time
+
+sysfs_bonding_masters = root_prefix() + "/sys/class/net/bonding_masters"
+
+def open_pif_ifcfg(pif):
+    pifrec = db().get_pif_record(pif)
+
+    interface = pif_netdev_name(pif)
+    log("Configuring %s (%s)" % (interface, pifrec['MAC']))
+
+    f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), interface))
+
+    f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
+            (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
+    f.write("XEMANAGED=yes\n")
+    f.write("DEVICE=%s\n" % interface)
+    f.write("ONBOOT=no\n")
+
+    return f
+
+#
+# Bare Network Devices -- network devices without IP configuration
+#
+
+def netdev_down(netdev):
+    """Bring down a bare network device"""
+    if not netdev_exists(netdev):
+        log("netdev: down: device %s does not exist, ignoring" % netdev)
+        return
+    run_command(["/sbin/ifdown", netdev])
+
+def netdev_up(netdev, mtu=None):
+    """Bring up a bare network device"""
+    #if not netdev_exists(netdev):
+    #    raise Error("netdev: up: device %s does not exist" % netdev)
+
+    run_command(["/sbin/ifup", netdev])
+
+#
+# Bonding driver
+#
+
+def load_bonding_driver():
+    log("Loading bonding driver")
+    run_command(["/sbin/modprobe", "bonding"])
+    try:
+        # bond_device_exists() uses the contents of sysfs_bonding_masters to work out which devices
+        # have already been created.  Unfortunately the driver creates "bond0" automatically at
+        # modprobe init.  Get rid of this now or our accounting will go wrong.
+        f = open(sysfs_bonding_masters, "w")
+        f.write("-bond0")
+        f.close()
+    except IOError, e:
+        log("Failed to load bonding driver: %s" % e)
+
+def bonding_driver_loaded():
+    lines = open(root_prefix() + "/proc/modules").read().split("\n")
+    modules = [line.split(" ")[0] for line in lines]
+    return "bonding" in modules
+
+def bond_device_exists(name):
+    f = open(sysfs_bonding_masters, "r")
+    bonds = f.readline().split()
+    f.close()
+    return name in bonds
+
+def __create_bond_device(name):
+
+    if not bonding_driver_loaded():
+        load_bonding_driver()
+
+    if bond_device_exists(name):
+        log("bond master %s already exists, not creating" % name)
+    else:
+        log("Creating bond master %s" % name)
+        try:
+            f = open(sysfs_bonding_masters, "w")
+            f.write("+" + name)
+            f.close()
+        except IOError, e:
+            log("Failed to create %s: %s" % (name, e))
+
+def create_bond_device(pif):
+    """Ensures that a bond master device exists in the kernel."""
+
+    if not pif_is_bond(pif):
+        return
+
+    __create_bond_device(pif_netdev_name(pif))
+
+def __destroy_bond_device(name):
+    if bond_device_exists(name):
+        retries = 10 # 10 * 0.5 seconds
+        while retries > 0:
+            retries = retries - 1
+            log("Destroying bond master %s (%d attempts remain)" % (name,retries))
+            try:
+                f = open(sysfs_bonding_masters, "w")
+                f.write("-" + name)
+                f.close()
+                retries = 0
+            except IOError, e:
+                time.sleep(0.5)
+    else:
+        log("bond master %s does not exist, not destroying" % name)
+
+def destroy_bond_device(pif):
+    """No, Mr. Bond, I expect you to die."""
+
+    pifrec = db().get_pif_record(pif)
+
+    if not pif_is_bond(pif):
+        return
+
+    # If the bonding module isn't loaded then do nothing.
+    if not os.access(sysfs_bonding_masters, os.F_OK):
+        return
+
+    name = pif_netdev_name(pif)
+
+    __destroy_bond_device(name)
+
+#
+# Bring Interface up/down.
+#
+
+def bring_down_interface(pif, destroy=False):
+    """Bring down the interface associated with PIF.
+
+    Brings down the given interface as well as any physical interfaces
+    which are bond slaves of this one. This is because they will be
+    required when the bond is brought up."""
+
+    def destroy_bridge(pif):
+        """Bring down the bridge associated with a PIF."""
+        #if not pif_is_bridged(pif):
+        #    return
+        bridge = pif_bridge_name(pif)
+        if not netdev_exists(bridge):
+            log("destroy_bridge: bridge %s does not exist, ignoring" % bridge)
+            return
+        log("Destroy bridge %s" % bridge)
+        netdev_down(bridge)
+        run_command(["/usr/sbin/brctl", "delbr", bridge])
+
+    def destroy_vlan(pif):
+        vlan = pif_netdev_name(pif)
+        if not netdev_exists(vlan):
+            log("vconfig del: vlan %s does not exist, ignoring" % vlan)
+            return
+        log("Destroy vlan device %s" % vlan)
+        run_command(["/sbin/vconfig", "rem", vlan])
+
+    if pif_is_vlan(pif):
+        interface = pif_netdev_name(pif)
+        log("bring_down_interface: %s is a VLAN" % interface)
+        netdev_down(interface)
+
+        if destroy:
+            destroy_vlan(pif)
+            destroy_bridge(pif)
+        else:
+            return
+
+        slave = pif_get_vlan_slave(pif)
+        if db().get_pif_record(slave)['currently_attached']:
+            log("bring_down_interface: vlan slave is currently attached")
+            return
+
+        masters = pif_get_vlan_masters(slave)
+        masters = [m for m in masters if m != pif and db().get_pif_record(m)['currently_attached']]
+        if len(masters) > 0:
+            log("bring_down_interface: vlan slave has other masters")
+            return
+
+        log("bring_down_interface: no more masters, bring down vlan slave %s" % pif_netdev_name(slave))
+        pif = slave
+    else:
+        vlan_masters = pif_get_vlan_masters(pif)
+        log("vlan masters of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(m) for m in vlan_masters]))
+        if len([m for m in vlan_masters if db().get_pif_record(m)['currently_attached']]) > 0:
+            log("Leaving %s up due to currently attached VLAN masters" % pif_netdev_name(pif))
+            return
+
+    # pif is now either a bond or a physical device which needs to be brought down
+
+    # Need to bring down bond slaves first since the bond device
+    # must be up to enslave/unenslave.
+    bond_slaves = pif_get_bond_slaves_sorted(pif)
+    log("bond slaves of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(s) for s in bond_slaves]))
+    for slave in bond_slaves:
+        slave_interface = pif_netdev_name(slave)
+        if db().get_pif_record(slave)['currently_attached']:
+            log("leave bond slave %s up (currently attached)" % slave_interface)
+            continue
+        log("bring down bond slave %s" % slave_interface)
+        netdev_down(slave_interface)
+        # Also destroy the bridge associated with the slave, since
+        # it will carry the MAC address and possibly an IP address
+        # leading to confusion.
+        destroy_bridge(slave)
+
+    interface = pif_netdev_name(pif)
+    log("Bring interface %s down" % interface)
+    netdev_down(interface)
+
+    if destroy:
+        destroy_bond_device(pif)
+        destroy_bridge(pif)
+
+def interface_is_up(pif):
+    try:
+        interface = pif_netdev_name(pif)
+        state = open("%s/sys/class/net/%s/operstate" % (root_prefix(), interface)).read().strip()
+        return state == "up"
+    except:
+        return False # interface prolly doesn't exist
+
+def bring_up_interface(pif):
+    """Bring up the interface associated with a PIF.
+
+    Also bring up the interfaces listed in additional.
+    """
+
+    # VLAN on bond seems to need bond brought up explicitly, but VLAN
+    # on normal device does not. Might as well always bring it up.
+    if pif_is_vlan(pif):
+        slave = pif_get_vlan_slave(pif)
+        if not interface_is_up(slave):
+            bring_up_interface(slave)
+
+    interface = pif_netdev_name(pif)
+
+    create_bond_device(pif)
+
+    log("Bring interface %s up" % interface)
+    netdev_up(interface)
+
+
+#
+# Datapath topology configuration.
+#
+
+def _configure_physical_interface(pif):
+    """Write the configuration for a physical interface.
+
+    Writes the configuration file for the physical interface described by
+    the pif object.
+
+    Returns the open file handle for the interface configuration file.
+    """
+
+    pifrec = db().get_pif_record(pif)
+
+    log("Configuring physical interface %s" % pifrec['device'])
+
+    f = open_pif_ifcfg(pif)
+
+    f.write("TYPE=Ethernet\n")
+    f.write("HWADDR=%(MAC)s\n" % pifrec)
+
+    settings,offload = ethtool_settings(pifrec['other_config'])
+    if len(settings):
+        f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
+    if len(offload):
+        f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
+
+    mtu = mtu_setting(pifrec['network'], "PIF", pifrec['other_config'])
+    if mtu:
+        f.write("MTU=%s\n" % mtu)
+
+    return f
+
+def pif_get_bond_slaves_sorted(pif):
+    pifrec = db().get_pif_record(pif)
+
+    # build a list of slave's pifs
+    slave_pifs = pif_get_bond_slaves(pif)
+
+    # Ensure any currently attached slaves are listed in the opposite order to the order in
+    # which they were attached.  The first slave attached must be the last detached since
+    # the bond is using its MAC address.
+    try:
+        attached_slaves = open("%s/sys/class/net/%s/bonding/slaves" % (root_prefix(), pifrec['device'])).readline().split()
+        for slave in attached_slaves:
+            pifs = [p for p in db().get_pifs_by_device(slave) if not pif_is_vlan(p)]
+            slave_pif = pifs[0]
+            slave_pifs.remove(slave_pif)
+            slave_pifs.insert(0, slave_pif)
+    except IOError:
+        pass
+
+    return slave_pifs
+
+def _configure_bond_interface(pif):
+    """Write the configuration for a bond interface.
+
+    Writes the configuration file for the bond interface described by
+    the pif object. Handles writing the configuration for the slave
+    interfaces.
+
+    Returns the open file handle for the bond interface configuration
+    file.
+    """
+
+    pifrec = db().get_pif_record(pif)
+
+    f = open_pif_ifcfg(pif)
+
+    if pifrec['MAC'] != "":
+        f.write("MACADDR=%s\n" % pifrec['MAC'])
+
+    for slave in pif_get_bond_slaves(pif):
+        s = _configure_physical_interface(slave)
+        s.write("MASTER=%(device)s\n" % pifrec)
+        s.write("SLAVE=yes\n")
+        s.close()
+        f.attach_child(s)
+
+    settings,offload = ethtool_settings(pifrec['other_config'])
+    if len(settings):
+        f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
+    if len(offload):
+        f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
+
+    mtu = mtu_setting(pifrec['network'], "VLAN-PIF", pifrec['other_config'])
+    if mtu:
+        f.write("MTU=%s\n" % mtu)
+
+    # The bond option defaults
+    bond_options = {
+        "mode":   "balance-slb",
+        "miimon": "100",
+        "downdelay": "200",
+        "updelay": "31000",
+        "use_carrier": "1",
+        }
+
+    # override defaults with values from other-config whose keys being with "bond-"
+    oc = pifrec['other_config']
+    overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
+    overrides = map(lambda (key,val): (key[5:], val), overrides)
+    bond_options.update(overrides)
+
+    # write the bond options to ifcfg-bondX
+    f.write('BONDING_OPTS="')
+    for (name,val) in bond_options.items():
+        f.write("%s=%s " % (name,val))
+    f.write('"\n')
+    return f
+
+def _configure_vlan_interface(pif):
+    """Write the configuration for a VLAN interface.
+
+    Writes the configuration file for the VLAN interface described by
+    the pif object. Handles writing the configuration for the master
+    interface if necessary.
+
+    Returns the open file handle for the VLAN interface configuration
+    file.
+    """
+
+    slave = _configure_pif(pif_get_vlan_slave(pif))
+
+    pifrec = db().get_pif_record(pif)
+
+    f = open_pif_ifcfg(pif)
+    f.write("VLAN=yes\n")
+
+    settings,offload = ethtool_settings(pifrec['other_config'])
+    if len(settings):
+        f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
+    if len(offload):
+        f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
+
+    mtu = mtu_setting(pifrec['network'], "Bond-PIF", pifrec['other_config'])
+    if mtu:
+        f.write("MTU=%s\n" % mtu)
+
+    f.attach_child(slave)
+
+    return f
+
+def _configure_pif(pif):
+    """Write the configuration for a PIF object.
+
+    Writes the configuration file the PIF and all dependent
+    interfaces (bond slaves and VLAN masters etc).
+
+    Returns the open file handle for the interface configuration file.
+    """
+
+    if pif_is_vlan(pif):
+        f = _configure_vlan_interface(pif)
+    elif pif_is_bond(pif):
+        f = _configure_bond_interface(pif)
+    else:
+        f = _configure_physical_interface(pif)
+
+    f.write("BRIDGE=%s\n" % pif_bridge_name(pif))
+    f.close()
+
+    return f
+
+#
+#
+#
+
+class DatapathBridge(Datapath):
+    def __init__(self, pif):
+        Datapath.__init__(self, pif)
+        log("Configured for Bridge datapath")
+
+    def configure_ipdev(self, cfg):
+        if pif_is_bridged(self._pif):
+            cfg.write("TYPE=Bridge\n")
+            cfg.write("DELAY=0\n")
+            cfg.write("STP=off\n")
+            cfg.write("PIFDEV=%s\n" % pif_netdev_name(self._pif))
+        else:
+            cfg.write("TYPE=Ethernet\n")
+        
+    def preconfigure(self, parent):
+        pf = _configure_pif(self._pif)
+        parent.attach_child(pf)
+
+    def bring_down_existing(self):
+        # Bring down any VLAN masters so that we can reconfigure the slave.
+        for master in pif_get_vlan_masters(self._pif):
+            name = pif_netdev_name(master)
+            log("action_up: bring down vlan master %s" % (name))
+            netdev_down(name)
+
+        # interface-reconfigure is never explicitly called to down a bond master.
+        # However, when we are called to up a slave it is implicit that we are destroying the master.
+        bond_masters = pif_get_bond_masters(self._pif)
+        for master in bond_masters:
+            log("action_up: bring down bond master %s" % (pif_netdev_name(master)))
+            # bring down master
+            bring_down_interface(master, destroy=True)
+
+        # No masters left - now its safe to reconfigure the slave.
+        bring_down_interface(self._pif)
+        
+    def configure(self):
+        bring_up_interface(self._pif)
+
+    def post(self):
+        # Bring back any currently-attached VLAN masters
+        for master in [v for v in pif_get_vlan_masters(self._pif) if db().get_pif_record(v)['currently_attached']]:
+            name = pif_netdev_name(master)
+            log("action_up: bring up %s" % (name))
+            netdev_up(name)
+
+    def bring_down(self):
+        bring_down_interface(self._pif, destroy=True)
diff --git a/xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py b/xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py
new file mode 100644 (file)
index 0000000..6b60cb0
--- /dev/null
@@ -0,0 +1,484 @@
+# Copyright (c) 2008,2009 Citrix Systems, Inc.
+# Copyright (c) 2009,2010 Nicira Networks.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; version 2.1 only. with the special
+# exception on linking described in file LICENSE.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+from InterfaceReconfigure import *
+import re
+
+#
+# Bare Network Devices -- network devices without IP configuration
+#
+
+def netdev_down(netdev):
+    """Bring down a bare network device"""
+    if not netdev_exists(netdev):
+        log("netdev: down: device %s does not exist, ignoring" % netdev)
+        return
+    run_command(["/sbin/ifconfig", netdev, 'down'])
+
+def netdev_up(netdev, mtu=None):
+    """Bring up a bare network device"""
+    if not netdev_exists(netdev):
+        raise Error("netdev: up: device %s does not exist" % netdev)
+
+    if mtu:
+        mtu = ["mtu", mtu]
+    else:
+        mtu = []
+
+    run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
+
+#
+# PIF miscellanea
+#
+
+def pif_currently_in_use(pif):
+    """Determine if a PIF is currently in use.
+
+    A PIF is determined to be currently in use if
+    - PIF.currently-attached is true
+    - Any bond master is currently attached
+    - Any VLAN master is currently attached
+    """
+    rec = db().get_pif_record(pif)
+    if rec['currently_attached']:
+        log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
+        return True
+    for b in pif_get_bond_masters(pif):
+        if pif_currently_in_use(b):
+            log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
+            return True
+    for v in pif_get_vlan_masters(pif):
+        if pif_currently_in_use(v):
+            log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
+            return True
+    return False
+
+#
+# Datapath Configuration
+#
+
+def pif_datapath(pif):
+    """Return the datapath PIF associated with PIF.
+A non-VLAN PIF is its own datapath PIF, except that a bridgeless PIF has
+no datapath PIF at all.
+A VLAN PIF's datapath PIF is its VLAN slave's datapath PIF.
+"""
+    if pif_is_vlan(pif):
+        return pif_datapath(pif_get_vlan_slave(pif))
+
+    pifrec = db().get_pif_record(pif)
+    nwrec = db().get_network_record(pifrec['network'])
+    if not nwrec['bridge']:
+        return None
+    else:
+        return pif
+
+def datapath_get_physical_pifs(pif):
+    """Return the PIFs for the physical network device(s) associated with a datapath PIF.
+For a bond master PIF, these are the bond slave PIFs.
+For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
+
+A VLAN PIF cannot be a datapath PIF.
+"""
+    if pif_is_vlan(pif):
+        # Seems like overkill...
+        raise Error("get-physical-pifs should not get passed a VLAN")
+    elif pif_is_bond(pif):
+        return pif_get_bond_slaves(pif)
+    else:
+        return [pif]
+
+def datapath_deconfigure_physical(netdev):
+    return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
+
+def vsctl_escape(s):
+    if s.isalnum():
+        return s
+
+    def escape(match):
+        c = match.group(0)
+        if c == '\0':
+            raise Error("strings may not contain null bytes")
+        elif c == '\\':
+            return r'\\'
+        elif c == '\n':
+            return r'\n'
+        elif c == '\r':
+            return r'\r'
+        elif c == '\t':
+            return r'\t'
+        elif c == '\b':
+            return r'\b'
+        elif c == '\a':
+            return r'\a'
+        else:
+            return r'\x%02x' % ord(c)
+    return '"' + re.sub(r'["\\\000-\037]', escape, s) + '"'
+
+def datapath_configure_bond(pif,slaves):
+    bridge = pif_bridge_name(pif)
+    pifrec = db().get_pif_record(pif)
+    interface = pif_netdev_name(pif)
+
+    argv = ['--', '--fake-iface', 'add-bond', bridge, interface]
+    for slave in slaves:
+        argv += [pif_netdev_name(slave)]
+
+    # Bonding options.
+    bond_options = {
+        "mode":   "balance-slb",
+        "miimon": "100",
+        "downdelay": "200",
+        "updelay": "31000",
+        "use_carrier": "1",
+        }
+    # override defaults with values from other-config whose keys
+    # being with "bond-"
+    oc = pifrec['other_config']
+    overrides = filter(lambda (key,val):
+                           key.startswith("bond-"), oc.items())
+    overrides = map(lambda (key,val): (key[5:], val), overrides)
+    bond_options.update(overrides)
+
+    argv += ['--', 'set', 'Port', interface]
+    if pifrec['MAC'] != "":
+        argv += ['MAC=%s' % vsctl_escape(pifrec['MAC'])]
+    for (name,val) in bond_options.items():
+        if name in ['updelay', 'downdelay']:
+            # updelay and downdelay have dedicated schema columns.
+            # The value must be a nonnegative integer.
+            try:
+                value = int(val)
+                if value < 0:
+                    raise ValueError
+
+                argv += ['bond_%s=%d' % (name, value)]
+            except ValueError:
+                log("bridge %s has invalid %s '%s'" % (bridge, name, value))
+        else:
+            # Pass other bond options into other_config.
+            argv += ["other-config:%s=%s" % (vsctl_escape("bond-%s" % name),
+                                             vsctl_escape(val))]
+    return argv
+
+def datapath_deconfigure_bond(netdev):
+    return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
+
+def datapath_deconfigure_ipdev(interface):
+    return ['--', '--with-iface', '--if-exists', 'del-port', interface]
+
+def datapath_modify_config(commands):
+    #log("modifying configuration:")
+    #for c in commands:
+    #    log("  %s" % c)
+            
+    rc = run_command(['/usr/bin/ovs-vsctl'] + ['--timeout=20']
+                     + [c for c in commands if not c.startswith('#')])
+    if not rc:       
+        raise Error("Failed to modify vswitch configuration")
+    return True
+
+#
+# Toplevel Datapath Configuration.
+#
+
+def configure_datapath(pif):
+    """Bring up the configuration for 'pif', which must not be a VLAN PIF, by:
+    - Tearing down other PIFs that use the same physical devices as 'pif'.
+    - Ensuring that 'pif' itself is set up.
+    - *Not* tearing down any PIFs that are stacked on top of 'pif' (i.e. VLANs
+      on top of 'pif'.
+
+    Returns a tuple containing
+    - A list containing the necessary vsctl command line arguments
+    - A list of additional devices which should be brought up after
+      the configuration is applied.
+    """
+
+    vsctl_argv = []
+    extra_up_ports = []
+
+    assert not pif_is_vlan(pif)
+    bridge = pif_bridge_name(pif)
+
+    physical_devices = datapath_get_physical_pifs(pif)
+
+    vsctl_argv += ['## configuring datapath %s' % bridge]
+
+    # Determine additional devices to deconfigure.
+    #
+    # Given all physical devices which are part of this PIF we need to
+    # consider:
+    # - any additional bond which a physical device is part of.
+    # - any additional physical devices which are part of an additional bond.
+    #
+    # Any of these which are not currently in use should be brought
+    # down and deconfigured.
+    extra_down_bonds = []
+    extra_down_ports = []
+    for p in physical_devices:
+        for bond in pif_get_bond_masters(p):
+            if bond == pif:
+                log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
+                continue
+            if bond in extra_down_bonds:
+                continue
+            if db().get_pif_record(bond)['currently_attached']:
+                log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
+
+            extra_down_bonds += [bond]
+
+            for s in pif_get_bond_slaves(bond):
+                if s in physical_devices:
+                    continue
+                if s in extra_down_ports:
+                    continue
+                if pif_currently_in_use(s):
+                    continue
+                extra_down_ports += [s]
+
+    log("configure_datapath: bridge      - %s" % bridge)
+    log("configure_datapath: physical    - %s" % [pif_netdev_name(p) for p in physical_devices])
+    log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
+    log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
+
+    # Need to fully deconfigure any bridge which any of the:
+    # - physical devices
+    # - bond devices
+    # - sibling devices
+    # refers to
+    for brpif in physical_devices + extra_down_ports + extra_down_bonds:
+        if brpif == pif:
+            continue
+        b = pif_bridge_name(brpif)
+        #ifdown(b)
+        # XXX
+        netdev_down(b)
+        vsctl_argv += ['# remove bridge %s' % b]
+        vsctl_argv += ['--', '--if-exists', 'del-br', b]
+
+    for n in extra_down_ports:
+        dev = pif_netdev_name(n)
+        vsctl_argv += ['# deconfigure sibling physical device %s' % dev]
+        vsctl_argv += datapath_deconfigure_physical(dev)
+        netdev_down(dev)
+
+    for n in extra_down_bonds:
+        dev = pif_netdev_name(n)
+        vsctl_argv += ['# deconfigure bond device %s' % dev]
+        vsctl_argv += datapath_deconfigure_bond(dev)
+        netdev_down(dev)
+
+    for p in physical_devices:
+        dev = pif_netdev_name(p)
+        vsctl_argv += ['# deconfigure physical port %s' % dev]
+        vsctl_argv += datapath_deconfigure_physical(dev)
+
+    vsctl_argv += ['--', '--may-exist', 'add-br', bridge]
+
+    if len(physical_devices) > 1:
+        vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
+        vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
+        vsctl_argv += ['# configure bond %s' % pif_netdev_name(pif)]
+        vsctl_argv += datapath_configure_bond(pif, physical_devices)
+        extra_up_ports += [pif_netdev_name(pif)]
+    else:
+        iface = pif_netdev_name(physical_devices[0])
+        vsctl_argv += ['# add physical device %s' % iface]
+        vsctl_argv += ['--', '--may-exist', 'add-port', bridge, iface]
+
+    vsctl_argv += set_br_external_ids(pif)
+    vsctl_argv += ['## done configuring datapath %s' % bridge]
+
+    return vsctl_argv,extra_up_ports
+
+def deconfigure_bridge(pif):
+    vsctl_argv = []
+
+    bridge = pif_bridge_name(pif)
+
+    log("deconfigure_bridge: bridge           - %s" % bridge)
+
+    vsctl_argv += ['# deconfigure bridge %s' % bridge]
+    vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
+
+    return vsctl_argv
+
+def set_br_external_ids(pif):
+    pifrec = db().get_pif_record(pif)
+    dp = pif_datapath(pif)
+    dprec = db().get_pif_record(dp)
+
+    xs_network_uuids = []
+    for nwpif in db().get_pifs_by_device(pifrec['device']):
+        rec = db().get_pif_record(nwpif)
+
+        # When state is read from dbcache PIF.currently_attached
+        # is always assumed to be false... Err on the side of
+        # listing even detached networks for the time being.
+        #if nwpif != pif and not rec['currently_attached']:
+        #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
+        #    continue
+        nwrec = db().get_network_record(rec['network'])
+        xs_network_uuids += [nwrec['uuid']]
+
+    vsctl_argv = []
+    vsctl_argv += ['# configure network-uuids']
+    vsctl_argv += ['--', 'br-set-external-id', pif_bridge_name(pif),
+            'network-uuids', ';'.join(xs_network_uuids)]
+
+    vsctl_argv += ['# configure MAC']
+    vsctl_argv += ['--', 'set', 'Interface', pif_ipdev_name(pif),
+                   'MAC=%s' % vsctl_escape(dprec['MAC'])]
+
+    return vsctl_argv
+
+#
+#
+#
+
+class DatapathVswitch(Datapath):
+    def __init__(self, pif):
+        Datapath.__init__(self, pif)
+        self._dp = pif_datapath(pif)
+        self._ipdev = pif_ipdev_name(pif)
+
+        if pif_is_vlan(pif) and not self._dp:
+            raise Error("Unbridged VLAN devices not implemented yet")
+        
+        log("Configured for Vswitch datapath")
+
+    def configure_ipdev(self, cfg):
+        cfg.write("TYPE=Ethernet\n")
+
+    def preconfigure(self, parent):
+        vsctl_argv = []
+        extra_ports = []
+
+        pifrec = db().get_pif_record(self._pif)
+        dprec = db().get_pif_record(self._dp)
+
+        ipdev = self._ipdev
+        c,e = configure_datapath(self._dp)
+        bridge = pif_bridge_name(self._pif)
+        vsctl_argv += c
+        extra_ports += e
+
+        if pif_is_vlan(self._pif):
+            # XXX this is only needed on XS5.5, because XAPI misguidedly
+            # creates the fake bridge (via bridge ioctl) before it calls us.
+            vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
+
+            # configure_datapath() set up the underlying datapath bridge.
+            # Stack a VLAN bridge on top of it.
+            vsctl_argv += ['--', '--may-exist', 'add-br',
+                           bridge, pif_bridge_name(self._dp), pifrec['VLAN']]
+
+            vsctl_argv += set_br_external_ids(self._pif)
+
+        if ipdev != bridge:
+            vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
+            vsctl_argv += datapath_deconfigure_ipdev(ipdev)
+            vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
+            vsctl_argv += ['--', 'add-port', bridge, ipdev]
+
+        self._vsctl_argv = vsctl_argv
+        self._extra_ports = extra_ports
+
+    def bring_down_existing(self):
+        pass
+
+    def configure(self):
+        # Bring up physical devices. ovs-vswitchd initially enables or
+        # disables bond slaves based on whether carrier is detected
+        # when they are added, and a network device that is down
+        # always reports "no carrier".
+        physical_devices = datapath_get_physical_pifs(self._dp)
+        
+        for p in physical_devices:
+            prec = db().get_pif_record(p)
+            oc = prec['other_config']
+
+            dev = pif_netdev_name(p)
+
+            mtu = mtu_setting(prec['network'], "PIF", oc)
+
+            netdev_up(dev, mtu)
+
+            settings, offload = ethtool_settings(oc)
+            if len(settings):
+                run_command(['/sbin/ethtool', '-s', dev] + settings)
+            if len(offload):
+                run_command(['/sbin/ethtool', '-K', dev] + offload)
+
+        datapath_modify_config(self._vsctl_argv)
+
+    def post(self):
+        for p in self._extra_ports:
+            log("action_up: bring up %s" % p)
+            netdev_up(p)
+
+    def bring_down(self):
+        vsctl_argv = []
+
+        dp = self._dp
+        ipdev = self._ipdev
+        
+        bridge = pif_bridge_name(dp)
+
+        #nw = db().get_pif_record(self._pif)['network']
+        #nwrec = db().get_network_record(nw)
+        #vsctl_argv += ['# deconfigure network-uuids']
+        #vsctl_argv += ['--del-entry=bridge.%s.network-uuids=%s' % (bridge,nwrec['uuid'])]
+
+        log("deconfigure ipdev %s on %s" % (ipdev,bridge))
+        vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
+        vsctl_argv += datapath_deconfigure_ipdev(ipdev)
+
+        if pif_is_vlan(self._pif):
+            # Delete the VLAN bridge.
+            vsctl_argv += deconfigure_bridge(self._pif)
+
+            # If the VLAN's slave is attached, leave datapath setup.
+            slave = pif_get_vlan_slave(self._pif)
+            if db().get_pif_record(slave)['currently_attached']:
+                log("action_down: vlan slave is currently attached")
+                dp = None
+
+            # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
+            for master in pif_get_vlan_masters(slave):
+                if master != self._pif and db().get_pif_record(master)['currently_attached']:
+                    log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
+                    dp = None
+
+            # Otherwise, take down the datapath too (fall through)
+            if dp:
+                log("action_down: no more masters, bring down slave %s" % bridge)
+        else:
+            # Stop here if this PIF has attached VLAN masters.
+            masters = [db().get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(self._pif) if db().get_pif_record(m)['currently_attached']]
+            if len(masters) > 0:
+                log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
+                dp = None
+
+        if dp:
+            vsctl_argv += deconfigure_bridge(dp)
+
+            physical_devices = [pif_netdev_name(p) for p in datapath_get_physical_pifs(dp)]
+
+            log("action_down: bring down physical devices - %s" % physical_devices)
+        
+            for p in physical_devices:
+                netdev_down(p)
+
+        datapath_modify_config(vsctl_argv)
index 5d48fc7..f756bab 100755 (executable)
@@ -1,7 +1,6 @@
 #!/usr/bin/python
 #
 # Copyright (c) 2008,2009 Citrix Systems, Inc.
-# Copyright (c) 2009 Nicira Networks.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published
 
     %(command-name)s <PIF> up
     %(command-name)s <PIF> down
-    %(command-name)s [<PIF>] rewrite
+    %(command-name)s rewrite
     %(command-name)s --force <BRIDGE> up
     %(command-name)s --force <BRIDGE> down
-    %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> <CONFIG>
-    %(command-name)s --force all down
+    %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> --mac=<MAC-ADDRESS> <CONFIG>
 
     where <PIF> is one of:
        --session <SESSION-REF> --pif <PIF-REF>
        --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
 
   Options:
-    --session          A session reference to use to access the xapi DB
+    --session           A session reference to use to access the xapi DB
     --pif               A PIF reference within the session.
     --pif-uuid          The UUID of a PIF.
     --force             An interface name.
+    --root-prefix=DIR   Use DIR as alternate root directory (for testing).
+    --no-syslog         Write log messages to stderr instead of system log.
 """
 
-#
-# Undocumented parameters for test & dev:
-#
-#  --output-directory=<DIR>    Write configuration to <DIR>. Also disables actually
-#                               raising/lowering the interfaces
-#
-#
-#
 # Notes:
 # 1. Every pif belongs to exactly one network
 # 2. Every network has zero or one pifs
 # 3. A network may have an associated bridge, allowing vifs to be attached
 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
 
-import XenAPI
-import os, sys, getopt, time, signal
+from InterfaceReconfigure import *
+
+import os, sys, getopt
 import syslog
 import traceback
 import re
 import random
-from xml.dom.minidom import getDOMImplementation
-from xml.dom.minidom import parse as parseXML
 
-output_directory = None
-
-db = None
 management_pif = None
 
-vswitch_state_dir = "/var/lib/openvswitch/"
-dbcache_file = vswitch_state_dir + "dbcache"
+dbcache_file = "/var/xapi/network.dbcache"
 
 #
-# Debugging and Logging.
+# Logging.
 #
 
-def debug_mode():
-    return output_directory is not None
-
-def log(s):
-    if debug_mode():
-        print >>sys.stderr, s
-    else:
-        syslog.syslog(s)
-
 def log_pif_action(action, pif):
-    pifrec = db.get_pif_record(pif)
+    pifrec = db().get_pif_record(pif)
     rec = {}
     rec['uuid'] = pifrec['uuid']
     rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
@@ -91,15 +69,6 @@ def log_pif_action(action, pif):
     rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
     log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
 
-
-def run_command(command):
-    log("Running command: " + ' '.join(command))
-    rc = os.spawnl(os.P_WAIT, command[0], *command)
-    if rc != 0:
-        log("Command failed %d: " % rc + ' '.join(command))
-        return False
-    return True
-
 #
 # Exceptions.
 #
@@ -109,521 +78,6 @@ class Usage(Exception):
         Exception.__init__(self)
         self.msg = msg
 
-class Error(Exception):
-    def __init__(self, msg):
-        Exception.__init__(self)
-        self.msg = msg
-
-#
-# Configuration File Handling.
-#
-
-class ConfigurationFile(object):
-    """Write a file, tracking old and new versions.
-
-    Supports writing a new version of a file and applying and
-    reverting those changes.
-    """
-
-    __STATE = {"OPEN":"OPEN",
-               "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
-               "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
-
-    def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
-
-        self.__state = self.__STATE['OPEN']
-        self.__fname = fname
-        self.__children = []
-
-        if debug_mode():
-            dirname = output_directory
-        else:
-            dirname = path
-
-        self.__path    = os.path.join(dirname, fname)
-        self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
-        self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
-        self.__unlink = False
-
-        self.__f = open(self.__newpath, "w")
-
-    def attach_child(self, child):
-        self.__children.append(child)
-
-    def path(self):
-        return self.__path
-
-    def readlines(self):
-        try:
-            return open(self.path()).readlines()
-        except:
-            return ""
-
-    def write(self, args):
-        if self.__state != self.__STATE['OPEN']:
-            raise Error("Attempt to write to file in state %s" % self.__state)
-        self.__f.write(args)
-
-    def unlink(self):
-        if self.__state != self.__STATE['OPEN']:
-            raise Error("Attempt to unlink file in state %s" % self.__state)
-        self.__unlink = True
-        self.__f.close()
-        self.__state = self.__STATE['NOT-APPLIED']
-
-    def close(self):
-        if self.__state != self.__STATE['OPEN']:
-            raise Error("Attempt to close file in state %s" % self.__state)
-
-        self.__f.close()
-        self.__state = self.__STATE['NOT-APPLIED']
-
-    def changed(self):
-        if self.__state != self.__STATE['NOT-APPLIED']:
-            raise Error("Attempt to compare file in state %s" % self.__state)
-
-        return True
-
-    def apply(self):
-        if self.__state != self.__STATE['NOT-APPLIED']:
-            raise Error("Attempt to apply configuration from state %s" % self.__state)
-
-        for child in self.__children:
-            child.apply()
-
-        log("Applying changes to %s configuration" % self.__fname)
-
-        # Remove previous backup.
-        if os.access(self.__oldpath, os.F_OK):
-            os.unlink(self.__oldpath)
-
-        # Save current configuration.
-        if os.access(self.__path, os.F_OK):
-            os.link(self.__path, self.__oldpath)
-            os.unlink(self.__path)
-
-        # Apply new configuration.
-        assert(os.path.exists(self.__newpath))
-        if not self.__unlink:
-            os.link(self.__newpath, self.__path)
-        else:
-            pass # implicit unlink of original file
-
-        # Remove temporary file.
-        os.unlink(self.__newpath)
-
-        self.__state = self.__STATE['APPLIED']
-
-    def revert(self):
-        if self.__state != self.__STATE['APPLIED']:
-            raise Error("Attempt to revert configuration from state %s" % self.__state)
-
-        for child in self.__children:
-            child.revert()
-
-        log("Reverting changes to %s configuration" % self.__fname)
-
-        # Remove existing new configuration
-        if os.access(self.__newpath, os.F_OK):
-            os.unlink(self.__newpath)
-
-        # Revert new configuration.
-        if os.access(self.__path, os.F_OK):
-            os.link(self.__path, self.__newpath)
-            os.unlink(self.__path)
-
-        # Revert to old configuration.
-        if os.access(self.__oldpath, os.F_OK):
-            os.link(self.__oldpath, self.__path)
-            os.unlink(self.__oldpath)
-
-        # Leave .*.xapi-new as an aid to debugging.
-
-        self.__state = self.__STATE['REVERTED']
-
-    def commit(self):
-        if self.__state != self.__STATE['APPLIED']:
-            raise Error("Attempt to commit configuration from state %s" % self.__state)
-
-        for child in self.__children:
-            child.commit()
-
-        log("Committing changes to %s configuration" % self.__fname)
-
-        if os.access(self.__oldpath, os.F_OK):
-            os.unlink(self.__oldpath)
-        if os.access(self.__newpath, os.F_OK):
-            os.unlink(self.__newpath)
-
-        self.__state = self.__STATE['COMMITTED']
-
-#
-# Helper functions for encoding/decoding database attributes to/from XML.
-#
-
-def str_to_xml(xml, parent, tag, val):
-    e = xml.createElement(tag)
-    parent.appendChild(e)
-    v = xml.createTextNode(val)
-    e.appendChild(v)
-def str_from_xml(n):
-    def getText(nodelist):
-        rc = ""
-        for node in nodelist:
-            if node.nodeType == node.TEXT_NODE:
-                rc = rc + node.data
-        return rc
-    return getText(n.childNodes).strip()
-
-def bool_to_xml(xml, parent, tag, val):
-    if val:
-        str_to_xml(xml, parent, tag, "True")
-    else:
-        str_to_xml(xml, parent, tag, "False")
-def bool_from_xml(n):
-    s = str_from_xml(n)
-    if s == "True":
-        return True
-    elif s == "False":
-        return False
-    else:
-        raise Error("Unknown boolean value %s" % s)
-
-def strlist_to_xml(xml, parent, ltag, itag, val):
-    e = xml.createElement(ltag)
-    parent.appendChild(e)
-    for v in val:
-        c = xml.createElement(itag)
-        e.appendChild(c)
-        cv = xml.createTextNode(v)
-        c.appendChild(cv)
-def strlist_from_xml(n, ltag, itag):
-    ret = []
-    for n in n.childNodes:
-        if n.nodeName == itag:
-            ret.append(str_from_xml(n))
-    return ret
-
-def otherconfig_to_xml(xml, parent, val, attrs):
-    otherconfig = xml.createElement("other_config")
-    parent.appendChild(otherconfig)
-    for n,v in val.items():
-        if not n in attrs:
-            raise Error("Unknown other-config attribute: %s" % n)
-        str_to_xml(xml, otherconfig, n, v)
-def otherconfig_from_xml(n, attrs):
-    ret = {}
-    for n in n.childNodes:
-        if n.nodeName in attrs:
-            ret[n.nodeName] = str_from_xml(n)
-    return ret
-
-#
-# Definitions of the database objects (and their attributes) used by interface-reconfigure.
-#
-# Each object is defined by a dictionary mapping an attribute name in
-# the xapi database to a tuple containing two items:
-#  - a function which takes this attribute and encodes it as XML.
-#  - a function which takes XML and decocdes it into a value.
-#
-# other-config attributes are specified as a simple array of strings
-
-PIF_XML_TAG = "pif"
-VLAN_XML_TAG = "vlan"
-BOND_XML_TAG = "bond"
-NETWORK_XML_TAG = "network"
-
-ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
-
-PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
-                        [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
-                        ETHTOOL_OTHERCONFIG_ATTRS
-
-PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
-              'management': (bool_to_xml,bool_from_xml),
-              'network': (str_to_xml,str_from_xml),
-              'device': (str_to_xml,str_from_xml),
-              'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
-                                 lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
-              'bond_slave_of': (str_to_xml,str_from_xml),
-              'VLAN': (str_to_xml,str_from_xml),
-              'VLAN_master_of': (str_to_xml,str_from_xml),
-              'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
-                                lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
-              'ip_configuration_mode': (str_to_xml,str_from_xml),
-              'IP': (str_to_xml,str_from_xml),
-              'netmask': (str_to_xml,str_from_xml),
-              'gateway': (str_to_xml,str_from_xml),
-              'DNS': (str_to_xml,str_from_xml),
-              'MAC': (str_to_xml,str_from_xml),
-              'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
-                               lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
-
-              # Special case: We write the current value
-              # PIF.currently-attached to the cache but since it will
-              # not be valid when we come to use the cache later
-              # (i.e. after a reboot) we always read it as False.
-              'currently_attached': (bool_to_xml, lambda n: False),
-            }
-
-VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
-               'tagged_PIF': (str_to_xml,str_from_xml),
-               'untagged_PIF': (str_to_xml,str_from_xml),
-             }
-
-BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
-               'master': (str_to_xml,str_from_xml),
-               'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
-                          lambda n: strlist_from_xml(n, 'slaves', 'slave')),
-             }
-
-NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
-
-NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
-                  'bridge': (str_to_xml,str_from_xml),
-                  'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
-                           lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
-                  'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
-                                   lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
-                }
-
-class DatabaseCache(object):
-    def __read_xensource_inventory(self):
-        filename = "/etc/xensource-inventory"
-        f = open(filename, "r")
-        lines = [x.strip("\n") for x in f.readlines()]
-        f.close()
-
-        defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
-        defs = [ (a, b.strip("'")) for (a,b) in defs ]
-
-        return dict(defs)
-    def __pif_on_host(self,pif):
-        return self.__pifs.has_key(pif)
-
-    def __get_pif_records_from_xapi(self, session, host):
-        self.__pifs = {}
-        for (p,rec) in session.xenapi.PIF.get_all_records().items():
-            if rec['host'] != host:
-                continue
-            self.__pifs[p] = {}
-            for f in PIF_ATTRS:
-                self.__pifs[p][f] = rec[f]
-            self.__pifs[p]['other_config'] = {}
-            for f in PIF_OTHERCONFIG_ATTRS:
-                if not rec['other_config'].has_key(f): continue
-                self.__pifs[p]['other_config'][f] = rec['other_config'][f]
-
-    def __get_vlan_records_from_xapi(self, session):
-        self.__vlans = {}
-        for v in session.xenapi.VLAN.get_all():
-            rec = session.xenapi.VLAN.get_record(v)
-            if not self.__pif_on_host(rec['untagged_PIF']):
-                continue
-            self.__vlans[v] = {}
-            for f in VLAN_ATTRS:
-                self.__vlans[v][f] = rec[f]
-
-    def __get_bond_records_from_xapi(self, session):
-        self.__bonds = {}
-        for b in session.xenapi.Bond.get_all():
-            rec = session.xenapi.Bond.get_record(b)
-            if not self.__pif_on_host(rec['master']):
-                continue
-            self.__bonds[b] = {}
-            for f in BOND_ATTRS:
-                self.__bonds[b][f] = rec[f]
-
-    def __get_network_records_from_xapi(self, session):
-        self.__networks = {}
-        for n in session.xenapi.network.get_all():
-            rec = session.xenapi.network.get_record(n)
-            self.__networks[n] = {}
-            for f in NETWORK_ATTRS:
-                if f == "PIFs":
-                    # drop PIFs on other hosts
-                    self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
-                else:
-                    self.__networks[n][f] = rec[f]
-            self.__networks[n]['other_config'] = {}
-            for f in NETWORK_OTHERCONFIG_ATTRS:
-                if not rec['other_config'].has_key(f): continue
-                self.__networks[n]['other_config'][f] = rec['other_config'][f]
-
-    def __to_xml(self, xml, parent, key, ref, rec, attrs):
-        """Encode a database object as XML"""
-        e = xml.createElement(key)
-        parent.appendChild(e)
-        if ref:
-            e.setAttribute('ref', ref)
-
-        for n,v in rec.items():
-            if attrs.has_key(n):
-                h,_ = attrs[n]
-                h(xml, e, n, v)
-            else:
-                raise Error("Unknown attribute %s" % n)
-    def __from_xml(self, e, attrs):
-        """Decode a database object from XML"""
-        ref = e.attributes['ref'].value
-        rec = {}
-        for n in e.childNodes:
-            if n.nodeName in attrs:
-                _,h = attrs[n.nodeName]
-                rec[n.nodeName] = h(n)
-        return (ref,rec)
-
-    def __init__(self, session_ref=None, cache_file=None):
-        if session_ref and cache_file:
-            raise Error("can't specify session reference and cache file")
-        if cache_file == None:
-            session = XenAPI.xapi_local()
-
-            if not session_ref:
-                log("No session ref given on command line, logging in.")
-                session.xenapi.login_with_password("root", "")
-            else:
-                session._session = session_ref
-
-            try:
-
-                inventory = self.__read_xensource_inventory()
-                assert(inventory.has_key('INSTALLATION_UUID'))
-                log("host uuid is %s" % inventory['INSTALLATION_UUID'])
-
-                host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
-
-                self.__get_pif_records_from_xapi(session, host)
-
-                self.__get_vlan_records_from_xapi(session)
-                self.__get_bond_records_from_xapi(session)
-                self.__get_network_records_from_xapi(session)
-            finally:
-                if not session_ref:
-                    session.xenapi.session.logout()
-        else:
-            log("Loading xapi database cache from %s" % cache_file)
-
-            xml = parseXML(cache_file)
-
-            self.__pifs = {}
-            self.__bonds = {}
-            self.__vlans = {}
-            self.__networks = {}
-
-            assert(len(xml.childNodes) == 1)
-            toplevel = xml.childNodes[0]
-
-            assert(toplevel.nodeName == "xenserver-network-configuration")
-
-            for n in toplevel.childNodes:
-                if n.nodeName == "#text":
-                    pass
-                elif n.nodeName == PIF_XML_TAG:
-                    (ref,rec) = self.__from_xml(n, PIF_ATTRS)
-                    self.__pifs[ref] = rec
-                elif n.nodeName == BOND_XML_TAG:
-                    (ref,rec) = self.__from_xml(n, BOND_ATTRS)
-                    self.__bonds[ref] = rec
-                elif n.nodeName == VLAN_XML_TAG:
-                    (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
-                    self.__vlans[ref] = rec
-                elif n.nodeName == NETWORK_XML_TAG:
-                    (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
-                    self.__networks[ref] = rec
-                else:
-                    raise Error("Unknown XML element %s" % n.nodeName)
-
-    def save(self, cache_file):
-
-        xml = getDOMImplementation().createDocument(
-            None, "xenserver-network-configuration", None)
-        for (ref,rec) in self.__pifs.items():
-            self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
-        for (ref,rec) in self.__bonds.items():
-            self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
-        for (ref,rec) in self.__vlans.items():
-            self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
-        for (ref,rec) in self.__networks.items():
-            self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
-                          NETWORK_ATTRS)
-
-        f = open(cache_file, 'w')
-        f.write(xml.toprettyxml())
-        f.close()
-
-    def get_pif_by_uuid(self, uuid):
-        pifs = map(lambda (ref,rec): ref,
-                  filter(lambda (ref,rec): uuid == rec['uuid'],
-                         self.__pifs.items()))
-        if len(pifs) == 0:
-            raise Error("Unknown PIF \"%s\"" % uuid)
-        elif len(pifs) > 1:
-            raise Error("Non-unique PIF \"%s\"" % uuid)
-
-        return pifs[0]
-
-    def get_pifs_by_device(self, device):
-        return map(lambda (ref,rec): ref,
-                   filter(lambda (ref,rec): rec['device'] == device,
-                          self.__pifs.items()))
-
-    def get_pif_by_bridge(self, bridge):
-        networks = map(lambda (ref,rec): ref,
-                       filter(lambda (ref,rec): rec['bridge'] == bridge,
-                              self.__networks.items()))
-        if len(networks) == 0:
-            raise Error("No matching network \"%s\"" % bridge)
-
-        answer = None
-        for network in networks:
-            nwrec = self.get_network_record(network)
-            for pif in nwrec['PIFs']:
-                pifrec = self.get_pif_record(pif)
-                if answer:
-                    raise Error("Multiple PIFs on host for network %s" % (bridge))
-                answer = pif
-        if not answer:
-            raise Error("No PIF on host for network %s" % (bridge))
-        return answer
-
-    def get_pif_record(self, pif):
-        if self.__pifs.has_key(pif):
-            return self.__pifs[pif]
-        raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
-    def get_all_pifs(self):
-        return self.__pifs
-    def pif_exists(self, pif):
-        return self.__pifs.has_key(pif)
-
-    def get_management_pif(self):
-        """ Returns the management pif on host
-        """
-        all = self.get_all_pifs()
-        for pif in all:
-            pifrec = self.get_pif_record(pif)
-            if pifrec['management']: return pif
-        return None
-
-    def get_network_record(self, network):
-        if self.__networks.has_key(network):
-            return self.__networks[network]
-        raise Error("Unknown network \"%s\"" % network)
-    def get_all_networks(self):
-        return self.__networks
-
-    def get_bond_record(self, bond):
-        if self.__bonds.has_key(bond):
-            return self.__bonds[bond]
-        else:
-            return None
-
-    def get_vlan_record(self, vlan):
-        if self.__vlans.has_key(vlan):
-            return self.__vlans[vlan]
-        else:
-            return None
-
 #
 # Boot from Network filesystem or device.
 #
@@ -634,9 +88,9 @@ def check_allowed(pif):
     Used to prevent system PIFs (such as network root disk) from being interfered with.
     """
 
-    pifrec = db.get_pif_record(pif)
+    pifrec = db().get_pif_record(pif)
     try:
-        f = open("/proc/ardence")
+        f = open(root_prefix() + "/proc/ardence")
         macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
         f.close()
         if len(macline) == 1:
@@ -652,48 +106,12 @@ def check_allowed(pif):
 # Bare Network Devices -- network devices without IP configuration
 #
 
-def netdev_exists(netdev):
-    return os.path.exists("/sys/class/net/" + netdev)
-
-def pif_netdev_name(pif):
-    """Get the netdev name for a PIF."""
-
-    pifrec = db.get_pif_record(pif)
-
-    if pif_is_vlan(pif):
-        return "%(device)s.%(VLAN)s" % pifrec
-    else:
-        return pifrec['device']
-
-def netdev_down(netdev):
-    """Bring down a bare network device"""
-    if debug_mode():
-        return
-    if not netdev_exists(netdev):
-        log("netdev: down: device %s does not exist, ignoring" % netdev)
-        return
-    run_command(["/sbin/ifconfig", netdev, 'down'])
-
-def netdev_up(netdev, mtu=None):
-    """Bring up a bare network device"""
-    if debug_mode():
-        return
-    if not netdev_exists(netdev):
-        raise Error("netdev: up: device %s does not exist" % netdev)
-
-    if mtu:
-        mtu = ["mtu", mtu]
-    else:
-        mtu = []
-        
-    run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
-
 def netdev_remap_name(pif, already_renamed=[]):
     """Check whether 'pif' exists and has the correct MAC.
     If not, try to find a device with the correct MAC and rename it.
     'already_renamed' is used to avoid infinite recursion.
     """
-    
+
     def read1(name):
         file = None
         try:
@@ -705,20 +123,20 @@ def netdev_remap_name(pif, already_renamed=[]):
 
     def get_netdev_mac(device):
         try:
-            return read1("/sys/class/net/%s/address" % device)
+            return read1("%s/sys/class/net/%s/address" % (root_prefix(), device))
         except:
             # Probably no such device.
             return None
 
     def get_netdev_tx_queue_len(device):
         try:
-            return int(read1("/sys/class/net/%s/tx_queue_len" % device))
+            return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device)))
         except:
             # Probably no such device.
             return None
 
     def get_netdev_by_mac(mac):
-        for device in os.listdir("/sys/class/net"):
+        for device in os.listdir(root_prefix() + "/sys/class/net"):
             dev_mac = get_netdev_mac(device)
             if (dev_mac and mac.lower() == dev_mac.lower() and
                 get_netdev_tx_queue_len(device)):
@@ -731,7 +149,7 @@ def netdev_remap_name(pif, already_renamed=[]):
         if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
             raise Error("Could not rename %s to %s" % (old_name, new_name))
 
-    pifrec = db.get_pif_record(pif)
+    pifrec = db().get_pif_record(pif)
     device = pifrec['device']
     mac = pifrec['MAC']
 
@@ -764,205 +182,39 @@ def netdev_remap_name(pif, already_renamed=[]):
 # IP Network Devices -- network devices with IP configuration
 #
 
-def pif_ipdev_name(pif):
-    """Return the ipdev name associated with pif"""
-    pifrec = db.get_pif_record(pif)
-    nwrec = db.get_network_record(pifrec['network'])
-
-    if nwrec['bridge']:
-        # TODO: sanity check that nwrec['bridgeless'] != 'true'
-        return nwrec['bridge']
-    else:
-        # TODO: sanity check that nwrec['bridgeless'] == 'true'
-        return pif_netdev_name(pif)
-
 def ifdown(netdev):
     """Bring down a network interface"""
-    if debug_mode():
-        return
     if not netdev_exists(netdev):
         log("ifdown: device %s does not exist, ignoring" % netdev)
         return
-    if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
-        log("ifdown: device %s exists but ifcfg %s does not" % (netdev,netdev))
-        netdev_down(netdev)
+    if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)):
+        log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
+        run_command(["/sbin/ifconfig", netdev, 'down'])
+        return
     run_command(["/sbin/ifdown", netdev])
 
 def ifup(netdev):
     """Bring up a network interface"""
-    if debug_mode():
-        return
-    if not netdev_exists(netdev):
-        raise Error("ifup: device %s does not exist, ignoring" % netdev)
-    if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
+    if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
         raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
     run_command(["/sbin/ifup", netdev])
 
 #
-# Bridges
+#
 #
 
-def pif_bridge_name(pif):
-    """Return the bridge name of a pif.
-
-    PIF must not be a VLAN and must be a bridged PIF."""
-
-    pifrec = db.get_pif_record(pif)
+def pif_rename_physical_devices(pif):
 
     if pif_is_vlan(pif):
-        raise Error("PIF %(uuid)s cannot be a bridge, VLAN is %(VLAN)s" % pifrec)
-        
-    nwrec = db.get_network_record(pifrec['network'])
+        pif = pif_get_vlan_slave(pif)
 
-    if nwrec['bridge']:
-        return nwrec['bridge']
+    if pif_is_bond(pif):
+        pifs = pif_get_bond_slaves(pif)
     else:
-        raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
+        pifs = [pif]
 
-#
-# PIF miscellanea
-#
-
-def pif_currently_in_use(pif):
-    """Determine if a PIF is currently in use.
-
-    A PIF is determined to be currently in use if
-    - PIF.currently-attached is true
-    - Any bond master is currently attached
-    - Any VLAN master is currently attached
-    """
-    rec = db.get_pif_record(pif)
-    if rec['currently_attached']:
-        log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
-        return True
-    for b in pif_get_bond_masters(pif):
-        if pif_currently_in_use(b):
-            log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
-            return True
-    for v in pif_get_vlan_masters(pif):
-        if pif_currently_in_use(v):
-            log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
-            return True
-    return False
-
-#
-#
-#
-
-def ethtool_settings(oc):
-    settings = []
-    if oc.has_key('ethtool-speed'):
-        val = oc['ethtool-speed']
-        if val in ["10", "100", "1000"]:
-            settings += ['speed', val]
-        else:
-            log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
-    if oc.has_key('ethtool-duplex'):
-        val = oc['ethtool-duplex']
-        if val in ["10", "100", "1000"]:
-            settings += ['duplex', 'val']
-        else:
-            log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
-    if oc.has_key('ethtool-autoneg'):
-        val = oc['ethtool-autoneg']
-        if val in ["true", "on"]:
-            settings += ['autoneg', 'on']
-        elif val in ["false", "off"]:
-            settings += ['autoneg', 'off']
-        else:
-            log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
-    offload = []
-    for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
-        if oc.has_key("ethtool-" + opt):
-            val = oc["ethtool-" + opt]
-            if val in ["true", "on"]:
-                offload += [opt, 'on']
-            elif val in ["false", "off"]:
-                offload += [opt, 'off']
-            else:
-                log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
-    return settings,offload
-
-def mtu_setting(oc):
-    if oc.has_key('mtu'):
-        try:
-            int(oc['mtu'])      # Check that the value is an integer
-            return oc['mtu']
-        except ValueError, x:
-            log("Invalid value for mtu = %s" % oc['mtu'])
-    return None
-
-#
-# Bonded PIFs
-#
-def pif_get_bond_masters(pif):
-    """Returns a list of PIFs which are bond masters of this PIF"""
-
-    pifrec = db.get_pif_record(pif)
-
-    bso = pifrec['bond_slave_of']
-
-    # bond-slave-of is currently a single reference but in principle a
-    # PIF could be a member of several bonds which are not
-    # concurrently attached. Be robust to this possibility.
-    if not bso or bso == "OpaqueRef:NULL":
-        bso = []
-    elif not type(bso) == list:
-        bso = [bso]
-
-    bondrecs = [db.get_bond_record(bond) for bond in bso]
-    bondrecs = [rec for rec in bondrecs if rec]
-
-    return [bond['master'] for bond in bondrecs]
-
-def pif_get_bond_slaves(pif):
-    """Returns a list of PIFs which make up the given bonded pif."""
-
-    pifrec = db.get_pif_record(pif)
-
-    bmo = pifrec['bond_master_of']
-    if len(bmo) > 1:
-        raise Error("Bond-master-of contains too many elements")
-
-    if len(bmo) == 0:
-        return []
-
-    bondrec = db.get_bond_record(bmo[0])
-    if not bondrec:
-        raise Error("No bond record for bond master PIF")
-
-    return bondrec['slaves']
-
-#
-# VLAN PIFs
-#
-
-def pif_is_vlan(pif):
-    return db.get_pif_record(pif)['VLAN'] != '-1'
-
-def pif_get_vlan_slave(pif):
-    """Find the PIF which is the VLAN slave of pif.
-
-Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
-
-    pifrec = db.get_pif_record(pif)
-
-    vlan = pifrec['VLAN_master_of']
-    if not vlan or vlan == "OpaqueRef:NULL":
-        raise Error("PIF is not a VLAN master")
-
-    vlanrec = db.get_vlan_record(vlan)
-    if not vlanrec:
-        raise Error("No VLAN record found for PIF")
-
-    return vlanrec['tagged_PIF']
-
-def pif_get_vlan_masters(pif):
-    """Returns a list of PIFs which are VLANs on top of the given pif."""
-
-    pifrec = db.get_pif_record(pif)
-    vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
-    return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
+    for pif in pifs:
+        netdev_remap_name(pif)
 
 #
 # IP device configuration
@@ -981,7 +233,6 @@ def ipdev_configure_static_routes(interface, oc, f):
           172.16.0.0/15 via 192.168.0.3 dev xenbr1
           172.18.0.0/16 via 192.168.0.4 dev xenbr1
     """
-    fname = "route-%s" % interface
     if oc.has_key('static-routes'):
         # The key is present - extract comma seperates entries
         lines = oc['static-routes'].split(',')
@@ -989,7 +240,7 @@ def ipdev_configure_static_routes(interface, oc, f):
         # The key is not present, i.e. there are no static routes
         lines = []
 
-    child = ConfigurationFile(fname)
+    child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
     child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
             (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
 
@@ -1009,7 +260,7 @@ def ipdev_open_ifcfg(pif):
 
     log("Writing network configuration for %s" % ipdev)
 
-    f = ConfigurationFile("ifcfg-%s" % ipdev)
+    f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
 
     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
@@ -1019,7 +270,7 @@ def ipdev_open_ifcfg(pif):
 
     return f
 
-def ipdev_configure_network(pif):
+def ipdev_configure_network(pif, dp):
     """Write the configuration file for a network.
 
     Writes configuration derived from the network object into the relevant
@@ -1031,11 +282,12 @@ def ipdev_configure_network(pif):
 
     params:
         pif:  Opaque_ref of pif
-        f :   ConfigurationFile(/path/to/ifcfg) to which we append network configuration
+        dp:   Datapath object
     """
 
-    pifrec = db.get_pif_record(pif)
-    nwrec = db.get_network_record(pifrec['network'])
+    pifrec = db().get_pif_record(pif)
+    nw = pifrec['network']
+    nwrec = db().get_network_record(nw)
 
     ipdev = pif_ipdev_name(pif)
 
@@ -1048,7 +300,8 @@ def ipdev_configure_network(pif):
     if pifrec.has_key('other_config'):
         oc = pifrec['other_config']
 
-    f.write("TYPE=Ethernet\n")
+    dp.configure_ipdev(f)
+
     if pifrec['ip_configuration_mode'] == "DHCP":
         f.write("BOOTPROTO=dhcp\n")
         f.write("PERSISTENT_DHCLIENT=yes\n")
@@ -1069,20 +322,20 @@ def ipdev_configure_network(pif):
         if len(offload):
             f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
 
-        mtu = mtu_setting(nwrec['other_config'])
-        if mtu:
-            f.write("MTU=%s\n" % mtu)
-
         ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
 
+    mtu = mtu_setting(nw, "Network", nwrec['other_config'])
+    if mtu:
+        f.write("MTU=%s\n" % mtu)
+
+
     if pifrec.has_key('DNS') and pifrec['DNS'] != "":
         ServerList = pifrec['DNS'].split(",")
         for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
     if oc and oc.has_key('domain'):
         f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
 
-    # We only allow one ifcfg-* to have PEERDNS=yes and there can be
-    # only one GATEWAYDEV in /etc/sysconfig/network.
+    # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
     #
     # The peerdns pif will be the one with
     # pif::other-config:peerdns=true, or the mgmt pif if none have
@@ -1092,37 +345,35 @@ def ipdev_configure_network(pif):
     # pif::other-config:defaultroute=true, or the mgmt pif if none
     # have this set.
 
-    # Work out which pif on this host should be the one with
-    # PEERDNS=yes and which should be the GATEWAYDEV
+    # Work out which pif on this host should be the DNSDEV and which
+    # should be the GATEWAYDEV
     #
-    # Note: we prune out the bond master pif (if it exists).  This is
+    # Note: we prune out the bond master pif (if it exists). This is
     # because when we are called to bring up an interface with a bond
     # master, it is implicit that we should bring down that master.
-    pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
-                     not __pif in pif_get_bond_masters(pif) ]
-    other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
 
-    peerdns_pif = None
-    defaultroute_pif = None
+    pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
 
     # loop through all the pifs on this host looking for one with
     #   other-config:peerdns = true, and one with
     #   other-config:default-route=true
+    peerdns_pif = None
+    defaultroute_pif = None
     for __pif in pifs_on_host:
-        __pifrec = db.get_pif_record(__pif)
+        __pifrec = db().get_pif_record(__pif)
         __oc = __pifrec['other_config']
         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
             if peerdns_pif == None:
                 peerdns_pif = __pif
             else:
                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
-                        (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
+                        (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
             if defaultroute_pif == None:
                 defaultroute_pif = __pif
             else:
                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
-                        (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
+                        (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
 
     # If no pif is explicitly specified then use the mgmt pif for
     # peerdns/defaultroute.
@@ -1131,384 +382,70 @@ def ipdev_configure_network(pif):
     if defaultroute_pif == None:
         defaultroute_pif = management_pif
 
-    # Update all the other network's ifcfg files and ensure
-    # consistency.
-    for __pif in other_pifs_on_host:
-        __f = ipdev_open_ifcfg(__pif)
-        peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
-        lines =  __f.readlines()
-
-        if not peerdns_line_wanted in lines:
-            # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
-            for line in lines:
-                if not line.lstrip().startswith('PEERDNS'):
-                    __f.write(line)
-            log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
-            __f.write(peerdns_line_wanted)
-            __f.close()
-            f.attach_child(__f)
-
-        else:
-            # There is no need to change this ifcfg file.  So don't attach_child.
-            pass
-
-    # ... and for this pif too
-    f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
-
-    # Update gatewaydev
-    fnetwork = ConfigurationFile("network", "/etc/sysconfig")
-    for line in fnetwork.readlines():
-        if line.lstrip().startswith('GATEWAY') :
-            continue
-        fnetwork.write(line)
-    if defaultroute_pif:
-        gatewaydev = pif_ipdev_name(defaultroute_pif)
-        if not gatewaydev:
-            gatewaydev = pif_netdev_name(defaultroute_pif)
-        fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
-    fnetwork.close()
-    f.attach_child(fnetwork)
-
-    return f
-
-#
-# Datapath Configuration
-#
-
-def pif_datapath(pif):
-    """Return the OpenFlow datapath name associated with pif.
-For a non-VLAN PIF, the datapath name is the bridge name.
-For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
-"""
-    if pif_is_vlan(pif):
-        return pif_datapath(pif_get_vlan_slave(pif))
-    
-    pifrec = db.get_pif_record(pif)
-    nwrec = db.get_network_record(pifrec['network'])
-    if not nwrec['bridge']:
-        raise Error("datapath PIF cannot be bridgeless")
-    else:
-        return pif
+    is_dnsdev = peerdns_pif == pif
+    is_gatewaydev = defaultroute_pif == pif
+
+    if is_dnsdev or is_gatewaydev:
+        fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
+        for line in fnetwork.readlines():
+            if is_dnsdev and line.lstrip().startswith('DNSDEV='):
+                fnetwork.write('DNSDEV=%s\n' % ipdev)
+                is_dnsdev = False
+            elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
+                fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
+                is_gatewaydev = False
+            else:
+                fnetwork.write(line)
 
-def datapath_get_physical_pifs(pif):
-    """Return the PIFs for the physical network device(s) associated with a datapath PIF.
-For a bond master PIF, these are the bond slave PIFs.
-For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
+        if is_dnsdev:
+            fnetwork.write('DNSDEV=%s\n' % ipdev)
+        if is_gatewaydev:
+            fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
 
-A VLAN PIF cannot be a datapath PIF.
-"""
-    pifrec = db.get_pif_record(pif)
+        fnetwork.close()
+        f.attach_child(fnetwork)
 
-    if pif_is_vlan(pif):
-        raise Error("get-physical-pifs should not get passed a VLAN")
-    elif len(pifrec['bond_master_of']) != 0:
-        return pif_get_bond_slaves(pif)
-    else:
-        return [pif]
-
-def datapath_deconfigure_physical(netdev):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bridge.*.port=%s' % netdev,
-            '--del-match=port.%s.[!0-9]*' % netdev,
-            '--del-match=bonding.*.slave=%s' % netdev,
-            '--del-match=iface.%s.[!0-9]*' % netdev]
-
-def datapath_configure_bond(pif,slaves):
-    pifrec = db.get_pif_record(pif)
-    interface = pif_netdev_name(pif)
-
-    argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
-    argv += ["--add=bonding.%s.slave=%s" % (interface, pif_netdev_name(slave))
-             for slave in slaves]
-    argv += ['--add=bonding.%s.fake-iface=true' % interface]
-
-    if pifrec['MAC'] != "":
-        argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
-
-    # Bonding options.
-    bond_options = {
-        "mode":   "balance-slb",
-        "miimon": "100",
-        "downdelay": "200",
-        "updelay": "31000",
-        "use_carrier": "1",
-        }
-    # override defaults with values from other-config whose keys
-    # being with "bond-"
-    oc = pifrec['other_config']
-    overrides = filter(lambda (key,val):
-                           key.startswith("bond-"), oc.items())
-    overrides = map(lambda (key,val): (key[5:], val), overrides)
-    bond_options.update(overrides)
-    for (name,val) in bond_options.items():
-        argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
-    return argv
-
-def datapath_deconfigure_bond(netdev):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bonding.%s.[!0-9]*' % netdev,
-            '--del-match=port.%s.[!0-9]*' % netdev]
-
-def datapath_deconfigure_ipdev(interface):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bridge.*.port=%s' % interface,
-            '--del-match=port.%s.[!0-9]*' % interface,
-            '--del-match=iface.%s.[!0-9]*' % interface,
-            '--del-match=vlan.%s.trunks=*' % interface,
-            '--del-match=vlan.%s.tag=*' % interface]
-
-def datapath_modify_config(commands):
-    if debug_mode():
-        log("modifying configuration:")
-        for c in commands:
-            log("  %s" % c)
-
-    rc = run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
-                 '-F', '/etc/ovs-vswitchd.conf']
-                + [c for c in commands if c[0] != '#'] + ['-c'])
-    if not rc:
-        raise Error("Failed to modify vswitch configuration")
-    run_command(['/sbin/service', 'vswitch', 'reload'])
-    return True
+    return f
 
 #
-# Toplevel Datapath Configuration.
+# Toplevel actions
 #
 
-def configure_datapath(pif):
-    """Bring up the datapath configuration for PIF.
-
-    Should be careful not to glitch existing users of the datapath, e.g. other VLANs etc.
-
-    Should take care of tearing down other PIFs which encompass common physical devices.
-
-    Returns a tuple containing
-    - A list containing the necessary cfgmod command line arguments
-    - A list of additional devices which should be brought up after
-      the configuration is applied.    
-    """
-
-    cfgmod_argv = []
-    extra_up_ports = []
-
-    bridge = pif_bridge_name(pif)
+def action_up(pif, force):
+    pifrec = db().get_pif_record(pif)
 
-    physical_devices = datapath_get_physical_pifs(pif)
-
-    # Determine additional devices to deconfigure.
-    #
-    # Given all physical devices which are part of this PIF we need to
-    # consider:
-    # - any additional bond which a physical device is part of.
-    # - any additional physical devices which are part of an additional bond.
-    #
-    # Any of these which are not currently in use should be brought
-    # down and deconfigured.
-    extra_down_bonds = []
-    extra_down_ports = []
-    for p in physical_devices:
-        for bond in pif_get_bond_masters(p):
-            if bond == pif:
-                log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
-                continue
-            if bond in extra_down_bonds:
-                continue
-            if db.get_pif_record(bond)['currently_attached']:
-                log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
-
-            extra_down_bonds += [bond]
-
-            for s in pif_get_bond_slaves(bond):
-                if s in physical_devices:
-                    continue
-                if s in extra_down_ports:
-                    continue
-                if pif_currently_in_use(s):
-                    continue
-                extra_down_ports += [s]
-
-    log("configure_datapath: bridge      - %s" % bridge)
-    log("configure_datapath: physical    - %s" % [pif_netdev_name(p) for p in physical_devices])
-    log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
-    log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
-
-    # Need to fully deconfigure any bridge which any of the:
-    # - physical devices
-    # - bond devices
-    # - sibling devices
-    # refers to
-    for brpif in physical_devices + extra_down_ports + extra_down_bonds:
-        if brpif == pif:
-            continue
-        b = pif_bridge_name(brpif)
-        ifdown(b)
-        cfgmod_argv += ['# remove bridge %s' % b]
-        cfgmod_argv += ['--del-match=bridge.%s.*' % b]
-
-    for n in extra_down_ports:
-        dev = pif_netdev_name(n)
-        cfgmod_argv += ['# deconfigure sibling physical device %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
-        netdev_down(dev)
-
-    for n in extra_down_bonds:
-        dev = pif_netdev_name(n)
-        cfgmod_argv += ['# deconfigure bond device %s' % dev]
-        cfgmod_argv += datapath_deconfigure_bond(dev)
-        netdev_down(dev)
-
-    for p in physical_devices:
-        dev = pif_netdev_name(p)
-        cfgmod_argv += ['# deconfigure physical port %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
-
-    # Check the MAC address of each network device and remap if
-    # necessary to make names match our expectations.
-    for p in physical_devices:
-        netdev_remap_name(p)
-
-    # Bring up physical devices early, because ovs-vswitchd initially
-    # enables or disables bond slaves based on whether carrier is
-    # detected when they are added, and a network device that is down
-    # always reports "no carrier".
-    for p in physical_devices:
-        oc = db.get_pif_record(p)['other_config']
-
-        dev = pif_netdev_name(p)
-
-        mtu = mtu_setting(oc)
-
-        netdev_up(dev, mtu)
-        
-        settings, offload = ethtool_settings(oc)
-        if len(settings):
-            run_command(['/sbin/ethtool', '-s', dev] + settings)
-        if len(offload):
-            run_command(['/sbin/ethtool', '-K', dev] + offload)
-
-    if len(physical_devices) > 1:
-        cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
-        cfgmod_argv += ['--del-entry=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif))]
-        cfgmod_argv += ['# configure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_configure_bond(pif, physical_devices)
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif)) ]
-        extra_up_ports += [pif_netdev_name(pif)]
-    else:
-        iface = pif_netdev_name(physical_devices[0])
-        cfgmod_argv += ['# add physical device %s' % iface]
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,iface) ]
-
-    return cfgmod_argv,extra_up_ports
-
-def deconfigure_datapath(pif):
-    cfgmod_argv = []
-
-    bridge = pif_bridge_name(pif)
-
-    physical_devices = datapath_get_physical_pifs(pif)
-
-    log("deconfigure_datapath: bridge           - %s" % bridge)
-    log("deconfigure_datapath: physical devices - %s" % [pif_netdev_name(p) for p in physical_devices])
-
-    for p in physical_devices:
-        dev = pif_netdev_name(p)
-        cfgmod_argv += ['# deconfigure physical port %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
-        netdev_down(dev)
-
-    if len(physical_devices) > 1:
-        cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
+    ipdev = pif_ipdev_name(pif)
+    dp = DatapathFactory(pif)
 
-    cfgmod_argv += ['# deconfigure bridge %s' % bridge]
-    cfgmod_argv += ['--del-match=bridge.%s.*' % bridge]
-    
-    return cfgmod_argv
+    log("action_up: %s" % ipdev)
 
-#
-# Toplevel actions
-#
+    f = ipdev_configure_network(pif, dp)
 
-def action_up(pif):
-    pifrec = db.get_pif_record(pif)
-    cfgmod_argv = []
-    extra_ports = []
+    dp.preconfigure(f)
 
-    ipdev = pif_ipdev_name(pif)
-    dp = pif_datapath(pif)
-    bridge = pif_bridge_name(dp)
+    f.close()
 
-    log("action_up: %s on bridge %s" % (ipdev, bridge))
-    
-    ifdown(ipdev)
+    pif_rename_physical_devices(pif)
 
-    if dp:
-        c,e = configure_datapath(dp)
-        cfgmod_argv += c
-        extra_ports += e
-
-        cfgmod_argv += ['# configure xs-network-uuids']
-        cfgmod_argv += ['--del-match=bridge.%s.xs-network-uuids=*' % bridge]
-
-        for nwpif in db.get_pifs_by_device(db.get_pif_record(pif)['device']):
-            rec = db.get_pif_record(nwpif)
-            
-            # When state is read from dbcache PIF.currently_attached
-            # is always assumed to be false... Err on the side of
-            # listing even detached networks for the time being.
-            #if nwpif != pif and not rec['currently_attached']:
-            #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
-            #    continue
-            nwrec = db.get_network_record(rec['network'])
-            cfgmod_argv += ['--add=bridge.%s.xs-network-uuids=%s' % (bridge, nwrec['uuid'])]
-
-        cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
-        cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
-        cfgmod_argv += ["# reconfigure ipdev %s" % ipdev]
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge, ipdev)]
-
-    f = ipdev_configure_network(pif)
-    f.close()
+    # if we are not forcing the interface up then attempt to tear down
+    # any existing devices which might interfere with brinign this one
+    # up.
+    if not force:
+        ifdown(ipdev)
 
-    # /etc/xensource/scripts/vif needs to know where to add VIFs.
-    if pif_is_vlan(pif):
-        if not bridge:
-            raise Error("Unbridged VLAN devices not implemented yet")
-        cfgmod_argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
-        cfgmod_argv += ['--add=iface.%s.internal=true' % (ipdev)]
-        cfgmod_argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
-        if not os.path.exists(vswitch_state_dir):
-            os.mkdir(vswitch_state_dir)
-        br = ConfigurationFile("br-%s" % ipdev, vswitch_state_dir)
-        br.write("VLAN_SLAVE=%s\n" % bridge)
-        br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
-        br.close()
-        f.attach_child(br)
-    else:
-        br = ConfigurationFile("br-%s" % ipdev, vswitch_state_dir)
-        br.unlink()
-        f.attach_child(br)
+        dp.bring_down_existing()
 
-    # Apply updated configuration.
     try:
         f.apply()
 
-        datapath_modify_config(cfgmod_argv)
+        dp.configure()
 
         ifup(ipdev)
 
-        for p in extra_ports:
-            netdev_up(p)
+        dp.post()
 
         # Update /etc/issue (which contains the IP address of the management interface)
-        os.system("/sbin/update-issue")
+        os.system(root_prefix() + "/sbin/update-issue")
 
         f.commit()
     except Error, e:
@@ -1517,74 +454,106 @@ def action_up(pif):
         raise
 
 def action_down(pif):
-    pifrec = db.get_pif_record(pif)
-    cfgmod_argv = []
-
     ipdev = pif_ipdev_name(pif)
-    dp = pif_datapath(pif)
-    bridge = pif_bridge_name(dp)
-    
-    log("action_down: %s on bridge %s" % (ipdev, bridge))
+    dp = DatapathFactory(pif)
 
-    ifdown(ipdev)
-
-    if dp:
-        nw = db.get_pif_record(pif)['network']
-        nwrec = db.get_network_record(nw)
-        cfgmod_argv += ['# deconfigure xs-network-uuids']
-        cfgmod_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
+    log("action_down: %s" % ipdev)
 
-        log("deconfigure ipdev %s on %s" % (ipdev,bridge))
-        cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
-        cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
+    ifdown(ipdev)
 
-    f = ipdev_open_ifcfg(pif)
-    f.unlink()
+    dp.bring_down()
 
-    if pif_is_vlan(pif):
-        br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
-        br.unlink()
-        f.attach_child(br)
-
-        # If the VLAN's slave is attached, leave datapath setup.
-        slave = pif_get_vlan_slave(pif)
-        if db.get_pif_record(slave)['currently_attached']:
-            log("action_down: vlan slave is currently attached")
-            dp = None
-
-        # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
-        for master in pif_get_vlan_masters(slave):
-            if master != pif and db.get_pif_record(master)['currently_attached']:
-                log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
-                dp = None
-
-        # Otherwise, take down the datapath too (fall through)
-        if dp:
-            log("action_down: no more masters, bring down slave %s" % bridge)
-    else:
-        # Stop here if this PIF has attached VLAN masters.
-        masters = [db.get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(pif) if db.get_pif_record(m)['currently_attached']]
-        if len(masters) > 0:
-            log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
-            dp = None
+# This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
+def action_force_rewrite(bridge, config):
+    def getUUID():
+        import subprocess
+        uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
+        return uuid.strip()
 
-    if dp:
-        cfgmod_argv += deconfigure_datapath(dp)
+    # Notes:
+    # 1. that this assumes the interface is bridged
+    # 2. If --gateway is given it will make that the default gateway for the host
 
+    # extract the configuration
     try:
-        f.apply()
+        mode = config['mode']
+        mac = config['mac']
+        interface = config['device']
+    except:
+        raise Usage("Please supply --mode, --mac and --device")
 
-        datapath_modify_config(cfgmod_argv)
+    if mode == 'static':
+        try:
+            netmask = config['netmask']
+            ip = config['ip']
+        except:
+            raise Usage("Please supply --netmask and --ip")
+        try:
+            gateway = config['gateway']
+        except:
+            gateway = None
+    elif mode != 'dhcp':
+        raise Usage("--mode must be either static or dhcp")
 
-        f.commit()
-    except Error, e:
-        log("action_down failed to apply changes: %s" % e.msg)
-        f.revert()
-        raise
+    if config.has_key('vlan'):
+        is_vlan = True
+        vlan_slave, vlan_vid = config['vlan'].split('.')
+    else:
+        is_vlan = False
+
+    if is_vlan:
+        raise Error("Force rewrite of VLAN not implemented")
+
+    log("Configuring %s using %s configuration" % (bridge, mode))
+
+    f = ConfigurationFile(root_prefix() + dbcache_file)
+
+    pif_uuid = getUUID()
+    network_uuid = getUUID()
+
+    f.write('<?xml version="1.0" ?>\n')
+    f.write('<xenserver-network-configuration>\n')
+    f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
+    f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
+    f.write('\t\t<management>True</management>\n')
+    f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
+    f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
+    f.write('\t\t<bond_master_of/>\n')
+    f.write('\t\t<VLAN_slave_of/>\n')
+    f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
+    f.write('\t\t<VLAN>-1</VLAN>\n')
+    f.write('\t\t<device>%s</device>\n' % interface)
+    f.write('\t\t<MAC>%s</MAC>\n' % mac)
+    f.write('\t\t<other_config/>\n')
+    if mode == 'dhcp':
+        f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
+        f.write('\t\t<IP></IP>\n')
+        f.write('\t\t<netmask></netmask>\n')
+        f.write('\t\t<gateway></gateway>\n')
+        f.write('\t\t<DNS></DNS>\n')
+    elif mode == 'static':
+        f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
+        f.write('\t\t<IP>%s</IP>\n' % ip)
+        f.write('\t\t<netmask>%s</netmask>\n' % netmask)
+        if gateway is not None:
+            f.write('\t\t<gateway>%s</gateway>\n' % gateway)
+        f.write('\t\t<DNS></DNS>\n')
+    else:
+        raise Error("Unknown mode %s" % mode)
+    f.write('\t</pif>\n')
+
+    f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
+    f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
+    f.write('\t\t<PIFs>\n')
+    f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
+    f.write('\t\t</PIFs>\n')
+    f.write('\t\t<bridge>%s</bridge>\n' % bridge)
+    f.write('\t\t<other_config/>\n')
+    f.write('\t</network>\n')
+    f.write('</xenserver-network-configuration>\n')
 
-def action_rewrite(pif):
-    f = ipdev_configure_network(pif)
     f.close()
+
     try:
         f.apply()
         f.commit()
@@ -1593,11 +562,8 @@ def action_rewrite(pif):
         f.revert()
         raise
 
-def action_force_rewrite(bridge, config):
-    raise Error("Force rewrite is not implemented yet.")
-
 def main(argv=None):
-    global output_directory, management_pif
+    global management_pif
 
     session = None
     pif_uuid = None
@@ -1612,13 +578,14 @@ def main(argv=None):
     try:
         try:
             shortops = "h"
-            longops = [ "output-directory=",
-                        "pif=", "pif-uuid=",
+            longops = [ "pif=", "pif-uuid=",
                         "session=",
                         "force=",
                         "force-interface=",
                         "management",
-                        "device=", "mode=", "ip=", "netmask=", "gateway=",
+                        "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
+                        "root-prefix=",
+                        "no-syslog",
                         "help" ]
             arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
         except getopt.GetoptError, msg:
@@ -1627,9 +594,7 @@ def main(argv=None):
         force_rewrite_config = {}
 
         for o,a in arglist:
-            if o == "--output-directory":
-                output_directory = a
-            elif o == "--pif":
+            if o == "--pif":
                 pif = a
             elif o == "--pif-uuid":
                 pif_uuid = a
@@ -1639,15 +604,20 @@ def main(argv=None):
                 force_interface = a
             elif o == "--management":
                 force_management = True
-            elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
+            elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
                 force_rewrite_config[o[2:]] = a
+            elif o == "--root-prefix":
+                set_root_prefix(a)
+            elif o == "--no-syslog":
+                set_log_destination("stderr")
             elif o == "-h" or o == "--help":
                 print __doc__ % {'command-name': os.path.basename(argv[0])}
                 return 0
 
-        if not debug_mode():
+        if get_log_destination() == "syslog":
             syslog.openlog(os.path.basename(argv[0]))
             log("Called as " + str.join(" ", argv))
+
         if len(args) < 1:
             raise Usage("Required option <action> not present")
         if len(args) > 1:
@@ -1661,19 +631,17 @@ def main(argv=None):
         # backwards compatibility
         if action == "rewrite-configuration": action = "rewrite"
 
-        if output_directory and ( session or pif ):
-            raise Usage("--session/--pif cannot be used with --output-directory")
         if ( session or pif ) and pif_uuid:
             raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
         if ( session and not pif ) or ( not session and pif ):
             raise Usage("--session and --pif must be used together.")
         if force_interface and ( session or pif or pif_uuid ):
             raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
-        if force_interface == "all" and action != "down":
-            raise Usage("\"--force all\" only valid for down action")
         if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
             raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
-
+        if (action == "rewrite") and (pif or pif_uuid ):
+            raise Usage("rewrite action does not take --pif or --pif-uuid")
+        
         global db
         if force_interface:
             log("Force interface %s %s" % (force_interface, action))
@@ -1681,26 +649,23 @@ def main(argv=None):
             if action == "rewrite":
                 action_force_rewrite(force_interface, force_rewrite_config)
             elif action in ["up", "down"]:
-                if action == "down" and force_interface == "all":
-                    raise Error("Force all interfaces down not implemented yet")
-
-                db = DatabaseCache(cache_file=dbcache_file)
-                pif = db.get_pif_by_bridge(force_interface)
-                management_pif = db.get_management_pif()
+                db_init_from_cache(dbcache_file)
+                pif = db().get_pif_by_bridge(force_interface)
+                management_pif = db().get_management_pif()
 
                 if action == "up":
-                    action_up(pif)
+                    action_up(pif, True)
                 elif action == "down":
                     action_down(pif)
             else:
                 raise Error("Unknown action %s"  % action)
         else:
-            db = DatabaseCache(session_ref=session)
+            db_init_from_xenapi(session)
 
             if pif_uuid:
-                pif = db.get_pif_by_uuid(pif_uuid)
+                pif = db().get_pif_by_uuid(pif_uuid)
 
-            if action == "rewrite" and not pif:
+            if action == "rewrite":
                 pass
             else:
                 if not pif:
@@ -1712,8 +677,8 @@ def main(argv=None):
                 else:
                     # pif is not going to be the management pif.
                     # Search DB cache for pif on same host with management=true
-                    pifrec = db.get_pif_record(pif)
-                    management_pif = db.get_management_pif()
+                    pifrec = db().get_pif_record(pif)
+                    management_pif = db().get_management_pif()
 
                 log_pif_action(action, pif)
 
@@ -1721,16 +686,14 @@ def main(argv=None):
                     return 0
 
                 if action == "up":
-                    action_up(pif)
+                    action_up(pif, False)
                 elif action == "down":
                     action_down(pif)
-                elif action == "rewrite":
-                    action_rewrite(pif)
                 else:
                     raise Error("Unknown action %s"  % action)
 
             # Save cache.
-            db.save(dbcache_file)
+            db().save(dbcache_file)
 
     except Usage, err:
         print >>sys.stderr, err.msg
@@ -1752,7 +715,6 @@ if __name__ == "__main__":
         for exline in err:
             log(exline)
 
-    if not debug_mode():
-        syslog.closelog()
+    syslog.closelog()
 
     sys.exit(rc)
diff --git a/xenserver/root_vswitch_scripts_dump-vif-details b/xenserver/root_vswitch_scripts_dump-vif-details
deleted file mode 100755 (executable)
index 7ce8bf7..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/python
-#
-# Script to retrieve extended information about VIFs that are
-# needed by the controller.  This is called by the "vif" script,
-# which is run when virtual interfaces are added and removed.
-
-# Copyright (C) 2009 Nicira Networks, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved.  This file is offered as-is,
-# without warranty of any kind.
-
-import sys
-import XenAPI
-import xen.lowlevel.xs
-
-# Query XenStore for the opaque reference of this vif
-def get_vif_ref(domid, devid):
-       xenstore = xen.lowlevel.xs.xs()
-       t = xenstore.transaction_start()
-       vif_ref = xenstore.read(t, '/xapi/%s/private/vif/%s/ref' % (domid, devid))
-       xenstore.transaction_end(t)
-       return vif_ref
-
-# Query XAPI for the information we need using the vif's opaque reference
-def dump_vif_info(domid, devid, vif_ref):
-       vif_info = []
-       session = XenAPI.xapi_local()
-       session.xenapi.login_with_password("root", "")
-       try: 
-               vif_rec = session.xenapi.VIF.get_record(vif_ref)
-               net_rec = session.xenapi.network.get_record(vif_rec["network"])
-               vm_uuid = session.xenapi.VM.get_uuid(vif_rec["VM"])
-
-               # Data to allow vNetManager to associate VIFs with xapi data
-               add_port = '--add=port.vif%s.%s' % (domid, devid)
-               vif_info.append('%s.net-uuid=%s' % (add_port, net_rec["uuid"]))
-               vif_info.append('%s.vif-mac=%s' % (add_port, vif_rec["MAC"]))
-               vif_info.append('%s.vif-uuid=%s' % (add_port, vif_rec["uuid"]))
-               vif_info.append('%s.vm-uuid=%s' % (add_port, vm_uuid))
-
-               # vNetManager needs to know the network UUID(s) associated with
-               # each datapath.  Normally interface-reconfigure adds them, but
-               # interface-reconfigure never gets called for internal networks
-               # (xapi does the addbr ioctl internally), so we have to do it
-               # here instead for internal networks.  This is only acceptable
-               # because xapi is lazy about creating internal networks: it
-               # only creates one just before it adds the first vif to it.
-               # There may still be a brief delay between the initial
-               # ovs-vswitchd connection to vNetManager and setting this
-               # configuration variable, but vNetManager can tolerate that.
-               if not net_rec['PIFs']:
-                       key = 'bridge.%s.xs-network-uuids' % net_rec['bridge']
-                       value = net_rec['uuid']
-                       vif_info.append('--del-match=%s=*' % key)
-                       vif_info.append('--add=%s=%s' % (key, value))
-       finally:
-               session.xenapi.session.logout()
-       print ' '.join(vif_info)
-       
-if __name__ == '__main__':
-       if len(sys.argv) != 3:
-               sys.stderr.write("ERROR: %s <domid> <devid>\n" % sys.argv[0])
-               sys.exit(1)
-
-       domid = sys.argv[1]
-       devid = sys.argv[2]
-
-       vif_ref = get_vif_ref(domid, devid)
-       if not vif_ref:
-               sys.stderr.write("ERROR: Could not find interface vif%s.%s\n" 
-                                % (domid, devid))
-               sys.exit(1)
-
-       dump_vif_info(domid, devid, vif_ref)
-       sys.exit(0)
index dbd00a4..6ecf478 100644 (file)
@@ -7,16 +7,13 @@
 
 # Copyright (c) 2009 Nicira Networks.
 
-import logging
-log = logging.getLogger("vswitch-cfg-update")
-logging.basicConfig(filename="/var/log/vswitch-xsplugin.log", level=logging.DEBUG)
+from XSConsoleLog import *
 
 import os
 import socket
 import subprocess
 
-cfg_mod="/root/vswitch/bin/ovs-cfg-mod"
-vswitchd_cfg_filename="/etc/ovs-vswitchd.conf"
+vsctl="/usr/bin/ovs-vsctl"
 
 if __name__ == "__main__":
     raise Exception("This script is a plugin for xsconsole and cannot run independently")
@@ -36,7 +33,7 @@ class VSwitchService:
         try:
             output = ShellPipe(["service", self.name, "version"]).Stdout()
         except StandardError, e:
-            log.error("version retrieval error: " + str(e))
+            XSLogError("vswitch version retrieval error: " + str(e))
             return "<unknown>"
         for line in output:
             if self.processname in line:
@@ -47,7 +44,7 @@ class VSwitchService:
         try:
             output = ShellPipe(["service", self.name, "status"]).Stdout()
         except StandardError, e:
-            log.error("status retrieval error: " + str(e))
+            XSLogError("vswitch status retrieval error: " + str(e))
             return "<unknown>"
         if len(output) == 0:
             return "<unknown>"
@@ -66,7 +63,7 @@ class VSwitchService:
         try:
             ShellPipe(["service", self.name, "restart"]).Call()
         except StandardError, e:
-            log.error("restart error: " + str(e))
+            XSLogError("vswitch restart error: " + str(e))
 
     @classmethod
     def Inst(cls, name, processname=None):
@@ -80,12 +77,12 @@ class VSwitchService:
 class VSwitchConfig:
 
     @staticmethod
-    def Get(key):
+    def Get(action):
         try:
-            output = ShellPipe([cfg_mod, "-vANY:console:emer", "-F", 
-                    vswitchd_cfg_filename, "-q", key]).Stdout()
+            arg = [vsctl, "-vANY:console:emer"] + action.split()
+            output = ShellPipe(arg).Stdout()
         except StandardError, e:
-            log.error("config retrieval error: " + str(e))
+            XSLogError("config retrieval error: " + str(e))
             return "<unknown>"
 
         if len(output) == 0:
@@ -287,11 +284,13 @@ class XSFeatureVSwitch:
         if dbController == "":
             dbController = Lang("<None>")
         inPane.AddStatusField(Lang("Controller (config)", 20), dbController)
-        controller = VSwitchConfig.Get("mgmt.controller")
+        controller = VSwitchConfig.Get("get Open_vSwitch . managers")
+        controller = controller.strip('[]"')
+
         if controller == "":
             controller = Lang("<None>")
         elif controller[0:4] == "ssl:":
-            controller = controller[4:]
+            controller = controller.split(':')[1]
         inPane.AddStatusField(Lang("Controller (in-use)", 20), controller)
 
         inPane.NewLine()
index b97b078..fbaa6ad 100755 (executable)
@@ -22,8 +22,9 @@ import sys
 
 argv0 = sys.argv[0]
 
-BRCTL = "/root/vswitch/xs-original/brctl"
-VSWITCHD_CONF = "/etc/ovs-vswitchd.conf"
+BRCTL = "/usr/lib/vswitch/xs-original/brctl"
+VSCTL = "/usr/bin/ovs-vsctl"
+OVSDB_SERVER = "unix:/var/run/ovsdb-server"
 
 # Execute the real brctl program, passing the same arguments that were passed
 # to us.
@@ -32,77 +33,29 @@ def delegate():
     # execl should never return.  We only arrive here if brctl failed to exec.
     sys.exit(1)
 
-# Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
-# dictionary that maps from string keys to lists of string values.  (Even
-# singleton values are represented as lists.)
-def cfg_read(filename):
-    try:
-        f = open(filename)
-    except IOError, e:
-        sys.stderr.write("%s: could not open %s (%s)\n"
-                         % (argv0, filename, e.strerror))
-        sys.exit(1)
+def call_vsctl(cmd, arg=""):
+    database = '--db=' + OVSDB_SERVER
+    command = [VSCTL, database, cmd]
+    if (arg):
+        command.append(arg)
+    return subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0].split()
 
-    cfg = {}
-    rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
-    for line in f:
-        line = line.strip()
-        if len(line) == 0 or line[0] == '#':
-            continue
-
-        match = rx.match(line)
-        if match == None:
-            continue
-
-        key, value = match.groups()
-        if key not in cfg:
-            cfg[key] = []
-        cfg[key].append(value)
-    return cfg
-
-# Returns a set of the immediate subsections of 'section' within 'cfg'.  For
-# example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
-# and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
-def cfg_get_subsections(cfg, section):
-    subsections = set()
-    for key in cfg:
-        if key.startswith(section + "."):
-            dot = key.find(".", len(section) + 1)
-            if dot == -1:
-                dot = len(key)
-            subsections.add(key[len(section) + 1:dot])
-    return subsections
-
-# Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
-# returns False.
-def cfg_get_bool(cfg, name):
-    return name in cfg and cfg[name] == ['true']
-
-# If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
-# that VLAN number.  Otherwise, returns 0.
-def get_port_vlan(cfg, port):
-    try:
-        return int(cfg["vlan.%s.tag" % port][0])
-    except (ValueError, KeyError):
-        return 0
-
-# Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
-# the ports returned are only those configured with implicit VLAN 'vlan'.
-def get_bridge_ports(cfg, bridge, vlan):
-    ports = []
-    for port in cfg["bridge.%s.port" % bridge]:
-        if vlan < 0 or get_port_vlan(cfg, port) == vlan:
-            ports.append(port)
-    return ports
-
-# Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
-# nonnegative, the interfaces returned are only those whose ports are
-# configured with implicit VLAN 'vlan'.
-def get_bridge_ifaces(cfg, bridge, vlan):
-    ifaces = []
-    for port in get_bridge_ports(cfg, bridge, vlan):
-        ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
-    return ifaces
+# Returns a list of all the bridges 
+def get_bridges():
+    return call_vsctl('list-br')
+
+# Returns a list of all ports on 'bridge' 
+def get_bridge_ports(bridge):
+    return call_vsctl('list-ports', bridge)
+
+# Returns a list of all interfaces on 'bridge' 
+def get_bridge_ifaces(bridge):
+    return call_vsctl('list-ifaces', bridge)
+
+# Returns the parent of 'bridge'.  If 'bridge' does not have a parent,
+# 'bridge' is returned.
+def get_bridge_parent(bridge):
+    return call_vsctl('br-to-parent', bridge)
 
 # Returns the first line of the file named 'name', with the trailing new-line
 # (if any) stripped off.
@@ -126,29 +79,22 @@ def get_bridge_id(netdev):
 
 def cmd_show():
     print "bridge name\tbridge id\t\tSTP enabled\tinterfaces"
-    cfg = cfg_read(VSWITCHD_CONF)
 
     # Find all the bridges.
-    real_bridges = [(br, br, 0) for br in cfg_get_subsections(cfg, "bridge")]
-    fake_bridges = []
-    for linux_bridge, ovs_bridge, vlan in real_bridges:
-        for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
-            if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
-                fake_bridges.append((iface, ovs_bridge,
-                                     get_port_vlan(cfg, iface)))
-    bridges = real_bridges + fake_bridges
+    bridges = get_bridges()
 
     # Find all the interfaces on each bridge.
-    for linux_bridge, ovs_bridge, vlan in bridges:
-        bridge_ports = get_bridge_ports(cfg, ovs_bridge, vlan)
-        if linux_bridge in bridge_ports:
-            bridge_ports.remove(linux_bridge)
+    for bridge in bridges:
+        bridge_ports = get_bridge_ports(bridge)
+        parent = get_bridge_parent(bridge)
+        if parent in bridge_ports:
+            bridge_ports.remove(parent)
         bridge_ports.sort()
-        bridge_id = get_bridge_id(linux_bridge)
+        bridge_id = get_bridge_id(bridge)
         first_port = ""
         if bridge_ports:
             first_port = bridge_ports[0]
-        print "%s\t\t%s\t%s\t\t%s" % (linux_bridge, bridge_id, "no", first_port)
+        print "%s\t\t%s\t%s\t\t%s" % (bridge, bridge_id, "no", first_port)
         for port in bridge_ports[1:]:
             print "\t\t\t\t\t\t\t%s" % port
 
index 93d833c..92fd27f 100755 (executable)
@@ -120,8 +120,8 @@ HP_HPASMD_LOG = '/var/spool/compaq/hpasmd.log'
 VAR_LOG_DIR = '/var/log/'
 VNCTERM_CORE_DIR = '/var/xen/vncterm'
 VSWITCH_CORE_DIR = '/var/xen/vswitch'
-OVS_VSWITCH_CONF = '/etc/ovs-vswitchd.conf'
-OVS_VSWITCH_DBCACHE = '/etc/ovs-vswitch.dbcache'
+OVS_VSWITCH_CONF = '/etc/ovs-vswitchd.conf.db'
+OVS_VSWITCH_DBCACHE = '/var/xapi/network.dbcache'
 XENSOURCE_INVENTORY = '/etc/xensource-inventory'
 OEM_CONFIG_DIR = '/var/xsconfig'
 OEM_CONFIG_FILES_RE = re.compile(r'^.*xensource-inventory$')
@@ -196,8 +196,8 @@ MD5SUM = '/usr/bin/md5sum'
 MULTIPATHD = '/sbin/multipathd'
 NETSTAT = '/bin/netstat'
 OMREPORT = '/opt/dell/srvadmin/oma/bin/omreport'
-OVS_DPCTL = '/root/vswitch/bin/ovs-dpctl'
-OVS_OFCTL = '/root/vswitch/bin/ovs-ofctl'
+OVS_DPCTL = '/usr/bin/ovs-dpctl'
+OVS_OFCTL = '/usr/bin/ovs-ofctl'
 PS = '/bin/ps'
 PVS = '/usr/sbin/pvs'
 ROUTE = '/sbin/route'
@@ -715,10 +715,11 @@ exclude those logs from the archive.
 
     file_output(CAP_VSWITCH_LOGS, 
          [ VAR_LOG_DIR + x for x in
-           [ 'ovs-brcompatd.log', 'ovs-vswitchd.log', 'vswitch-cfg-update.log', 'vswitch-xsplugin.log' ] +
+           [ 'ovs-brcompatd.log', 'ovs-vswitchd.log', 'ovsdb-server.log', 'vswitch-cfg-update.log', 'vswitch-xsplugin.log' ] +
            [ f % n for n in range(1, 20) \
                  for f in ['ovs-brcompatd.log.%d', 'ovs-brcompatd.log.%d.gz', 
-                           'ovs-vswitchd.log.%d', 'ovs-vswitchd.log.%d.gz']]])
+                           'ovs-vswitchd.log.%d', 'ovs-vswitchd.log.%d.gz',
+                           'ovsdb-server.log.%d', 'ovsdb-server.log.%d.gz']]])
 
     cmd_output(CAP_VSWITCH_STATUS, [OVS_DPCTL, 'show'])
     tree_output(CAP_VSWITCH_STATUS, VSWITCH_CORE_DIR)
@@ -1,15 +1,12 @@
 ### Configuration options for vswitch
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
 # notice and this notice are preserved.  This file is offered as-is,
 # without warranty of any kind.
 
-# VSWITCH_BASE: Root directory where vswitch binaries are installed
-# VSWITCH_BASE=/root/vswitch/openvswitch/build
-
 # ENABLE_BRCOMPAT: If 'y' than emulate linux bridging interfaces
 #    using the brcompat kernel module and ovs-brcompatd daemon
 # ENABLE_BRCOMPAT=y
 #    This is useful in some cases when replacing existing solutions.
 # ENABLE_FAKE_PROC_NET=y
 
+# ENABLE_MONITOR: If 'y' then monitor daemon processes and restart them
+#    if they die due to an error signal.
+# ENABLE_MONITOR=y
+
 # FORCE_COREFILES: If 'y' then core files will be enabled.
 # FORCE_COREFILES=y
 
 #     See the manpage for "core".
 # COREFILE_PATTERN="/var/log/%e-%t"
 
-# VSWITCHD_CONF: File in which ovs-vswitchd stores its configuration.
-# VSWITCHD_CONF=/etc/ovs-vswitchd.conf
+# OVSDB_SERVER_REMOTES: Space-separated list of methods on which to have
+#     ovsdb-server listen or connect for a JSON-RPC connection.
+# OVSDB_SERVER_REMOTES="punix:/var/run/ovsdb-server db:Open_vSwitch,managers"
+
+# OVSDB_SERVER_DB: File for which ovsdb-server uses for storage.
+# OVSDB_SERVER_DB=/etc/ovs-vswitchd.conf.db
+
+# OVSDB_SERVER_PIDFILE: File in which to store the pid of the running
+#     ovsdb-server.
+# OVSDB_SERVER_PIDFILE=/var/run/ovsdb-server.pid
+
+# OVSDB_SERVER_RUN_DIR: Set the directory in which ovsdb-server should be
+#     run.  This mainly affects where core files will be placed.
+# OVSDB_SERVER_RUN_DIR=/var/xen/vswitch
+
+# OVSDB_SERVER_PRIORITY: "nice" priority at which to run ovsdb-server and 
+#     related processes.
+# OVSDB_SERVER_PRIORITY=-10
+
+# OVSDB_SERVER_LOGFILE: File to send the FILE_LOGLEVEL log messages to.
+# OVSDB_SERVER_LOGFILE=/var/log/ovsdb-server.log
+
+# OVSDB_SERVER_FILE_LOGLEVEL: Log level at which to log into the
+#     OVSDB_SERVER_LOG file.  If this is null or not set the logfile will
+#     not be created and nothing will be sent to it.  This is the
+#     default.  The available options are: EMER, WARN, INFO and DBG.
+# OVSDB_SERVER_FILE_LOGLEVEL=""
+
+# OVSDB_SERVER_SYSLOG_LOGLEVEL: Log level at which to log into syslog.  If
+#     this is null or not set the default is to log to syslog
+#     emergency and warning level messages only.
+# OVSDB_SERVER_SYSLOG_LOGLEVEL="WARN"
+
+# OVSDB_SERVER_MEMLEAK_LOGFILE: File for logging memory leak data.
+#     Enabling this option will slow ovsdb-server significantly.  Do not
+#     enable it except to debug a suspected memory leak.  Use the
+#     ovs-parse-leaks utility included with Open vSwitch to parse the
+#     log file.  For best results, you also need debug symbols.
+# OVSDB_SERVER_MEMLEAK_LOGFILE=""
+
+# OVSDB_SERVER_STRACE_LOG: File for logging strace output.
+#     If this is set to a nonempty string, then ovsdb-server will run
+#     under strace, whose output will be logged to the specified file.
+#     Enabling this option will slow ovsdb-server significantly.
+#     OVSDB_SERVER_STRACE_LOG and OVSDB_SERVER_VALGRIND_LOG are mutually 
+#     exclusive.
+# OVSDB_SERVER_STRACE_LOG=""
+
+# OVSDB_SERVER_STRACE_OPT: Options to pass to strace.
+#     This option's value is honored only when OVSDB_SERVER_STRACE_LOG is
+#     set to a nonempty string.
+# OVSDB_SERVER_STRACE_OPT=""
+
+# OVSDB_SERVER_VALGRIND_LOG: File for logging valgrind output.
+#     If this is set to a nonempty string, then ovsdb-server will run
+#     under valgrind, whose output will be logged to the specified file.
+#     Enabling this option will slow ovsdb-server by 100X or more.
+#     valgrind is not installed by default on XenServer systems; you must
+#     install it by hand to usefully enable this option.
+#     OVSDB_SERVER_STRACE_LOG and OVSDB_SERVER_VALGRIND_LOG are mutually 
+#     exclusive.
+# OVSDB_SERVER_VALGRIND_LOG=""
+
+# OVSDB_SERVER_VALGRIND_OPT: Options to pass to valgrind.
+#     This option's value is honored only when OVSDB_SERVER_VALGRIND_LOG is
+#     set to a nonempty string.
+# OVSDB_SERVER_VALGRIND_OPT=""
+
+# VSWITCHD_OVSDB_SERVER: Method to have ovs-vswitchd initiate a JSON-RPC
+#     connection to an ovsdb-server instance.
+# VSWITCHD_OVSDB_SERVER=unix:/var/run/ovsdb-server
+
+# VSWITCHD_OVSDB_SCHEMA: Schema file to use for generating a new OVSDB
+#     ovs-vswitchd database.
+# VSWITCHD_OVSDB_SCHEMA=/usr/share/vswitch/vswitch.ovsschema
 
 # VSWITCHD_PIDFILE: File in which to store the pid of the running
 #     ovs-vswitchd.
 # VSWITCHD_LOGFILE: File to send the FILE_LOGLEVEL log messages to.
 # VSWITCHD_LOGFILE=/var/log/ovs-vswitchd.log
 
-# VSWITCHD_MEMLEAK_LOGFILE: File for logging memory leak data.
-#     Enabling this option will slow ovs-vswitchd significantly.  Do not
-#     enable it except to debug a suspected memory leak.  Use the
-#     ovs-parse-leaks utility included with Open vSwitch to parse the
-#     log file.  For best results, you also need debug symbols.
-# VSWITCHD_MEMLEAK_LOGFILE=""
-
 # VSWITCHD_FILE_LOGLEVEL: Log level at which to log into the
 #     VSWITCHD_LOG file.  If this is null or not set the logfile will
 #     not be created and nothing will be sent to it.  This is the
 #     emergency and warning level messages only.
 # VSWITCHD_SYSLOG_LOGLEVEL="WARN"
 
+# VSWITCHD_MEMLEAK_LOGFILE: File for logging memory leak data.
+#     Enabling this option will slow ovs-vswitchd significantly.  Do not
+#     enable it except to debug a suspected memory leak.  Use the
+#     ovs-parse-leaks utility included with Open vSwitch to parse the
+#     log file.  For best results, you also need debug symbols.
+# VSWITCHD_MEMLEAK_LOGFILE=""
+
 # VSWITCHD_STRACE_LOG: File for logging strace output.
 #     If this is set to a nonempty string, then ovs-vswitchd will run
 #     under strace, whose output will be logged to the specified file.
index c79309d..ad0fba4 100644 (file)
@@ -1,6 +1,6 @@
 # Spec file for vswitch and related programs.
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
 #    rpmbuild -D "vswitch_version 0.8.9~1+build123" -D "xen_version 2.6.18-128.1.1.el5.xs5.1.0.483.1000xen" -D "build_number --with-build-number=123" -bb /usr/src/redhat/SPECS/vswitch-xen.spec
 #
 %define version %{vswitch_version}-%{xen_version}
-%define _prefix /root/vswitch
 
 Name: vswitch
 Summary: Virtual switch
 Group: System Environment/Daemons
 URL: http://www.openvswitch.org/
+Vendor: Nicira Networks, Inc.
 Version: %{vswitch_version}
 
 # The entire source code is ASL 2.0 except datapath/ which is GPLv2
@@ -28,6 +28,13 @@ Release: 1
 Source: openvswitch-%{vswitch_version}.tar.gz
 Buildroot: /tmp/vswitch-xen-rpm
 Requires: kernel-xen = %(echo '%{xen_version}' | sed 's/xen$//')
+# The following Conflicts prevents the "vswitch" package generated by
+# this spec file from installing at the same time as the "openvswitch"
+# package shipped with XenServer 5.5.900.  In fact, the packages
+# contain some files with identical names anyhow, so they will not
+# coexist, but adding an explicit Conflicts makes this conflict more
+# obvious.
+Conflicts: openvswitch
 
 %description
 The vswitch provides standard network bridging functions augmented with
@@ -38,12 +45,12 @@ traffic.
 %setup -q -n openvswitch-%{vswitch_version}
 
 %build
-./configure --prefix=%{_prefix} --localstatedir=%{_localstatedir} --with-l26=/lib/modules/%{xen_version}/build --enable-ssl %{build_number}
+./configure --prefix=/usr --sysconfdir=/etc --localstatedir=%{_localstatedir} --with-l26=/lib/modules/%{xen_version}/build --enable-ssl %{build_number}
 make %{_smp_mflags}
 
 %install
 rm -rf $RPM_BUILD_ROOT
-make install DESTDIR=$RPM_BUILD_ROOT prefix=%{_prefix}
+make install DESTDIR=$RPM_BUILD_ROOT
 install -d -m 755 $RPM_BUILD_ROOT/etc
 install -d -m 755 $RPM_BUILD_ROOT/etc/init.d
 install -m 755 xenserver/etc_init.d_vswitch \
@@ -60,45 +67,51 @@ install -m 755 xenserver/etc_profile.d_vswitch.sh \
 install -d -m 755 $RPM_BUILD_ROOT/etc/xapi.d/plugins
 install -m 755 xenserver/etc_xapi.d_plugins_vswitch-cfg-update \
          $RPM_BUILD_ROOT/etc/xapi.d/plugins/vswitch-cfg-update
-install -d -m 755 $RPM_BUILD_ROOT%{_prefix}/scripts
+install -d -m 755 $RPM_BUILD_ROOT/usr/share/vswitch/scripts
+install -m 644 vswitchd/vswitch.ovsschema \
+         $RPM_BUILD_ROOT/usr/share/vswitch/vswitch.ovsschema
 install -m 755 xenserver/opt_xensource_libexec_interface-reconfigure \
-             $RPM_BUILD_ROOT%{_prefix}/scripts/interface-reconfigure
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/interface-reconfigure
+install -m 644 xenserver/opt_xensource_libexec_InterfaceReconfigure.py \
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/InterfaceReconfigure.py
+install -m 644 xenserver/opt_xensource_libexec_InterfaceReconfigureBridge.py \
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/InterfaceReconfigureBridge.py
+install -m 644 xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py \
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/InterfaceReconfigureVswitch.py
 install -m 755 xenserver/etc_xensource_scripts_vif \
-             $RPM_BUILD_ROOT%{_prefix}/scripts/vif
-install -m 755 xenserver/root_vswitch_scripts_dump-vif-details \
-               $RPM_BUILD_ROOT%{_prefix}/scripts/dump-vif-details
-install -m 755 xenserver/root_vswitch_scripts_refresh-xs-network-uuids \
-               $RPM_BUILD_ROOT%{_prefix}/scripts/refresh-xs-network-uuids
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/vif
+install -m 755 xenserver/usr_share_vswitch_scripts_refresh-network-uuids \
+               $RPM_BUILD_ROOT/usr/share/vswitch/scripts/refresh-network-uuids
 install -m 755 xenserver/usr_sbin_xen-bugtool \
-             $RPM_BUILD_ROOT%{_prefix}/scripts/xen-bugtool
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/xen-bugtool
 install -m 755 xenserver/usr_sbin_brctl \
-             $RPM_BUILD_ROOT%{_prefix}/scripts/brctl
-install -m 755 xenserver/root_vswitch_scripts_sysconfig.template \
-         $RPM_BUILD_ROOT/root/vswitch/scripts/sysconfig.template
+             $RPM_BUILD_ROOT/usr/share/vswitch/scripts/brctl
+install -m 755 xenserver/usr_share_vswitch_scripts_sysconfig.template \
+         $RPM_BUILD_ROOT/usr/share/vswitch/scripts/sysconfig.template
 install -m 644 \
         xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py \
-               $RPM_BUILD_ROOT%{_prefix}/scripts/XSFeatureVSwitch.py
+               $RPM_BUILD_ROOT/usr/share/vswitch/scripts/XSFeatureVSwitch.py
 
-install -d -m 755 $RPM_BUILD_ROOT%{_prefix}/kernel_modules
-find datapath/linux-2.6 -name *.ko -exec install -m 755  \{\} $RPM_BUILD_ROOT%{_prefix}/kernel_modules/ \;
+install -d -m 755 $RPM_BUILD_ROOT/lib/modules/%{xen_version}/kernel/net/vswitch
+find datapath/linux-2.6 -name *.ko -exec install -m 755  \{\} $RPM_BUILD_ROOT/lib/modules/%{xen_version}/kernel/net/vswitch \;
 
 # Get rid of stuff we don't want to make RPM happy.
-rm -rf \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ezio-term \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-controller \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-discover \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-kill \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-pki \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-switchui \
-    $RPM_BUILD_ROOT/root/vswitch/bin/ovs-wdt \
-    $RPM_BUILD_ROOT/root/vswitch/bin/secchan \
-    $RPM_BUILD_ROOT/root/vswitch/sbin/ovs-monitor \
-    $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-controller.8 \
-    $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-discover.8 \
-    $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-kill.8 \
-    $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-pki.8 \
-    $RPM_BUILD_ROOT/root/vswitch/share/man/man8/secchan.8 \
-    $RPM_BUILD_ROOT/root/vswitch/share/openvswitch
+rm \
+    $RPM_BUILD_ROOT/usr/bin/ovs-controller \
+    $RPM_BUILD_ROOT/usr/bin/ovs-discover \
+    $RPM_BUILD_ROOT/usr/bin/ovs-kill \
+    $RPM_BUILD_ROOT/usr/bin/ovs-openflowd \
+    $RPM_BUILD_ROOT/usr/bin/ovs-pki \
+    $RPM_BUILD_ROOT/usr/bin/ovs-wdt \
+    $RPM_BUILD_ROOT/usr/sbin/ovs-monitor \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-discover.8 \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-kill.8 \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-openflowd.8 \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8
+rm -f $RPM_BUILD_ROOT/lib/modules/%{xen_version}/kernel/net/vswitch/veth_mod.ko
+
+install -d -m 755 $RPM_BUILD_ROOT/var/lib/openvswitch
 
 %clean
 rm -rf $RPM_BUILD_ROOT
@@ -110,40 +123,46 @@ if [ ! -f /etc/xensource-inventory ]; then
 fi
 
 if [ "$1" = "1" ]; then
-    if ! md5sum -c --status <<EOF
+    if md5sum -c --status <<EOF
+ca141d60061dcfdade73e75abc6529b5  /usr/sbin/brctl
 b8e9835862ef1a9cec2a3f477d26c989  /etc/xensource/scripts/vif
 51970ad613a3996d5997e18e44db47da  /opt/xensource/libexec/interface-reconfigure
 5654c8c36699fcc8744ca9cd5b855414  /usr/sbin/xen-bugtool
 EOF
     then
-        printf "\nThe original XenServer scripts replaced by this package\n"
-        printf "are different than expected.  This could lead to unexpected\n"
-        printf "behavior of your server.  Unless you are sure you know what\n"
-        printf "you are doing, it is highly recommended that you remove this\n"
-        printf "package immediately after the install completes, which\n"
-        printf "will restore the XenServer scripts that you were previously\n"
-        printf "using.\n\n"
-    fi
-    if test "`/usr/sbin/brctl --version`" != "bridge-utils, 1.1"; then
+        printf "\nVerified host scripts from XenServer 5.5.0.\n\n"
+    elif md5sum -c --status <<EOF
+ca141d60061dcfdade73e75abc6529b5  /usr/sbin/brctl
+b8e9835862ef1a9cec2a3f477d26c989  /etc/xensource/scripts/vif
+51970ad613a3996d5997e18e44db47da  /opt/xensource/libexec/interface-reconfigure
+f6519085c2fc5f7bc4eccc294ed62000  /usr/sbin/xen-bugtool
+EOF
+    then
+        printf "\nVerified host scripts from XenServer 5.5.0-24648p (Update 1)\n"
+        printf "or 5.5.0-25727p (Update 2).\n\n"
+    elif md5sum -c --status <<EOF
+ca141d60061dcfdade73e75abc6529b5  /usr/sbin/brctl
+b8e9835862ef1a9cec2a3f477d26c989  /etc/xensource/scripts/vif
+ce451d3c985fd1db6497a363f0d9dedb  /opt/xensource/libexec/interface-reconfigure
+2b53f500431fcba5276c896e9e4281b9  /usr/sbin/xen-bugtool
+EOF
+    then
+        printf "\nVerified host scripts from XenServer 5.5.900.\n\n"
+    else
 cat <<EOF
 
-/usr/sbin/brctl replaced by this package reports the following version:
-
-`/usr/sbin/brctl --version`
-
-The expected version was:
-
-bridge-utils, 1.1
-
-Unless you are sure you know what you are doing, it is highly recommended that
-you remove this package immediately after the install completes, which will
-restore the original /usr/sbin/brctl.
+The original XenServer scripts replaced by this package are not those
+of any supported version of XenServer.  This could lead to unexpected
+behavior of your server.  Unless you are sure you know what you are
+doing, it is highly recommended that you remove this package
+immediately after the install completes, which will restore the
+XenServer scripts that you were previously using.
 
 EOF
     fi
 fi
 
-if test ! -e /var/lib/openvswitch/dbcache; then
+if test ! -e /var/xapi/network.dbcache; then
     if test "$1" = 1; then
         printf "Creating xapi database cache...  "
     else
@@ -152,8 +171,7 @@ if test ! -e /var/lib/openvswitch/dbcache; then
         printf "Re-creating xapi database cache...  "
     fi
 
-    mkdir -p /var/lib/openvswitch
-    if /root/vswitch/scripts/interface-reconfigure rewrite; then
+    if /usr/share/vswitch/scripts/interface-reconfigure rewrite; then
         printf "done.\n"
     else
         printf "FAILED\n"
@@ -164,6 +182,9 @@ if test ! -e /var/lib/openvswitch/dbcache; then
     fi
 fi
 
+# Ensure that modprobe will find our modules.
+depmod %{xen_version}
+
 if grep -F net.ipv4.conf.all.arp_filter /etc/sysctl.conf >/dev/null 2>&1; then :; else
     cat >>/etc/sysctl.conf <<EOF
 # This works around an issue in xhad, which binds to a particular
@@ -179,12 +200,20 @@ net.ipv4.conf.all.arp_filter = 1
 EOF
 fi
 
-# Ensure ovs-vswitchd.conf exists
-touch /etc/ovs-vswitchd.conf
+if test ! -e /etc/ovs-vswitchd.conf.db; then
+    # Create ovs-vswitchd config database
+    ovsdb-tool -vANY:console:emer create /etc/ovs-vswitchd.conf.db \
+            /usr/share/vswitch/vswitch.ovsschema \
+
+    # Create initial table in config database
+    ovsdb-tool -vANY:console:emer transact /etc/ovs-vswitchd.conf.db \
+            '[{"op": "insert", "table": "Open_vSwitch", "row": {}}]' \
+            > /dev/null
+fi
 
 # Create default or update existing /etc/sysconfig/vswitch.
 SYSCONFIG=/etc/sysconfig/vswitch
-TEMPLATE=/root/vswitch/scripts/sysconfig.template
+TEMPLATE=/usr/share/vswitch/scripts/sysconfig.template
 if [ ! -e $SYSCONFIG ]; then
     cp $TEMPLATE $SYSCONFIG
 else
@@ -198,29 +227,32 @@ else
 fi
 
 # Replace XenServer files by our versions.
-mkdir -p %{_prefix}/xs-original \
+mkdir -p /usr/lib/vswitch/xs-original \
     || printf "Could not create script backup directory.\n"
 for f in \
     /opt/xensource/libexec/interface-reconfigure \
+    /opt/xensource/libexec/InterfaceReconfigure.py \
+    /opt/xensource/libexec/InterfaceReconfigureBridge.py \
+    /opt/xensource/libexec/InterfaceReconfigureVswitch.py \
     /etc/xensource/scripts/vif \
     /usr/sbin/xen-bugtool \
     /usr/sbin/brctl
 do
     s=$(basename "$f")
     t=$(readlink "$f")
-    if [ "$t" != "%{_prefix}/scripts/$s" ]; then
-        mv "$f" %{_prefix}/xs-original/ \
+    if [ -f "$f" ] && [ "$t" != "/usr/share/vswitch/scripts/$s" ]; then
+        mv "$f" /usr/lib/vswitch/xs-original/ \
             || printf "Could not save original XenServer $s script\n"
-        ln -s "%{_prefix}/scripts/$s" "$f" \
+        ln -s "/usr/share/vswitch/scripts/$s" "$f" \
             || printf "Could not link to vSwitch $s script\n"
     fi
 done
 
 # Install xsconsole plugin
 plugin=$(readlink /usr/lib/xsconsole/plugins-base/XSFeatureVSwitch.py)
-if [ "$plugin" != "/root/vswitch/scripts/XSFeatureVSwitch.py" ]; then
+if [ "$plugin" != "/usr/share/vswitch/scripts/XSFeatureVSwitch.py" ]; then
     rm -f /usr/lib/xsconsole/plugins-base/XSFeatureVSwitch.py
-    ln -s /root/vswitch/scripts/XSFeatureVSwitch.py /usr/lib/xsconsole/plugins-base/ || printf "Could not link to vSswitch xsconsole plugin.\n"
+    ln -s /usr/share/vswitch/scripts/XSFeatureVSwitch.py /usr/lib/xsconsole/plugins-base/ || printf "Could not link to vSswitch xsconsole plugin.\n"
 fi
 
 # Ensure all required services are set to run
@@ -232,6 +264,9 @@ for s in vswitch vswitch-xapi-update; do
     chkconfig $s on || printf "Could not enable $s init script."
 done
 
+# Configure system to use vswitch
+echo vswitch > /etc/xensource/network.conf
+
 if [ "$1" = "1" ]; then    # $1 = 2 for upgrade
     printf "\nYou MUST reboot the server NOW to complete the change to the\n"
     printf "the vSwitch.  Attempts to modify networking on the server\n"
@@ -266,25 +301,24 @@ if [ "$1" = "0" ]; then     # $1 = 1 for upgrade
         /usr/sbin/brctl
     do
         s=$(basename "$f")
-        if [ ! -f "%{_prefix}/xs-original/$s" ]; then
-            printf "Original XenServer $s script not present in %{_prefix}/xs-original\n"
+        if [ ! -f "/usr/lib/vswitch/xs-original/$s" ]; then
+            printf "Original XenServer $s script not present in /usr/lib/vswitch/xs-original\n"
             printf "Could not restore original XenServer script.\n"
         else
             (rm -f "$f" \
-                && mv "%{_prefix}/xs-original/$s" "$f") \
+                && mv "/usr/lib/vswitch/xs-original/$s" "$f") \
                 || printf "Could not restore original XenServer $s script.\n"
         fi
     done
 
-    find  %{_prefix} -type d -depth -exec rmdir \{\} \; \
-        || printf "Could not remove vSwitch install directory.\n"
-
-    # Remove all configuration and log files
-    rm -f /etc/ovs-vswitchd.conf
+    # Remove all configuration files
+    rm -f /etc/ovs-vswitchd.conf.db
     rm -f /etc/sysconfig/vswitch
-    rm -f /var/log/vswitch*
     rm -f /etc/ovs-vswitchd.cacert
-    rm -f /var/lib/openvswitch/dbcache
+    rm -f /var/xapi/network.dbcache
+
+    # Configure system to use bridge
+    echo bridge > /etc/xensource/network.conf
 
     printf "\nYou MUST reboot the server now to complete the change to\n"
     printf "standard Xen networking.  Attempts to modify networking on the\n"
@@ -300,34 +334,41 @@ fi
 /etc/xapi.d/plugins/vswitch-cfg-update
 /etc/logrotate.d/vswitch
 /etc/profile.d/vswitch.sh
-/root/vswitch/kernel_modules/brcompat_mod.ko
-/root/vswitch/kernel_modules/openvswitch_mod.ko
-/root/vswitch/kernel_modules/veth_mod.ko
-/root/vswitch/scripts/dump-vif-details
-/root/vswitch/scripts/refresh-xs-network-uuids
-/root/vswitch/scripts/interface-reconfigure
-/root/vswitch/scripts/vif
-/root/vswitch/scripts/xen-bugtool
-/root/vswitch/scripts/XSFeatureVSwitch.py
-/root/vswitch/scripts/brctl
-/root/vswitch/scripts/sysconfig.template
-# Following two files are generated automatically by rpm.  We don't
-# really need them and they won't be used on the XenServer, but there
-# isn't an obvious place to get rid of them since they are generated
-# after the install script runs.  Since they are small, we just
-# include them.
-/root/vswitch/scripts/XSFeatureVSwitch.pyc
-/root/vswitch/scripts/XSFeatureVSwitch.pyo
-/root/vswitch/sbin/ovs-brcompatd
-/root/vswitch/sbin/ovs-vswitchd
-/root/vswitch/bin/ovs-appctl
-/root/vswitch/bin/ovs-cfg-mod
-/root/vswitch/bin/ovs-dpctl
-/root/vswitch/bin/ovs-ofctl
-/root/vswitch/share/man/man5/ovs-vswitchd.conf.5
-/root/vswitch/share/man/man8/ovs-appctl.8
-/root/vswitch/share/man/man8/ovs-brcompatd.8
-/root/vswitch/share/man/man8/ovs-cfg-mod.8
-/root/vswitch/share/man/man8/ovs-dpctl.8
-/root/vswitch/share/man/man8/ovs-ofctl.8
-/root/vswitch/share/man/man8/ovs-vswitchd.8
+/lib/modules/%{xen_version}/kernel/net/vswitch/openvswitch_mod.ko
+/lib/modules/%{xen_version}/kernel/net/vswitch/brcompat_mod.ko
+%if %(echo '%{xen_version}'|awk -F"." '{if ($3>=18) print 1; else print 0;}')
+/lib/modules/%{xen_version}/kernel/net/vswitch/ip_gre_mod.ko
+%endif
+/usr/share/vswitch/scripts/refresh-network-uuids
+/usr/share/vswitch/scripts/interface-reconfigure
+/usr/share/vswitch/scripts/InterfaceReconfigure.py
+/usr/share/vswitch/scripts/InterfaceReconfigureBridge.py
+/usr/share/vswitch/scripts/InterfaceReconfigureVswitch.py
+/usr/share/vswitch/scripts/vif
+/usr/share/vswitch/scripts/xen-bugtool
+/usr/share/vswitch/scripts/XSFeatureVSwitch.py
+/usr/share/vswitch/scripts/brctl
+/usr/share/vswitch/scripts/sysconfig.template
+/usr/share/vswitch/vswitch.ovsschema
+/usr/sbin/ovs-brcompatd
+/usr/sbin/ovs-vswitchd
+/usr/sbin/ovsdb-server
+/usr/bin/ovs-appctl
+/usr/bin/ovs-dpctl
+/usr/bin/ovs-ofctl
+/usr/bin/ovs-vsctl
+/usr/bin/ovsdb-client
+/usr/bin/ovsdb-tool
+/usr/share/man/man1/ovsdb-client.1.gz
+/usr/share/man/man1/ovsdb-server.1.gz
+/usr/share/man/man1/ovsdb-tool.1.gz
+/usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+/usr/share/man/man8/ovs-appctl.8.gz
+/usr/share/man/man8/ovs-brcompatd.8.gz
+/usr/share/man/man8/ovs-dpctl.8.gz
+/usr/share/man/man8/ovs-ofctl.8.gz
+/usr/share/man/man8/ovs-vsctl.8.gz
+/usr/share/man/man8/ovs-vswitchd.8.gz
+/var/lib/openvswitch
+%exclude /usr/share/vswitch/scripts/*.pyc
+%exclude /usr/share/vswitch/scripts/*.pyo