From patchwork Tue Aug 2 17:37:54 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alin Serdean X-Patchwork-Id: 655036 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 3s3k2L6LSJz9tFj for ; Wed, 3 Aug 2016 03:38:34 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id E5481108C5; Tue, 2 Aug 2016 10:38:27 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e4.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id 82072108C5 for ; Tue, 2 Aug 2016 10:38:26 -0700 (PDT) Received: from bar5.cudamail.com (unknown [192.168.21.12]) by mx1e4.cudamail.com (Postfix) with ESMTPS id EF1201E0418 for ; Tue, 2 Aug 2016 11:38:25 -0600 (MDT) X-ASG-Debug-ID: 1470159503-09eadd39d14e2760001-byXFYA Received: from mx1-pf2.cudamail.com ([192.168.24.2]) by bar5.cudamail.com with ESMTP id luvTIMQS16Ta8yYL (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Tue, 02 Aug 2016 11:38:23 -0600 (MDT) X-Barracuda-Envelope-From: aserdean@cloudbasesolutions.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.2 Received: from unknown (HELO cbssmtp1.cloudbase.local) (91.232.152.5) by mx1-pf2.cudamail.com with SMTP; 2 Aug 2016 17:38:22 -0000 Received-SPF: pass (mx1-pf2.cudamail.com: SPF record at cloudbasesolutions.com designates 91.232.152.5 as permitted sender) X-Barracuda-Apparent-Source-IP: 91.232.152.5 X-Barracuda-RBL-IP: 91.232.152.5 Received: from localhost (localhost [127.0.0.1]) by cbssmtp1.cloudbase.local (Postfix) with ESMTP id 7DB6341610 for ; Tue, 2 Aug 2016 20:38:20 +0300 (EEST) X-Virus-Scanned: amavisd-new at cloudbasesolutions.com Received: from cbssmtp1.cloudbase.local ([127.0.0.1]) by localhost (cbssmtp1.cloudbase.local [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 8kFHxXG3Vxmw for ; Tue, 2 Aug 2016 20:37:58 +0300 (EEST) Received: from CBSEX1.cloudbase.local (unknown [10.77.78.3]) by cbssmtp1.cloudbase.local (Postfix) with ESMTP id F136741608 for ; Tue, 2 Aug 2016 20:37:55 +0300 (EEST) Received: from CBSEX1.cloudbase.local ([10.77.78.3]) by CBSEX1.cloudbase.local ([10.77.78.3]) with mapi id 14.03.0301.000; Tue, 2 Aug 2016 19:37:55 +0200 X-CudaMail-Envelope-Sender: aserdean@cloudbasesolutions.com From: Alin Serdean To: "dev@openvswitch.org" X-CudaMail-MID: CM-E2-801049741 X-CudaMail-DTE: 080216 X-CudaMail-Originating-IP: 91.232.152.5 Thread-Topic: [PATCH v6] Windows: Local named pipe implementation X-ASG-Orig-Subj: [##CM-E2-801049741##][PATCH v6] Windows: Local named pipe implementation Thread-Index: AQHR7OSZ9n1X1cxmSE2aLX2Z0bvzPA== Date: Tue, 2 Aug 2016 17:37:54 +0000 Message-ID: <1470159462-7776-1-git-send-email-aserdean@cloudbasesolutions.com> Accept-Language: en-US, it-IT Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [10.77.78.1] MIME-Version: 1.0 X-GBUdb-Analysis: 0, 91.232.152.5, Ugly c=0.303425 p=-0.473684 Source Normal X-MessageSniffer-Rules: 0-0-0-32767-c X-Barracuda-Connect: UNKNOWN[192.168.24.2] X-Barracuda-Start-Time: 1470159503 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 X-Barracuda-Spam-Score: 0.10 X-Barracuda-Spam-Status: No, SCORE=0.10 using global scores of TAG_LEVEL=3.5 QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0 tests=RDNS_NONE X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.31697 Rule breakdown below pts rule name description ---- ---------------------- -------------------------------------------------- 0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS Subject: [ovs-dev] [PATCH v6] Windows: Local named pipe implementation X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@openvswitch.org Sender: "dev" 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. This patch drops the TCP socket implementation behind command line arguments punix/unix and switches to the local named pipe implementation. Since we do not write anything to the file created by the punix/unix arguments, switch tests to plain file existence. Man pages and code comments have been updated. Signed-off-by: Alin Gabriel Serdean Acked-by: Paul Boca --- v6: resubmit for patchwork v5: fix coding style v4: improve spelling in man pages v3: squash commits update documentation and code comments v2: Address comments, fix handle leaks. --- lib/automake.mk | 1 + lib/stream-tcp.c | 115 ---------- lib/stream-windows.c | 581 +++++++++++++++++++++++++++++++++++++++++++++++ lib/unixctl.c | 5 +- lib/unixctl.man | 10 +- lib/vconn-active.man | 3 +- ovsdb/remote-active.man | 4 +- ovsdb/remote-passive.man | 4 +- tests/ovsdb-server.at | 6 +- 9 files changed, 599 insertions(+), 130 deletions(-) create mode 100644 lib/stream-windows.c diff --git a/lib/automake.mk b/lib/automake.mk index 646306d..97c83e9 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -302,6 +302,7 @@ lib_libopenvswitch_la_SOURCES += \ lib/latch-windows.c \ lib/route-table-stub.c \ lib/if-notifier-stub.c \ + lib/stream-windows.c \ lib/strsep.c else lib_libopenvswitch_la_SOURCES += \ diff --git a/lib/stream-tcp.c b/lib/stream-tcp.c index 2b57ca7..1749fad 100644 --- a/lib/stream-tcp.c +++ b/lib/stream-tcp.c @@ -74,64 +74,6 @@ const struct stream_class tcp_stream_class = { NULL, /* run_wait */ NULL, /* wait */ }; - -#ifdef _WIN32 -#include "dirs.h" - -static int -windows_open(const char *name, char *suffix, struct stream **streamp, - uint8_t dscp) -{ - int error, port; - FILE *file; - char *suffix_new, *path; - - /* 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); - } - - file = fopen(path, "r"); - if (!file) { - error = errno; - VLOG_DBG("%s: could not open %s (%s)", name, suffix, - ovs_strerror(error)); - return error; - } - - error = fscanf(file, "%d", &port); - if (error != 1) { - VLOG_ERR("failed to read port from %s", suffix); - fclose(file); - return EINVAL; - } - fclose(file); - - suffix_new = xasprintf("127.0.0.1:%d", port); - - error = tcp_open(name, suffix_new, streamp, dscp); - - free(suffix_new); - free(path); - return error; -} - -const struct stream_class windows_stream_class = { - "unix", /* name */ - false, /* needs_probes */ - windows_open, /* open */ - NULL, /* close */ - NULL, /* connect */ - NULL, /* recv */ - NULL, /* send */ - NULL, /* run */ - NULL, /* run_wait */ - NULL, /* wait */ -}; -#endif /* Passive TCP. */ @@ -198,60 +140,3 @@ const struct pstream_class ptcp_pstream_class = { NULL, NULL, }; - -#ifdef _WIN32 -static int -pwindows_open(const char *name, char *suffix, struct pstream **pstreamp, - uint8_t dscp) -{ - int error; - char *suffix_new, *path; - FILE *file; - struct pstream *listener; - - suffix_new = xstrdup("0:127.0.0.1"); - - /* 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); - } - - error = new_pstream(suffix_new, name, pstreamp, dscp, path, false); - if (error) { - goto exit; - } - listener = *pstreamp; - - file = fopen(path, "w"); - if (!file) { - error = errno; - VLOG_DBG("could not open %s (%s)", path, ovs_strerror(error)); - goto exit; - } - - fprintf(file, "%d\n", ntohs(listener->bound_port)); - if (fflush(file) == EOF) { - error = EIO; - VLOG_ERR("write failed for %s", path); - fclose(file); - goto exit; - } - fclose(file); - -exit: - free(suffix_new); - return error; -} - -const struct pstream_class pwindows_pstream_class = { - "punix", - false, - pwindows_open, - NULL, - NULL, - NULL, -}; -#endif diff --git a/lib/stream-windows.c b/lib/stream-windows.c new file mode 100644 index 0000000..e0fe012 --- /dev/null +++ b/lib/stream-windows.c @@ -0,0 +1,581 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include "poll-loop.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); + +/* Suggested buffer size at the creation of the named pipe for reading and + * and writing operations. */ +#define BUFSIZE 65000 + +/* Default prefix of a local named pipe. */ +#define LOCAL_PREFIX "\\\\.\\pipe\\" + +/* This function has the purpose to remove all the slashes received in s. */ +static 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. */ +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 operation 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); +} + +static 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 case of "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 that the named + * pipe was created with the name "path", with slashes removed and the + * default prefix \\.\pipe\ appended. + * Strip the slashes 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 case all pipe instances are + * busy we set the retry flag to true and retry again during the + * connect function. Use overlapped flag and file no buffering to ensure + * 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 an 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 created from a passive stream. + */ + 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) +{ + 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) { + /* Mark the read operation as pending. */ + 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) { + /* Mark the send operation as pending. */ + 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. */ +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; + /* String used to create the named pipe. */ + 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 and with a maximum of 64 active instances. */ +static 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."); + 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) { + /* Mark the accept operation as pending. */ + 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. */ +static 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 newly created + * string. */ + 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); + } +} diff --git a/lib/unixctl.c b/lib/unixctl.c index 5e5d26c..57d6577 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -195,7 +195,7 @@ unixctl_command_reply_error(struct unixctl_conn *conn, const char *error) * - An absolute path (starting with '/') that gives the exact name of * the Unix domain socket to listen on. * - * For Windows, a kernel assigned TCP port is used and written in 'path' + * For Windows, a local named pipe is used. A file is created in 'path' * which may be: * * - NULL, in which case /.ctl is used. @@ -442,7 +442,8 @@ unixctl_server_destroy(struct unixctl_server *server) * be the name of a unixctl server socket. If it does not start with '/', it * will be prefixed with the rundir (e.g. /usr/local/var/run/openvswitch). * - * On Windows, connects to a localhost TCP port as written inside 'path'. + * On Windows, connects to a local named pipe. A file which resides in + * 'path' is used to mimic the behavior of a Unix domain socket. * 'path' should be an absolute path of the file. * * Returns 0 if successful, otherwise a positive errno value. If successful, diff --git a/lib/unixctl.man b/lib/unixctl.man index b681c7d..a7967c4 100644 --- a/lib/unixctl.man +++ b/lib/unixctl.man @@ -7,11 +7,11 @@ not used at all, the default socket is \fB@RUNDIR@/\*(PN.\fIpid\fB.ctl\fR, where \fIpid\fR is \fB\*(PN\fR's process ID. .IP -On Windows, uses a kernel chosen TCP port on the localhost to listen -for runtime management commands. The kernel chosen TCP port value is written -in a file whose absolute path is pointed by \fIsocket\fR. If \fB\-\-unixctl\fR -is not used at all, the file is created as \fB\*(PN.ctl\fR in the configured -\fIOVS_RUNDIR\fR directory. +On Windows a local named pipe is used to listen for runtime management +commands. A file is created in the absolute path as pointed by \fIsocket\fR +or if \fB\-\-unixctl\fR is not used at all, a file is created as +\fB\*(PN.ctl\fR in the configured \fIOVS_RUNDIR\fR directory. +The file exists just to mimic the behavior of a Unix domain socket. .IP Specifying \fBnone\fR for \fIsocket\fR disables the control socket feature. diff --git a/lib/vconn-active.man b/lib/vconn-active.man index 252438d..8c12fbd 100644 --- a/lib/vconn-active.man +++ b/lib/vconn-active.man @@ -11,4 +11,5 @@ If \fIport\fR is not specified, it defaults to 6653. \fBunix:\fIfile\fR On POSIX, a Unix domain server socket named \fIfile\fR. .IP -On Windows, a localhost TCP port written in \fIfile\fR. +On Windows, a local named pipe and a file is created in the path +\fIfile\fR to mimic the behavior of a Unix domain socket. diff --git a/ovsdb/remote-active.man b/ovsdb/remote-active.man index 22b350c..cf3cbb0 100644 --- a/ovsdb/remote-active.man +++ b/ovsdb/remote-active.man @@ -14,5 +14,5 @@ square brackets, e.g.: \fBtcp:[::1]:6640\fR. .IP "\fBunix:\fIfile\fR" On POSIX, connect to the Unix domain server socket named \fIfile\fR. .IP -On Windows, connect to a localhost TCP port whose value is written in -\fIfile\fR. +On Windows, connect to a local named pipe. A file is created in the +path \fIfile\fR to mimic the behavior of a Unix domain socket. diff --git a/ovsdb/remote-passive.man b/ovsdb/remote-passive.man index a05f796..5da2de8 100644 --- a/ovsdb/remote-passive.man +++ b/ovsdb/remote-passive.man @@ -22,5 +22,5 @@ an IPv6 address, then wrap \fIip\fR with square brackets, e.g.: On POSIX, listen on the Unix domain server socket named \fIfile\fR for a connection. .IP -On Windows, listen on a kernel chosen TCP port on the localhost. The kernel -chosen TCP port value is written in \fIfile\fR. +On Windows, listen on a local named pipe. A file is created in the +path \fIfile\fR to mimic the behavior of a Unix domain socket. diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at index e70498d..d9d2469 100644 --- a/tests/ovsdb-server.at +++ b/tests/ovsdb-server.at @@ -388,7 +388,7 @@ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile db]) AT_CHECK([test ! -e socket1]) AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-remote punix:socket1]) if test "$IS_WIN32" = "yes"; then - OVS_WAIT_UNTIL([test -s socket1]) + OVS_WAIT_UNTIL([test -e socket1]) else OVS_WAIT_UNTIL([test -S socket1]) fi @@ -399,7 +399,7 @@ AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes], AT_CHECK([test ! -e socket2]) AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-remote punix:socket2]) if test "$IS_WIN32" = "yes"; then - OVS_WAIT_UNTIL([test -s socket2]) + OVS_WAIT_UNTIL([test -e socket2]) else OVS_WAIT_UNTIL([test -S socket2]) fi @@ -416,7 +416,7 @@ ovs-appctl: ovsdb-server: server returned an error AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-remote punix:socket1]) OVS_WAIT_UNTIL([test ! -e socket1]) if test "$IS_WIN32" = "yes"; then - AT_CHECK([test -s socket2]) + AT_CHECK([test -e socket2]) else AT_CHECK([test -S socket2]) fi