[ovs-dev] [PATCH v2 1/3] Windows: Local named pipe implementation
Paul Boca
pboca at cloudbasesolutions.com
Wed Jul 13 08:01:51 UTC 2016
Looks good to me.
Acked-by: Paul Boca <pboca at cloudbasesolutions.com>
> -----Original Message-----
> From: dev [mailto:dev-bounces at openvswitch.org] On Behalf Of Alin Serdean
> Sent: Wednesday, July 13, 2016 6:33 AM
> To: dev at openvswitch.org
> Subject: [ovs-dev] [PATCH v2 1/3] Windows: Local named pipe
> implementation
>
> Currently in the case of command line arguments punix/unix, on Windows
> we create a file, write a TCP port number to connect. This is a security
> concern.
>
> This patch adds support for the command line arguments punix/unix trying
> to mimic AF_UNIX behind a local named pipe.
>
> Signed-off-by: Alin Gabriel Serdean <aserdean at cloudbasesolutions.com>
> ---
> v2: Address comments, fix leaks.
> ---
> lib/stream-windows.c | 587
> +++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 587 insertions(+)
> create mode 100644 lib/stream-windows.c
>
> diff --git a/lib/stream-windows.c b/lib/stream-windows.c
> new file mode 100644
> index 0000000..97e6f02
> --- /dev/null
> +++ b/lib/stream-windows.c
> @@ -0,0 +1,587 @@
> +/*
> + * Copyright (c) 2016 Cloudbase Solutions Srl
> + *
> + * 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 <inttypes.h>
> +#include <netdb.h>
> +#include <poll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <sys/un.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <strsafe.h>
> +#include <unistd.h>
> +#include "packets.h"
> +#include "poll-loop.h"
> +#include "socket-util.h"
> +#include "dirs.h"
> +#include "util.h"
> +#include "stream-provider.h"
> +#include "openvswitch/vlog.h"
> +
> +VLOG_DEFINE_THIS_MODULE(stream_windows);
> +
> +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
> +
> +static void maybe_unlink_and_free(char *path);
> +
> +/* Buffer size suggested at the creation of the named pipe for reading and
> + * and writing operations */
> +#define BUFSIZE 65000
> +
> +/* Default local prefix of a named pipe */
> +#define LOCAL_PREFIX "\\\\.\\pipe\\"
> +
> +/* This function has the purpose to remove all the slashes received in s */
> +char*
> +remove_slashes(char *s) {
> + char *p1, *p2;
> + p1 = p2 = s;
> +
> + while (*p1) {
> + if ((*p1) == '\\' || (*p1) == '/') {
> + p1++;
> + } else {
> + *p2 = *p1;
> + p2++;
> + p1++;
> + }
> + }
> + *p2 = '\0';
> + return s;
> +}
> +
> +/* Active named pipe descriptor stream. */
> +struct windows_stream
> +{
> + struct stream stream;
> + HANDLE fd;
> + /* Overlapped operations used for reading/writing */
> + OVERLAPPED read;
> + OVERLAPPED write;
> + /* Flag to check if a reading/writing is pending*/
> + bool read_pending;
> + bool write_pending;
> + /* Flag to check if fd is a server HANDLE. In the case of a server handle
> + * we have to issue a disconnect before closing the actual handle */
> + bool server;
> + bool retry_connect;
> + char *pipe_path;
> +};
> +
> +static struct windows_stream *
> +stream_windows_cast(struct stream *stream)
> +{
> + stream_assert_class(stream, &windows_stream_class);
> + return CONTAINER_OF(stream, struct windows_stream, stream);
> +}
> +
> +HANDLE
> +create_snpipe(char *path) {
> + return CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
> + OPEN_EXISTING,
> + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
> + FILE_FLAG_NO_BUFFERING,
> + NULL);
> +}
> +
> +/* Active named pipe open */
> +static int
> +windows_open(const char *name, char *suffix, struct stream **streamp,
> + uint8_t dscp OVS_UNUSED)
> +{
> + char *connect_path;
> + HANDLE npipe;
> + DWORD mode = PIPE_READMODE_BYTE;
> + char* path;
> + FILE* file;
> + bool retry = false;
> + /* If the path does not contain a ':', assume it is relative to
> + * OVS_RUNDIR. */
> + if (!strchr(suffix, ':')) {
> + path = xasprintf("%s/%s", ovs_rundir(), suffix);
> + } else {
> + path = xstrdup(suffix);
> + }
> +
> + /* In the punix/unix argument the assumption is that there is a file
> + * created in the path (name) */
> + file = fopen(path, "r");
> + if (!file) {
> + free(path);
> + VLOG_DBG_RL(&rl, "%s: could not open %s (%s)", name, suffix,
> + ovs_strerror(errno));
> + return ENOENT;
> + } else {
> + fclose(file);
> + }
> +
> + /* Valid pipe names do not have slashes. The assumption is the named
> pipe
> + * was created on the same path with the slash stripped path and the
> + * default prefix \\.\pipe\ appended.
> + * Strip the slashed from the parameter name and append the default
> prefix
> + */
> + connect_path = xasprintf("%s%s", LOCAL_PREFIX, remove_slashes(path));
> + free(path);
> +
> + /* Try to connect to the named pipe. In the case all pipe instances are
> + * busy we set the retry flag to true and we retry again during the
> + * connect function. Use overlapped flag and no buffering to ensure
> + * an asynchronous operations */
> + npipe = create_snpipe(connect_path);
> +
> + if (npipe == INVALID_HANDLE_VALUE && GetLastError() ==
> ERROR_PIPE_BUSY) {
> + retry = true;
> + }
> +
> + if (!retry && npipe == INVALID_HANDLE_VALUE) {
> + VLOG_ERR_RL(&rl, "Could not connect to named pipe: %s",
> + ovs_lasterror_to_string());
> + free(connect_path);
> + return ENOENT;
> + }
> + if (!retry && !SetNamedPipeHandleState(npipe, &mode, NULL, NULL)) {
> + VLOG_ERR_RL(&rl, "Could not set named pipe options: %s",
> + ovs_lasterror_to_string());
> + free(connect_path);
> + CloseHandle(npipe);
> + return ENOENT;
> + }
> + struct windows_stream *s = xmalloc(sizeof *s);
> + stream_init(&s->stream, &windows_stream_class, 0, name);
> + s->pipe_path = connect_path;
> + s->fd = npipe;
> + /* This is a active stream */
> + s->server = false;
> + /* Create events for reading and writing to be signaled later */
> + memset(&s->read, 0, sizeof(s->read));
> + memset(&s->write, 0, sizeof(s->write));
> + s->read.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + s->write.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + /* Initial read and write operations are not pending */
> + s->read_pending = false;
> + s->write_pending = false;
> + s->retry_connect = retry;
> + *streamp = &s->stream;
> + return 0;
> +}
> +
> +/* Active named pipe close */
> +static void
> +windows_close(struct stream *stream)
> +{
> + struct windows_stream *s = stream_windows_cast(stream);
> + /* Disconnect the named pipe in case it was a passive stream at first */
> + if (s->server) {
> + DisconnectNamedPipe(s->fd);
> + }
> + CloseHandle(s->fd);
> + CloseHandle(s->read.hEvent);
> + CloseHandle(s->write.hEvent);
> + if (s->pipe_path) {
> + free(s->pipe_path);
> + }
> + free(s);
> +}
> +
> +/* Active named pipe connect */
> +static int
> +windows_connect(struct stream *stream)
> +{
> + /* Stub function for connect. We already to the connect in the open
> + * function */
> + struct windows_stream *s = stream_windows_cast(stream);
> +
> + if (!s->retry_connect) {
> + return 0;
> + } else {
> + HANDLE npipe;
> + npipe = create_snpipe(s->pipe_path);
> + if (npipe == INVALID_HANDLE_VALUE) {
> + if (GetLastError() == ERROR_PIPE_BUSY) {
> + return EAGAIN;
> + } else {
> + s->retry_connect = false;
> + return ENOENT;
> + }
> + }
> + s->retry_connect = false;
> + s->fd = npipe;
> + return 0;
> + }
> +}
> +
> +/* Active named pipe receive */
> +static ssize_t
> +windows_recv(struct stream *stream, void *buffer, size_t n)
> +{
> + struct windows_stream *s = stream_windows_cast(stream);
> + ssize_t retval = 0;
> + boolean result = false;
> + DWORD last_error = 0;
> + LPOVERLAPPED ov = NULL;
> + ov = &s->read;
> +
> + /* If the read operation was pending we verify its result */
> + if (s->read_pending) {
> + if (!GetOverlappedResult(s->fd, ov, &(DWORD)retval, FALSE)) {
> + last_error = GetLastError();
> + if (last_error == ERROR_IO_INCOMPLETE) {
> + /* If the operation is still pending retry again */
> + s->read_pending = true;
> + return -EAGAIN;
> + } else if (last_error == ERROR_PIPE_NOT_CONNECTED ||
> + last_error == ERROR_BAD_PIPE ||
> + last_error == ERROR_NO_DATA ||
> + last_error == ERROR_BROKEN_PIPE) {
> + /* If the pipe was disconnected return 0 */
> + return 0;
> + } else {
> + VLOG_ERR_RL(&rl, "Could not receive data on named pipe. Last "
> + "error: %s", ovs_lasterror_to_string());
> + return -EINVAL;
> + }
> + }
> + s->read_pending = false;
> + return retval;
> + }
> +
> + result = ReadFile(s->fd, buffer, n, &(DWORD)retval, ov);
> +
> + if (!result && GetLastError() == ERROR_IO_PENDING) {
> + /* Pend the read operation */
> + s->read_pending = true;
> + return -EAGAIN;
> + } else if (!result) {
> + last_error = GetLastError();
> + if (last_error == ERROR_PIPE_NOT_CONNECTED ||
> + last_error == ERROR_BAD_PIPE ||
> + last_error == ERROR_NO_DATA ||
> + last_error == ERROR_BROKEN_PIPE) {
> + /* If the pipe was disconnected return 0 */
> + return 0;
> + }
> + VLOG_ERR_RL(&rl, "Could not receive data synchronous on named
> pipe."
> + "Last error: %s", ovs_lasterror_to_string());
> + return -EINVAL;
> + }
> +
> + return retval;
> +}
> +
> +/* Active named pipe send */
> +static ssize_t
> +windows_send(struct stream *stream, const void *buffer, size_t n)
> +{
> + struct windows_stream *s = stream_windows_cast(stream);
> + ssize_t retval = 0;
> + boolean result = false;
> + DWORD last_error = 0;
> + LPOVERLAPPED ov = NULL;
> + ov = &s->write;
> +
> + /* If the send operation was pending we verify the result */
> + if (s->write_pending) {
> + if (!GetOverlappedResult(s->fd, ov, &(DWORD)retval, FALSE)) {
> + last_error = GetLastError();
> + if (last_error == ERROR_IO_INCOMPLETE) {
> + /* If the operation is still pending retry again */
> + s->write_pending = true;
> + return -EAGAIN;
> + } else if (last_error == ERROR_PIPE_NOT_CONNECTED ||
> + last_error == ERROR_BAD_PIPE ||
> + last_error == ERROR_NO_DATA ||
> + last_error == ERROR_BROKEN_PIPE) {
> + /* If the pipe was disconnected return connection reset */
> + return -WSAECONNRESET;
> + } else {
> + VLOG_ERR_RL(&rl, "Could not send data on named pipe. Last "
> + "error: %s", ovs_lasterror_to_string());
> + return -EINVAL;
> + }
> + }
> + s->write_pending = false;
> + return retval;
> + }
> +
> + result = WriteFile(s->fd, buffer, n, &(DWORD)retval, ov);
> + last_error = GetLastError();
> + if (!result && GetLastError() == ERROR_IO_PENDING) {
> + /* Pend the send operation */
> + s->write_pending = true;
> + return -EAGAIN;
> + } else if (!result && (last_error == ERROR_PIPE_NOT_CONNECTED ||
> + last_error == ERROR_BAD_PIPE ||
> + last_error == ERROR_NO_DATA ||
> + last_error == ERROR_BROKEN_PIPE)) {
> + /* If the pipe was disconnected return connection reset */
> + return -WSAECONNRESET;
> + } else if (!result) {
> + VLOG_ERR_RL(&rl, "Could not send data on synchronous named pipe.
> Last "
> + "error: %s", ovs_lasterror_to_string());
> + return -EINVAL;
> + }
> + return (retval > 0 ? retval : -EAGAIN);
> +}
> +
> +/* Active named pipe wait */
> +static void
> +windows_wait(struct stream *stream, enum stream_wait_type wait)
> +{
> + struct windows_stream *s = stream_windows_cast(stream);
> + switch (wait) {
> + case STREAM_SEND:
> + poll_wevent_wait(s->write.hEvent);
> + break;
> +
> + case STREAM_CONNECT:
> + poll_immediate_wake();
> + break;
> +
> + case STREAM_RECV:
> + poll_wevent_wait(s->read.hEvent);
> + break;
> +
> + default:
> + OVS_NOT_REACHED();
> + }
> +}
> +
> +/* Passive named pipe descriptor stream. */
> +const struct stream_class windows_stream_class = {
> + "unix", /* name */
> + false, /* needs_probes */
> + windows_open, /* open */
> + windows_close, /* close */
> + windows_connect, /* connect */
> + windows_recv, /* recv */
> + windows_send, /* send */
> + NULL, /* run */
> + NULL, /* run_wait */
> + windows_wait, /* wait */
> +};
> +
> +struct pwindows_pstream
> +{
> + struct pstream pstream;
> + HANDLE fd;
> + /* Unlink path to be deleted during close */
> + char* unlink_path;
> + /* Overlapped operation used for connect */
> + OVERLAPPED connect;
> + /* Flag to check if an operation is pending */
> + bool pending;
> + /* Named pipe path used for creation */
> + char* pipe_path;
> +};
> +
> +const struct pstream_class pwindows_pstream_class;
> +
> +static struct pwindows_pstream *
> +pwindows_pstream_cast(struct pstream *pstream)
> +{
> + pstream_assert_class(pstream, &pwindows_pstream_class);
> + return CONTAINER_OF(pstream, struct pwindows_pstream, pstream);
> +}
> +
> +/* Create a named pipe with read/write access, overlapped, message mode
> for
> + * writing, byte mode for reading with a maximum of 64 active instances */
> +HANDLE
> +create_pnpipe(char *name)
> +{
> + SECURITY_ATTRIBUTES sa;
> + sa.nLength = sizeof(sa);
> + sa.lpSecurityDescriptor = NULL;
> + sa.bInheritHandle = TRUE;
> + if (strlen(name) > 256) {
> + VLOG_ERR_RL(&rl, "Named pipe name too long. Creation terminated");
> + return INVALID_HANDLE_VALUE;
> + }
> + return CreateNamedPipe(name, PIPE_ACCESS_DUPLEX |
> FILE_FLAG_OVERLAPPED,
> + PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT,
> + 64, BUFSIZE, BUFSIZE, 0, &sa);
> +}
> +
> +/* Passive named pipe connect. This function creates a new named pipe
> and
> + * passes the old handle to the active stream */
> +static int
> +pwindows_accept(struct pstream *pstream, struct stream **new_streamp)
> +{
> + struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
> + DWORD last_error = 0;
> + DWORD cbRet;
> + HANDLE npipe;
> +
> + /* If the connect operation was pending verify the result */
> + if (p->pending) {
> + if (!GetOverlappedResult(p->fd, &p->connect, &cbRet, FALSE)) {
> + last_error = GetLastError();
> + if (last_error == ERROR_IO_INCOMPLETE) {
> + /* If the operation is still pending retry again */
> + p->pending = true;
> + return EAGAIN;
> + } else {
> + VLOG_ERR_RL(&rl, "Could not connect named pipe. Last "
> + "error: %s", ovs_lasterror_to_string());
> + return EINVAL;
> + }
> + }
> + p->pending = false;
> + }
> +
> + if (!p->pending && !ConnectNamedPipe(p->fd, &p->connect)) {
> + last_error = GetLastError();
> + if (last_error == ERROR_IO_PENDING) {
> + /* Pend the connect operation */
> + p->pending = true;
> + return EAGAIN;
> + } else if (last_error != ERROR_PIPE_CONNECTED) {
> + VLOG_ERR_RL(&rl, "Could not connect synchronous named pipe. Last
> "
> + "error: %s", ovs_lasterror_to_string());
> + return EINVAL;
> + } else {
> + /* If the pipe is connected signal an event */
> + SetEvent(&p->connect.hEvent);
> + }
> + }
> +
> + npipe = create_pnpipe(p->pipe_path);
> + if (npipe == INVALID_HANDLE_VALUE) {
> + VLOG_ERR_RL(&rl, "Could not create a new named pipe after connect.
> ",
> + ovs_lasterror_to_string());
> + return ENOENT;
> + }
> +
> + /* Give the handle p->fd to the new created active stream and specify it
> + * was created by an active stream */
> + struct windows_stream *p_temp = xmalloc(sizeof *p_temp);
> + stream_init(&p_temp->stream, &windows_stream_class, 0, "unix");
> + p_temp->fd = p->fd;
> + /* Specify it was created by a passive stream */
> + p_temp->server = true;
> + /* Create events for read/write operations */
> + memset(&p_temp->read, 0, sizeof(p_temp->read));
> + memset(&p_temp->write, 0, sizeof(p_temp->write));
> + p_temp->read.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + p_temp->write.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + p_temp->read_pending = false;
> + p_temp->write_pending = false;
> + p_temp->retry_connect = false;
> + p_temp->pipe_path = NULL;
> + *new_streamp = &p_temp->stream;
> +
> + /* The passive handle p->fd will be the new created handle */
> + p->fd = npipe;
> + memset(&p->connect, 0, sizeof(p->connect));
> + p->connect.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + p->pending = false;
> + return 0;
> +}
> +
> +/* Passive named pipe close */
> +static void
> +pwindows_close(struct pstream *pstream)
> +{
> + struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
> + DisconnectNamedPipe(p->fd);
> + CloseHandle(p->fd);
> + CloseHandle(p->connect.hEvent);
> + maybe_unlink_and_free(p->unlink_path);
> + free(p->pipe_path);
> + free(p);
> +}
> +
> +/* Passive named pipe wait */
> +void
> +pwindows_wait(struct pstream *pstream)
> +{
> + struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
> + poll_wevent_wait(p->connect.hEvent);
> +}
> +
> +/* Passive named pipe */
> +static int
> +pwindows_open(const char *name OVS_UNUSED, char *suffix,
> + struct pstream **pstreamp, uint8_t dscp OVS_UNUSED)
> +{
> + char *bind_path;
> + int error;
> + HANDLE npipe;
> + char* orig_path;
> +
> + char* path;
> + if (!strchr(suffix, ':')) {
> + path = xasprintf("%s/%s", ovs_rundir(), suffix);
> + } else {
> + path = xstrdup(suffix);
> + }
> +
> + /* Try to create a file under the path location */
> + FILE *file = fopen(path, "w");
> + if (!file) {
> + free(path);
> + error = errno;
> + VLOG_DBG_RL(&rl, "could not open %s (%s)", path,
> ovs_strerror(error));
> + return error;
> + } else {
> + fclose(file);
> + }
> +
> + orig_path = xstrdup(path);
> + /* Strip slashes from path and create a named pipe using that name */
> + bind_path = xasprintf("%s%s", LOCAL_PREFIX, remove_slashes(path));
> + free(path);
> +
> + npipe = create_pnpipe(bind_path);
> +
> + if (npipe == INVALID_HANDLE_VALUE) {
> + VLOG_ERR_RL(&rl, "Could not create named pipe. Last error: %s",
> + ovs_lasterror_to_string());
> + return ENOENT;
> + }
> +
> + struct pwindows_pstream *p = xmalloc(sizeof *p);
> + pstream_init(&p->pstream, &pwindows_pstream_class, name);
> + p->fd = npipe;
> + p->unlink_path = orig_path;
> + memset(&p->connect, 0, sizeof(p->connect));
> + p->connect.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
> + p->pending = false;
> + p->pipe_path = bind_path;
> + *pstreamp = &p->pstream;
> + return 0;
> +}
> +
> +const struct pstream_class pwindows_pstream_class = {
> + "punix",
> + false, /* probes */
> + pwindows_open, /* open */
> + pwindows_close, /* close */
> + pwindows_accept, /* accept */
> + pwindows_wait, /* wait */
> +};
> +
> +/* Helper functions. */
> +static void
> +maybe_unlink_and_free(char *path)
> +{
> + if (path) {
> + fatal_signal_unlink_file_now(path);
> + free(path);
> + }
> +}
> --
> 1.9.5.msysgit.0
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
More information about the dev
mailing list