X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fstream.c;h=4d894e7ede743e1640854a83d14589aae97a9eeb;hb=5136ce492c414f377f7be9ae32b259abb9f76580;hp=1a0ea7ba7466699ca80f1318e16a963e8ad1277c;hpb=c34b65c731a1b6dae014efe8895141e5b2fe758a;p=sliver-openvswitch.git diff --git a/lib/stream.c b/lib/stream.c index 1a0ea7ba7..4d894e7ed 100644 --- a/lib/stream.c +++ b/lib/stream.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. @@ -25,6 +25,7 @@ #include #include "coverage.h" #include "dynamic-string.h" +#include "fatal-signal.h" #include "flow.h" #include "ofp-print.h" #include "ofpbuf.h" @@ -34,10 +35,10 @@ #include "poll-loop.h" #include "random.h" #include "util.h" - -#define THIS_MODULE VLM_stream #include "vlog.h" +VLOG_DEFINE_THIS_MODULE(stream) + /* State of an active stream.*/ enum stream_state { SCS_CONNECTING, /* Underlying stream is not connected. */ @@ -48,11 +49,17 @@ enum stream_state { 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. */ @@ -66,7 +73,8 @@ check_stream_classes(void) struct stream_class *class = stream_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); @@ -94,7 +102,8 @@ check_stream_classes(void) /* 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) +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 @@ -105,6 +114,10 @@ stream_usage(const char *name, bool active, bool passive) 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"); } @@ -113,64 +126,130 @@ stream_usage(const char *name, bool active, bool 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) +/* Given 'name', a stream 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 +stream_lookup_class(const char *name, struct stream_class **classp) { size_t prefix_len; size_t i; - COVERAGE_INC(stream_open); check_stream_classes(); - *streamp = NULL; + *classp = NULL; prefix_len = strcspn(name, ":"); - if (prefix_len == strlen(name)) { + if (name[prefix_len] == '\0') { 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; + *classp = class; + return 0; } } return EAFNOSUPPORT; } +/* Returns 0 if 'name' is a stream name in the form "TYPE:ARGS" and TYPE is + * a supported stream type, otherwise EAFNOSUPPORT. */ int -stream_open_block(const char *name, struct stream **streamp) +stream_verify_name(const char *name) { + struct stream_class *class; + return stream_lookup_class(name, &class); +} + +/* 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) +{ + struct stream_class *class; struct stream *stream; + char *suffix_copy; int error; - error = stream_open(name, &stream); - while (error == EAGAIN) { - stream_connect_wait(stream); - poll_block(); - error = stream_connect(stream); + COVERAGE_INC(stream_open); + + /* Look up the class. */ + error = stream_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, &stream); + free(suffix_copy); + if (error) { + goto error; + } + + /* Success. */ + *streamp = stream; + return 0; + +error: + *streamp = NULL; + return error; +} + +/* Blocks until a previously started stream connection attempt succeeds or + * fails. 'error' should be the value returned by stream_open() and 'streamp' + * should point to the stream pointer set by stream_open(). Returns 0 if + * successful, otherwise a positive errno value other than EAGAIN or + * EINPROGRESS. If successful, leaves '*streamp' untouched; on error, closes + * '*streamp' and sets '*streamp' to null. + * + * Typical usage: + * error = stream_open_block(stream_open("tcp:1.2.3.4:5", &stream), &stream); + */ +int +stream_open_block(int error, struct stream **streamp) +{ + struct stream *stream = *streamp; + + fatal_signal_run(); + + if (!error) { + while ((error = stream_connect(stream)) == EAGAIN) { + stream_run(stream); + stream_run_wait(stream); + stream_connect_wait(stream); + poll_block(); + } assert(error != EINPROGRESS); } + if (error) { stream_close(stream); *streamp = NULL; @@ -312,6 +391,28 @@ stream_send(struct stream *stream, const void *buffer, size_t n) : (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) { @@ -348,42 +449,83 @@ 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) +/* Given 'name', a pstream 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 +pstream_lookup_class(const char *name, struct pstream_class **classp) { size_t prefix_len; size_t i; check_stream_classes(); - *pstreamp = NULL; + *classp = NULL; prefix_len = strcspn(name, ":"); - if (prefix_len == strlen(name)) { + if (name[prefix_len] == '\0') { 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; + *classp = class; + return 0; } } return EAFNOSUPPORT; } +/* Returns 0 if 'name' is a pstream name in the form "TYPE:ARGS" and TYPE is + * a supported pstream type, otherwise EAFNOSUPPORT. */ +int +pstream_verify_name(const char *name) +{ + struct pstream_class *class; + return pstream_lookup_class(name, &class); +} + +/* 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) +{ + struct pstream_class *class; + struct pstream *pstream; + char *suffix_copy; + int error; + + COVERAGE_INC(pstream_open); + + /* Look up the class. */ + error = pstream_lookup_class(name, &class); + if (!class) { + goto error; + } + + /* Call class's "open" function. */ + suffix_copy = xstrdup(strchr(name, ':') + 1); + error = class->listen(name, suffix_copy, &pstream); + free(suffix_copy); + if (error) { + goto error; + } + + /* Success. */ + *pstreamp = pstream; + return 0; + +error: + *pstreamp = NULL; + return error; +} + /* Returns the name that was used to open 'pstream'. The caller must not * modify or free the name. */ const char * @@ -422,6 +564,28 @@ pstream_accept(struct pstream *pstream, struct stream **new_stream) 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; + + fatal_signal_run(); + 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) { @@ -455,6 +619,7 @@ stream_init(struct stream *stream, struct stream_class *class, : SCS_DISCONNECTED); stream->error = connect_status; stream->name = xstrdup(name); + assert(stream->state != SCS_CONNECTING || class->connect); } void @@ -488,3 +653,133 @@ pstream_init(struct pstream *pstream, struct pstream_class *class, pstream->class = class; pstream->name = xstrdup(name); } + +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); + + return n; +} + +/* Like stream_open(), but for tcp streams the port defaults to + * 'default_tcp_port' if no port number is given and for SSL streams the port + * defaults to 'default_ssl_port' if no port number is given. */ +int +stream_open_with_default_ports(const char *name_, + uint16_t default_tcp_port, + uint16_t default_ssl_port, + struct stream **streamp) +{ + char *name; + int error; + + if (!strncmp(name_, "tcp:", 4) && count_fields(name_) < 3) { + name = xasprintf("%s:%d", name_, default_tcp_port); + } else if (!strncmp(name_, "ssl:", 4) && count_fields(name_) < 3) { + name = xasprintf("%s:%d", name_, default_ssl_port); + } else { + name = xstrdup(name_); + } + error = stream_open(name, streamp); + free(name); + + return error; +} + +/* Like pstream_open(), but for ptcp streams the port defaults to + * 'default_ptcp_port' if no port number is given and for passive SSL streams + * the port defaults to 'default_pssl_port' if no port number is given. */ +int +pstream_open_with_default_ports(const char *name_, + uint16_t default_ptcp_port, + uint16_t default_pssl_port, + struct pstream **pstreamp) +{ + char *name; + int error; + + if (!strncmp(name_, "ptcp:", 5) && count_fields(name_) < 2) { + name = xasprintf("%s%d", name_, default_ptcp_port); + } else if (!strncmp(name_, "pssl:", 5) && count_fields(name_) < 2) { + name = xasprintf("%s%d", name_, default_pssl_port); + } else { + name = xstrdup(name_); + } + error = pstream_open(name, pstreamp); + free(name); + + return error; +} + +/* Attempts to guess the content type of a stream whose first few bytes were + * the 'size' bytes of 'data'. */ +static enum stream_content_type +stream_guess_content(const uint8_t *data, size_t size) +{ + if (size >= 2) { +#define PAIR(A, B) (((A) << 8) | (B)) + switch (PAIR(data[0], data[1])) { + case PAIR(0x16, 0x03): /* Handshake, version 3. */ + return STREAM_SSL; + case PAIR('{', '"'): + return STREAM_JSONRPC; + case PAIR(OFP_VERSION, OFPT_HELLO): + return STREAM_OPENFLOW; + } + } + + return STREAM_UNKNOWN; +} + +/* Returns a string represenation of 'type'. */ +static const char * +stream_content_type_to_string(enum stream_content_type type) +{ + switch (type) { + case STREAM_UNKNOWN: + default: + return "unknown"; + + case STREAM_JSONRPC: + return "JSON-RPC"; + + case STREAM_OPENFLOW: + return "OpenFlow"; + + case STREAM_SSL: + return "SSL"; + } +} + +/* Attempts to guess the content type of a stream whose first few bytes were + * the 'size' bytes of 'data'. If this is done successfully, and the guessed + * content type is other than 'expected_type', then log a message in vlog + * module 'module', naming 'stream_name' as the source, explaining what + * content was expected and what was actually received. */ +void +stream_report_content(const void *data, size_t size, + enum stream_content_type expected_type, + enum vlog_module module, const char *stream_name) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + enum stream_content_type actual_type; + + actual_type = stream_guess_content(data, size); + if (actual_type != expected_type && actual_type != STREAM_UNKNOWN) { + vlog_rate_limit(module, VLL_WARN, &rl, + "%s: received %s data on %s channel", + stream_name, + stream_content_type_to_string(expected_type), + stream_content_type_to_string(actual_type)); + } +}