[libvirt] [PATCH 0/8] Early preview: Re-factoring XDR RPC code

NB: This patch series isn't intended to be applied yet. The current remote driver and libvirtd daemon have a horrible mix of network I/O, RPC dispatch and RPC handling functionality. It it neccessary to introduce a new daemon to provide a lock manager for VM disks. Doing so would effectively require cut+paste of a large part of libvirtd code since there are no reusable APis in it. Likewise for the client side. To address this problem, this series is an attempt to produce a reusable set of APIs for building RPC servers and clients. The new APIs will handle all the networking/sockets functionality and all the RPC dispatch/serialization/deserialization code. This will enable libvirtd and the remote driver to be gutted, so that they only contain the code for actually implementing the RPC handlers. Thus a lock daemon + lock plugin can be provided without any code duplication. This will also make it far easier for us to introduce new RPC programs, eg an administrative program to allow libvirtd itself to be manipulated, say, to changing logging levels on the fly, kill mis-behaving network client, etc, etc. It would also make it practical to experiment with splitting libvirtd up into smaller daemons each handling one area of functionality. This would in turn make it practical to write useful SELinux policy for confining libvirt daemons This series is *far* from complete. The streams stuff is completely missing in the new APis. I have also not yet gutted libvirtd, only the remote driver. It is also not fully tested for ABI wire compatibility with existing libvirtd, though I believe it is correctly preserving ABI. The diffstat is slightly mis-leading because lots of code remains to be deleted from daemon/libvirtd.c and daemon/dispatch.c. None the less, it is expected that the total lines will increase, but this wll be offset by the improved code clarity and separation of functionality. Daniel daemon/event.c | 3 src/Makefile.am | 90 + src/remote/remote_driver.c | 2530 ++++++-------------------------------- src/rpc/virnetclient.c | 1237 ++++++++++++++++++ src/rpc/virnetclient.h | 60 src/rpc/virnetclientprogram.c | 258 +++ src/rpc/virnetclientprogram.h | 71 + src/rpc/virnetclientsaslcontext.c | 246 +++ src/rpc/virnetclientsaslcontext.h | 66 src/rpc/virnetmessage.c | 215 +++ src/rpc/virnetmessage.h | 31 src/rpc/virnetprotocol.c | 108 + src/rpc/virnetprotocol.h | 81 + src/rpc/virnetprotocol.x | 162 ++ src/rpc/virnetserver.c | 654 +++++++++ src/rpc/virnetserver.h | 74 + src/rpc/virnetserverclient.c | 974 ++++++++++++++ src/rpc/virnetserverclient.h | 40 src/rpc/virnetservermessage.h | 20 src/rpc/virnetserverprogram.c | 437 ++++++ src/rpc/virnetserverprogram.h | 76 + src/rpc/virnetserverservice.c | 208 +++ src/rpc/virnetserverservice.h | 32 src/rpc/virnetsocket.c | 715 ++++++++++ src/rpc/virnetsocket.h | 97 + src/rpc/virnettlscontext.c | 611 +++++++++ src/rpc/virnettlscontext.h | 63 src/util/logging.c | 4 src/util/threadpool.c | 178 ++ src/util/threadpool.h | 23 src/virtlockd.c | 620 +++++++++ 31 files changed, 7866 insertions(+), 2118 deletions(-)

From: Hu Tao <hutao@cn.fujitsu.com> * src/util/threadpool.c, src/util/threadpool.h: Thread pool implementation * src/Makefile.am: Build thread pool --- src/Makefile.am | 1 + src/util/threadpool.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util/threadpool.h | 23 ++++++ 3 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 src/util/threadpool.c create mode 100644 src/util/threadpool.h diff --git a/src/Makefile.am b/src/Makefile.am index a9a1986..d71c644 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -73,6 +73,7 @@ UTIL_SOURCES = \ util/threads.c util/threads.h \ util/threads-pthread.h \ util/threads-win32.h \ + util/threadpool.c util/threadpool.h \ util/uuid.c util/uuid.h \ util/util.c util/util.h \ util/xml.c util/xml.h \ diff --git a/src/util/threadpool.c b/src/util/threadpool.c new file mode 100644 index 0000000..cf998bf --- /dev/null +++ b/src/util/threadpool.c @@ -0,0 +1,178 @@ + +#include <config.h> + +#include "threadpool.h" +#include "memory.h" +#include "threads.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _virThreadPoolJob virThreadPoolJob; +typedef virThreadPoolJob *virThreadPoolJobPtr; + +struct _virThreadPoolJob { + virThreadPoolJobPtr next; + + void *data; +}; + +struct _virThreadPool { + int quit; + + virThreadPoolJobFunc jobFunc; + void *jobOpaque; + virThreadPoolJobPtr jobList; + + virMutex mutex; + virCond cond; + virCond quit_cond; + + size_t maxWorkers; + size_t freeWorkers; + size_t nWorkers; + virThreadPtr workers; +}; + +static void virThreadPoolWorker(void *opaque) +{ + virThreadPoolPtr pool = opaque; + + virMutexLock(&pool->mutex); + + while (1) { + while (!pool->quit && + !pool->jobList) { + pool->freeWorkers++; + if (virCondWait(&pool->cond, &pool->mutex) < 0) { + pool->freeWorkers--; + break; + } + pool->freeWorkers--; + } + + if (pool->quit) + break; + + virThreadPoolJobPtr job = pool->jobList; + pool->jobList = pool->jobList->next; + job->next = NULL; + + virMutexUnlock(&pool->mutex); + (pool->jobFunc)(job->data, pool->jobOpaque); + VIR_FREE(job); + virMutexLock(&pool->mutex); + } + + pool->nWorkers--; + if (pool->nWorkers == 0) + virCondSignal(&pool->quit_cond); + virMutexUnlock(&pool->mutex); +} + +virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque) +{ + virThreadPoolPtr pool; + int i; + + if (VIR_ALLOC(pool) < 0) { + virReportOOMError(); + return NULL; + } + + pool->jobFunc = func; + pool->jobOpaque = opaque; + + if (virMutexInit(&pool->mutex) < 0) + goto error; + if (virCondInit(&pool->cond) < 0) + goto error; + if (virCondInit(&pool->quit_cond) < 0) + goto error; + + if (VIR_ALLOC_N(pool->workers, minWorkers) < 0) + goto error; + + pool->maxWorkers = maxWorkers; + for (i = 0; i < minWorkers; i++) { + if (virThreadCreate(&pool->workers[i], + true, + virThreadPoolWorker, + pool) < 0) + goto error; + pool->nWorkers++; + } + + return pool; + +error: + virThreadPoolFree(pool); + return NULL; +} + +void virThreadPoolFree(virThreadPoolPtr pool) +{ + virMutexLock(&pool->mutex); + pool->quit = 1; + if (pool->nWorkers > 0) { + virCondBroadcast(&pool->cond); + if (virCondWait(&pool->quit_cond, &pool->mutex) < 0) + {} + } + VIR_FREE(pool->workers); + virMutexUnlock(&pool->mutex); + VIR_FREE(pool); +} + +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobData) +{ + virThreadPoolJobPtr job; + virThreadPoolJobPtr tmp; + + virMutexLock(&pool->mutex); + if (pool->quit) + goto error; + + if (pool->freeWorkers == 0 && + pool->nWorkers < pool->maxWorkers) { + if (VIR_EXPAND_N(pool->workers, pool->nWorkers, 1) < 0) { + virReportOOMError(); + goto error; + } + + if (virThreadCreate(&pool->workers[pool->nWorkers], + true, + virThreadPoolWorker, + pool) < 0) + goto error; + pool->nWorkers++; + } + + if (VIR_ALLOC(job) < 0) { + virReportOOMError(); + goto error; + } + + job->data = jobData; + + tmp = pool->jobList; + while (tmp && tmp->next) + tmp = tmp->next; + if (tmp) + tmp->next = job; + else + pool->jobList = job; + + virCondSignal(&pool->cond); + virMutexUnlock(&pool->mutex); + + return 0; + +error: + virMutexUnlock(&pool->mutex); + return -1; +} diff --git a/src/util/threadpool.h b/src/util/threadpool.h new file mode 100644 index 0000000..093786f --- /dev/null +++ b/src/util/threadpool.h @@ -0,0 +1,23 @@ +#ifndef __THREADPOOL_H__ +#define __THREADPOOL_H__ + +#include <pthread.h> + +typedef struct _virThreadPool virThreadPool; +typedef virThreadPool *virThreadPoolPtr; + +typedef void (*virThreadPoolJobFunc)(void *jobdata, void *opaque); + +virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque); + +void virThreadPoolShutdown(virThreadPoolPtr pool); + +void virThreadPoolFree(virThreadPoolPtr pool); + +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobdata); + +#endif -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
From: Hu Tao <hutao@cn.fujitsu.com>
Not sure whether Daniel or Hu will be the next person to post an improvement of this patch. Some of my comments are repeated from Hu's first attempt, although many of them have been addressed in updating to libvirt APIs.
* src/util/threadpool.c, src/util/threadpool.h: Thread pool implementation * src/Makefile.am: Build thread pool --- src/Makefile.am | 1 + src/util/threadpool.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util/threadpool.h | 23 ++++++ 3 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 src/util/threadpool.c create mode 100644 src/util/threadpool.h
+++ b/src/util/threadpool.c @@ -0,0 +1,178 @@
Still missing copyright blurb.
+ +struct _virThreadPool { + int quit;
s/int/bool/ (which means including <stdbool.h> earlier)
+virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque) +{ + virThreadPoolPtr pool; + int i;
s/int/size_t/
+void virThreadPoolFree(virThreadPoolPtr pool) +{
if (!pool) return;
+ virMutexLock(&pool->mutex);
What happens if the reason we called this was because virMutexInit failed during virThreadPoolNew? Is it still safe to blindly call virMutexLock on a broken mutex?
+ pool->quit = 1;
s/1/true/
+ if (pool->nWorkers > 0) { + virCondBroadcast(&pool->cond); + if (virCondWait(&pool->quit_cond, &pool->mutex) < 0) + {}
Is it any better to use "ignore-value.h" and ignore_value(virCondWait), instead of the empty if () {}?
+ } + VIR_FREE(pool->workers); + virMutexUnlock(&pool->mutex);
missing cleanups for mutex, cond, quit_cond (virMutexDestroy, virCondDestroy). And if you are forcefully quitting while jobs are still unclaimed, is it possible that jobList might also need freeing, or is that guaranteed to be taken care of by the virCondWait?
+ VIR_FREE(pool); +} + +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobData) +{ + virThreadPoolJobPtr job; + virThreadPoolJobPtr tmp; + + virMutexLock(&pool->mutex); + if (pool->quit) + goto error; + + if (pool->freeWorkers == 0 && + pool->nWorkers < pool->maxWorkers) { + if (VIR_EXPAND_N(pool->workers, pool->nWorkers, 1) < 0) { + virReportOOMError(); + goto error; + } + + if (virThreadCreate(&pool->workers[pool->nWorkers], + true, + virThreadPoolWorker, + pool) < 0) + goto error; + pool->nWorkers++;
Problem. VIR_EXPAND_N already incremented pool->nWorkers, but you are incrementing it again. You either need to use: VIR_RESIZE_N(pool->workers, pool->maxworkers, pool->nWorkers, 1) pool->nWorkers++ or keep the VIR_EXPAND_N, but set pool->workers[pool->nWorkers - 1] to the created thread.
+ } + + if (VIR_ALLOC(job) < 0) { + virReportOOMError(); + goto error; + } + + job->data = jobData; + + tmp = pool->jobList; + while (tmp && tmp->next) + tmp = tmp->next; + if (tmp) + tmp->next = job; + else + pool->jobList = job;
Good - FIFO job ordering. However, this is O(n) list traversal; would it make sense to keep track of a list head and list tail pointer, so that we can have O(1) job insertion, or is that extra bookkeeping not worth it given that our maximum job queue size is not going to typically be that large?
+ + virCondSignal(&pool->cond); + virMutexUnlock(&pool->mutex); + + return 0; + +error: + virMutexUnlock(&pool->mutex); + return -1; +} diff --git a/src/util/threadpool.h b/src/util/threadpool.h new file mode 100644 index 0000000..093786f --- /dev/null +++ b/src/util/threadpool.h @@ -0,0 +1,23 @@
Missing copyright blurb.
+#ifndef __THREADPOOL_H__ +#define __THREADPOOL_H__
I'd prefer __VIR_THREADPOOL_H__, to avoid potential collisions in the __ namespace.
+ +#include <pthread.h>
Wrong header; this should be "threads.h"
+ +typedef struct _virThreadPool virThreadPool; +typedef virThreadPool *virThreadPoolPtr; + +typedef void (*virThreadPoolJobFunc)(void *jobdata, void *opaque); + +virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque);
ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK
+ +void virThreadPoolShutdown(virThreadPoolPtr pool);
ATTRIBUTE_NONNULL(1)
+ +void virThreadPoolFree(virThreadPoolPtr pool);
add this to the list of free-like functions in cfg.mk
+ +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobdata);
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Wed, Dec 01, 2010 at 05:26:27PM +0000, Daniel P. Berrange wrote:
From: Hu Tao <hutao@cn.fujitsu.com>
* src/util/threadpool.c, src/util/threadpool.h: Thread pool implementation * src/Makefile.am: Build thread pool --- src/Makefile.am | 1 + src/util/threadpool.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util/threadpool.h | 23 ++++++ 3 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 src/util/threadpool.c create mode 100644 src/util/threadpool.h
diff --git a/src/Makefile.am b/src/Makefile.am index a9a1986..d71c644 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -73,6 +73,7 @@ UTIL_SOURCES = \ util/threads.c util/threads.h \ util/threads-pthread.h \ util/threads-win32.h \ + util/threadpool.c util/threadpool.h \ util/uuid.c util/uuid.h \ util/util.c util/util.h \ util/xml.c util/xml.h \ diff --git a/src/util/threadpool.c b/src/util/threadpool.c new file mode 100644 index 0000000..cf998bf --- /dev/null +++ b/src/util/threadpool.c @@ -0,0 +1,178 @@ + +#include <config.h> + +#include "threadpool.h" +#include "memory.h" +#include "threads.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _virThreadPoolJob virThreadPoolJob; +typedef virThreadPoolJob *virThreadPoolJobPtr; + +struct _virThreadPoolJob { + virThreadPoolJobPtr next; + + void *data; +}; + +struct _virThreadPool { + int quit; + + virThreadPoolJobFunc jobFunc; + void *jobOpaque; + virThreadPoolJobPtr jobList; + + virMutex mutex; + virCond cond; + virCond quit_cond; + + size_t maxWorkers; + size_t freeWorkers; + size_t nWorkers; + virThreadPtr workers; +}; + +static void virThreadPoolWorker(void *opaque) +{ + virThreadPoolPtr pool = opaque; + + virMutexLock(&pool->mutex); + + while (1) { + while (!pool->quit && + !pool->jobList) { + pool->freeWorkers++; + if (virCondWait(&pool->cond, &pool->mutex) < 0) { + pool->freeWorkers--; + break; + } + pool->freeWorkers--; + } + + if (pool->quit) + break; + + virThreadPoolJobPtr job = pool->jobList; + pool->jobList = pool->jobList->next; + job->next = NULL; + + virMutexUnlock(&pool->mutex); + (pool->jobFunc)(job->data, pool->jobOpaque);
This could race if jobFunc does something with jobOpaque unless jobFunc is aware of this and provides a lock to protect jobOpaque.
+ VIR_FREE(job); + virMutexLock(&pool->mutex); + } + + pool->nWorkers--; + if (pool->nWorkers == 0) + virCondSignal(&pool->quit_cond); + virMutexUnlock(&pool->mutex); +} + +virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque) +{ + virThreadPoolPtr pool; + int i; + + if (VIR_ALLOC(pool) < 0) { + virReportOOMError(); + return NULL; + } + + pool->jobFunc = func; + pool->jobOpaque = opaque; + + if (virMutexInit(&pool->mutex) < 0) + goto error; + if (virCondInit(&pool->cond) < 0) + goto error; + if (virCondInit(&pool->quit_cond) < 0) + goto error; + + if (VIR_ALLOC_N(pool->workers, minWorkers) < 0) + goto error; + + pool->maxWorkers = maxWorkers; + for (i = 0; i < minWorkers; i++) { + if (virThreadCreate(&pool->workers[i], + true, + virThreadPoolWorker, + pool) < 0) + goto error; + pool->nWorkers++; + }
There will be more than maxWorkers threads created if minWorkers > maxWorkers
+ + return pool; + +error: + virThreadPoolFree(pool); + return NULL; +} + +void virThreadPoolFree(virThreadPoolPtr pool) +{ + virMutexLock(&pool->mutex); + pool->quit = 1; + if (pool->nWorkers > 0) { + virCondBroadcast(&pool->cond); + if (virCondWait(&pool->quit_cond, &pool->mutex) < 0) + {} + } + VIR_FREE(pool->workers); + virMutexUnlock(&pool->mutex); + VIR_FREE(pool); +} + +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobData) +{ + virThreadPoolJobPtr job; + virThreadPoolJobPtr tmp; + + virMutexLock(&pool->mutex); + if (pool->quit) + goto error; + + if (pool->freeWorkers == 0 && + pool->nWorkers < pool->maxWorkers) { + if (VIR_EXPAND_N(pool->workers, pool->nWorkers, 1) < 0) { + virReportOOMError(); + goto error; + } + + if (virThreadCreate(&pool->workers[pool->nWorkers], + true, + virThreadPoolWorker, + pool) < 0) + goto error; + pool->nWorkers++; + } + + if (VIR_ALLOC(job) < 0) { + virReportOOMError(); + goto error; + } + + job->data = jobData; + + tmp = pool->jobList; + while (tmp && tmp->next) + tmp = tmp->next; + if (tmp) + tmp->next = job; + else + pool->jobList = job; + + virCondSignal(&pool->cond); + virMutexUnlock(&pool->mutex); + + return 0; + +error: + virMutexUnlock(&pool->mutex); + return -1; +} diff --git a/src/util/threadpool.h b/src/util/threadpool.h new file mode 100644 index 0000000..093786f --- /dev/null +++ b/src/util/threadpool.h @@ -0,0 +1,23 @@ +#ifndef __THREADPOOL_H__ +#define __THREADPOOL_H__ + +#include <pthread.h> + +typedef struct _virThreadPool virThreadPool; +typedef virThreadPool *virThreadPoolPtr; + +typedef void (*virThreadPoolJobFunc)(void *jobdata, void *opaque); + +virThreadPoolPtr virThreadPoolNew(size_t minWorkers, + size_t maxWorkers, + virThreadPoolJobFunc func, + void *opaque); + +void virThreadPoolShutdown(virThreadPoolPtr pool); + +void virThreadPoolFree(virThreadPoolPtr pool); + +int virThreadPoolSendJob(virThreadPoolPtr pool, + void *jobdata); + +#endif -- 1.7.2.3

On Thu, Dec 02, 2010 at 09:43:12AM +0800, Hu Tao wrote:
On Wed, Dec 01, 2010 at 05:26:27PM +0000, Daniel P. Berrange wrote:
From: Hu Tao <hutao@cn.fujitsu.com>
* src/util/threadpool.c, src/util/threadpool.h: Thread pool implementation * src/Makefile.am: Build thread pool --- src/Makefile.am | 1 + src/util/threadpool.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util/threadpool.h | 23 ++++++ 3 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 src/util/threadpool.c create mode 100644 src/util/threadpool.h +static void virThreadPoolWorker(void *opaque) +{ + virThreadPoolPtr pool = opaque; + + virMutexLock(&pool->mutex); + + while (1) { + while (!pool->quit && + !pool->jobList) { + pool->freeWorkers++; + if (virCondWait(&pool->cond, &pool->mutex) < 0) { + pool->freeWorkers--; + break; + } + pool->freeWorkers--; + } + + if (pool->quit) + break; + + virThreadPoolJobPtr job = pool->jobList; + pool->jobList = pool->jobList->next; + job->next = NULL; + + virMutexUnlock(&pool->mutex); + (pool->jobFunc)(job->data, pool->jobOpaque);
This could race if jobFunc does something with jobOpaque unless jobFunc is aware of this and provides a lock to protect jobOpaque.
Yes, it is up to jobFunc to provide locking on jobOpaque if it needs to do so for thread safety. Regards, Daniel

The logging setup requires const char * strings, but the virLogSetFromEnv() strdup's the env variables, thus causing a memory leak * src/util/logging.c: Avoid strdup'ing env variables --- src/util/logging.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/logging.c b/src/util/logging.c index d65dec0..83cc358 100644 --- a/src/util/logging.c +++ b/src/util/logging.c @@ -980,8 +980,8 @@ void virLogSetFromEnv(void) { virLogParseDefaultPriority(debugEnv); debugEnv = getenv("LIBVIRT_LOG_FILTERS"); if (debugEnv && *debugEnv) - virLogParseFilters(strdup(debugEnv)); + virLogParseFilters(debugEnv); debugEnv = getenv("LIBVIRT_LOG_OUTPUTS"); if (debugEnv && *debugEnv) - virLogParseOutputs(strdup(debugEnv)); + virLogParseOutputs(debugEnv); } -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
The logging setup requires const char * strings, but the virLogSetFromEnv() strdup's the env variables, thus causing a memory leak
* src/util/logging.c: Avoid strdup'ing env variables --- src/util/logging.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/util/logging.c b/src/util/logging.c index d65dec0..83cc358 100644 --- a/src/util/logging.c +++ b/src/util/logging.c @@ -980,8 +980,8 @@ void virLogSetFromEnv(void) { virLogParseDefaultPriority(debugEnv); debugEnv = getenv("LIBVIRT_LOG_FILTERS"); if (debugEnv && *debugEnv) - virLogParseFilters(strdup(debugEnv)); + virLogParseFilters(debugEnv);
ACK; independently useful to push now, even if the rest of your series is not ready for prime time. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Wed, Dec 01, 2010 at 12:21:51PM -0700, Eric Blake wrote:
On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
The logging setup requires const char * strings, but the virLogSetFromEnv() strdup's the env variables, thus causing a memory leak
* src/util/logging.c: Avoid strdup'ing env variables --- src/util/logging.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/util/logging.c b/src/util/logging.c index d65dec0..83cc358 100644 --- a/src/util/logging.c +++ b/src/util/logging.c @@ -980,8 +980,8 @@ void virLogSetFromEnv(void) { virLogParseDefaultPriority(debugEnv); debugEnv = getenv("LIBVIRT_LOG_FILTERS"); if (debugEnv && *debugEnv) - virLogParseFilters(strdup(debugEnv)); + virLogParseFilters(debugEnv);
ACK; independently useful to push now, even if the rest of your series is not ready for prime time.
Yep, pushed that Daniel

* daemon/event.c: Include errno in debug info upon poll() failure --- daemon/event.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/daemon/event.c b/daemon/event.c index a983b35..0920748 100644 --- a/daemon/event.c +++ b/daemon/event.c @@ -576,13 +576,14 @@ int virEventRunOnce(void) { retry: EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); ret = poll(fds, nfds, timeout); - EVENT_DEBUG("Poll got %d event", ret); if (ret < 0) { + EVENT_DEBUG("Poll got error event %d", errno); if (errno == EINTR) { goto retry; } goto error_unlocked; } + EVENT_DEBUG("Poll got %d event(s)", ret); virMutexLock(&eventLoop.lock); if (virEventDispatchTimeouts() < 0) -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
* daemon/event.c: Include errno in debug info upon poll() failure --- daemon/event.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/daemon/event.c b/daemon/event.c index a983b35..0920748 100644 --- a/daemon/event.c +++ b/daemon/event.c @@ -576,13 +576,14 @@ int virEventRunOnce(void) { retry: EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); ret = poll(fds, nfds, timeout); - EVENT_DEBUG("Poll got %d event", ret); if (ret < 0) { + EVENT_DEBUG("Poll got error event %d", errno); if (errno == EINTR) { goto retry; } goto error_unlocked; } + EVENT_DEBUG("Poll got %d event(s)", ret);
ACK; independently useful, if you want to push now. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Wed, Dec 01, 2010 at 12:22:53PM -0700, Eric Blake wrote:
On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
* daemon/event.c: Include errno in debug info upon poll() failure --- daemon/event.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/daemon/event.c b/daemon/event.c index a983b35..0920748 100644 --- a/daemon/event.c +++ b/daemon/event.c @@ -576,13 +576,14 @@ int virEventRunOnce(void) { retry: EVENT_DEBUG("Poll on %d handles %p timeout %d", nfds, fds, timeout); ret = poll(fds, nfds, timeout); - EVENT_DEBUG("Poll got %d event", ret); if (ret < 0) { + EVENT_DEBUG("Poll got error event %d", errno); if (errno == EINTR) { goto retry; } goto error_unlocked; } + EVENT_DEBUG("Poll got %d event(s)", ret);
ACK; independently useful, if you want to push now.
Pushed too Daniel

Introduces a set of generic objects which are to be used in building RPC servers/clients based on XDR. - virNetMessageHeader - standardize the XDR format for any RPC program. Copied from remote protocol for back compat - virNetMessage - Provides a buffer for (de-)serializing messages, and a copy of the decoded virNetMessageHeader. Provides APIs for encoding/decoding message headers and payloads, thus isolating all the XDR api calls in one file. Callers no longer need to use XDR themselves. - virNetSocket - a wrapper around a socket file descriptor, to simplify creation of new sockets, both for clients and services. Encapsulates all the hairy getaddrinfo code and sockaddr manipulation. Will eventually include transparent support for TLS and SASL encoding of data - virNetTLSContext - encapsulates the credentials required to setup TLS sessions. eg the set of x509 certificates and keys, optional DH parameters and x509 DName whitelist Provides APIs for easily validating certificates from a TLS session - virNetTLSSession - encapsulates the TLS session handling, so that callers no longer have a direct dependancy on gnutls. This will facilitate adding alternate TLS impls. Makes the read/write TLS functions work with same semantics as the native socket read/write functions. ie they set errno, instead of a gnutls specific error code. This code is taken from either the daemon/libvirtd.c, daemon/dispatch.c or src/remote/remote_driver.c files, which all duplicated alot of functionality. --- src/Makefile.am | 42 +++- src/rpc/virnetmessage.c | 215 +++++++++++++ src/rpc/virnetmessage.h | 31 ++ src/rpc/virnetprotocol.c | 108 +++++++ src/rpc/virnetprotocol.h | 81 +++++ src/rpc/virnetprotocol.x | 162 ++++++++++ src/rpc/virnetsocket.c | 715 ++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetsocket.h | 97 ++++++ src/rpc/virnettlscontext.c | 611 +++++++++++++++++++++++++++++++++++++ src/rpc/virnettlscontext.h | 63 ++++ 10 files changed, 2124 insertions(+), 1 deletions(-) create mode 100644 src/rpc/virnetmessage.c create mode 100644 src/rpc/virnetmessage.h create mode 100644 src/rpc/virnetprotocol.c create mode 100644 src/rpc/virnetprotocol.h create mode 100644 src/rpc/virnetprotocol.x create mode 100644 src/rpc/virnetsocket.c create mode 100644 src/rpc/virnetsocket.h create mode 100644 src/rpc/virnettlscontext.c create mode 100644 src/rpc/virnettlscontext.h diff --git a/src/Makefile.am b/src/Makefile.am index d71c644..613ff0a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -530,6 +530,24 @@ else mv -f rp_qemu.c-t $(srcdir)/remote/qemu_protocol.c endif +rpcgen-net: + rm -f rp_net.c-t rp_net.h-t rp_net.c-t1 rp_net.c-t2 rp_net.h-t1 + $(RPCGEN) -h -o rp_net.h-t $(srcdir)/rpc/virnetprotocol.x + $(RPCGEN) -c -o rp_net.c-t $(srcdir)/rpc/virnetprotocol.x +if HAVE_GLIBC_RPCGEN + perl -w $(srcdir)/remote/rpcgen_fix.pl rp_net.h-t > rp_net.h-t1 + perl -w $(srcdir)/remote/rpcgen_fix.pl rp_net.c-t > rp_net.c-t1 + (echo '#include <config.h>'; cat rp_net.c-t1) > rp_net.c-t2 + chmod 0444 rp_net.c-t2 rp_net.h-t1 + mv -f rp_net.h-t1 $(srcdir)/rpc/virnetprotocol.h + mv -f rp_net.c-t2 $(srcdir)/rpc/virnetprotocol.c + rm -f rp_net.c-t rp_net.h-t rp_net.c-t1 +else + chmod 0444 rp_net.c-t rp_net.h-t + mv -f rp_net.h-t $(srcdir)/rpc/virnetprotocol.h + mv -f rp_net.c-t $(srcdir)/rpc/virnetprotocol.c +endif + # # Maintainer-only target for re-generating the derived .c/.h source # files, which are actually derived from the .x file. @@ -540,7 +558,7 @@ endif # Support for non-GLIB rpcgen is here as a convenience for # non-Linux people needing to test changes during dev. # -rpcgen: rpcgen-normal rpcgen-qemu +rpcgen: rpcgen-normal rpcgen-qemu rpcgen-net endif @@ -1098,6 +1116,28 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) + +noinst_LTLIBRARIES += libvirt-net-rpc.la + +libvirt_net_rpc_la_SOURCES = \ + ../daemon/event.c \ + rpc/virnetprotocol.h rpc/virnetprotocol.c \ + rpc/virnetmessage.h rpc/virnetmessage.c \ + rpc/virnettlscontext.h rpc/virnettlscontext.c \ + rpc/virnetsocket.h rpc/virnetsocket.c +libvirt_net_rpc_la_CFLAGS = \ + $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ + $(AM_CFLAGS) +libvirt_net_rpc_la_LDFLAGS = \ + $(GNUTLS_LIBS) \ + $(SASL_LIBS) \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS)l +libvirt_net_rpc_la_LIBADD = \ + $(CYGWIN_EXTRA_LIBADD) + libexec_PROGRAMS = if WITH_STORAGE_DISK diff --git a/src/rpc/virnetmessage.c b/src/rpc/virnetmessage.c new file mode 100644 index 0000000..9bd7557 --- /dev/null +++ b/src/rpc/virnetmessage.c @@ -0,0 +1,215 @@ +#include <config.h> + +#include "virnetmessage.h" + +#include "virterror_internal.h" +#include "logging.h" + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + + +int virNetMessageDecodeLength(virNetMessagePtr msg) +{ + XDR xdr; + unsigned int len; + int ret = -1; + + xdrmem_create(&xdr, msg->buffer, + msg->bufferLength, XDR_DECODE); + if (!xdr_u_int(&xdr, &len)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to decode message length")); + goto cleanup; + } + msg->bufferOffset = xdr_getpos(&xdr); + + if (len < VIR_NET_MESSAGE_LEN_MAX) { + virNetError(VIR_ERR_RPC, "%s", + _("packet received from server too small")); + goto cleanup; + } + + /* Length includes length word - adjust to real length to read. */ + len -= VIR_NET_MESSAGE_LEN_MAX; + + if (len > VIR_NET_MESSAGE_MAX) { + virNetError(VIR_ERR_RPC, "%s", + _("packet received from server too large")); + goto cleanup; + } + + /* Extend our declared buffer length and carry + on reading the header + payload */ + msg->bufferLength += len; + + VIR_DEBUG("Got length, now need %d total (%d more)", msg->bufferLength, len); + + ret = 0; + +cleanup: + xdr_destroy(&xdr); + return ret; +} + + +/* + * @msg: the complete incoming message, whose header to decode + * + * Decodes the header part of the message, but does not + * validate the decoded fields in the header. It expects + * bufferLength to refer to length of the data packet. Upon + * return bufferOffset will refer to the amount of the packet + * consumed by decoding of the header. + * + * returns 0 if successfully decoded, -1 upon fatal error + */ +int virNetMessageDecodeHeader(virNetMessagePtr msg) +{ + XDR xdr; + int ret = -1; + + msg->bufferOffset = VIR_NET_MESSAGE_LEN_MAX; + + /* Parse the header. */ + xdrmem_create(&xdr, + msg->buffer + msg->bufferOffset, + msg->bufferLength - msg->bufferOffset, + XDR_DECODE); + + if (!xdr_virNetMessageHeader(&xdr, &msg->header)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to decode message header")); + goto cleanup; + } + + msg->bufferOffset += xdr_getpos(&xdr); + + ret = 0; + +cleanup: + xdr_destroy(&xdr); + return ret; +} + + +/* + * @msg: the outgoing message, whose header to encode + * + * Encodes the length word and header of the message, setting the + * message offset ready to encode the payload. Leaves space + * for the length field later. Upon return bufferLength will + * refer to the total available space for message, while + * bufferOffset will refer to current space used by header + * + * returns 0 if successfully encoded, -1 upon fatal error + */ +int virNetMessageEncodeHeader(virNetMessagePtr msg) +{ + XDR xdr; + int ret = -1; + unsigned int len = 0; + + msg->bufferLength = sizeof(msg->buffer); + msg->bufferOffset = 0; + + /* Format the header. */ + xdrmem_create(&xdr, + msg->buffer, + msg->bufferLength, + XDR_ENCODE); + + /* The real value is filled in shortly */ + if (!xdr_u_int(&xdr, &len)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to encode message length")); + goto cleanup; + } + + if (!xdr_virNetMessageHeader(&xdr, &msg->header)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to encode message header")); + goto cleanup; + } + + len = xdr_getpos(&xdr); + xdr_setpos(&xdr, 0); + + /* Fill in current length - may be re-written later + * if a payload is added + */ + if (!xdr_u_int(&xdr, &len)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to re-encode message length")); + goto cleanup; + } + + msg->bufferOffset += len; + + ret = 0; + +cleanup: + xdr_destroy(&xdr); + return ret; +} + + +int virNetMessageEncodePayload(virNetMessagePtr msg, + xdrproc_t filter, + void *data) +{ + XDR xdr; + + /* Serialise header followed by args. */ + xdrmem_create(&xdr, msg->buffer + msg->bufferOffset, + msg->bufferLength - msg->bufferOffset, XDR_ENCODE); + + if (!(*filter)(&xdr, data)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to encode message payload")); + goto error; + } + + /* Get the length stored in buffer. */ + msg->bufferOffset += xdr_getpos(&xdr); + xdr_destroy(&xdr); + + /* Re-encode the length word. */ + VIR_DEBUG("Encode length as %d", msg->bufferLength); + xdrmem_create(&xdr, msg->buffer, VIR_NET_MESSAGE_HEADER_XDR_LEN, XDR_ENCODE); + if (!xdr_u_int(&xdr, &msg->bufferOffset)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to encode message length")); + goto error; + } + xdr_destroy(&xdr); + + msg->bufferLength = msg->bufferOffset; + msg->bufferOffset = 0; + return 0; + +error: + xdr_destroy(&xdr); + return -1; +} + + +int virNetMessageDecodePayload(virNetMessagePtr msg, + xdrproc_t filter, + void *data) +{ + XDR xdr; + + /* Serialise header followed by args. */ + xdrmem_create(&xdr, msg->buffer + msg->bufferOffset, + msg->bufferLength - msg->bufferOffset, XDR_DECODE); + + if (!(*filter)(&xdr, data)) { + virNetError(VIR_ERR_RPC, "%s", _("Unable to decode message payload")); + goto error; + } + + /* Get the length stored in buffer. */ + msg->bufferLength += xdr_getpos(&xdr); + xdr_destroy(&xdr); + return 0; + +error: + xdr_destroy(&xdr); + return -1; +} + diff --git a/src/rpc/virnetmessage.h b/src/rpc/virnetmessage.h new file mode 100644 index 0000000..73d417b --- /dev/null +++ b/src/rpc/virnetmessage.h @@ -0,0 +1,31 @@ +#ifndef __VIR_NET_MESSAGE_H__ +#define __VIR_NET_MESSAGE_H__ + +#include "virnetprotocol.h" + +typedef struct virNetMessageHeader *virNetMessageHeaderPtr; + +typedef struct _virNetMessage virNetMessage; +typedef virNetMessage *virNetMessagePtr; + +struct _virNetMessage { + char buffer[VIR_NET_MESSAGE_MAX + VIR_NET_MESSAGE_LEN_MAX]; + unsigned int bufferLength; + unsigned int bufferOffset; + + virNetMessageHeader header; +}; + +int virNetMessageEncodeHeader(virNetMessagePtr msg); +int virNetMessageDecodeLength(virNetMessagePtr msg); +int virNetMessageDecodeHeader(virNetMessagePtr msg); + +int virNetMessageEncodePayload(virNetMessagePtr msg, + xdrproc_t filter, + void *data); +int virNetMessageDecodePayload(virNetMessagePtr msg, + xdrproc_t filter, + void *data); + +#endif /* __VIR_NET_MESSAGE_H__ */ + diff --git a/src/rpc/virnetprotocol.c b/src/rpc/virnetprotocol.c new file mode 100644 index 0000000..0a803ae --- /dev/null +++ b/src/rpc/virnetprotocol.c @@ -0,0 +1,108 @@ +#include <config.h> +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#include "./rpc/virnetprotocol.h" +#include "internal.h" +#ifdef HAVE_XDR_U_INT64_T +# define xdr_uint64_t xdr_u_int64_t +#endif +#ifndef IXDR_PUT_INT32 +# define IXDR_PUT_INT32 IXDR_PUT_LONG +#endif +#ifndef IXDR_GET_INT32 +# define IXDR_GET_INT32 IXDR_GET_LONG +#endif +#ifndef IXDR_PUT_U_INT32 +# define IXDR_PUT_U_INT32 IXDR_PUT_U_LONG +#endif +#ifndef IXDR_GET_U_INT32 +# define IXDR_GET_U_INT32 IXDR_GET_U_LONG +#endif + +bool_t +xdr_virNetMessageType (XDR *xdrs, virNetMessageType *objp) +{ + + if (!xdr_enum (xdrs, (enum_t *) objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_virNetMessageStatus (XDR *xdrs, virNetMessageStatus *objp) +{ + + if (!xdr_enum (xdrs, (enum_t *) objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_virNetMessageHeader (XDR *xdrs, virNetMessageHeader *objp) +{ + register int32_t *buf; + + + if (xdrs->x_op == XDR_ENCODE) { + buf = (int32_t*)XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int (xdrs, &objp->prog)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->vers)) + return FALSE; + if (!xdr_int (xdrs, &objp->proc)) + return FALSE; + + } else { + (void)IXDR_PUT_U_INT32(buf, objp->prog); + (void)IXDR_PUT_U_INT32(buf, objp->vers); + (void)IXDR_PUT_INT32(buf, objp->proc); + } + if (!xdr_virNetMessageType (xdrs, &objp->type)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->serial)) + return FALSE; + if (!xdr_virNetMessageStatus (xdrs, &objp->status)) + return FALSE; + return TRUE; + } else if (xdrs->x_op == XDR_DECODE) { + buf = (int32_t*)XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int (xdrs, &objp->prog)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->vers)) + return FALSE; + if (!xdr_int (xdrs, &objp->proc)) + return FALSE; + + } else { + objp->prog = IXDR_GET_U_LONG(buf); + objp->vers = IXDR_GET_U_LONG(buf); + objp->proc = IXDR_GET_INT32(buf); + } + if (!xdr_virNetMessageType (xdrs, &objp->type)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->serial)) + return FALSE; + if (!xdr_virNetMessageStatus (xdrs, &objp->status)) + return FALSE; + return TRUE; + } + + if (!xdr_u_int (xdrs, &objp->prog)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->vers)) + return FALSE; + if (!xdr_int (xdrs, &objp->proc)) + return FALSE; + if (!xdr_virNetMessageType (xdrs, &objp->type)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->serial)) + return FALSE; + if (!xdr_virNetMessageStatus (xdrs, &objp->status)) + return FALSE; + return TRUE; +} diff --git a/src/rpc/virnetprotocol.h b/src/rpc/virnetprotocol.h new file mode 100644 index 0000000..4ef8e20 --- /dev/null +++ b/src/rpc/virnetprotocol.h @@ -0,0 +1,81 @@ +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#ifndef _RP_NET_H_RPCGEN +#define _RP_NET_H_RPCGEN + +#include <rpc/rpc.h> + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "internal.h" +#ifdef HAVE_XDR_U_INT64_T +# define xdr_uint64_t xdr_u_int64_t +#endif +#ifndef IXDR_PUT_INT32 +# define IXDR_PUT_INT32 IXDR_PUT_LONG +#endif +#ifndef IXDR_GET_INT32 +# define IXDR_GET_INT32 IXDR_GET_LONG +#endif +#ifndef IXDR_PUT_U_INT32 +# define IXDR_PUT_U_INT32 IXDR_PUT_U_LONG +#endif +#ifndef IXDR_GET_U_INT32 +# define IXDR_GET_U_INT32 IXDR_GET_U_LONG +#endif +#define VIR_NET_MESSAGE_MAX 262144 +#define VIR_NET_MESSAGE_HEADER_MAX 24 +#define VIR_NET_MESSAGE_PAYLOAD_MAX 262120 +#define VIR_NET_MESSAGE_LEN_MAX 4 + +enum virNetMessageType { + VIR_NET_CALL = 0, + VIR_NET_REPLY = 1, + VIR_NET_MESSAGE = 2, + VIR_NET_STREAM = 3, +}; +typedef enum virNetMessageType virNetMessageType; + +enum virNetMessageStatus { + VIR_NET_OK = 0, + VIR_NET_ERROR = 1, + VIR_NET_CONTINUE = 2, +}; +typedef enum virNetMessageStatus virNetMessageStatus; +#define VIR_NET_MESSAGE_HEADER_XDR_LEN 4 + +struct virNetMessageHeader { + u_int prog; + u_int vers; + int proc; + virNetMessageType type; + u_int serial; + virNetMessageStatus status; +}; +typedef struct virNetMessageHeader virNetMessageHeader; + +/* the xdr functions */ + +#if defined(__STDC__) || defined(__cplusplus) +extern bool_t xdr_virNetMessageType (XDR *, virNetMessageType*); +extern bool_t xdr_virNetMessageStatus (XDR *, virNetMessageStatus*); +extern bool_t xdr_virNetMessageHeader (XDR *, virNetMessageHeader*); + +#else /* K&R C */ +extern bool_t xdr_virNetMessageType (); +extern bool_t xdr_virNetMessageStatus (); +extern bool_t xdr_virNetMessageHeader (); + +#endif /* K&R C */ + +#ifdef __cplusplus +} +#endif + +#endif /* !_RP_NET_H_RPCGEN */ diff --git a/src/rpc/virnetprotocol.x b/src/rpc/virnetprotocol.x new file mode 100644 index 0000000..1d74153 --- /dev/null +++ b/src/rpc/virnetprotocol.x @@ -0,0 +1,162 @@ +/* -*- c -*- + * virnetprotocol.x: basic protocol for all RPC services. + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Richard Jones <rjones@redhat.com> + */ + +%#include "internal.h" + +/* cygwin's xdr implementation defines xdr_u_int64_t instead of xdr_uint64_t + * and lacks IXDR_PUT_INT32 and IXDR_GET_INT32 + */ +%#ifdef HAVE_XDR_U_INT64_T +%# define xdr_uint64_t xdr_u_int64_t +%#endif +%#ifndef IXDR_PUT_INT32 +%# define IXDR_PUT_INT32 IXDR_PUT_LONG +%#endif +%#ifndef IXDR_GET_INT32 +%# define IXDR_GET_INT32 IXDR_GET_LONG +%#endif +%#ifndef IXDR_PUT_U_INT32 +%# define IXDR_PUT_U_INT32 IXDR_PUT_U_LONG +%#endif +%#ifndef IXDR_GET_U_INT32 +%# define IXDR_GET_U_INT32 IXDR_GET_U_LONG +%#endif + +/*----- Data types. -----*/ + +/* Maximum total message size (serialised). */ +const VIR_NET_MESSAGE_MAX = 262144; + +/* Size of struct virNetMessageHeader (serialized)*/ +const VIR_NET_MESSAGE_HEADER_MAX = 24; + +/* Size of message payload */ +const VIR_NET_MESSAGE_PAYLOAD_MAX = 262120; + +/* Size of message length field. Not counted in VIR_NET_MESSAGE_MAX */ +const VIR_NET_MESSAGE_LEN_MAX = 4; + +/* + * RPC wire format + * + * Each message consists of: + * + * Name | Type | Description + * -----------+-----------------------+------------------ + * Length | int | Total number of bytes in message _including_ length. + * Header | virNetMessageHeader | Control information about procedure call + * Payload | - | Variable payload data per procedure + * + * In header, the 'serial' field varies according to: + * + * - type == VIR_NET_CALL + * * serial is set by client, incrementing by 1 each time + * + * - type == VIR_NET_REPLY + * * serial matches that from the corresponding VIR_NET_CALL + * + * - type == VIR_NET_MESSAGE + * * serial is always zero + * + * - type == VIR_NET_STREAM + * * serial matches that from the corresponding VIR_NET_CALL + * + * and the 'status' field varies according to: + * + * - type == VIR_NET_CALL + * * VIR_NET_OK always + * + * - type == VIR_NET_REPLY + * * VIR_NET_OK if RPC finished successfully + * * VIR_NET_ERROR if something failed + * + * - type == VIR_NET_MESSAGE + * * VIR_NET_OK always + * + * - type == VIR_NET_STREAM + * * VIR_NET_CONTINUE if more data is following + * * VIR_NET_OK if stream is complete + * * VIR_NET_ERROR if stream had an error + * + * Payload varies according to type and status: + * + * - type == VIR_NET_CALL + * XXX_args for procedure + * + * - type == VIR_NET_REPLY + * * status == VIR_NET_OK + * XXX_ret for procedure + * * status == VIR_NET_ERROR + * remote_error Error information + * + * - type == VIR_NET_MESSAGE + * * status == VIR_NET_OK + * XXX_args for procedure + * * status == VIR_NET_ERROR + * remote_error Error information + * + * - type == VIR_NET_STREAM + * * status == VIR_NET_CONTINUE + * byte[] raw stream data + * * status == VIR_NET_ERROR + * remote_error error information + * * status == VIR_NET_OK + * <empty> + */ +enum virNetMessageType { + /* client -> server. args from a method call */ + VIR_NET_CALL = 0, + /* server -> client. reply/error from a method call */ + VIR_NET_REPLY = 1, + /* either direction. async notification */ + VIR_NET_MESSAGE = 2, + /* either direction. stream data packet */ + VIR_NET_STREAM = 3 +}; + +enum virNetMessageStatus { + /* Status is always VIR_NET_OK for calls. + * For replies, indicates no error. + */ + VIR_NET_OK = 0, + + /* For replies, indicates that an error happened, and a struct + * remote_error follows. + */ + VIR_NET_ERROR = 1, + + /* For streams, indicates that more data is still expected + */ + VIR_NET_CONTINUE = 2 +}; + +/* 4 byte length word per header */ +const VIR_NET_MESSAGE_HEADER_XDR_LEN = 4; + +struct virNetMessageHeader { + unsigned prog; /* Unique ID for the program */ + unsigned vers; /* Program version number */ + int proc; /* Unique ID for the procedure within the program */ + virNetMessageType type; /* Type of message */ + unsigned serial; /* Serial number of message. */ + virNetMessageStatus status; +}; diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c new file mode 100644 index 0000000..de66103 --- /dev/null +++ b/src/rpc/virnetsocket.c @@ -0,0 +1,715 @@ +/* + * virnetsocket.h: generic network socket handling + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <sys/stat.h> +#include <unistd.h> +#include <netinet/tcp.h> +#include <sys/wait.h> + +#include "virnetsocket.h" +#include "util.h" +#include "memory.h" +#include "virterror_internal.h" +#include "logging.h" +#include "files.h" +#include "event.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + + +struct _virNetSocket { + int fd; + int watch; + pid_t pid; + int errfd; + virNetSocketIOFunc func; + void *opaque; + virSocketAddr localAddr; + virSocketAddr remoteAddr; + char *localAddrStr; + char *remoteAddrStr; +}; + + +static int virNetSocketForkDaemon(const char *binary) +{ + const char *const daemonargs[] = { binary, "--timeout=30", NULL }; + pid_t pid; + + if (virExecDaemonize(daemonargs, NULL, NULL, + &pid, -1, NULL, NULL, + VIR_EXEC_CLEAR_CAPS, + NULL, NULL, NULL) < 0) + return -1; + + return 0; +} + + +static virNetSocketPtr virNetSocketNew(virSocketAddrPtr localAddr, + virSocketAddrPtr remoteAddr, + int fd, int errfd, pid_t pid) +{ + virNetSocketPtr sock; + int no_slow_start = 1; + + if (virSetCloseExec(fd) < 0 || + virSetNonBlock(fd) < 0) + return NULL; + + if (VIR_ALLOC(sock) < 0) { + virReportOOMError(); + return NULL; + } + + sock->localAddr = *localAddr; + if (remoteAddr) + sock->remoteAddr = *remoteAddr; + sock->fd = fd; + sock->errfd = errfd; + sock->pid = pid; + + /* Disable nagle */ + if (sock->localAddr.data.sa.sa_family != AF_UNIX) + setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, + (void *)&no_slow_start, + sizeof(no_slow_start)); + + + if (!(sock->localAddrStr = virSocketFormatAddrFull(localAddr, true, ";"))) { + VIR_FREE(sock); + return NULL; + } + + if (remoteAddr && + !(sock->remoteAddrStr = virSocketFormatAddrFull(remoteAddr, true, ";"))) { + VIR_FREE(sock); + return NULL; + } + + VIR_DEBUG("sock=%p", sock); + + return sock; +} + + +int virNetSocketNewListenTCP(const char *nodename, + const char *service, + virNetSocketPtr **retsocks, + size_t *nretsocks) +{ + virNetSocketPtr *socks = NULL; + size_t nsocks = 0; + struct addrinfo *ai; + struct addrinfo hints; + int fd = -1; + + *retsocks = NULL; + *nretsocks = 0; + + memset (&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + int e = getaddrinfo (nodename, service, &hints, &ai); + if (e != 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to resolve address '%s' service '%s': %s"), + nodename, service, gai_strerror (e)); + goto error; + } + + struct addrinfo *runp = ai; + while (runp) { + virSocketAddr addr; + + if ((fd = socket(runp->ai_family, runp->ai_socktype, + runp->ai_protocol)) < 0) { + virReportSystemError(errno, "%s", _("Unable to create socket")); + goto error; + } + + int opt = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + +#ifdef IPV6_V6ONLY + if (runp->ai_family == PF_INET6) { + int on = 1; + /* + * Normally on Linux an INET6 socket will bind to the INET4 + * address too. If getaddrinfo returns results with INET4 + * first though, this will result in INET6 binding failing. + * We can trivially cope with multiple server sockets, so + * we force it to only listen on IPv6 + */ + setsockopt(fd, IPPROTO_IPV6,IPV6_V6ONLY, + (void*)&on, sizeof on); + } +#endif + + if (bind(fd, runp->ai_addr, runp->ai_addrlen) < 0) { + if (errno != EADDRINUSE) { + virReportSystemError(errno, "%s", _("Unable to bind to port")); + goto error; + } + VIR_FORCE_CLOSE(fd); + continue; + } + + if (getsockname(fd, &addr.data.sa, &addr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + goto error; + } + + + if (VIR_EXPAND_N(socks, nsocks, 1) < 0) { + virReportOOMError(); + goto error; + } + + if (!(socks[nsocks-1] = virNetSocketNew(&addr, NULL, fd, -1, 0))) + goto error; + runp = runp->ai_next; + } + + freeaddrinfo (ai); + + *retsocks = socks; + *nretsocks = nsocks; + return 0; + +error: + VIR_FORCE_CLOSE(fd); + return -1; +} + +int virNetSocketNewListenUNIX(const char *path, + mode_t mask, + gid_t grp, + virNetSocketPtr *retsock) +{ + virSocketAddr addr; + mode_t oldmask; + gid_t oldgrp; + int fd; + + *retsock = NULL; + + memset(&addr, 0, sizeof(addr)); + + addr.len = sizeof(addr.data.un); + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + virReportSystemError(errno, "%s", _("Failed to create socket")); + goto error; + } + + addr.data.un.sun_family = AF_UNIX; + if (virStrcpyStatic(addr.data.un.sun_path, path) == NULL) { + virReportSystemError(ENOMEM, _("Path %s too long for unix socket"), path); + goto error; + } + if (addr.data.un.sun_path[0] == '@') + addr.data.un.sun_path[0] = '\0'; + else + unlink(addr.data.un.sun_path); + + oldgrp = getgid(); + oldmask = umask(~mask); + if (grp != 0 && setgid(grp) < 0) { + virReportSystemError(errno, + _("Failed to set group ID to %d"), grp); + goto error; + } + + if (bind(fd, &addr.data.sa, addr.len) < 0) { + virReportSystemError(errno, + _("Failed to bind socket to '%s'"), + path); + goto error; + } + umask(oldmask); + if (grp != 0 && setgid(oldgrp)) { + virReportSystemError(errno, + _("Failed to restore group ID to %d"), oldgrp); + goto error; + } + + if (!(*retsock = virNetSocketNew(&addr, NULL, fd, -1, 0))) + goto error; + + return 0; + +error: + VIR_FORCE_CLOSE(fd); + return -1; +} + + +int virNetSocketNewConnectTCP(const char *nodename, + const char *service, + virNetSocketPtr *retsock) +{ + virNetSocketPtr *socks = NULL; + size_t nsocks = 0; + struct addrinfo *ai; + struct addrinfo hints; + int fd = -1; + virSocketAddr localAddr; + virSocketAddr remoteAddr; + struct addrinfo *runp; + int savedErrno = ENOENT; + + *retsock = NULL; + + memset(&localAddr, 0, sizeof(localAddr)); + memset(&remoteAddr, 0, sizeof(remoteAddr)); + + memset (&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + int e = getaddrinfo (nodename, service, &hints, &ai); + if (e != 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to resolve address '%s' service '%s': %s"), + nodename, service, gai_strerror (e)); + goto error; + } + + runp = ai; + while (runp) { + int opt = 1; + + if ((fd = socket(runp->ai_family, runp->ai_socktype, + runp->ai_protocol)) < 0) { + virReportSystemError(errno, "%s", _("Unable to create socket")); + goto error; + } + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + + if (connect(fd, runp->ai_addr, runp->ai_addrlen) >= 0) + break; + + savedErrno = errno; + VIR_FORCE_CLOSE(fd); + runp = runp->ai_next; + } + + if (fd == -1) { + virReportSystemError(savedErrno, + _("unable to connect to server at '%s:%s'"), + nodename, service); + return -1; + } + + freeaddrinfo (ai); + + if (getsockname(fd, &localAddr.data.sa, &localAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + goto error; + } + + if (getpeername(fd, &remoteAddr.data.sa, &remoteAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get remote socket name")); + goto error; + } + + if (VIR_EXPAND_N(socks, nsocks, 1) < 0) { + virReportOOMError(); + goto error; + } + + if (!(*retsock = virNetSocketNew(&localAddr, &remoteAddr, fd, -1, 0))) + goto error; + + return 0; + +error: + VIR_FORCE_CLOSE(fd); + return -1; +} + + +int virNetSocketNewConnectUNIX(const char *path, + bool spawnDaemon, + const char *binary, + virNetSocketPtr *retsock) +{ + virSocketAddr localAddr; + virSocketAddr remoteAddr; + int fd; + int retries = 0; + + memset(&localAddr, 0, sizeof(localAddr)); + memset(&remoteAddr, 0, sizeof(remoteAddr)); + + remoteAddr.len = sizeof(remoteAddr.data.un); + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + virReportSystemError(errno, "%s", _("Failed to create socket")); + goto error; + } + + remoteAddr.data.un.sun_family = AF_UNIX; + if (virStrcpyStatic(remoteAddr.data.un.sun_path, path) == NULL) { + virReportSystemError(ENOMEM, _("Path %s too long for unix socket"), path); + goto error; + } + if (remoteAddr.data.un.sun_path[0] == '@') + remoteAddr.data.un.sun_path[0] = '\0'; + else + unlink(remoteAddr.data.un.sun_path); + +retry: + if (connect(fd, &remoteAddr.data.sa, remoteAddr.len) < 0) { + if (errno == ECONNREFUSED && spawnDaemon && retries < 20) { + if (retries == 0 && + virNetSocketForkDaemon(binary) < 0) + goto error; + + retries++; + usleep(1000 * 100 * retries); + goto retry; + } + + virReportSystemError(errno, + _("Failed to connect socket to '%s'"), + path); + goto error; + } + +#if 0 + /* There is no meaningful local addr for UNIX sockets, + * and getsockname() returns something with AF_INET + * in sa_family when run against AF_JUNIX sockets ! + */ + if (getsockname(fd, &localAddr.data.sa, &localAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + goto error; + } +#else + localAddr.data.sa.sa_family = AF_UNIX; +#endif + + VIR_WARN("%d %d", localAddr.len, localAddr.data.sa.sa_family); + + if (!(*retsock = virNetSocketNew(&localAddr, &remoteAddr, fd, -1, 0))) + goto error; + + return 0; + +error: + VIR_FORCE_CLOSE(fd); + return -1; +} + +int virNetSocketNewConnectSSH(const char *nodename, + const char *service, + const char *binary, + const char *username, + bool noTTY, + const char *netcat, + const char *path, + virNetSocketPtr *retsock) +{ + const char **cmdargv = NULL; + int ncmdargv; + + *retsock = NULL; + + ncmdargv = 6; + if (username) ncmdargv += 2; /* For -l username */ + if (noTTY) ncmdargv += 5; /* For -T -o BatchMode=yes -e none */ + if (service) ncmdargv += 2; /* For -p port */ + + /* + * Generate the final command argv[] array. + * ssh [-p $port] [-l $username] $hostname $netcat -U $sockname [NULL] + */ + if (VIR_ALLOC_N(cmdargv, ncmdargv) < 0) + goto no_memory; + + ncmdargv = 0; + cmdargv[ncmdargv++] = binary; + if (service) { + cmdargv[ncmdargv++] = "-p"; + cmdargv[ncmdargv++] = service; + } + if (username) { + cmdargv[ncmdargv++] = "-l"; + cmdargv[ncmdargv++] = username; + } + if (noTTY) { + cmdargv[ncmdargv++] = "-T"; + cmdargv[ncmdargv++] = "-o"; + cmdargv[ncmdargv++] = "BatchMode=yes"; + cmdargv[ncmdargv++] = "-e"; + cmdargv[ncmdargv++] = "none"; + } + cmdargv[ncmdargv++] = nodename; + cmdargv[ncmdargv++] = netcat ? netcat : "nc"; + cmdargv[ncmdargv++] = "-U"; + cmdargv[ncmdargv++] = path; + cmdargv[ncmdargv++] = NULL; + + return virNetSocketNewConnectCommand(cmdargv, NULL, retsock); + +no_memory: + VIR_FREE(cmdargv); + virReportOOMError(); + return -1; +} + + +int virNetSocketNewConnectCommand(const char **cmdargv, + const char **cmdenv, + virNetSocketPtr *retsock) +{ + pid_t pid = 0; + int sv[2]; + int errfd[2]; + + *retsock = NULL; + + /* Fork off the external process. Use socketpair to create a private + * (unnamed) Unix domain socket to the child process so we don't have + * to faff around with two file descriptors (a la 'pipe(2)'). + */ + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) { + virReportSystemError(errno, "%s", + _("unable to create socket pair")); + goto error; + } + + if (pipe(errfd) < 0) { + virReportSystemError(errno, "%s", + _("unable to create socket pair")); + goto error; + } + + if (virExec(cmdargv, cmdenv, NULL, + &pid, sv[1], &(sv[1]), &(errfd[1]), + VIR_EXEC_CLEAR_CAPS) < 0) + goto error; + + /* Parent continues here. */ + VIR_FORCE_CLOSE(sv[1]); + VIR_FORCE_CLOSE(errfd[1]); + + if (!(*retsock = virNetSocketNew(NULL, NULL, sv[0], errfd[0], pid))) + goto error; + + return 0; + +error: + VIR_FORCE_CLOSE(sv[0]); + VIR_FORCE_CLOSE(sv[1]); + VIR_FORCE_CLOSE(errfd[1]); + VIR_FORCE_CLOSE(errfd[1]); + + if (pid > 0) { + pid_t reap; + do { +retry: + reap = waitpid(pid, NULL, 0); + if (reap == -1 && errno == EINTR) + goto retry; + } while (reap != -1 && reap != pid); + } + + return -1; +} + + +void virNetSocketFree(virNetSocketPtr sock) +{ + VIR_DEBUG("sock=%p", sock); + + if (!sock) + return; + + if (sock->watch) { + virEventRemoveHandle(sock->watch); + sock->watch = -1; + } + + if (sock->localAddr.data.sa.sa_family == AF_UNIX && + sock->localAddr.data.un.sun_path[0] != '\0') + unlink(sock->localAddr.data.un.sun_path); + + VIR_FORCE_CLOSE(sock->fd); + VIR_FORCE_CLOSE(sock->errfd); + + if (sock->pid > 0) { + pid_t reap; + kill(sock->pid, SIGTERM); + do { +retry: + reap = waitpid(sock->pid, NULL, 0); + if (reap == -1 && errno == EINTR) + goto retry; + } while (reap != -1 && reap != sock->pid); + } + + VIR_FREE(sock->localAddrStr); + VIR_FREE(sock->remoteAddrStr); + + VIR_FREE(sock); +} + + +int virNetSocketFD(virNetSocketPtr sock) +{ + return sock->fd; +} + + +const char *virNetSocketLocalAddrString(virNetSocketPtr sock) +{ + return sock->localAddrStr; +} + +const char *virNetSocketRemoteAddrString(virNetSocketPtr sock) +{ + return sock->remoteAddrStr; +} + +ssize_t virNetSocketRead(virNetSocketPtr sock, char *buf, size_t len) +{ + return read(sock->fd, buf, len); +} + +ssize_t virNetSocketWrite(virNetSocketPtr sock, const char *buf, size_t len) +{ + return write(sock->fd, buf, len); +} + + +int virNetSocketListen(virNetSocketPtr sock) +{ + if (listen(sock->fd, 30) < 0) { + virReportSystemError(errno, "%s", _("Unable to listen on socket")); + return -1; + } + return 0; +} + +int virNetSocketAccept(virNetSocketPtr sock, virNetSocketPtr *clientsock) +{ + int fd; + virSocketAddr localAddr; + virSocketAddr remoteAddr; + + *clientsock = NULL; + + memset(&localAddr, 0, sizeof(localAddr)); + memset(&remoteAddr, 0, sizeof(remoteAddr)); + + remoteAddr.len = sizeof(remoteAddr.data.stor); + if ((fd = accept(sock->fd, &remoteAddr.data.sa, &remoteAddr.len)) < 0) { + if (errno == ECONNABORTED || + errno == EAGAIN) + return 0; + + virReportSystemError(errno, "%s", + _("Unable to accept client")); + return -1; + } + + if (getsockname(fd, &localAddr.data.sa, &localAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + VIR_FORCE_CLOSE(fd); + return -1; + } + + + if (!(*clientsock = virNetSocketNew(&localAddr, &remoteAddr, fd, -1, 0))) + return -1; + + return 0; +} + + +static void virNetSocketEventHandle(int fd ATTRIBUTE_UNUSED, + int watch ATTRIBUTE_UNUSED, + int events, + void *opaque) +{ + virNetSocketPtr sock = opaque; + + sock->func(sock, events, sock->opaque); +} + +int virNetSocketAddIOCallback(virNetSocketPtr sock, + int events, + virNetSocketIOFunc func, + void *opaque) +{ + if (sock->watch) { + VIR_DEBUG("Watch already registered on socket %p", sock); + return -1; + } + + if ((sock->watch = virEventAddHandle(sock->fd, + events, + virNetSocketEventHandle, + sock, + NULL)) < 0) { + VIR_WARN("Failed to register watch on socket %p", sock); + return -1; + } + sock->func = func; + sock->opaque = opaque; + + return 0; +} + +void virNetSocketUpdateIOCallback(virNetSocketPtr sock, + int events) +{ + if (!sock->watch) { + VIR_DEBUG("Watch not registered on socket %p", sock); + return; + } + + virEventUpdateHandle(sock->watch, events); +} + +void virNetSocketRemoveIOCallback(virNetSocketPtr sock) +{ + if (!sock->watch) { + VIR_DEBUG("Watch not registered on socket %p", sock); + return; + } + + virEventRemoveHandle(sock->watch); +} + diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h new file mode 100644 index 0000000..a25918d --- /dev/null +++ b/src/rpc/virnetsocket.h @@ -0,0 +1,97 @@ +/* + * virnetsocket.h: generic network socket handling + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __VIR_NET_SOCKET_H__ +#define __VIR_NET_SOCKET_H__ + +#include "network.h" + +typedef struct _virNetSocket virNetSocket; +typedef virNetSocket *virNetSocketPtr; + + +typedef void (*virNetSocketIOFunc)(virNetSocketPtr sock, + int events, + void *opaque); + + +int virNetSocketNewListenTCP(const char *nodename, + const char *service, + virNetSocketPtr **addrs, + size_t *naddrs); + +int virNetSocketNewListenUNIX(const char *path, + mode_t mask, + gid_t grp, + virNetSocketPtr *addr); + +int virNetSocketNewConnectTCP(const char *nodename, + const char *service, + virNetSocketPtr *addr); + +int virNetSocketNewConnectUNIX(const char *path, + bool spawnDaemon, + const char *binary, + virNetSocketPtr *addr); + +int virNetSocketNewConnectSSH(const char *nodename, + const char *service, + const char *binary, + const char *username, + bool noTTY, + const char *netcat, + const char *path, + virNetSocketPtr *addr); + +int virNetSocketNewConnectCommand(const char **cmdargv, + const char **cmdenv, + virNetSocketPtr *addr); + +/* XXX bad */ +int virNetSocketFD(virNetSocketPtr sock); + +ssize_t virNetSocketRead(virNetSocketPtr sock, char *buf, size_t len); +ssize_t virNetSocketWrite(virNetSocketPtr sock, const char *buf, size_t len); + +void virNetSocketFree(virNetSocketPtr sock); + +const char *virNetSocketLocalAddrString(virNetSocketPtr sock); +const char *virNetSocketRemoteAddrString(virNetSocketPtr sock); + +int virNetSocketListen(virNetSocketPtr sock); +int virNetSocketAccept(virNetSocketPtr sock, + virNetSocketPtr *clientsock); + +int virNetSocketAddIOCallback(virNetSocketPtr sock, + int events, + virNetSocketIOFunc func, + void *opaque); + +void virNetSocketUpdateIOCallback(virNetSocketPtr sock, + int events); + +void virNetSocketRemoveIOCallback(virNetSocketPtr sock); + + + +#endif /* __VIR_NET_SOCKET_H__ */ diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c new file mode 100644 index 0000000..9690328 --- /dev/null +++ b/src/rpc/virnettlscontext.c @@ -0,0 +1,611 @@ + + +#include <config.h> + +#include <unistd.h> +#include <fnmatch.h> +#include <stdlib.h> + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include "gnutls_1_0_compat.h" + +#include "virnettlscontext.h" + +#include "memory.h" +#include "virterror_internal.h" +#include "util.h" +#include "logging.h" + +#define DH_BITS 1024 + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +struct _virNetTLSContext { + int refs; + + gnutls_certificate_credentials_t x509cred; + gnutls_dh_params_t dhParams; + + bool isServer; + bool requireValidCert; + const char *const*x509dnWhitelist; +}; + +struct _virNetTLSSession { + int refs; + + char *hostname; + gnutls_session_t session; + virNetTLSSessionWriteFunc writeFunc; + virNetTLSSessionReadFunc readFunc; + void *opaque; +}; + + +static int +virNetTLSContextCheckCertFile(const char *type, const char *file) +{ + if (access(file, R_OK) < 0) { + virReportSystemError(errno, + _("Cannot read %s '%s'"), + type, file); + return -1; + } + return 0; +} + + +static void virNetTLSLog(int level, const char *str) { + VIR_DEBUG("%d %s", level, str); +} + +static virNetTLSContextPtr virNetTLSContextNew(const char *ca_file, + const char *crl_file, + const char *cert_file, + const char *key_file, + const char *const*x509dnWhitelist, + bool requireValidCert, + bool isServer) +{ + virNetTLSContextPtr ctxt; + char *gnutlsdebug; + int err; + + if (VIR_ALLOC(ctxt) < 0) { + virReportOOMError(); + return NULL; + } + + ctxt->refs = 1; + + /* Initialise GnuTLS. */ + gnutls_global_init(); + + if ((gnutlsdebug = getenv("LIBVIRT_GNUTLS_DEBUG")) != NULL) { + int val; + if (virStrToLong_i(gnutlsdebug, NULL, 10, &val) < 0) + val = 10; + gnutls_global_set_log_level(val); + gnutls_global_set_log_function(virNetTLSLog); + } + + + err = gnutls_certificate_allocate_credentials(&ctxt->x509cred); + if (err) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to allocate x509 credentials: %s"), + gnutls_strerror (err)); + goto error; + } + + if (ca_file && ca_file[0] != '\0') { + if (virNetTLSContextCheckCertFile("CA certificate", ca_file) < 0) + goto error; + + VIR_DEBUG("loading CA cert from %s", ca_file); + err = gnutls_certificate_set_x509_trust_file(ctxt->x509cred, + ca_file, + GNUTLS_X509_FMT_PEM); + if (err < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to set x509 CA certificate: %s"), + gnutls_strerror (err)); + goto error; + } + } + + if (crl_file && crl_file[0] != '\0') { + if (virNetTLSContextCheckCertFile("CA revocation list", crl_file) < 0) + goto error; + + VIR_DEBUG("loading CRL from %s", crl_file); + err = gnutls_certificate_set_x509_crl_file(ctxt->x509cred, + crl_file, + GNUTLS_X509_FMT_PEM); + if (err < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to set x509 certificate revocation list: %s"), + gnutls_strerror (err)); + goto error; + } + } + + if (cert_file && cert_file[0] != '\0' && key_file && key_file[0] != '\0') { + if (virNetTLSContextCheckCertFile("server certificate", cert_file) < 0) + goto error; + if (virNetTLSContextCheckCertFile("server key", key_file) < 0) + goto error; + VIR_DEBUG("loading cert and key from %s and %s", cert_file, key_file); + err = + gnutls_certificate_set_x509_key_file(ctxt->x509cred, + cert_file, key_file, + GNUTLS_X509_FMT_PEM); + if (err < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to set x509 key and certificate: %s"), + gnutls_strerror (err)); + goto error; + } + } + + /* Generate Diffie Hellman parameters - for use with DHE + * kx algorithms. These should be discarded and regenerated + * once a day, once a week or once a month. Depending on the + * security requirements. + */ + if (isServer) { + err = gnutls_dh_params_init(&ctxt->dhParams); + if (err < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to initialize diffie-hellman parameters: %s"), + gnutls_strerror (err)); + goto error; + } + err = gnutls_dh_params_generate2(ctxt->dhParams, DH_BITS); + if (err < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to generate diffie-hellman parameters: %s"), + gnutls_strerror (err)); + goto error; + } + + gnutls_certificate_set_dh_params(ctxt->x509cred, + ctxt->dhParams); + } + + ctxt->requireValidCert = requireValidCert; + ctxt->x509dnWhitelist = x509dnWhitelist; + ctxt->isServer = isServer; + + return ctxt; + +error: + if (isServer) + gnutls_dh_params_deinit(ctxt->dhParams); + gnutls_certificate_free_credentials(ctxt->x509cred); + VIR_FREE(ctxt); + return NULL; +} + + +virNetTLSContextPtr virNetTLSContextNewServer(const char *ca_file, + const char *crl_file, + const char *cert_file, + const char *key_file, + const char *const*x509dnWhitelist, + bool requireValidCert) +{ + return virNetTLSContextNew(ca_file, crl_file, cert_file, key_file, + x509dnWhitelist, requireValidCert, true); +} + +virNetTLSContextPtr virNetTLSContextNewClient(const char *ca_file, + const char *cert_file, + const char *key_file, + bool requireValidCert) +{ + return virNetTLSContextNew(ca_file, NULL, cert_file, key_file, + NULL, requireValidCert, false); +} + + +void virNetTLSContextRef(virNetTLSContextPtr ctxt) +{ + ctxt->refs++; +} + + +/* Check DN is on tls_allowed_dn_list. */ +static int +virNetTLSContextCheckDN(virNetTLSContextPtr ctxt, + const char *dname) +{ + const char *const*wildcards; + + /* If the list is not set, allow any DN. */ + wildcards = ctxt->x509dnWhitelist; + if (!wildcards) + return 1; + + while (*wildcards) { + if (fnmatch (*wildcards, dname, 0) == 0) + return 1; + wildcards++; + } + + /* Print the client's DN. */ + DEBUG(_("Failed whitelist check for client DN '%s'"), dname); + + return 0; // Not found. +} + +static int virNetTLSContextValidCertificate(virNetTLSContextPtr ctxt, + virNetTLSSessionPtr sess) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + char name[256]; + size_t namesize = sizeof name; + + memset(name, 0, namesize); + + if ((ret = gnutls_certificate_verify_peers2(sess->session, &status)) < 0){ + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to verify TLS peer: %s"), + gnutls_strerror(ret)); + goto authdeny; + } + + if ((now = time(NULL)) == ((time_t)-1)) { + virReportSystemError(errno, "%s", + _("cannot get current time")); + goto authfail; + } + + if (status != 0) { + const char *reason = _("Invalid certificate"); + + if (status & GNUTLS_CERT_INVALID) + reason = _("The certificate is not trusted."); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + reason = _("The certificate hasn't got a known issuer."); + + if (status & GNUTLS_CERT_REVOKED) + reason = _("The certificate has been revoked."); + +#ifndef GNUTLS_1_0_COMPAT + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + reason = _("The certificate uses an insecure algorithm"); +#endif + + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate failed validation: %s"), + reason); + goto authdeny; + } + + if (gnutls_certificate_type_get(sess->session) != GNUTLS_CRT_X509) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("Only x509 certificates are supported")); + goto authdeny; + } + + if (!(certs = gnutls_certificate_get_peers(sess->session, &nCerts))) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("The certificate has no peers")); + goto authdeny; + } + + for (i = 0; i < nCerts; i++) { + gnutls_x509_crt_t cert; + + if (gnutls_x509_crt_init (&cert) < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("Unable to initialize certificate")); + goto authfail; + } + + if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("Unable to load certificate")); + gnutls_x509_crt_deinit(cert); + goto authfail; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("The client certificate has expired")); + gnutls_x509_crt_deinit(cert); + goto authdeny; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("The client certificate is not yet active")); + gnutls_x509_crt_deinit(cert); + goto authdeny; + } + + if (i == 0) { + ret = gnutls_x509_crt_get_dn(cert, name, &namesize); + if (ret != 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Failed to get certificate distinguished name: %s"), + gnutls_strerror(ret)); + gnutls_x509_crt_deinit(cert); + goto authfail; + } + + if (!virNetTLSContextCheckDN(ctxt, name)) { + /* This is the most common error: make it informative. */ + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("Client's Distinguished Name is not on the list " + "of allowed clients (tls_allowed_dn_list). Use " + "'certtool -i --infile clientcert.pem' to view the" + "Distinguished Name field in the client certificate," + "or run this daemon with --verbose option.")); + gnutls_x509_crt_deinit(cert); + goto authdeny; + } + + if (sess->hostname && + !gnutls_x509_crt_check_hostname(cert, sess->hostname)) { + virNetError(VIR_ERR_RPC, + _("Certificate's owner does not match the hostname (%s)"), + sess->hostname); + gnutls_x509_crt_deinit(cert); + goto authdeny; + } + } + } + +#if 0 + PROBE(CLIENT_TLS_ALLOW, "fd=%d, name=%s", client->fd, (char *)name); +#endif + return 0; + +authdeny: +#if 0 + PROBE(CLIENT_TLS_DENY, "fd=%d, name=%s", client->fd, (char *)name); +#endif + return -1; + +authfail: +#if 0 + PROBE(CLIENT_TLS_FAIL, "fd=%d", client->fd); +#endif + return -1; +} + +int virNetTLSContextCheckCertificate(virNetTLSContextPtr ctxt, + virNetTLSSessionPtr sess) { + if (virNetTLSContextValidCertificate(ctxt, sess) < 0) { + if (ctxt->requireValidCert) { + virNetError(VIR_ERR_AUTH_FAILED, "%s", + _("Failed to verify peer's certificate")); + return -1; + } + VIR_INFO0(_("Ignoring bad certificate at user request")); + } + return 0; +} + +void virNetTLSContextFree(virNetTLSContextPtr ctxt) +{ + if (!ctxt) + return; + + ctxt->refs--; + if (ctxt->refs > 0) + return; + + gnutls_dh_params_deinit(ctxt->dhParams); + gnutls_certificate_free_credentials(ctxt->x509cred); + VIR_FREE(ctxt); +} + + + +static ssize_t +virNetTLSSessionPush(void *opaque, const void *buf, size_t len) +{ + virNetTLSSessionPtr sess = opaque; + return sess->writeFunc(buf, len, sess->opaque); +} + + +static ssize_t +virNetTLSSessionPull(void *opaque, void *buf, size_t len) +{ + virNetTLSSessionPtr sess = opaque; + return sess->readFunc(buf, len, sess->opaque); +} + + +virNetTLSSessionPtr virNetTLSSessionNew(virNetTLSContextPtr ctxt, + const char *hostname, + virNetTLSSessionWriteFunc writeFunc, + virNetTLSSessionReadFunc readFunc, + void *opaque) +{ + virNetTLSSessionPtr sess; + int err; + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + + if (VIR_ALLOC(sess) < 0) { + virReportOOMError(); + return NULL; + } + + sess->refs = 1; + sess->writeFunc = writeFunc; + sess->readFunc = readFunc; + sess->opaque = opaque; + if (!(sess->hostname = strdup(hostname))) { + virReportOOMError(); + goto error; + } + + if ((err = gnutls_init(&sess->session, + ctxt->isServer ? GNUTLS_SERVER : GNUTLS_CLIENT)) != 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Failed to initialize TLS session: %s"), + gnutls_strerror(err)); + goto error; + } + + /* avoid calling all the priority functions, since the defaults + * are adequate. + */ + if ((err = gnutls_set_default_priority(sess->session)) != 0 || + (err = gnutls_certificate_type_set_priority(sess->session, + cert_type_priority))) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Failed to set TLS session priority %s"), + gnutls_strerror(err)); + goto error; + } + + if ((err = gnutls_credentials_set(sess->session, + GNUTLS_CRD_CERTIFICATE, + ctxt->x509cred)) != 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Failed set TLS x509 credentials: %s"), + gnutls_strerror(err)); + goto error; + } + + /* request client certificate if any. + */ + if (ctxt->isServer) { + gnutls_certificate_server_set_request(sess->session, GNUTLS_CERT_REQUEST); + + gnutls_dh_set_prime_bits(sess->session, DH_BITS); + } + + gnutls_transport_set_ptr(sess->session, sess); + gnutls_transport_set_push_function(sess->session, + virNetTLSSessionPush); + gnutls_transport_set_pull_function(sess->session, + virNetTLSSessionPull); + + return sess; + +error: + virNetTLSSessionFree(sess); + return NULL; +} + + +void virNetTLSSessionRef(virNetTLSSessionPtr sess) +{ + sess->refs++; +} + + +ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess, + const char *buf, size_t len) +{ + int ret; + ret = gnutls_record_send(sess->session, buf, len); + + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + case 0: + break; + default: + errno = EIO; + } + + return ret >= 0 ? ret : -1; +} + +ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess, + char *buf, size_t len) +{ + int ret; + + ret = gnutls_record_recv(sess->session, buf, len); + + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + case 0: + break; + default: + errno = EIO; + } + + return ret >= 0 ? ret : -1; +} + +int virNetTLSSessionHandshake(virNetTLSSessionPtr sess) +{ + int ret = gnutls_handshake(sess->session); + + if (ret == 0) + return 0; + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) + return 1; + + virNetError(VIR_ERR_AUTH_FAILED, + _("TLS handshake failed %s"), + gnutls_strerror (ret)); + return -1; +} + +int virNetTLSSessionHandshakeDirection(virNetTLSSessionPtr sess) +{ + if (gnutls_record_get_direction (sess->session) == 0) + return 0; + else + return 1; +} + +int virNetTLSSessionGetKeySize(virNetTLSSessionPtr sess) +{ + gnutls_cipher_algorithm_t cipher; + int ssf; + + cipher = gnutls_cipher_get(sess->session); + if (!(ssf = gnutls_cipher_get_key_size(cipher))) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid cipher size for TLS session")); + return -1; + } + + return ssf; +} + + +void virNetTLSSessionFree(virNetTLSSessionPtr sess) +{ + if (!sess) + return; + + sess->refs--; + if (sess->refs > 0) + return; + + VIR_FREE(sess->hostname); + gnutls_deinit(sess->session); + VIR_FREE(sess); +} diff --git a/src/rpc/virnettlscontext.h b/src/rpc/virnettlscontext.h new file mode 100644 index 0000000..b93b379 --- /dev/null +++ b/src/rpc/virnettlscontext.h @@ -0,0 +1,63 @@ + + +#ifndef __VIR_NET_TLS_CONTEXT_H__ +#define __VIR_NET_TLS_CONTEXT_H__ + +#include <stdbool.h> +#include <sys/types.h> + +typedef struct _virNetTLSContext virNetTLSContext; +typedef virNetTLSContext *virNetTLSContextPtr; + +typedef struct _virNetTLSSession virNetTLSSession; +typedef virNetTLSSession *virNetTLSSessionPtr; + + +virNetTLSContextPtr virNetTLSContextNewServer(const char *ca_file, + const char *crl_file, + const char *cert_file, + const char *key_file, + const char *const*x509dnWhitelist, + bool requireValidCert); + +virNetTLSContextPtr virNetTLSContextNewClient(const char *ca_file, + const char *cert_file, + const char *key_file, + bool requireValidCert); + +void virNetTLSContextRef(virNetTLSContextPtr ctxt); + +int virNetTLSContextCheckCertificate(virNetTLSContextPtr ctxt, + virNetTLSSessionPtr sess); + +void virNetTLSContextFree(virNetTLSContextPtr ctxt); + + +typedef ssize_t (*virNetTLSSessionWriteFunc)(const char *buf, size_t len, + void *opaque); +typedef ssize_t (*virNetTLSSessionReadFunc)(char *buf, size_t len, + void *opaque); + +virNetTLSSessionPtr virNetTLSSessionNew(virNetTLSContextPtr ctxt, + const char *hostname, + virNetTLSSessionWriteFunc writeFunc, + virNetTLSSessionReadFunc readFunc, + void *opaque); + +void virNetTLSSessionRef(virNetTLSSessionPtr sess); + +ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess, + const char *buf, size_t len); +ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess, + char *buf, size_t len); + +int virNetTLSSessionHandshake(virNetTLSSessionPtr sess); + +int virNetTLSSessionHandshakeDirection(virNetTLSSessionPtr sess); + +int virNetTLSSessionGetKeySize(virNetTLSSessionPtr sess); + +void virNetTLSSessionFree(virNetTLSSessionPtr sess); + + +#endif -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
Introduces a set of generic objects which are to be used in building RPC servers/clients based on XDR.
- virNetMessageHeader - standardize the XDR format for any RPC program. Copied from remote protocol for back compat
- virNetMessage - Provides a buffer for (de-)serializing messages, and a copy of the decoded virNetMessageHeader. Provides APIs for encoding/decoding message headers and payloads, thus isolating all the XDR api calls in one file. Callers no longer need to use XDR themselves.
- virNetSocket - a wrapper around a socket file descriptor, to simplify creation of new sockets, both for clients and services. Encapsulates all the hairy getaddrinfo code and sockaddr manipulation. Will eventually include transparent support for TLS and SASL encoding of data
- virNetTLSContext - encapsulates the credentials required to setup TLS sessions. eg the set of x509 certificates and keys, optional DH parameters and x509 DName whitelist Provides APIs for easily validating certificates from a TLS session
- virNetTLSSession - encapsulates the TLS session handling, so that callers no longer have a direct dependancy on gnutls. This will facilitate adding alternate TLS impls. Makes the read/write TLS functions work with same semantics as the native socket read/write functions. ie they set errno, instead of a gnutls specific error code.
Is it worth introducing these in separate patches, instead of all in one go? At any rate, this is big enough that I haven't reviewed it in detail yet, but the concept of factoring out the common code seems nice. Let's make a deal :) If you look at my virCommand series, I'll look at this! -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Wed, Dec 01, 2010 at 12:30:25PM -0700, Eric Blake wrote:
On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
Introduces a set of generic objects which are to be used in building RPC servers/clients based on XDR.
- virNetMessageHeader - standardize the XDR format for any RPC program. Copied from remote protocol for back compat
- virNetMessage - Provides a buffer for (de-)serializing messages, and a copy of the decoded virNetMessageHeader. Provides APIs for encoding/decoding message headers and payloads, thus isolating all the XDR api calls in one file. Callers no longer need to use XDR themselves.
- virNetSocket - a wrapper around a socket file descriptor, to simplify creation of new sockets, both for clients and services. Encapsulates all the hairy getaddrinfo code and sockaddr manipulation. Will eventually include transparent support for TLS and SASL encoding of data
- virNetTLSContext - encapsulates the credentials required to setup TLS sessions. eg the set of x509 certificates and keys, optional DH parameters and x509 DName whitelist Provides APIs for easily validating certificates from a TLS session
- virNetTLSSession - encapsulates the TLS session handling, so that callers no longer have a direct dependancy on gnutls. This will facilitate adding alternate TLS impls. Makes the read/write TLS functions work with same semantics as the native socket read/write functions. ie they set errno, instead of a gnutls specific error code.
Is it worth introducing these in separate patches, instead of all in one go? At any rate, this is big enough that I haven't reviewed it in detail yet, but the concept of factoring out the common code seems nice.
Yep, I could probably split this into 3 patches, without too much pain Daniel

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
Introduces a set of generic objects which are to be used in building RPC servers/clients based on XDR.
- virNetMessageHeader - standardize the XDR format for any RPC program. Copied from remote protocol for back compat
- virNetMessage - Provides a buffer for (de-)serializing messages, and a copy of the decoded virNetMessageHeader. Provides APIs for encoding/decoding message headers and payloads, thus isolating all the XDR api calls in one file. Callers no longer need to use XDR themselves.
- virNetSocket - a wrapper around a socket file descriptor, to simplify creation of new sockets, both for clients and services. Encapsulates all the hairy getaddrinfo code and sockaddr manipulation. Will eventually include transparent support for TLS and SASL encoding of data
- virNetTLSContext - encapsulates the credentials required to setup TLS sessions. eg the set of x509 certificates and keys, optional DH parameters and x509 DName whitelist Provides APIs for easily validating certificates from a TLS session
- virNetTLSSession - encapsulates the TLS session handling, so that callers no longer have a direct dependancy on gnutls. This will facilitate adding alternate TLS impls. Makes the read/write TLS functions work with same semantics as the native socket read/write functions. ie they set errno, instead of a gnutls specific error code.
This code is taken from either the daemon/libvirtd.c, daemon/dispatch.c or src/remote/remote_driver.c files, which all duplicated alot of functionality.
Whether or not you decide to break this into multiple patches (one per API addition) or keep it as one (since this is all pretty much pure addition), here's my first round of review:
@@ -1098,6 +1116,28 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
+ +noinst_LTLIBRARIES += libvirt-net-rpc.la + +libvirt_net_rpc_la_SOURCES = \ + ../daemon/event.c \ + rpc/virnetprotocol.h rpc/virnetprotocol.c \ + rpc/virnetmessage.h rpc/virnetmessage.c \ + rpc/virnettlscontext.h rpc/virnettlscontext.c \ + rpc/virnetsocket.h rpc/virnetsocket.c +libvirt_net_rpc_la_CFLAGS = \ + $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ + $(AM_CFLAGS)
If my cygwin patch is approved first, this will need $(XDR_CFLAGS). https://www.redhat.com/archives/libvir-list/2010-December/msg00404.html
+++ b/src/rpc/virnetmessage.c @@ -0,0 +1,215 @@ +#include <config.h> + +#include "virnetmessage.h" + +#include "virterror_internal.h" +#include "logging.h" + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) +
Does this need #define VIR_FROM_THIS VIR_FROM_RPC? Should virNetError be added to the list of error() functions in cfg.mk?
+++ b/src/rpc/virnetmessage.h @@ -0,0 +1,31 @@ +#ifndef __VIR_NET_MESSAGE_H__ +#define __VIR_NET_MESSAGE_H__ + +#include "virnetprotocol.h" + +typedef struct virNetMessageHeader *virNetMessageHeaderPtr; + +typedef struct _virNetMessage virNetMessage; +typedef virNetMessage *virNetMessagePtr; + +struct _virNetMessage { + char buffer[VIR_NET_MESSAGE_MAX + VIR_NET_MESSAGE_LEN_MAX]; + unsigned int bufferLength; + unsigned int bufferOffset; + + virNetMessageHeader header; +};
That's a big struct; where lots of space will typically be unused. It should never be stack-allocated. Should we rearrange the fields to put buffer last, and then use variable-sized array (or something similar) to trim the size down to what is needed, rather than always allocating the largest possible message?
+ +static int virNetSocketForkDaemon(const char *binary) +{ + const char *const daemonargs[] = { binary, "--timeout=30", NULL }; + pid_t pid; + + if (virExecDaemonize(daemonargs, NULL, NULL, + &pid, -1, NULL, NULL, + VIR_EXEC_CLEAR_CAPS, + NULL, NULL, NULL) < 0) + return -1;
Should this use virCommand instead?
+ +#if 0 + /* There is no meaningful local addr for UNIX sockets, + * and getsockname() returns something with AF_INET + * in sa_family when run against AF_JUNIX sockets !
Is it worth copying this typo around?
+ +int virNetSocketNewConnectSSH(const char *nodename, + const char *service, + const char *binary, + const char *username, + bool noTTY, + const char *netcat, + const char *path, + virNetSocketPtr *retsock) +{ + const char **cmdargv = NULL; + int ncmdargv; + + *retsock = NULL; + + ncmdargv = 6; + if (username) ncmdargv += 2; /* For -l username */ + if (noTTY) ncmdargv += 5; /* For -T -o BatchMode=yes -e none */ + if (service) ncmdargv += 2; /* For -p port */
Wow - this would be much easier to write with virCommand.
+ +int virNetSocketNewConnectCommand(const char **cmdargv, + const char **cmdenv, + virNetSocketPtr *retsock) +{
Which means this should take a virCommandPtr, rather than a cmdargv and cmdenv.
+void virNetSocketFree(virNetSocketPtr sock) +{ + VIR_DEBUG("sock=%p", sock); + + if (!sock) + return; + + if (sock->watch) { + virEventRemoveHandle(sock->watch); + sock->watch = -1; + }
Should this be sock->watch = 0; after removing the handle, or should the condition be if (sock->watch > 0)?
+ +int virNetSocketListen(virNetSocketPtr sock) +{ + if (listen(sock->fd, 30) < 0) {
Why 30 and not some other magic number? Why not let the caller pass in their desired backlog parameter?
+int virNetSocketAddIOCallback(virNetSocketPtr sock, + int events, + virNetSocketIOFunc func, + void *opaque) +{ + if (sock->watch) {
Again, should this be sock->watch > 0?
+ +/* XXX bad */ +int virNetSocketFD(virNetSocketPtr sock);
Is your intent to remove just this one function, to force all fd usage to go through the wrappers instead?
+ +#endif /* __VIR_NET_SOCKET_H__ */
Should you be marking some parameters ATTRIBUTE_NONNULL in this header?
+ +ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess, + const char *buf, size_t len) +{ + int ret;
s/int/ssize_t/
+ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess, + char *buf, size_t len) +{ + int ret;
s/int/ssize_t/ -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Thu, Dec 09, 2010 at 03:29:20PM -0700, Eric Blake wrote:
@@ -1098,6 +1116,28 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
+ +noinst_LTLIBRARIES += libvirt-net-rpc.la + +libvirt_net_rpc_la_SOURCES = \ + ../daemon/event.c \ + rpc/virnetprotocol.h rpc/virnetprotocol.c \ + rpc/virnetmessage.h rpc/virnetmessage.c \ + rpc/virnettlscontext.h rpc/virnettlscontext.c \ + rpc/virnetsocket.h rpc/virnetsocket.c +libvirt_net_rpc_la_CFLAGS = \ + $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ + $(AM_CFLAGS)
If my cygwin patch is approved first, this will need $(XDR_CFLAGS). https://www.redhat.com/archives/libvir-list/2010-December/msg00404.html
That patch isn't in yet I see.
+++ b/src/rpc/virnetmessage.c @@ -0,0 +1,215 @@ +#include <config.h> + +#include "virnetmessage.h" + +#include "virterror_internal.h" +#include "logging.h" + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) +
Does this need #define VIR_FROM_THIS VIR_FROM_RPC?
Only if it uses virReportSystemError or virReportOOMError(), which it does now.
Should virNetError be added to the list of error() functions in cfg.mk?
Probably, I forgot to make this change in the new series
+++ b/src/rpc/virnetmessage.h @@ -0,0 +1,31 @@ +#ifndef __VIR_NET_MESSAGE_H__ +#define __VIR_NET_MESSAGE_H__ + +#include "virnetprotocol.h" + +typedef struct virNetMessageHeader *virNetMessageHeaderPtr; + +typedef struct _virNetMessage virNetMessage; +typedef virNetMessage *virNetMessagePtr; + +struct _virNetMessage { + char buffer[VIR_NET_MESSAGE_MAX + VIR_NET_MESSAGE_LEN_MAX]; + unsigned int bufferLength; + unsigned int bufferOffset; + + virNetMessageHeader header; +};
That's a big struct; where lots of space will typically be unused. It should never be stack-allocated. Should we rearrange the fields to put buffer last, and then use variable-sized array (or something similar) to trim the size down to what is needed, rather than always allocating the largest possible message?
This struct is always allocated on the heap. Making it dynamically sized is tricky. When serializing a message to XDR form, we need to allocate enough data before serialization, but we've no idea how much is big enough, so we have to allocate the full size. We could re-alloc smaller afterwards I guess. When de-serializing we could do an incremental allocation, because we read the header and get the total size and then read the payload.
+static int virNetSocketForkDaemon(const char *binary) +{ + const char *const daemonargs[] = { binary, "--timeout=30", NULL }; + pid_t pid; + + if (virExecDaemonize(daemonargs, NULL, NULL, + &pid, -1, NULL, NULL, + VIR_EXEC_CLEAR_CAPS, + NULL, NULL, NULL) < 0) + return -1;
Should this use virCommand instead?
This was just copying existing code, but I've changed it now to use virCommand...not actually tested my new code yet though.
+void virNetSocketFree(virNetSocketPtr sock) +{ + VIR_DEBUG("sock=%p", sock); + + if (!sock) + return; + + if (sock->watch) { + virEventRemoveHandle(sock->watch); + sock->watch = -1; + }
Should this be sock->watch = 0; after removing the handle, or should the condition be if (sock->watch > 0)?
Yep, the latter.
+int virNetSocketListen(virNetSocketPtr sock) +{ + if (listen(sock->fd, 30) < 0) {
Why 30 and not some other magic number? Why not let the caller pass in their desired backlog parameter?
The listen backlog is pretty arbitrary. None of our use cases have such high connection rates that we'd really need to tune this any higher, so I think the arbitrary limit is fine.
+/* XXX bad */ +int virNetSocketFD(virNetSocketPtr sock);
Is your intent to remove just this one function, to force all fd usage to go through the wrappers instead?
I had to keep this for the DTrace APIs and one other place in the client code which needs an FD to poll() on. Ideally we could address the latter, but the former is always going ot be required. So I removed the XXX.
+ +#endif /* __VIR_NET_SOCKET_H__ */
Should you be marking some parameters ATTRIBUTE_NONNULL in this header?
Possibly, but I've not considered it yet.
+ +ssize_t virNetTLSSessionWrite(virNetTLSSessionPtr sess, + const char *buf, size_t len) +{ + int ret;
s/int/ssize_t/
+ssize_t virNetTLSSessionRead(virNetTLSSessionPtr sess, + char *buf, size_t len) +{ + int ret;
s/int/ssize_t/
Done this and many many more Daniel

On 12/16/2010 04:34 AM, Daniel P. Berrange wrote:
On Thu, Dec 09, 2010 at 03:29:20PM -0700, Eric Blake wrote:
@@ -1098,6 +1116,28 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
+ +noinst_LTLIBRARIES += libvirt-net-rpc.la + +libvirt_net_rpc_la_SOURCES = \ + ../daemon/event.c \ + rpc/virnetprotocol.h rpc/virnetprotocol.c \ + rpc/virnetmessage.h rpc/virnetmessage.c \ + rpc/virnettlscontext.h rpc/virnettlscontext.c \ + rpc/virnetsocket.h rpc/virnetsocket.c +libvirt_net_rpc_la_CFLAGS = \ + $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ + $(AM_CFLAGS)
If my cygwin patch is approved first, this will need $(XDR_CFLAGS). https://www.redhat.com/archives/libvir-list/2010-December/msg00404.html
That patch isn't in yet I see.
Yep, I've got several un-ack'd patches on my queue. I guess I should post a refresher ping that summarizes them all. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

To facilitate creation of new daemons providing XDR RPC services, pull alot of the libvirtd daemon code into a set of reusable objects. * virNetServer: A server contains one or more services which accept incoming clients. It maintains the list of active clients. It has a list of RPC programs which can be used by clients. When clients produce a complete RPC message, the server passes this onto the corresponding program for handling, and queues any response back with the client. * virNetServerClient: Encapsulates a single client connection. All I/O for the client is handled, reading & writing RPC messages. Also contains the SASL/TLS code, but this will eventually move into the virNetSocket object * virNetServerProgram: Handles processing and dispatch of RPC method calls for a single RPC (program,version). Multiple programs can be registered with the server. * virNetServerService: Encapsulates socket(s) listening for new connections. Each service listens on a single host/port, but may have multiple sockets if on a dual IPv4/6 host. Each new daemon now merely has to define the list of RPC procedures & their handlers. It does not need to deal with any network related functionality at all. --- src/Makefile.am | 18 +- src/rpc/virnetserver.c | 654 +++++++++++++++++++++++++++ src/rpc/virnetserver.h | 74 ++++ src/rpc/virnetserverclient.c | 974 +++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverclient.h | 40 ++ src/rpc/virnetservermessage.h | 20 + src/rpc/virnetserverprogram.c | 437 ++++++++++++++++++ src/rpc/virnetserverprogram.h | 76 ++++ src/rpc/virnetserverservice.c | 208 +++++++++ src/rpc/virnetserverservice.h | 32 ++ 10 files changed, 2532 insertions(+), 1 deletions(-) create mode 100644 src/rpc/virnetserver.c create mode 100644 src/rpc/virnetserver.h create mode 100644 src/rpc/virnetserverclient.c create mode 100644 src/rpc/virnetserverclient.h create mode 100644 src/rpc/virnetservermessage.h create mode 100644 src/rpc/virnetserverprogram.c create mode 100644 src/rpc/virnetserverprogram.h create mode 100644 src/rpc/virnetserverservice.c create mode 100644 src/rpc/virnetserverservice.h diff --git a/src/Makefile.am b/src/Makefile.am index 613ff0a..e78a0af 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1117,7 +1117,7 @@ libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) -noinst_LTLIBRARIES += libvirt-net-rpc.la +noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la libvirt_net_rpc_la_SOURCES = \ ../daemon/event.c \ @@ -1138,6 +1138,22 @@ libvirt_net_rpc_la_LDFLAGS = \ libvirt_net_rpc_la_LIBADD = \ $(CYGWIN_EXTRA_LIBADD) +libvirt_net_server_la_SOURCES = \ + rpc/virnetservermessage.h \ + rpc/virnetserverprogram.h rpc/virnetserverprogram.c \ + rpc/virnetserverservice.h rpc/virnetserverservice.c \ + rpc/virnetserverclient.h rpc/virnetserverclient.c \ + rpc/virnetserver.h rpc/virnetserver.c +libvirt_net_server_la_CFLAGS = \ + $(AM_CFLAGS) +libvirt_net_server_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS)l +libvirt_net_server_la_LIBADD = \ + $(CYGWIN_EXTRA_LIBADD) + + libexec_PROGRAMS = if WITH_STORAGE_DISK diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c new file mode 100644 index 0000000..0384bb9 --- /dev/null +++ b/src/rpc/virnetserver.c @@ -0,0 +1,654 @@ +/* + * libvirtd.h: daemon data structure definitions + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <unistd.h> +#include <string.h> + +#include "virnetserver.h" +#include "logging.h" +#include "memory.h" +#include "virterror_internal.h" +#include "threads.h" +#include "threadpool.h" +#include "util.h" +#include "files.h" +#include "event.h" +#include "../daemon/event.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +typedef struct _virNetServerSignal virNetServerSignal; +typedef virNetServerSignal *virNetServerSignalPtr; + +struct _virNetServerSignal { + struct sigaction oldaction; + int signum; + virNetServerSignalFunc func; + void *opaque; +}; + +typedef struct _virNetServerJob virNetServerJob; +typedef virNetServerJob *virNetServerJobPtr; + +struct _virNetServerJob { + virNetServerClientPtr client; + virNetServerMessagePtr msg; +}; + +struct _virNetServer { + int refs; + + virMutex lock; + + virThreadPoolPtr workers; + + size_t nsignals; + virNetServerSignalPtr *signals; + int sigread; + int sigwrite; + int sigwatch; + + size_t nservices; + virNetServerServicePtr *services; + + size_t nprograms; + virNetServerProgramPtr *programs; + + size_t nclients; + size_t nclients_alloc; + size_t nclients_max; + virNetServerClientPtr *clients; + + unsigned int quit :1; + + virNetTLSContextPtr tls; + + unsigned int autoShutdownTimeout; + virNetServerAutoShutdownFunc autoShutdownFunc; + void *autoShutdownOpaque; +}; + + +static void virNetServerLock(virNetServerPtr srv) +{ + virMutexLock(&srv->lock); +} + +static void virNetServerUnlock(virNetServerPtr srv) +{ + virMutexUnlock(&srv->lock); +} + + +static void virNetServerHandleJob(void *jobOpaque, void *opaque) +{ + virNetServerPtr srv = opaque; + virNetServerJobPtr job = jobOpaque; + virNetServerProgramPtr prog = NULL; + int i; + + virNetServerLock(srv); + VIR_DEBUG("server=%p client=%p message=%p", + srv, job->client, job->msg); + + for (i = 0 ; i < srv->nprograms ; i++) { + if (virNetServerProgramMatches(srv->programs[i], job->msg)) { + prog = srv->programs[i]; + break; + } + } + + if (!prog) { + VIR_DEBUG("Cannot find program %d version %d", + job->msg->msg.header.prog, + job->msg->msg.header.vers); + goto error; + } + + if (virNetServerProgramDispatch(prog, + srv, + job->client, + job->msg) < 0) { + job->msg = NULL; + goto error; + } + + VIR_FREE(job); + virNetServerUnlock(srv); + return; + +error: + if (job->msg) + virNetServerClientFinishMessage(job->client, job->msg); + virNetServerClientClose(job->client); + VIR_FREE(job); + virNetServerUnlock(srv); +} + + +static int virNetServerDispatchNewMessage(virNetServerClientPtr client, + virNetServerMessagePtr msg, + void *opaque) +{ + virNetServerPtr srv = opaque; + virNetServerJobPtr job; + + VIR_DEBUG("server=%p client=%p message=%p", + srv, client, msg); + + if (VIR_ALLOC(job) < 0) { + virNetServerClientFinishMessage(client, msg); + virNetServerClientClose(client); + virReportOOMError(); + return -1; + } + + job->client = client; + job->msg = msg; + + virNetServerLock(srv); + virThreadPoolSendJob(srv->workers, job); + virNetServerUnlock(srv); + + return 0; +} + + +static int virNetServerDispatchNewClient(virNetServerServicePtr svc ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + void *opaque) +{ + virNetServerPtr srv = opaque; + + virNetServerLock(srv); + + if (srv->nclients >= srv->nclients_max) { + virNetError(VIR_ERR_RPC, + _("Too many active clients (%d), dropping connection from %s"), + (int)srv->nclients_max, virNetServerClientAddrString(client)); + goto error; + } + + if (virNetServerClientInit(client) < 0) + goto error; + + if (VIR_RESIZE_N(srv->clients, srv->nclients_alloc, + srv->nclients, 1) < 0) { + virReportOOMError(); + goto error; + } + srv->clients[srv->nclients++] = client; + virNetServerClientRef(client); + + virNetServerClientSetDispatcher(client, + virNetServerDispatchNewMessage, + srv); + + virNetServerUnlock(srv); + return 0; + +error: + virNetServerUnlock(srv); + return -1; +} + + +virNetServerPtr virNetServerNew(size_t min_workers, + size_t max_workers, + size_t max_clients) +{ + virNetServerPtr srv; + struct sigaction sig_action; + + if (VIR_ALLOC(srv) < 0) { + virReportOOMError(); + return NULL; + } + + srv->refs = 1; + + if (!(srv->workers = virThreadPoolNew(min_workers, max_workers, + virNetServerHandleJob, + srv))) + goto error; + + srv->nclients_max = max_clients; + srv->sigwrite = srv->sigread = -1; + + if (virMutexInit(&srv->lock) < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot initialize mutex")); + goto error; + } + + if (virEventInit() < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to initialize event system")); + goto error; + } + + virEventRegisterImpl(virEventAddHandleImpl, + virEventUpdateHandleImpl, + virEventRemoveHandleImpl, + virEventAddTimeoutImpl, + virEventUpdateTimeoutImpl, + virEventRemoveTimeoutImpl); + + memset(&sig_action, 0, sizeof(sig_action)); + sig_action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sig_action, NULL); + + return srv; + +error: + virNetServerFree(srv); + return NULL; +} + + +void virNetServerRef(virNetServerPtr srv) +{ + virNetServerLock(srv); + srv->refs++; + virNetServerUnlock(srv); +} + + +void virNetServerAutoShutdown(virNetServerPtr srv, + unsigned int timeout, + virNetServerAutoShutdownFunc func, + void *opaque) +{ + virNetServerLock(srv); + + srv->autoShutdownTimeout = timeout; + srv->autoShutdownFunc = func; + srv->autoShutdownOpaque = opaque; + + virNetServerUnlock(srv); +} + + +static sig_atomic_t sigErrors = 0; +static int sigLastErrno = 0; +static int sigWrite = -1; + +static void virNetServerSignalHandler(int sig, siginfo_t * siginfo, + void* context ATTRIBUTE_UNUSED) +{ + int origerrno; + int r; + + /* set the sig num in the struct */ + siginfo->si_signo = sig; + + origerrno = errno; + r = safewrite(sigWrite, siginfo, sizeof(*siginfo)); + if (r == -1) { + sigErrors++; + sigLastErrno = errno; + } + errno = origerrno; +} + +static void +virNetServerSignalEvent(int watch, + int fd ATTRIBUTE_UNUSED, + int events ATTRIBUTE_UNUSED, + void *opaque) { + virNetServerPtr srv = opaque; + siginfo_t siginfo; + int i; + + virNetServerLock(srv); + + if (saferead(srv->sigread, &siginfo, sizeof(siginfo)) != sizeof(siginfo)) { + virReportSystemError(errno, "%s", + _("Failed to read from signal pipe")); + virEventRemoveHandle(watch); + srv->sigwatch = -1; + goto cleanup; + } + + for (i = 0 ; i < srv->nsignals ; i++) { + if (siginfo.si_signo == srv->signals[i]->signum) { + virNetServerSignalFunc func = srv->signals[i]->func; + void *funcopaque = srv->signals[i]->opaque; + virNetServerUnlock(srv); + func(srv, &siginfo, funcopaque); + return; + } + } + + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected signal received: %d"), siginfo.si_signo); + +cleanup: + virNetServerUnlock(srv); +} + +static int virNetServerSignalSetup(virNetServerPtr srv) +{ + int fds[2]; + + if (srv->sigwrite != -1) + return 0; + + if (pipe(fds) < 0) { + virReportSystemError(errno, "%s", + _("Unable to create signal pipe")); + return -1; + } + + if (virSetNonBlock(fds[0]) < 0 || + virSetNonBlock(fds[1]) < 0 || + virSetCloseExec(fds[0]) < 0 || + virSetCloseExec(fds[1]) < 0) { + virReportSystemError(errno, "%s", + _("Failed to setup pipe flags")); + goto error; + } + + if ((srv->sigwatch = virEventAddHandle(fds[0], + VIR_EVENT_HANDLE_READABLE, + virNetServerSignalEvent, + srv, NULL)) < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to add signal handle watch")); + goto error; + } + + srv->sigread = fds[0]; + srv->sigwrite = fds[1]; + sigWrite = fds[1]; + + return 0; + +error: + VIR_FORCE_CLOSE(fds[0]); + VIR_FORCE_CLOSE(fds[1]); + return -1; +} + +int virNetServerAddSignalHandler(virNetServerPtr srv, + int signum, + virNetServerSignalFunc func, + void *opaque) +{ + virNetServerSignalPtr sigdata; + struct sigaction sig_action; + + virNetServerLock(srv); + + if (virNetServerSignalSetup(srv) < 0) + goto error; + + if (VIR_EXPAND_N(srv->signals, srv->nsignals, 1) < 0) + goto no_memory; + + if (VIR_ALLOC(sigdata) < 0) + goto no_memory; + + sigdata->signum = signum; + sigdata->func = func; + sigdata->opaque = opaque; + + memset(&sig_action, 0, sizeof(sig_action)); + sig_action.sa_sigaction = virNetServerSignalHandler; + sig_action.sa_flags = SA_SIGINFO; + sigemptyset(&sig_action.sa_mask); + + sigaction(signum, &sig_action, &sigdata->oldaction); + + srv->signals[srv->nsignals-1] = sigdata; + + virNetServerUnlock(srv); + return 0; + +no_memory: + virReportOOMError(); +error: + VIR_FREE(sigdata); + virNetServerUnlock(srv); + return -1; +} + + + +int virNetServerAddService(virNetServerPtr srv, + virNetServerServicePtr svc) +{ + virNetServerLock(srv); + + if (VIR_EXPAND_N(srv->services, srv->nservices, 1) < 0) + goto no_memory; + + srv->services[srv->nservices-1] = svc; + virNetServerServiceRef(svc); + + virNetServerServiceSetDispatcher(svc, + virNetServerDispatchNewClient, + srv); + + virNetServerUnlock(srv); + return 0; + +no_memory: + virReportOOMError(); + virNetServerUnlock(srv); + return -1; +} + +int virNetServerAddProgram(virNetServerPtr srv, + virNetServerProgramPtr prog) +{ + virNetServerLock(srv); + + if (VIR_EXPAND_N(srv->programs, srv->nprograms, 1) < 0) + goto no_memory; + + srv->programs[srv->nprograms-1] = prog; + virNetServerProgramRef(prog); + + virNetServerUnlock(srv); + return 0; + +no_memory: + virReportOOMError(); + virNetServerUnlock(srv); + return -1; +} + +int virNetServerSetTLSContext(virNetServerPtr srv, + virNetTLSContextPtr tls) +{ + srv->tls = tls; + virNetTLSContextRef(tls); + return 0; +} + + +static void virNetServerAutoShutdownTimer(int timerid ATTRIBUTE_UNUSED, + void *opaque) { + virNetServerPtr srv = opaque; + + virNetServerLock(srv); + + if (srv->autoShutdownFunc(srv, srv->autoShutdownOpaque)) { + VIR_DEBUG0("Automatic shutdown triggered"); + srv->quit = 1; + } + + virNetServerUnlock(srv); +} + + +void virNetServerUpdateServices(virNetServerPtr srv, + bool enabled) +{ + int i; + + virNetServerLock(srv); + for (i = 0 ; i < srv->nservices ; i++) + virNetServerServiceToggle(srv->services[i], enabled); + + virNetServerUnlock(srv); +} + + +void virNetServerRun(virNetServerPtr srv) +{ + int timerid = -1; + int timerActive = 0; + + virNetServerLock(srv); + + if (srv->autoShutdownTimeout && + (timerid = virEventAddTimeout(-1, + virNetServerAutoShutdownTimer, + srv, NULL)) < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to register shutdown timeout")); + goto cleanup; + } + + while (!srv->quit) { + /* A shutdown timeout is specified, so check + * if any drivers have active state, if not + * shutdown after timeout seconds + */ + if (srv->autoShutdownTimeout) { + if (timerActive) { + if (srv->clients) { + DEBUG("Deactivating shutdown timer %d", timerid); + virEventUpdateTimeout(timerid, -1); + timerActive = 0; + } + } else { + if (!srv->clients) { + DEBUG("Activating shutdown timer %d", timerid); + virEventUpdateTimeout(timerid, + srv->autoShutdownTimeout * 1000); + timerActive = 1; + } + } + } + + virNetServerUnlock(srv); + if (virEventRunOnce() < 0) { + virNetServerLock(srv); + DEBUG0("Loop iteration error, exiting"); + break; + } + virNetServerLock(srv); + +#if 0 + reprocess: + for (i = 0 ; i < srv->nclients ; i++) { + int inactive; + virMutexLock(&srv->clients[i]->lock); + inactive = srv->clients[i]->fd == -1 + && srv->clients[i]->refs == 0; + virMutexUnlock(&srv->clients[i]->lock); + if (inactive) { + qemudFreeClient(srv->clients[i]); + srv->nclients--; + if (i < srv->nclients) + memmove(srv->clients + i, + srv->clients + i + 1, + sizeof (*srv->clients) * (srv->nclients - i)); + + VIR_SHRINK_N(srv->clients, srv->nclients, 0); + goto reprocess; + } + } +#endif + + } + +cleanup: + virNetServerUnlock(srv); +} + + +void virNetServerQuit(virNetServerPtr srv) +{ + virNetServerLock(srv); + + srv->quit = 1; + + virNetServerUnlock(srv); +} + +void virNetServerFree(virNetServerPtr srv) +{ + int i; + + if (!srv) + return; + + virNetServerLock(srv); + srv->refs--; + if (srv->refs > 0) { + virNetServerUnlock(srv); + return; + } + + for (i = 0 ; i < srv->nservices ; i++) + virNetServerServiceToggle(srv->services[i], false); + + virThreadPoolFree(srv->workers); + + for (i = 0 ; i < srv->nsignals ; i++) { + sigaction(srv->signals[i]->signum, &srv->signals[i]->oldaction, NULL); + VIR_FREE(srv->signals[i]); + } + VIR_FREE(srv->signals); + VIR_FORCE_CLOSE(srv->sigread); + VIR_FORCE_CLOSE(srv->sigwrite); + if (srv->sigwatch > 0) + virEventRemoveHandle(srv->sigwatch); + + for (i = 0 ; i < srv->nservices ; i++) + virNetServerServiceFree(srv->services[i]); + VIR_FREE(srv->services); + + for (i = 0 ; i < srv->nprograms ; i++) + virNetServerProgramFree(srv->programs[i]); + VIR_FREE(srv->programs); + + for (i = 0 ; i < srv->nclients ; i++) + virNetServerClientFree(srv->clients[i]); + VIR_FREE(srv->clients); + + virNetServerUnlock(srv); + virMutexDestroy(&srv->lock); + VIR_FREE(srv); +} + diff --git a/src/rpc/virnetserver.h b/src/rpc/virnetserver.h new file mode 100644 index 0000000..df9e714 --- /dev/null +++ b/src/rpc/virnetserver.h @@ -0,0 +1,74 @@ +/* + * virnetserver.h: generic network RPC server + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __VIR_NET_SERVER_H__ +#define __VIR_NET_SERVER_H__ + +#include <stdbool.h> +#include <signal.h> + +#include "virnettlscontext.h" +#include "virnetserverprogram.h" +#include "virnetserverclient.h" +#include "virnetserverservice.h" + +virNetServerPtr virNetServerNew(size_t min_workers, + size_t max_workers, + size_t max_clients); + +typedef int (*virNetServerAutoShutdownFunc)(virNetServerPtr srv, void *opaque); + +void virNetServerRef(virNetServerPtr srv); + +void virNetServerAutoShutdown(virNetServerPtr srv, + unsigned int timeout, + virNetServerAutoShutdownFunc func, + void *opaque); + +typedef void (*virNetServerSignalFunc)(virNetServerPtr srv, siginfo_t *info, void *opaque); + +int virNetServerAddSignalHandler(virNetServerPtr srv, + int signum, + virNetServerSignalFunc func, + void *opaque); + +int virNetServerAddService(virNetServerPtr srv, + virNetServerServicePtr svc); + +int virNetServerAddProgram(virNetServerPtr srv, + virNetServerProgramPtr prog); + +int virNetServerSetTLSContext(virNetServerPtr srv, + virNetTLSContextPtr tls); + +void virNetServerUpdateServices(virNetServerPtr srv, + bool enabled); + +void virNetServerRun(virNetServerPtr srv); + +void virNetServerQuit(virNetServerPtr srv); + +void virNetServerFree(virNetServerPtr srv); + + +#endif diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c new file mode 100644 index 0000000..76d4b33 --- /dev/null +++ b/src/rpc/virnetserverclient.c @@ -0,0 +1,974 @@ + +#include <config.h> + +# if HAVE_SASL +# include <sasl/sasl.h> +# endif + +#include "virnetserverclient.h" + +#include "logging.h" +#include "virterror_internal.h" +#include "memory.h" +#include "threads.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +/* Allow for filtering of incoming messages to a custom + * dispatch processing queue, instead of client->dx. + */ + +typedef struct _virNetServerClientFilter virNetServerClientFilter; +typedef virNetServerClientFilter *virNetServerClientFilterPtr; + +typedef int (*virNetServerClientFilterFunc)(virNetServerClientPtr client, + virNetServerMessagePtr msg, + void *opaque); + +struct _virNetServerClientFilter { + virNetServerClientFilterFunc func; + void *opaque; + + virNetServerClientFilterPtr next; +}; + + +typedef struct _virNetServerClientStream virNetServerClientStream; +typedef virNetServerClientStream *virNetServerClientStreamPtr; + +struct _virNetServerClientStream { + void *opaque; + + int procedure; + int serial; + + unsigned int recvEOF : 1; + unsigned int closed : 1; + + virNetServerClientFilter filter; + + virNetServerMessagePtr rx; + int tx; + + virNetServerClientStreamPtr next; +}; + +#if HAVE_SASL +/* Whether we're passing reads & writes through a sasl SSF */ +enum virNetServerClientSSF { + VIR_NET_CLIENT_SSF_NONE = 0, + VIR_NET_CLIENT_SSF_READ = 1, + VIR_NET_CLIENT_SSF_WRITE = 2, +}; +#endif + +struct _virNetServerClient +{ + int refs; + + virMutex lock; + virNetSocketPtr sock; + int auth; + virNetTLSContextPtr tlsCtxt; + virNetTLSSessionPtr tls; + unsigned int handshake : 1; + unsigned int closing : 1; + +# if HAVE_SASL + sasl_conn_t *saslconn; + int saslSSF; + const char *saslDecoded; + unsigned int saslDecodedLength; + unsigned int saslDecodedOffset; + const char *saslEncoded; + unsigned int saslEncodedLength; + unsigned int saslEncodedOffset; + char *saslUsername; +# endif + + /* Count of messages in the 'tx' queue, + * and the server worker pool queue + * ie RPC calls in progress. Does not count + * async events which are not used for + * throttling calculations */ + size_t nrequests; + size_t nrequests_max; + /* Zero or one messages being received. Zero if + * nrequests >= max_clients and throttling */ + virNetServerMessagePtr rx; + /* Zero or many messages waiting for transmit + * back to client, including async events */ + virNetServerMessagePtr tx; + + /* Filters to capture messages that would otherwise + * end up on the 'dx' queue */ + virNetServerClientFilterPtr filters; + + /* Data streams */ + virNetServerClientStreamPtr streams; + + virNetServerClientDispatchFunc dispatchFunc; + void *dispatchOpaque; +}; + + +static void virNetServerClientDispatchEvent(virNetSocketPtr sock, int events, void *opaque); +static void virNetServerClientFinishMessageLocked(virNetServerClientPtr client, + virNetServerMessagePtr msg); + +static void virNetServerClientLock(virNetServerClientPtr client) +{ + virMutexLock(&client->lock); +} + +static void virNetServerClientUnlock(virNetServerClientPtr client) +{ + virMutexUnlock(&client->lock); +} + +/* + * @client: a locked client object + */ +static int +virNetServerClientCalculateHandleMode(virNetServerClientPtr client) { + int mode = 0; + + if (client->handshake) { + if (virNetTLSSessionHandshakeDirection(client->tls) == 0) + mode |= VIR_EVENT_HANDLE_READABLE; + else + mode |= VIR_EVENT_HANDLE_WRITABLE; + } else { + /* If there is a message on the rx queue then + * we're wanting more input */ + if (client->rx) + mode |= VIR_EVENT_HANDLE_READABLE; + + /* If there are one or more messages to send back to client, + then monitor for writability on socket */ + if (client->tx) + mode |= VIR_EVENT_HANDLE_WRITABLE; + } + + return mode; +} + +/* + * @server: a locked or unlocked server object + * @client: a locked client object + */ +static int virNetServerClientRegisterEvent(virNetServerClientPtr client) +{ + int mode = virNetServerClientCalculateHandleMode(client); + + VIR_DEBUG("Registering client event callback %d", mode); + if (virNetSocketAddIOCallback(client->sock, + mode, + virNetServerClientDispatchEvent, + client) < 0) + return -1; + + return 0; +} + +/* + * @client: a locked client object + */ +static void virNetServerClientUpdateEvent(virNetServerClientPtr client) +{ + int mode; + + if (!client->sock) + return; + + mode = virNetServerClientCalculateHandleMode(client); + + virNetSocketUpdateIOCallback(client->sock, mode); +} + + +static void virNetServerClientMessageQueuePush(virNetServerMessagePtr *queue, + virNetServerMessagePtr msg) +{ + virNetServerMessagePtr tmp = *queue; + + if (tmp) { + while (tmp->next) + tmp = tmp->next; + tmp->next = msg; + } else { + *queue = msg; + } +} + +static virNetServerMessagePtr +virNetServerClientMessageQueueServe(virNetServerMessagePtr *queue) +{ + virNetServerMessagePtr tmp = *queue; + + if (tmp) { + *queue = tmp->next; + tmp->next = NULL; + } + + return tmp; +} + + +static ssize_t virNetServerClientTLSWriteFunc(const char *buf, size_t len, + void *opaque) +{ + virNetServerClientPtr client = opaque; + + return virNetSocketWrite(client->sock, buf, len); +} + +static ssize_t virNetServerClientTLSReadFunc(char *buf, size_t len, + void *opaque) +{ + virNetServerClientPtr client = opaque; + + return virNetSocketRead(client->sock, buf, len); +} + + +/* Check the client's access. */ +static int +virNetServerClientCheckAccess(virNetServerClientPtr client) +{ + virNetServerMessagePtr confirm; + + /* Verify client certificate. */ + if (virNetTLSContextCheckCertificate(client->tlsCtxt, client->tls) < 0) + return -1; + + if (client->tx) { + VIR_INFO0(_("client had unexpected data pending tx after access check")); + return -1; + } + + if (VIR_ALLOC(confirm) < 0) { + virReportOOMError(); + return -1; + } + + /* Checks have succeeded. Write a '\1' byte back to the client to + * indicate this (otherwise the socket is abruptly closed). + * (NB. The '\1' byte is sent in an encrypted record). + */ + confirm->async = 1; + confirm->msg.bufferLength = 1; + confirm->msg.bufferOffset = 0; + confirm->msg.buffer[0] = '\1'; + + client->tx = confirm; + + return 0; +} + + +virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, + int auth, + virNetTLSContextPtr tls) +{ + virNetServerClientPtr client; + + VIR_DEBUG("sock=%p auth=%d tls=%p", sock, auth, tls); + + if (VIR_ALLOC(client) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&client->lock) < 0) + goto error; + + client->refs = 1; + client->sock = sock; + client->auth = auth; + client->tlsCtxt = tls; + client->nrequests_max = 10; /* XXX */ + + virNetTLSContextRef(tls); + + /* Prepare one for packet receive */ + if (VIR_ALLOC(client->rx) < 0) + goto error; + client->rx->msg.bufferLength = VIR_NET_MESSAGE_LEN_MAX; + + VIR_DEBUG("client=%p", client); + + return client; + +error: + /* XXX ref counting is better than this */ + client->sock = NULL; /* Caller owns 'sock' upon failure */ + virNetServerClientFree(client); + return NULL; +} + +void virNetServerClientRef(virNetServerClientPtr client) +{ + virNetServerClientLock(client); + client->refs++; + virNetServerClientUnlock(client); +} + + +void virNetServerClientSetDispatcher(virNetServerClientPtr client, + virNetServerClientDispatchFunc func, + void *opaque) +{ + virNetServerClientLock(client); + client->dispatchFunc = func; + client->dispatchOpaque = opaque; + virNetServerClientUnlock(client); +} + + +const char *virNetServerClientAddrString(virNetServerClientPtr client) +{ + return virNetSocketRemoteAddrString(client->sock); +} + + +void virNetServerClientFree(virNetServerClientPtr client) +{ + VIR_DEBUG("client=%p", client); + + if (!client) + return; + + virNetServerClientLock(client); + + client->refs--; + if (client->refs > 0) { + virNetServerClientUnlock(client); + return; + } + + while (client->rx) { + virNetServerMessagePtr msg + = virNetServerClientMessageQueueServe(&client->rx); + VIR_FREE(msg); + } + while (client->tx) { + virNetServerMessagePtr msg + = virNetServerClientMessageQueueServe(&client->tx); + VIR_FREE(msg); + } + + virNetTLSSessionFree(client->tls); + virNetTLSContextFree(client->tlsCtxt); + virNetSocketFree(client->sock); + virNetServerClientUnlock(client); + virMutexDestroy(&client->lock); + VIR_FREE(client); +} + + +/* + * You must hold lock for the client + * + * We don't free stuff here, merely disconnect the client's + * network socket & resources. + * + * Full free of the client is done later in a safe point + * where it can be guaranteed it is no longer in use + */ +static void virNetServerClientCloseLocked(virNetServerClientPtr client) +{ + /* Do now, even though we don't close the socket + * until end, to ensure we don't get invoked + * again due to tls shutdown */ + if (client->sock) + virNetSocketRemoveIOCallback(client->sock); + +#if HAVE_SASL + if (client->saslconn) { + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + } + VIR_FREE(client->saslUsername); +#endif + if (client->tls) { + virNetTLSSessionFree(client->tls); + client->tls = NULL; + } + if (client->sock) { + virNetSocketFree(client->sock); + client->sock = NULL; + } + +} + + +/* Client must be unlocked */ +void virNetServerClientClose(virNetServerClientPtr client) +{ + virNetServerClientLock(client); + virNetServerClientCloseLocked(client); + virNetServerClientUnlock(client); +} + + +int virNetServerClientInit(virNetServerClientPtr client) +{ + virNetServerClientLock(client); + + if (!client->tlsCtxt) { + /* Plain socket, so prepare to read first message */ + if (virNetServerClientRegisterEvent(client) < 0) + goto error; + } else { + int ret; + + if (!(client->tls = virNetTLSSessionNew(client->tlsCtxt, + NULL, + virNetServerClientTLSWriteFunc, + virNetServerClientTLSReadFunc, + client))) + goto error; + + /* Begin the TLS handshake. */ + ret = virNetTLSSessionHandshake(client->tls); + if (ret == 0) { + client->handshake = 0; + /* Unlikely, but ... Next step is to check the certificate. */ + if (virNetServerClientCheckAccess(client) < 0) + goto error; + + /* Handshake & cert check OK, so prepare to read first message */ + if (virNetServerClientRegisterEvent(client) < 0) + goto error; + } else if (ret > 0) { + /* Most likely, need to do more handshake data */ + client->handshake = 1; + + if (virNetServerClientRegisterEvent(client) < 0) + goto error; + } else { +#if 0 + PROBE(CLIENT_TLS_FAIL, "fd=%d", client->fd); +#endif + goto error; + } + } + + virNetServerClientUnlock(client); + return 0; + +error: + virNetServerClientUnlock(client); + return -1; +} + + + +/* + * Read data into buffer using wire decoding (plain or TLS) + * + * Returns: + * -1 on error or EOF + * 0 on EAGAIN + * n number of bytes + */ +static ssize_t virNetServerClientReadBuf(virNetServerClientPtr client, + char *data, ssize_t len) +{ + ssize_t ret; + + if (len < 0) { + virNetError(VIR_ERR_RPC, + _("unexpected negative length request %lld"), + (long long int) len); + virNetServerClientCloseLocked(client); + return -1; + } + + /*virNetServerClientDebug ("virNetServerClientRead: len = %d", len);*/ + + if (client->tls) + ret = virNetTLSSessionRead(client->tls, data, len); + else + ret = virNetSocketRead(client->sock, data, len); + + if (ret == -1 && (errno == EAGAIN || + errno == EINTR)) + return 0; + if (ret <= 0) { + if (ret != 0) + virReportSystemError(errno, "%s", + _("Unable to read from client")); + else + VIR_DEBUG0("EOF from client connection"); + virNetServerClientCloseLocked(client); + return -1; + } + + return ret; +} + +/* + * Read data into buffer without decoding + * + * Returns: + * -1 on error or EOF + * 0 on EAGAIN + * n number of bytes + */ +static ssize_t virNetServerClientReadPlain(virNetServerClientPtr client) +{ + ssize_t ret; + ret = virNetServerClientReadBuf(client, + client->rx->msg.buffer + client->rx->msg.bufferOffset, + client->rx->msg.bufferLength - client->rx->msg.bufferOffset); + if (ret <= 0) + return ret; /* -1 error, 0 eagain */ + + client->rx->msg.bufferOffset += ret; + return ret; +} + +#if HAVE_SASL +/* + * Read data into buffer decoding with SASL + * + * Returns: + * -1 on error or EOF + * 0 on EAGAIN + * n number of bytes + */ +static ssize_t virNetServerClientReadSASL(virNetServerClientPtr client) +{ + ssize_t got, want; + + /* We're doing a SSF data read, so now its times to ensure + * future writes are under SSF too. + * + * cf remoteSASLCheckSSF in remote.c + */ + client->saslSSF |= VIR_NET_CLIENT_SSF_WRITE; + + /* Need to read some more data off the wire */ + if (client->saslDecoded == NULL) { + int ret; + char encoded[8192]; + ssize_t encodedLen = sizeof(encoded); + encodedLen = virNetServerClientReadBuf(client, encoded, encodedLen); + + if (encodedLen <= 0) + return encodedLen; + + ret = sasl_decode(client->saslconn, encoded, encodedLen, + &client->saslDecoded, &client->saslDecodedLength); + if (ret != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to decode SASL data %s"), + sasl_errstring(ret, NULL, NULL)); + virNetServerClientCloseLocked(client); + return -1; + } + + client->saslDecodedOffset = 0; + } + + /* Some buffered decoded data to return now */ + got = client->saslDecodedLength - client->saslDecodedOffset; + want = client->rx->msg.bufferLength - client->rx->msg.bufferOffset; + + if (want > got) + want = got; + + memcpy(client->rx->msg.buffer + client->rx->msg.bufferOffset, + client->saslDecoded + client->saslDecodedOffset, want); + client->saslDecodedOffset += want; + client->rx->msg.bufferOffset += want; + + if (client->saslDecodedOffset == client->saslDecodedLength) { + client->saslDecoded = NULL; + client->saslDecodedOffset = client->saslDecodedLength = 0; + } + + return want; +} +#endif + +/* + * Read as much data off wire as possible till we fill our + * buffer, or would block on I/O + */ +static ssize_t virNetServerClientRead(virNetServerClientPtr client) +{ +#if HAVE_SASL + if (client->saslSSF & VIR_NET_CLIENT_SSF_READ) + return virNetServerClientReadSASL(client); + else +#endif + return virNetServerClientReadPlain(client); +} + + +/* + * Read data until we get a complete message to process + */ +static void virNetServerClientDispatchRead(virNetServerClientPtr client) +{ +readmore: + if (virNetServerClientRead(client) < 0) + return; /* Error */ + + if (client->rx->msg.bufferOffset < client->rx->msg.bufferLength) + return; /* Still not read enough */ + + /* Either done with length word header */ + if (client->rx->msg.bufferLength == VIR_NET_MESSAGE_LEN_MAX) { + if (virNetMessageDecodeLength(&client->rx->msg) < 0) + return; + + virNetServerClientUpdateEvent(client); + + /* Try and read payload immediately instead of going back + into poll() because chances are the data is already + waiting for us */ + goto readmore; + } else { + /* Grab the completed message */ + virNetServerMessagePtr msg = virNetServerClientMessageQueueServe(&client->rx); + virNetServerClientFilterPtr filter; + + /* Decode the header so we can use it for routing decisions */ + if (virNetMessageDecodeHeader(&msg->msg) < 0) { + VIR_FREE(msg); + virNetServerClientCloseLocked(client); + } + + /* Check if any filters match this message */ + filter = client->filters; + while (filter) { + int ret; + ret = (filter->func)(client, msg, filter->opaque); + if (ret == 1) { + msg = NULL; + break; + } else if (ret == -1) { + VIR_FREE(msg); + virNetServerClientCloseLocked(client); + return; + } + filter = filter->next; + } + + client->nrequests++; + + /* Possibly need to create another receive buffer */ + if ((client->nrequests < client->nrequests_max) && + VIR_ALLOC(client->rx) < 0) { + virNetServerClientCloseLocked(client); + } else { + if (client->rx) + client->rx->msg.bufferLength = VIR_NET_MESSAGE_LEN_MAX; + + virNetServerClientUpdateEvent(client); + + } + + /* Send it off for processing */ + if (msg) { + if (client->dispatchFunc) + client->dispatchFunc(client, msg, client->dispatchOpaque); + else + virNetServerClientFinishMessageLocked(client, msg); + } + } +} + + +/* + * Send a chunk of data using wire encoding (plain or TLS) + * + * Returns: + * -1 on error + * 0 on EAGAIN + * n number of bytes + */ +static ssize_t virNetServerClientWriteBuf(virNetServerClientPtr client, + const char *data, ssize_t len) +{ + ssize_t ret; + + if (len < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("unexpected negative length request %lld"), + (long long int) len); + virNetServerClientCloseLocked(client); + return -1; + } + + if (client->tls) + ret = virNetTLSSessionWrite(client->tls, data, len); + else + ret = virNetSocketWrite(client->sock, data, len); + + if (ret == -1 && (errno == EAGAIN || + errno == EINTR)) + return 0; + if (ret == -1) { + virReportSystemError(errno, "%s", + _("Unable to write to client")); + virNetServerClientCloseLocked(client); + return -1; + } + return ret; +} + + +/* + * Send client->tx using no encoding + * + * Returns: + * -1 on error or EOF + * 0 on EAGAIN + * n number of bytes + */ +static int virNetServerClientWritePlain(virNetServerClientPtr client) +{ + int ret = virNetServerClientWriteBuf(client, + client->tx->msg.buffer + client->tx->msg.bufferOffset, + client->tx->msg.bufferLength - client->tx->msg.bufferOffset); + if (ret <= 0) + return ret; /* -1 error, 0 = egain */ + client->tx->msg.bufferOffset += ret; + return ret; +} + + +#if HAVE_SASL +/* + * Send client->tx using SASL encoding + * + * Returns: + * -1 on error + * 0 on EAGAIN + * n number of bytes + */ +static int virNetServerClientWriteSASL(virNetServerClientPtr client) +{ + int ret; + + /* Not got any pending encoded data, so we need to encode raw stuff */ + if (client->saslEncoded == NULL) { + ret = sasl_encode(client->saslconn, + client->tx->msg.buffer + client->tx->msg.bufferOffset, + client->tx->msg.bufferLength - client->tx->msg.bufferOffset, + &client->saslEncoded, + &client->saslEncodedLength); + + if (ret != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to encode SASL data %s"), + sasl_errstring(ret, NULL, NULL)); + virNetServerClientCloseLocked(client); + return -1; + } + + client->saslEncodedOffset = 0; + } + + /* Send some of the encoded stuff out on the wire */ + ret = virNetServerClientWriteBuf(client, + client->saslEncoded + client->saslEncodedOffset, + client->saslEncodedLength - client->saslEncodedOffset); + + if (ret <= 0) + return ret; /* -1 error, 0 == egain */ + + /* Note how much we sent */ + client->saslEncodedOffset += ret; + + /* Sent all encoded, so update raw buffer to indicate completion */ + if (client->saslEncodedOffset == client->saslEncodedLength) { + client->saslEncoded = NULL; + client->saslEncodedOffset = client->saslEncodedLength = 0; + + /* Mark as complete, so caller detects completion */ + client->tx->msg.bufferOffset = client->tx->msg.bufferLength; + } + + return ret; +} +#endif + +/* + * Send as much data in the client->tx as possible + * + * Returns: + * -1 on error or EOF + * 0 on EAGAIN + * n number of bytes + */ +static ssize_t virNetServerClientWrite(virNetServerClientPtr client) +{ +#if HAVE_SASL + if (client->saslSSF & VIR_NET_CLIENT_SSF_WRITE) + return virNetServerClientWriteSASL(client); + else +#endif + return virNetServerClientWritePlain(client); +} + + +/* + * Process all queued client->tx messages until + * we would block on I/O + */ +static void +virNetServerClientDispatchWrite(virNetServerClientPtr client) +{ + while (client->tx) { + ssize_t ret; + + ret = virNetServerClientWrite(client); + if (ret < 0) { + virNetServerClientCloseLocked(client); + return; + } + if (ret == 0) + return; /* Would block on write EAGAIN */ + + if (client->tx->msg.bufferOffset == client->tx->msg.bufferLength) { + virNetServerMessagePtr reply; + + /* Get finished reply from head of tx queue */ + reply = virNetServerClientMessageQueueServe(&client->tx); + + virNetServerClientFinishMessageLocked(client, reply); + + if (client->closing) + virNetServerClientCloseLocked(client); + } + } +} + +static void +virNetServerClientDispatchHandshake(virNetServerClientPtr client) +{ + int ret; + /* Continue the handshake. */ + ret = virNetTLSSessionHandshake(client->tls); + if (ret == 0) { + client->handshake = 0; + + /* Finished. Next step is to check the certificate. */ + if (virNetServerClientCheckAccess(client) < 0) + virNetServerClientCloseLocked(client); + else + virNetServerClientUpdateEvent(client); + } else if (ret > 0) { + /* Carry on waiting for more handshake. Update + the events just in case handshake data flow + direction has changed */ + virNetServerClientUpdateEvent (client); + } else { +#if 0 + PROBE(CLIENT_TLS_FAIL, "fd=%d", client->fd); +#endif + /* Fatal error in handshake */ + virNetServerClientCloseLocked(client); + } +} + +static void +virNetServerClientDispatchEvent(virNetSocketPtr sock, int events, void *opaque) +{ + virNetServerClientPtr client = opaque; + + virNetServerClientLock(client); + + if (client->sock != sock) { + virNetSocketRemoveIOCallback(sock); + virNetServerClientUnlock(client); + return; + } + + if (events & (VIR_EVENT_HANDLE_WRITABLE | + VIR_EVENT_HANDLE_READABLE)) { + if (client->handshake) { + virNetServerClientDispatchHandshake(client); + } else { + if (events & VIR_EVENT_HANDLE_WRITABLE) + virNetServerClientDispatchWrite(client); + if (events & VIR_EVENT_HANDLE_READABLE) + virNetServerClientDispatchRead(client); + } + } + + /* NB, will get HANGUP + READABLE at same time upon + * disconnect */ + if (events & (VIR_EVENT_HANDLE_ERROR | + VIR_EVENT_HANDLE_HANGUP)) + virNetServerClientCloseLocked(client); + + virNetServerClientUnlock(client); +} + + +void virNetServerClientSendMessage(virNetServerClientPtr client, + virNetServerMessagePtr msg) +{ + virNetServerClientLock(client); + + virNetServerClientMessageQueuePush(&client->tx, msg); + + virNetServerClientUpdateEvent(client); + + virNetServerClientUnlock(client); +} + +static void virNetServerClientFinishMessageLocked(virNetServerClientPtr client, + virNetServerMessagePtr msg) +{ + if (msg->streamTX) { +#if 0 + XXX + remoteStreamMessageFinished(client, msg); +#endif + } else if (!msg->async) + client->nrequests--; + + /* See if the recv queue is currently throttled */ + if (!client->rx && + client->nrequests < client->nrequests_max) { + /* Reset message record for next RX attempt */ + memset(msg, 0, sizeof(*msg)); + client->rx = msg; + /* Get ready to receive next message */ + client->rx->msg.bufferLength = VIR_NET_MESSAGE_LEN_MAX; + } else { + VIR_FREE(msg); + } + + virNetServerClientUpdateEvent(client); +} + +void virNetServerClientFinishMessage(virNetServerClientPtr client, + virNetServerMessagePtr msg) +{ + virNetServerClientLock(client); + virNetServerClientFinishMessageLocked(client, msg); + virNetServerClientUnlock(client); +} + +bool virNetServerClientNeedAuth(virNetServerClientPtr client) +{ + bool need = false; + virNetServerClientLock(client); + if (client->auth) + need = true; + virNetServerClientUnlock(client); + return need; +} diff --git a/src/rpc/virnetserverclient.h b/src/rpc/virnetserverclient.h new file mode 100644 index 0000000..5fe239c --- /dev/null +++ b/src/rpc/virnetserverclient.h @@ -0,0 +1,40 @@ + + +#ifndef __VIR_NET_SERVER_CLIENT_H__ +#define __VIR_NET_SERVER_CLIENT_H__ + +#include "virnetsocket.h" +#include "virnetserverprogram.h" +#include "virnettlscontext.h" + +typedef int (*virNetServerClientDispatchFunc)(virNetServerClientPtr client, + virNetServerMessagePtr msg, + void *opaque); + +virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, + int auth, + virNetTLSContextPtr tls); + +void virNetServerClientRef(virNetServerClientPtr client); + +void virNetServerClientSetDispatcher(virNetServerClientPtr client, + virNetServerClientDispatchFunc func, + void *opaque); +void virNetServerClientClose(virNetServerClientPtr client); + +int virNetServerClientInit(virNetServerClientPtr client); + +const char *virNetServerClientAddrString(virNetServerClientPtr client); + +void virNetServerClientSendMessage(virNetServerClientPtr client, + virNetServerMessagePtr msg); + +void virNetServerClientFinishMessage(virNetServerClientPtr client, + virNetServerMessagePtr msg); + +bool virNetServerClientNeedAuth(virNetServerClientPtr client); + +void virNetServerClientFree(virNetServerClientPtr client); + + +#endif /* __VIR_NET_SERVER_CLIENT_H__ */ diff --git a/src/rpc/virnetservermessage.h b/src/rpc/virnetservermessage.h new file mode 100644 index 0000000..88624fe --- /dev/null +++ b/src/rpc/virnetservermessage.h @@ -0,0 +1,20 @@ + +#ifndef __VIR_NET_SERVER_MESSAGE_H__ +#define __VIR_NET_SERVER_MESSAGE_H__ + +#include "virnetmessage.h" + +typedef struct _virNetServerMessage virNetServerMessage; +typedef virNetServerMessage *virNetServerMessagePtr; + +struct _virNetServerMessage { + virNetMessage msg; + + unsigned int async : 1; + unsigned int streamTX : 1; + + virNetServerMessagePtr next; +}; + +#endif /* __VIR_NET_SERVER_MESSAGE_H__ */ + diff --git a/src/rpc/virnetserverprogram.c b/src/rpc/virnetserverprogram.c new file mode 100644 index 0000000..59328ac --- /dev/null +++ b/src/rpc/virnetserverprogram.c @@ -0,0 +1,437 @@ + + +#include <config.h> + +#include "virnetserverprogram.h" +#include "virnetserverclient.h" + +#include "memory.h" +#include "virterror_internal.h" +#include "logging.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +struct _virNetServerProgram { + int refs; + + unsigned program; + unsigned version; + virNetServerProgramProcPtr procs; + size_t nprocs; + virNetServerProgramErrorHanderPtr err; +}; + +virNetServerProgramPtr virNetServerProgramNew(unsigned program, + unsigned version, + virNetServerProgramProcPtr procs, + size_t nprocs, + virNetServerProgramErrorHanderPtr err) +{ + virNetServerProgramPtr prog; + + if (VIR_ALLOC(prog) < 0) { + virReportOOMError(); + return NULL; + } + + prog->refs = 1; + prog->program = program; + prog->version = version; + prog->procs = procs; + prog->nprocs = nprocs; + prog->err = err; + + return prog; +} + + +void virNetServerProgramRef(virNetServerProgramPtr prog) +{ + prog->refs++; +} + + +int virNetServerProgramMatches(virNetServerProgramPtr prog, + virNetServerMessagePtr msg) +{ + if (prog->program == msg->msg.header.prog && + prog->version == msg->msg.header.vers) + return 1; + return 0; +} + + +static virNetServerProgramProcPtr virNetServerProgramGetProc(virNetServerProgramPtr prog, + int procedure) +{ + if (procedure < 0) + return NULL; + if (procedure >= prog->nprocs) + return NULL; + + return &prog->procs[procedure]; +} + +static void +virNetServerProgramStringError(virNetServerProgramPtr prog, + void *rerr, + int code, + const char *str) +{ + prog->err->func(prog, rerr, code, str); +} + +static void +virNetServerProgramFormatError(virNetServerProgramPtr prog, + void *rerr, + int code, + const char *fmt, + ...) +{ + va_list args; + char msgbuf[1024]; + char *msg = msgbuf; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof msgbuf, fmt, args); + va_end(args); + + virNetServerProgramStringError(prog, rerr, code, msg); +} + + +#if 0 +static void +virNetServerProgramGenericError(virNetServerProgramPtr prog, + void *rerr) +{ + virNetServerProgramStringError(prog, rerr, + VIR_ERR_INTERNAL_ERROR, + _("function returned error code but did not set an error message")); +} +#endif + + +static void +virNetServerProgramOOMError(virNetServerProgramPtr prog, + void *rerr) +{ + virNetServerProgramStringError(prog, rerr, + VIR_ERR_NO_MEMORY, + _("out of memory")); +} + + +static int +remoteSerializeError(virNetServerProgramPtr prog, + virNetServerClientPtr client, + void *rerr, + int program, + int version, + int procedure, + int type, + int serial) +{ + virNetServerMessagePtr msg = NULL; + + DEBUG("prog=%d ver=%d proc=%d type=%d serial=%d, msg=%p", + program, version, procedure, type, serial, rerr); + + if (VIR_ALLOC(msg) < 0) { + virReportOOMError(); + return -1; + } + + + /* Return header. */ + msg->msg.header.prog = program; + msg->msg.header.vers = version; + msg->msg.header.proc = procedure; + msg->msg.header.type = type; + msg->msg.header.serial = serial; + msg->msg.header.status = VIR_NET_ERROR; + + if (virNetMessageEncodeHeader(&msg->msg) < 0) + goto error; + + if (virNetMessageEncodePayload(&msg->msg, prog->err->filter, rerr) < 0) + goto error; + + /* Put reply on end of tx queue to send out */ + virNetServerClientSendMessage(client, msg); + xdr_free(prog->err->filter, rerr); + + return 0; + +error: + VIR_WARN("Failed to serialize remote error '%p'", rerr); + VIR_FREE(msg); + xdr_free(prog->err->filter, rerr); + return -1; +} + + +/* + * @client: the client to send the error to + * @rerr: the error object to send + * @req: the message this error is in reply to + * + * Send an error message to the client + * + * Returns 0 if the error was sent, -1 upon fatal error + */ +static int +virNetServerProgramSerializeReplyError(virNetServerProgramPtr prog, + virNetServerClientPtr client, + void *rerr, + virNetMessageHeaderPtr req) +{ + /* + * For data streams, errors are sent back as data streams + * For method calls, errors are sent back as method replies + */ + return remoteSerializeError(prog, + client, + rerr, + req->prog, + req->vers, + req->proc, + req->type == VIR_NET_STREAM ? VIR_NET_STREAM : VIR_NET_REPLY, + req->serial); +} + +static int +virNetServerProgramDispatchCall(virNetServerProgramPtr prog, + void *rerr, + virNetServerPtr server, + virNetServerClientPtr client, + virNetServerMessagePtr msg); + +/* + * @server: the unlocked server object + * @client: the locked client object + * @msg: the complete incoming message packet, with header already decoded + * + * This function gets called from qemud when it pulls a incoming + * remote protocol message off the dispatch queue for processing. + * + * The @msg parameter must have had its header decoded already by + * calling remoteDecodeClientMessageHeader + * + * Returns 0 if the message was dispatched, -1 upon fatal error + */ +int virNetServerProgramDispatch(virNetServerProgramPtr prog, + virNetServerPtr server, + virNetServerClientPtr client, + virNetServerMessagePtr msg) +{ + int ret = -1; + char *rerr; + + if (VIR_ALLOC_N(rerr, prog->err->len) < 0) { + virReportOOMError(); + return -1; + } + + DEBUG("prog=%d ver=%d type=%d status=%d serial=%d proc=%d", + msg->msg.header.prog, msg->msg.header.vers, msg->msg.header.type, + msg->msg.header.status, msg->msg.header.serial, msg->msg.header.proc); + + /* Check version, etc. */ + if (msg->msg.header.prog != prog->program) { + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + _("program mismatch (actual %x, expected %x)"), + msg->msg.header.prog, prog->program); + goto error; + } + + if (msg->msg.header.vers != prog->version) { + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + _("version mismatch (actual %x, expected %x)"), + msg->msg.header.vers, prog->version); + goto error; + } + + switch (msg->msg.header.type) { + case VIR_NET_CALL: + ret = virNetServerProgramDispatchCall(prog, rerr, server, client, msg); + break; + + case VIR_NET_STREAM: + /* Since stream data is non-acked, async, we may continue to received + * stream packets after we closed down a stream. Just drop & ignore + * these. + */ + VIR_INFO("Ignoring unexpected stream data serial=%d proc=%d status=%d", + msg->msg.header.serial, msg->msg.header.proc, msg->msg.header.status); + virNetServerClientFinishMessage(client, msg); + ret = 0; + break; + + default: + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + _("Unexpected message type %d"), + (int)msg->msg.header.type); + goto error; + } + + VIR_FREE(rerr); + + return ret; + +error: + ret = virNetServerProgramSerializeReplyError(prog, client, rerr, &msg->msg.header); + + if (ret >= 0) + VIR_FREE(msg); + + VIR_FREE(rerr); + + return ret; +} + + +/* + * @server: the unlocked server object + * @client: the unlocked client object + * @msg: the complete incoming method call, with header already decoded + * + * This method is used to dispatch an message representing an + * incoming method call from a client. It decodes the payload + * to obtain method call arguments, invokves the method and + * then sends a reply packet with the return values + * + * Returns 0 if the reply was sent, or -1 upon fatal error + */ +static int +virNetServerProgramDispatchCall(virNetServerProgramPtr prog, + void *rerr, + virNetServerPtr server, + virNetServerClientPtr client, + virNetServerMessagePtr msg) +{ + char *arg = NULL; + char *ret = NULL; + int rv = -1; + unsigned int len; + virNetServerProgramProcPtr dispatcher; + + memset(rerr, 0, sizeof rerr); + + if (msg->msg.header.status != VIR_NET_OK) { + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + _("Unexpected message status %d"), + (int)msg->msg.header.status); + goto error; + } + + dispatcher = virNetServerProgramGetProc(prog, msg->msg.header.proc); + + if (!dispatcher) { + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + _("unknown procedure: %d"), + msg->msg.header.proc); + goto error; + } + + /* If client is marked as needing auth, don't allow any RPC ops + * which are except for authentication ones + */ + if (virNetServerClientNeedAuth(client) && + dispatcher->needAuth) { + /* Explicitly *NOT* calling remoteDispatchAuthError() because + we want back-compatability with libvirt clients which don't + support the VIR_ERR_AUTH_FAILED error code */ + virNetServerProgramFormatError(prog, rerr, VIR_ERR_RPC, + "%s", _("authentication required")); + goto error; + } + + if (VIR_ALLOC_N(arg, dispatcher->arg_len) < 0) { + virNetServerProgramOOMError(prog, rerr); + goto error; + } + if (VIR_ALLOC_N(ret, dispatcher->ret_len) < 0) { + virNetServerProgramOOMError(prog, rerr); + goto error; + } + + if (virNetMessageDecodePayload(&msg->msg, dispatcher->arg_filter, arg) < 0) + goto error; + + /* + * When the RPC handler is called: + * + * - Server object is unlocked + * - Client object is unlocked + * + * Without locking, it is safe to use: + * + * 'rerr', 'args and 'ret' + */ + rv = (dispatcher->func)(server, client, &msg->msg.header, rerr, arg, ret); + + xdr_free(dispatcher->arg_filter, arg); + + if (rv < 0) + goto error; + + /* Return header. We're re-using same message object, so + * only need to tweak type/status fields */ + /*msg->msg.header.prog = msg->msg.header.prog;*/ + /*msg->msg.header.vers = msg->msg.header.vers;*/ + /*msg->msg.header.proc = msg->msg.header.proc;*/ + msg->msg.header.type = VIR_NET_REPLY; + /*msg->msg.header.serial = msg->msg.header.serial;*/ + msg->msg.header.status = VIR_NET_OK; + + if (virNetMessageEncodeHeader(&msg->msg) < 0) { + xdr_free(dispatcher->ret_filter, ret); + goto error; + } + + if (virNetMessageEncodePayload(&msg->msg, dispatcher->ret_filter, ret) < 0) { + xdr_free(dispatcher->ret_filter, ret); + goto error; + } + + /* Reset ready for I/O */ + msg->msg.bufferLength = len; + msg->msg.bufferOffset = 0; + + VIR_FREE(arg); + VIR_FREE(ret); + + /* Put reply on end of tx queue to send out */ + virNetServerClientSendMessage(client, msg); + + return 0; + +error: + /* Bad stuff (de-)serializing message, but we have an + * RPC error message we can send back to the client */ + rv = virNetServerProgramSerializeReplyError(prog, client, rerr, &msg->msg.header); + + if (rv >= 0) + VIR_FREE(msg); + + VIR_FREE(arg); + VIR_FREE(ret); + + return rv; +} + + +void virNetServerProgramFree(virNetServerProgramPtr prog) +{ + if (!prog) + return; + + prog->refs--; + if (prog->refs > 0) + return; + + VIR_FREE(prog); +} + + diff --git a/src/rpc/virnetserverprogram.h b/src/rpc/virnetserverprogram.h new file mode 100644 index 0000000..d116867 --- /dev/null +++ b/src/rpc/virnetserverprogram.h @@ -0,0 +1,76 @@ + +#ifndef __VIR_NET_PROGRAM_H__ +#define __VIR_NET_PROGRAM_H__ + +#include <stdbool.h> + +#include "virnetservermessage.h" + +typedef struct _virNetServer virNetServer; +typedef virNetServer *virNetServerPtr; + +typedef struct _virNetServerClient virNetServerClient; +typedef virNetServerClient *virNetServerClientPtr; + +typedef struct _virNetServerService virNetServerService; +typedef virNetServerService *virNetServerServicePtr; + +typedef struct _virNetServerProgram virNetServerProgram; +typedef virNetServerProgram *virNetServerProgramPtr; + +typedef struct _virNetServerProgramProc virNetServerProgramProc; +typedef virNetServerProgramProc *virNetServerProgramProcPtr; + +typedef struct _virNetServerProgramErrorHandler virNetServerProgramErrorHander; +typedef virNetServerProgramErrorHander *virNetServerProgramErrorHanderPtr; + +typedef int (*virNetServerProgramErrorFunc)(virNetServerProgramPtr prog, + void *rerr, + int code, + const char *msg); + +struct _virNetServerProgramErrorHandler { + virNetServerProgramErrorFunc func; + size_t len; + xdrproc_t filter; +}; + + +typedef int (*virNetServerProgramDispatchFunc)(virNetServerPtr server, + virNetServerClientPtr client, + virNetMessageHeader *hdr, + void *err, + void *args, + void *ret); + +struct _virNetServerProgramProc { + virNetServerProgramDispatchFunc func; + size_t arg_len; + xdrproc_t arg_filter; + size_t ret_len; + xdrproc_t ret_filter; + bool needAuth; +}; + +virNetServerProgramPtr virNetServerProgramNew(unsigned program, + unsigned version, + virNetServerProgramProcPtr procs, + size_t nprocs, + virNetServerProgramErrorHanderPtr err); + +void virNetServerProgramRef(virNetServerProgramPtr prog); + +int virNetServerProgramMatches(virNetServerProgramPtr prog, + virNetServerMessagePtr msg); + +int virNetServerProgramDispatch(virNetServerProgramPtr prog, + virNetServerPtr server, + virNetServerClientPtr client, + virNetServerMessagePtr msg); + +void virNetServerProgramFree(virNetServerProgramPtr prog); + + + + +#endif /* __VIR_NET_SERVER_PROGRAM_H__ */ diff --git a/src/rpc/virnetserverservice.c b/src/rpc/virnetserverservice.c new file mode 100644 index 0000000..f985603 --- /dev/null +++ b/src/rpc/virnetserverservice.c @@ -0,0 +1,208 @@ + +#include <config.h> + + +#include "virnetserverservice.h" + +#include "memory.h" +#include "virterror_internal.h" + + +#define VIR_FROM_THIS VIR_FROM_RPC + +struct _virNetServerService { + int refs; + + size_t nsocks; + virNetSocketPtr *socks; + + int auth; + + virNetTLSContextPtr tls; + + virNetServerServiceDispatchFunc dispatchFunc; + void *dispatchOpaque; +}; + + + +static void virNetServerServiceAccept(virNetSocketPtr sock, + int events ATTRIBUTE_UNUSED, + void *opaque) +{ + virNetServerServicePtr svc = opaque; + virNetServerClientPtr client = NULL; + virNetSocketPtr clientsock = NULL; + + if (virNetSocketAccept(sock, &clientsock) < 0) + goto error; + + if (!clientsock) /* Connection already went away */ + goto cleanup; + + if (!(client = virNetServerClientNew(clientsock, + svc->auth, + svc->tls))) + goto error; + + if (!svc->dispatchFunc) + goto error; + + svc->dispatchFunc(svc, client, svc->dispatchOpaque); + + virNetServerClientFree(client); + +cleanup: + return; + +error: + virNetSocketFree(clientsock); +} + + +virNetServerServicePtr virNetServerServiceNewTCP(const char *nodename, + const char *service, + int auth, + virNetTLSContextPtr tls) +{ + virNetServerServicePtr svc; + int i; + + if (VIR_ALLOC(svc) < 0) + goto no_memory; + + svc->refs = 1; + svc->auth = auth; + svc->tls = tls; + if (tls) + virNetTLSContextRef(tls); + + if (virNetSocketNewListenTCP(nodename, + service, + &svc->socks, + &svc->nsocks) < 0) + goto error; + + for (i = 0 ; i < svc->nsocks ; i++) { + if (virNetSocketListen(svc->socks[i]) < 0) + goto error; + + /* IO callback is initially disabled, until we're ready + * to deal with incoming clients */ + if (virNetSocketAddIOCallback(svc->socks[i], + 0, + virNetServerServiceAccept, + svc) < 0) + goto error; + } + + + return svc; + +no_memory: + virReportOOMError(); +error: + virNetServerServiceFree(svc); + return NULL; +} + + +virNetServerServicePtr virNetServerServiceNewUNIX(const char *path, + mode_t mask, + gid_t grp, + int auth, + virNetTLSContextPtr tls) +{ + virNetServerServicePtr svc; + int i; + + if (VIR_ALLOC(svc) < 0) + goto no_memory; + + svc->refs = 1; + svc->auth = auth; + svc->tls = tls; + if (tls) + virNetTLSContextRef(tls); + + svc->nsocks = 1; + if (VIR_ALLOC_N(svc->socks, svc->nsocks) < 0) + goto no_memory; + + if (virNetSocketNewListenUNIX(path, + mask, + grp, + &svc->socks[0]) < 0) + goto error; + + for (i = 0 ; i < svc->nsocks ; i++) { + if (virNetSocketListen(svc->socks[i]) < 0) + goto error; + + /* IO callback is initially disabled, until we're ready + * to deal with incoming clients */ + if (virNetSocketAddIOCallback(svc->socks[i], + 0, + virNetServerServiceAccept, + svc) < 0) + goto error; + } + + + return svc; + +no_memory: + virReportOOMError(); +error: + virNetServerServiceFree(svc); + return NULL; +} + + +void virNetServerServiceRef(virNetServerServicePtr svc) +{ + svc->refs++; +} + + +void virNetServerServiceSetDispatcher(virNetServerServicePtr svc, + virNetServerServiceDispatchFunc func, + void *opaque) +{ + svc->dispatchFunc = func; + svc->dispatchOpaque = opaque; +} + + +void virNetServerServiceFree(virNetServerServicePtr svc) +{ + int i; + + if (!svc) + return; + + svc->refs--; + if (svc->refs > 0) + return; + + for (i = 0 ; i < svc->nsocks ; i++) + virNetSocketFree(svc->socks[i]); + VIR_FREE(svc->socks); + + virNetTLSContextFree(svc->tls); + + VIR_FREE(svc); +} + +void virNetServerServiceToggle(virNetServerServicePtr svc, + bool enabled) +{ + int i; + + for (i = 0 ; i < svc->nsocks ; i++) + virNetSocketUpdateIOCallback(svc->socks[i], + enabled ? + VIR_EVENT_HANDLE_READABLE : + 0); +} + diff --git a/src/rpc/virnetserverservice.h b/src/rpc/virnetserverservice.h new file mode 100644 index 0000000..4bedde7 --- /dev/null +++ b/src/rpc/virnetserverservice.h @@ -0,0 +1,32 @@ + +#ifndef __VIR_NET_SERVER_SERVICE_H__ +#define __VIR_NET_SERVER_SERVICE_H__ + +#include "virnetserverclient.h" + +typedef int (*virNetServerServiceDispatchFunc)(virNetServerServicePtr svc, + virNetServerClientPtr client, + void *opaque); + +virNetServerServicePtr virNetServerServiceNewTCP(const char *nodename, + const char *service, + int auth, + virNetTLSContextPtr tls); +virNetServerServicePtr virNetServerServiceNewUNIX(const char *path, + mode_t mask, + gid_t grp, + int auth, + virNetTLSContextPtr tls); + +void virNetServerServiceRef(virNetServerServicePtr svc); + +void virNetServerServiceSetDispatcher(virNetServerServicePtr svc, + virNetServerServiceDispatchFunc func, + void *opaque); + +void virNetServerServiceFree(virNetServerServicePtr svc); + +void virNetServerServiceToggle(virNetServerServicePtr svc, + bool enabled); + +#endif -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
To facilitate creation of new daemons providing XDR RPC services, pull alot of the libvirtd daemon code into a set of reusable objects.
* virNetServer: A server contains one or more services which accept incoming clients. It maintains the list of active clients. It has a list of RPC programs which can be used by clients. When clients produce a complete RPC message, the server passes this onto the corresponding program for handling, and queues any response back with the client.
* virNetServerClient: Encapsulates a single client connection. All I/O for the client is handled, reading & writing RPC messages. Also contains the SASL/TLS code, but this will eventually move into the virNetSocket object
* virNetServerProgram: Handles processing and dispatch of RPC method calls for a single RPC (program,version). Multiple programs can be registered with the server.
* virNetServerService: Encapsulates socket(s) listening for new connections. Each service listens on a single host/port, but may have multiple sockets if on a dual IPv4/6 host.
Each new daemon now merely has to define the list of RPC procedures & their handlers. It does not need to deal with any network related functionality at all.
@@ -1138,6 +1138,22 @@ libvirt_net_rpc_la_LDFLAGS = \ libvirt_net_rpc_la_LIBADD = \ $(CYGWIN_EXTRA_LIBADD)
+libvirt_net_server_la_SOURCES = \ + rpc/virnetservermessage.h \ + rpc/virnetserverprogram.h rpc/virnetserverprogram.c \ + rpc/virnetserverservice.h rpc/virnetserverservice.c \ + rpc/virnetserverclient.h rpc/virnetserverclient.c \ + rpc/virnetserver.h rpc/virnetserver.c +libvirt_net_server_la_CFLAGS = \ + $(AM_CFLAGS) +libvirt_net_server_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS)l
s/l$//
+ +struct _virNetServer { + int refs;
size_t?
+static void virNetServerHandleJob(void *jobOpaque, void *opaque) +{ + virNetServerPtr srv = opaque; + virNetServerJobPtr job = jobOpaque; + virNetServerProgramPtr prog = NULL; + int i;
s/int/size_t/
+ + if (virNetServerProgramDispatch(prog, + srv, + job->client, + job->msg) < 0) { + job->msg = NULL; + goto error; + }
Should this call be run while the virNetServerLock is still held, or should we drop and reacquire the lock to allow other virNetServer threads to make progress during the arbitrarily long dispatch?
+static int virNetServerDispatchNewClient(virNetServerServicePtr svc ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + void *opaque) +{ + virNetServerPtr srv = opaque; + + virNetServerLock(srv); + + if (srv->nclients >= srv->nclients_max) { + virNetError(VIR_ERR_RPC, + _("Too many active clients (%d), dropping connection from %s"), + (int)srv->nclients_max, virNetServerClientAddrString(client));
You can safely use %zu to avoid the case of size_t srv->nclients_max.
+ +#if 0 + reprocess: + for (i = 0 ; i < srv->nclients ; i++) { + int inactive;
Any reason to keep this block of code, since you commented it out?
+static void +virNetServerProgramFormatError(virNetServerProgramPtr prog, + void *rerr, + int code, + const char *fmt, + ...) +{ + va_list args; + char msgbuf[1024]; + char *msg = msgbuf; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof msgbuf, fmt, args);
Should we do something special if the message got truncated at sizeof(msgbuf)?
+ +virNetServerServicePtr virNetServerServiceNewTCP(const char *nodename, + const char *service, + int auth, + virNetTLSContextPtr tls) +{ + virNetServerServicePtr svc; + int i;
s/int/size_t/ Mostly looks reasonable (probably because it's mostly copying from existing working code, and just renaming into the new API names). -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Thu, Dec 09, 2010 at 04:09:20PM -0700, Eric Blake wrote:
On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
+ + if (virNetServerProgramDispatch(prog, + srv, + job->client, + job->msg) < 0) { + job->msg = NULL; + goto error; + }
Should this call be run while the virNetServerLock is still held, or should we drop and reacquire the lock to allow other virNetServer threads to make progress during the arbitrarily long dispatch?
Yes, the new posting does proper lock drop+reacquire now.
+#if 0 + reprocess: + for (i = 0 ; i < srv->nclients ; i++) { + int inactive;
Any reason to keep this block of code, since you commented it out?
Yep, I uncommented it now and made it work
+static void +virNetServerProgramFormatError(virNetServerProgramPtr prog, + void *rerr, + int code, + const char *fmt, + ...) +{ + va_list args; + char msgbuf[1024]; + char *msg = msgbuf; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof msgbuf, fmt, args);
Should we do something special if the message got truncated at sizeof(msgbuf)?
This and all similar methods have gone. We use normal internal error reporting APIs in libvirtd now. Daniel

<...snip...>
diff --git a/src/Makefile.am b/src/Makefile.am index 613ff0a..e78a0af 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1117,7 +1117,7 @@ libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
-noinst_LTLIBRARIES += libvirt-net-rpc.la +noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la
s/libvirt-net-rpc-server.la/libvirt-net-server.la/ to make it compile -- Thanks, Hu Tao

To facilitate creation of new clients using XDR RPC services, pull alot of the remote driver code into a set of reusable objects. - virNetClient: Encapsulates a socket connection to a remote RPC server. Handles all the network I/O for reading/writing RPC messages. Delegates RPC encoding and decoding to the registered programs - virNetClientProgram: Handles processing and dispatch of RPC messages for a single RPC (program,version). A program can register to receive async events from a client - virNetClientSASLContext: Handles everything todo with SASL authentication and encryption. The callers no longer need directly call any cyrus-sasl APIs, which means error handling is simpler & alternative SASL impls can be provided for Win32 Each new client program now merely needs to define the list of RPC procedures & events it wants and their handlers. It does not need to deal with any of the network I/O functionality at all. --- src/Makefile.am | 14 +- src/rpc/virnetclient.c | 1237 +++++++++++++++++++++++++++++++++++++ src/rpc/virnetclient.h | 60 ++ src/rpc/virnetclientprogram.c | 258 ++++++++ src/rpc/virnetclientprogram.h | 71 +++ src/rpc/virnetclientsaslcontext.c | 246 ++++++++ src/rpc/virnetclientsaslcontext.h | 66 ++ 7 files changed, 1951 insertions(+), 1 deletions(-) create mode 100644 src/rpc/virnetclient.c create mode 100644 src/rpc/virnetclient.h create mode 100644 src/rpc/virnetclientprogram.c create mode 100644 src/rpc/virnetclientprogram.h create mode 100644 src/rpc/virnetclientsaslcontext.c create mode 100644 src/rpc/virnetclientsaslcontext.h diff --git a/src/Makefile.am b/src/Makefile.am index e78a0af..4c6efa8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1117,7 +1117,7 @@ libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) -noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la +noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la libvirt-net-rpc-client.la libvirt_net_rpc_la_SOURCES = \ ../daemon/event.c \ @@ -1153,6 +1153,18 @@ libvirt_net_server_la_LDFLAGS = \ libvirt_net_server_la_LIBADD = \ $(CYGWIN_EXTRA_LIBADD) +libvirt_net_client_la_SOURCES = \ + rpc/virnetclientsaslcontext.h rpc/virnetclientsaslcontext.c \ + rpc/virnetclientprogram.h rpc/virnetclientprogram.c \ + rpc/virnetclient.h rpc/virnetclient.c +libvirt_net_client_la_CFLAGS = \ + $(AM_CFLAGS) +libvirt_net_client_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS)l +libvirt_net_client_la_LIBADD = \ + $(CYGWIN_EXTRA_LIBADD) libexec_PROGRAMS = diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c new file mode 100644 index 0000000..d3a8740 --- /dev/null +++ b/src/rpc/virnetclient.c @@ -0,0 +1,1237 @@ + + +#include <config.h> + +#include <unistd.h> +#include <poll.h> +#include <signal.h> + +#include "virnetclient.h" +#include "virnetsocket.h" +#include "memory.h" +#include "threads.h" +#include "files.h" +#include "logging.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +#ifdef WIN32 +# define pipe(fds) _pipe(fds,4096, _O_BINARY) +#endif + +typedef struct _virNetClientCall virNetClientCall; +typedef virNetClientCall *virNetClientCallPtr; + +enum { + VIR_NET_CLIENT_MODE_WAIT_TX, + VIR_NET_CLIENT_MODE_WAIT_RX, + VIR_NET_CLIENT_MODE_COMPLETE, +}; + +struct _virNetClientCall { + int mode; + + virNetMessagePtr msg; + int expectReply; + + virCond cond; + + +/* remote_error err; */ + + virNetClientCallPtr next; +}; + + +struct _virNetClient { + int refs; + + virMutex lock; + + virNetSocketPtr sock; + + virNetTLSSessionPtr tls; + char *hostname; + + virNetClientProgramPtr *programs; + size_t nprograms; + + /* For incoming message packets */ + virNetMessage msg; + +#if HAVE_SASL + virNetClientSaslContextPtr sasl; + + const char *saslDecoded; + size_t saslDecodedLength; + size_t saslDecodedOffset; + + const char *saslEncoded; + size_t saslEncodedLength; + size_t saslEncodedOffset; +#endif + + /* Self-pipe to wakeup threads waiting in poll() */ + int wakeupSendFD; + int wakeupReadFD; + + /* List of threads currently waiting for dispatch */ + virNetClientCallPtr waitDispatch; +}; + + +static void virNetClientLock(virNetClientPtr client) +{ + virMutexLock(&client->lock); +} + + +static void virNetClientUnlock(virNetClientPtr client) +{ + virMutexUnlock(&client->lock); +} + +static ssize_t virNetClientTLSWriteFunc(const char *buf, size_t len, + void *opaque) +{ + virNetClientPtr client = opaque; + + return virNetSocketWrite(client->sock, buf, len); +} + + +static ssize_t virNetClientTLSReadFunc(char *buf, size_t len, + void *opaque) +{ + virNetClientPtr client = opaque; + + return virNetSocketRead(client->sock, buf, len); +} + + +static void virNetClientIncomingEvent(virNetSocketPtr sock, + int events, + void *opaque); + +static virNetClientPtr virNetClientNew(virNetSocketPtr sock, + const char *hostname) +{ + virNetClientPtr client; + int wakeupFD[2] = { -1, -1 }; + + if (pipe(wakeupFD) < 0) { + virReportSystemError(errno, "%s", + _("unable to make pipe")); + goto error; + } + + if (VIR_ALLOC(client) < 0) + goto no_memory; + + client->refs = 1; + + if (virMutexInit(&client->lock) < 0) + goto error; + + client->sock = sock; + client->wakeupReadFD = wakeupFD[0]; + client->wakeupSendFD = wakeupFD[1]; + wakeupFD[0] = wakeupFD[1] = -1; + + if (hostname && + !(client->hostname = strdup(hostname))) + goto no_memory; + + /* Set up a callback to listen on the socket data */ + if (virNetSocketAddIOCallback(client->sock, + VIR_EVENT_HANDLE_READABLE, + virNetClientIncomingEvent, + client) < 0) + VIR_DEBUG0("Failed to add event watch, disabling events"); + + return client; + +no_memory: + virReportOOMError(); +error: + VIR_FORCE_CLOSE(wakeupFD[0]); + VIR_FORCE_CLOSE(wakeupFD[1]); + virNetClientFree(client); + return NULL; +} + + +virNetClientPtr virNetClientNewUNIX(const char *path, + bool spawnDaemon, + const char *binary) +{ + virNetSocketPtr sock; + + if (virNetSocketNewConnectUNIX(path, spawnDaemon, binary, &sock) < 0) + return NULL; + + return virNetClientNew(sock, NULL); +} + + +virNetClientPtr virNetClientNewTCP(const char *nodename, + const char *service) +{ + virNetSocketPtr sock; + + if (virNetSocketNewConnectTCP(nodename, service, &sock) < 0) + return NULL; + + return virNetClientNew(sock, nodename); +} + +virNetClientPtr virNetClientNewSSH(const char *nodename, + const char *service, + const char *binary, + const char *username, + bool noTTY, + const char *netcat, + const char *path) +{ + virNetSocketPtr sock; + + if (virNetSocketNewConnectSSH(nodename, service, binary, username, noTTY, netcat, path, &sock) < 0) + return NULL; + + return virNetClientNew(sock, NULL); +} + +virNetClientPtr virNetClientNewCommand(const char **cmdargv, + const char **cmdenv) +{ + virNetSocketPtr sock; + + if (virNetSocketNewConnectCommand(cmdargv, cmdenv, &sock) < 0) + return NULL; + + return virNetClientNew(sock, NULL); +} + + +void virNetClientRef(virNetClientPtr client) +{ + virNetClientLock(client); + client->refs++; + virNetClientUnlock(client); +} + + +void virNetClientFree(virNetClientPtr client) +{ + int i; + + if (!client) + return; + + virNetClientLock(client); + client->refs--; + if (client->refs > 0) { + virNetClientUnlock(client); + return; + } + + for (i = 0 ; i < client->nprograms ; i++) + virNetClientProgramFree(client->programs[i]); + VIR_FREE(client->programs); + + VIR_FORCE_CLOSE(client->wakeupSendFD); + VIR_FORCE_CLOSE(client->wakeupReadFD); + + VIR_FREE(client->hostname); + + virNetSocketRemoveIOCallback(client->sock); + virNetSocketFree(client->sock); + virNetTLSSessionFree(client->tls); + virNetClientSaslContextFree(client->sasl); + virNetClientUnlock(client); + virMutexDestroy(&client->lock); + + VIR_FREE(client); +} + + +void virNetClientSetSASLContext(virNetClientPtr client, + virNetClientSaslContextPtr ctxt) +{ + virNetClientLock(client); + client->sasl = ctxt; + virNetClientSaslContextRef(ctxt); + virNetClientUnlock(client); +} + + +int virNetClientSetTLSSession(virNetClientPtr client, + virNetTLSContextPtr tls) +{ + int ret; + char buf[1]; + int len; + struct pollfd fds[1]; +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t oldmask, blockedsigs; + + sigemptyset (&blockedsigs); + sigaddset (&blockedsigs, SIGWINCH); + sigaddset (&blockedsigs, SIGCHLD); + sigaddset (&blockedsigs, SIGPIPE); +#endif + + virNetClientLock(client); + + if (!(client->tls = virNetTLSSessionNew(tls, + client->hostname, + virNetClientTLSWriteFunc, + virNetClientTLSReadFunc, + client))) + goto error; + + for (;;) { + ret = virNetTLSSessionHandshake(client->tls); + + if (ret < 0) + goto error; + if (ret == 0) + break; + + fds[0].fd = virNetSocketFD(client->sock); + fds[0].revents = 0; + if (virNetTLSSessionHandshakeDirection(client->tls) == 0) + fds[0].events = POLLIN; + else + fds[0].events = POLLOUT; + + /* Block SIGWINCH from interrupting poll in curses programs, + * then restore the original signal mask again immediately + * after the call (RHBZ#567931). Same for SIGCHLD and SIGPIPE + * at the suggestion of Paolo Bonzini and Daniel Berrange. + */ +#ifdef HAVE_PTHREAD_SIGMASK + ignore_value(pthread_sigmask(SIG_BLOCK, &blockedsigs, &oldmask)); +#endif + + repoll: + ret = poll(fds, ARRAY_CARDINALITY(fds), -1); + if (ret < 0 && errno == EAGAIN) + goto repoll; + +#ifdef HAVE_PTHREAD_SIGMASK + ignore_value(pthread_sigmask(SIG_BLOCK, &oldmask, NULL)); +#endif + } + + ret = virNetTLSContextCheckCertificate(tls, client->tls); + + if (ret < 0) + goto error; + + /* At this point, the server is verifying _our_ certificate, IP address, + * etc. If we make the grade, it will send us a '\1' byte. + */ + + fds[0].fd = virNetSocketFD(client->sock); + fds[0].revents = 0; + fds[0].events = POLLIN; + +#ifdef HAVE_PTHREAD_SIGMASK + /* Block SIGWINCH from interrupting poll in curses programs */ + ignore_value(pthread_sigmask(SIG_BLOCK, &blockedsigs, &oldmask)); +#endif + + repoll2: + ret = poll(fds, ARRAY_CARDINALITY(fds), -1); + if (ret < 0 && errno == EAGAIN) + goto repoll2; + +#ifdef HAVE_PTHREAD_SIGMASK + ignore_value(pthread_sigmask(SIG_BLOCK, &oldmask, NULL)); +#endif + + len = virNetTLSSessionRead(client->tls, buf, 1); + if (len < 0) { + virReportSystemError(errno, "%s", + _("Unable to read TLS confirmation")); + goto error; + } + if (len != 1 || buf[0] != '\1') { + virNetError(VIR_ERR_RPC, "%s", + _("server verification (of our certificate or IP " + "address) failed")); + goto error; + } + + virNetClientUnlock(client); + return 0; + +error: + virNetTLSSessionFree(client->tls); + client->tls = NULL; + virNetClientUnlock(client); + return -1; +} + +bool virNetClientIsEncrypted(virNetClientPtr client) +{ + bool ret; + virNetClientLock(client); + ret = client->tls || client->sasl ? true : false; + virNetClientUnlock(client); + return ret; +} + + +int virNetClientAddProgram(virNetClientPtr client, + virNetClientProgramPtr prog) +{ + virNetClientLock(client); + + if (VIR_EXPAND_N(client->programs, client->nprograms, 1) < 0) + goto no_memory; + + client->programs[client->nprograms-1] = prog; + virNetClientProgramRef(prog); + + virNetClientUnlock(client); + return 0; + +no_memory: + virReportOOMError(); + virNetClientUnlock(client); + return -1; +} + + +const char *virNetClientLocalAddrString(virNetClientPtr client) +{ + return virNetSocketLocalAddrString(client->sock); +} + +const char *virNetClientRemoteAddrString(virNetClientPtr client) +{ + return virNetSocketRemoteAddrString(client->sock); +} + +int virNetClientGetTLSKeySize(virNetClientPtr client) +{ + int ret = 0; + virNetClientLock(client); + if (client->tls) + ret = virNetTLSSessionGetKeySize(client->tls); + virNetClientUnlock(client); + return ret; +} + +static int +virNetClientCallDispatchReply(virNetClientPtr client) +{ + virNetClientCallPtr thecall; + + /* Ok, definitely got an RPC reply now find + out who's been waiting for it */ + thecall = client->waitDispatch; + while (thecall && + !(thecall->msg->header.prog == client->msg.header.prog && + thecall->msg->header.vers == client->msg.header.vers && + thecall->msg->header.serial == client->msg.header.serial)) + thecall = thecall->next; + + if (!thecall) { + virNetError(VIR_ERR_RPC, + _("no call waiting for reply with prog %d vers %d serial %d"), + client->msg.header.prog, client->msg.header.vers, client->msg.header.serial); + return -1; + } + + memcpy(thecall->msg->buffer, client->msg.buffer, sizeof(client->msg.buffer)); + memcpy(&thecall->msg->header, &client->msg.header, sizeof(client->msg.header)); + thecall->msg->bufferLength = client->msg.bufferLength; + thecall->msg->bufferOffset = client->msg.bufferOffset; + + thecall->mode = VIR_NET_CLIENT_MODE_COMPLETE; + + return 0; +} + +static int virNetClientCallDispatchMessage(virNetClientPtr client) +{ + int i; + virNetClientProgramPtr prog = NULL; + + for (i = 0 ; i < client->nprograms ; i++) { + if (virNetClientProgramMatches(client->programs[i], + &client->msg)) { + prog = client->programs[i]; + break; + } + } + if (!prog) { + VIR_DEBUG("No program found for event with prog=%d vers=%d", + client->msg.header.prog, client->msg.header.vers); + return -1; + } + + virNetClientProgramDispatch(prog, client, &client->msg); + + return 0; +} + +static int virNetClientCallDispatchStream(virNetClientPtr client ATTRIBUTE_UNUSED) +{ +#if 0 + struct private_stream_data *privst; + virNetClientCallPtr thecall; + + /* Try and find a matching stream */ + privst = client->streams; + while (privst && + privst->serial != hdr->serial && + privst->proc_nr != hdr->proc) + privst = privst->next; + + if (!privst) { + VIR_DEBUG("No registered stream matching serial=%d, proc=%d", + hdr->serial, hdr->proc); + return -1; + } + + /* See if there's also a (optional) call waiting for this reply */ + thecall = client->waitDispatch; + while (thecall && + thecall->serial != hdr->serial) + thecall = thecall->next; + + + /* Status is either REMOTE_OK (meaning that what follows is a ret + * structure), or REMOTE_ERROR (and what follows is a remote_error + * structure). + */ + switch (hdr->status) { + case REMOTE_CONTINUE: { + int avail = privst->incomingLength - privst->incomingOffset; + int need = client->bufferLength - client->bufferOffset; + VIR_DEBUG0("Got a stream data packet"); + + /* XXX flag stream as complete somwhere if need==0 */ + + if (need > avail) { + int extra = need - avail; + if (VIR_REALLOC_N(privst->incoming, + privst->incomingLength + extra) < 0) { + VIR_DEBUG0("Out of memory handling stream data"); + return -1; + } + privst->incomingLength += extra; + } + + memcpy(privst->incoming + privst->incomingOffset, + client->buffer + client->bufferOffset, + client->bufferLength - client->bufferOffset); + privst->incomingOffset += (client->bufferLength - client->bufferOffset); + + if (thecall && thecall->want_reply) { + VIR_DEBUG("Got sync data packet offset=%d", privst->incomingOffset); + thecall->mode = REMOTE_MODE_COMPLETE; + } else { + VIR_DEBUG("Got aysnc data packet offset=%d", privst->incomingOffset); + remoteStreamEventTimerUpdate(privst); + } + return 0; + } + + case REMOTE_OK: + VIR_DEBUG0("Got a synchronous confirm"); + if (!thecall) { + VIR_DEBUG0("Got unexpected stream finish confirmation"); + return -1; + } + thecall->mode = REMOTE_MODE_COMPLETE; + return 0; + + case REMOTE_ERROR: + if (thecall && thecall->want_reply) { + VIR_DEBUG0("Got a synchronous error"); + /* Give the error straight to this call */ + memset (&thecall->err, 0, sizeof thecall->err); + if (!xdr_remote_error (xdr, &thecall->err)) { + remoteError(VIR_ERR_RPC, "%s", _("unmarshalling remote_error")); + return -1; + } + thecall->mode = REMOTE_MODE_ERROR; + } else { + VIR_DEBUG0("Got a asynchronous error"); + /* No call, so queue the error against the stream */ + if (privst->has_error) { + VIR_DEBUG0("Got unexpected duplicate stream error"); + return -1; + } + privst->has_error = 1; + memset (&privst->err, 0, sizeof privst->err); + if (!xdr_remote_error (xdr, &privst->err)) { + VIR_DEBUG0("Failed to unmarshall error"); + return -1; + } + } + return 0; + + default: + VIR_WARN("Stream with unexpected serial=%d, proc=%d, status=%d", + hdr->serial, hdr->proc, hdr->status); + return -1; + } +#endif + return 0; +} + + +static int +virNetClientCallDispatch(virNetClientPtr client) +{ + if (virNetMessageDecodeHeader(&client->msg) < 0) + return -1; + + switch (client->msg.header.type) { + case VIR_NET_REPLY: /* Normal RPC replies */ + return virNetClientCallDispatchReply(client); + + case VIR_NET_MESSAGE: /* Async notifications */ + return virNetClientCallDispatchMessage(client); + + case VIR_NET_STREAM: /* Stream protocol */ + return virNetClientCallDispatchStream(client); + + default: + virNetError(VIR_ERR_RPC, + _("got unexpected RPC call prog %d vers %d proc %d type %d"), + client->msg.header.prog, client->msg.header.vers, + client->msg.header.proc, client->msg.header.type); + return -1; + } +} + + +static ssize_t +virNetClientIOWriteBuffer(virNetClientPtr client, + const char *bytes, size_t len) +{ + ssize_t ret; + + resend: + if (client->tls) + ret = virNetTLSSessionWrite(client->tls, bytes, len); + else + ret = virNetSocketWrite(client->sock, bytes, len); + if (ret < 0) { + if (errno == EINTR) + goto resend; + if (errno == EAGAIN) + return 0; + + virReportSystemError(errno, "%s", _("cannot send data")); + return -1; + } + + return ret; +} + + +static ssize_t +virNetClientIOReadBuffer(virNetClientPtr client, + char *bytes, size_t len) +{ + size_t ret; + +resend: + if (client->tls) + ret = virNetTLSSessionRead(client->tls, bytes, len); + else + ret = virNetSocketRead(client->sock, bytes, len); + if (ret <= 0) { + if (ret == -1) { + if (errno == EINTR) + goto resend; + if (errno == EAGAIN) + return 0; + + virReportSystemError(errno, "%s", + _("cannot recv data")); + } else { + virNetError(VIR_ERR_SYSTEM_ERROR, "%s", + _("server closed connection")); + } + return -1; + } + + return ret; +} + + +static ssize_t +virNetClientIOWriteMessage(virNetClientPtr client, + virNetClientCallPtr thecall) +{ +#if HAVE_SASL + if (client->sasl) { + const char *output; + size_t outputlen; + ssize_t ret; + + if (!client->saslEncoded) { + if (virNetClientSaslContextEncode(client->sasl, + thecall->msg->buffer + thecall->msg->bufferOffset, + thecall->msg->bufferLength - thecall->msg->bufferOffset, + &output, &outputlen) < 0) + return -1; + + client->saslEncoded = output; + client->saslEncodedLength = outputlen; + client->saslEncodedOffset = 0; + + thecall->msg->bufferOffset = thecall->msg->bufferLength; + } + + ret = virNetClientIOWriteBuffer(client, + client->saslEncoded + client->saslEncodedOffset, + client->saslEncodedLength - client->saslEncodedOffset); + if (ret < 0) + return ret; + client->saslEncodedOffset += ret; + + if (client->saslEncodedOffset == client->saslEncodedLength) { + client->saslEncoded = NULL; + client->saslEncodedOffset = client->saslEncodedLength = 0; + if (thecall->expectReply) + thecall->mode = VIR_NET_CLIENT_MODE_WAIT_RX; + else + thecall->mode = VIR_NET_CLIENT_MODE_COMPLETE; + } + } else { +#endif + ssize_t ret; + ret = virNetClientIOWriteBuffer(client, + thecall->msg->buffer + thecall->msg->bufferOffset, + thecall->msg->bufferLength - thecall->msg->bufferOffset); + if (ret < 0) + return ret; + thecall->msg->bufferOffset += ret; + + if (thecall->msg->bufferOffset == thecall->msg->bufferLength) { + thecall->msg->bufferOffset = thecall->msg->bufferLength = 0; + if (thecall->expectReply) + thecall->mode = VIR_NET_CLIENT_MODE_WAIT_RX; + else + thecall->mode = VIR_NET_CLIENT_MODE_COMPLETE; + } +#if HAVE_SASL + } +#endif + return 0; +} + + +static ssize_t +virNetClientIOHandleOutput(virNetClientPtr client) +{ + virNetClientCallPtr thecall = client->waitDispatch; + + while (thecall && + thecall->mode != VIR_NET_CLIENT_MODE_WAIT_TX) + thecall = thecall->next; + + if (!thecall) + return -1; /* Shouldn't happen, but you never know... */ + + while (thecall) { + ssize_t ret = virNetClientIOWriteMessage(client, thecall); + if (ret < 0) + return ret; + + if (thecall->mode == VIR_NET_CLIENT_MODE_WAIT_TX) + return 0; /* Blocking write, to back to event loop */ + + thecall = thecall->next; + } + + return 0; /* No more calls to send, all done */ +} + +static ssize_t +virNetClientIOReadMessage(virNetClientPtr client) +{ + size_t wantData; + + /* Start by reading length word */ + if (client->msg.bufferLength == 0) + client->msg.bufferLength = 4; + + wantData = client->msg.bufferLength - client->msg.bufferOffset; + +#if HAVE_SASL + if (client->sasl) { + if (client->saslDecoded == NULL) { + char encoded[8192]; + ssize_t ret; + ret = virNetClientIOReadBuffer(client, encoded, sizeof(encoded)); + if (ret < 0) + return -1; + if (ret == 0) + return 0; + + if (virNetClientSaslContextDecode(client->sasl, + encoded, + ret, + &client->saslDecoded, + &client->saslDecodedLength) < 0) + return -1; + + client->saslDecodedOffset = 0; + } + + if ((client->saslDecodedLength - client->saslDecodedOffset) < wantData) + wantData = (client->saslDecodedLength - client->saslDecodedOffset); + + memcpy(client->msg.buffer + client->msg.bufferOffset, + client->saslDecoded + client->saslDecodedOffset, + wantData); + client->saslDecodedOffset += wantData; + client->msg.bufferOffset += wantData; + if (client->saslDecodedOffset == client->saslDecodedLength) { + client->saslDecodedOffset = client->saslDecodedLength = 0; + client->saslDecoded = NULL; + } + + return wantData; + } else { +#endif + ssize_t ret; + + ret = virNetClientIOReadBuffer(client, + client->msg.buffer + client->msg.bufferOffset, + wantData); + if (ret < 0) + return -1; + if (ret == 0) + return 0; + + client->msg.bufferOffset += ret; + + return ret; +#if HAVE_SASL + } +#endif +} + + +static ssize_t +virNetClientIOHandleInput(virNetClientPtr client) +{ + /* Read as much data as is available, until we get + * EAGAIN + */ + for (;;) { + ssize_t ret = virNetClientIOReadMessage(client); + + if (ret < 0) + return -1; + if (ret == 0) + return 0; /* Blocking on read */ + + /* Check for completion of our goal */ + if (client->msg.bufferOffset == client->msg.bufferLength) { + if (client->msg.bufferOffset == 4) { + ret = virNetMessageDecodeLength(&client->msg); + if (ret < 0) + return -1; + + /* + * We'll carry on around the loop to immediately + * process the message body, because it has probably + * already arrived. Worst case, we'll get EAGAIN on + * next iteration. + */ + } else { + ret = virNetClientCallDispatch(client); + client->msg.bufferOffset = client->msg.bufferLength = 0; + /* + * We've completed one call, so return even + * though there might still be more data on + * the wire. We need to actually let the caller + * deal with this arrived message to keep good + * response, and also to correctly handle EOF. + */ + return ret; + } + } + } +} + + +/* + * Process all calls pending dispatch/receive until we + * get a reply to our own call. Then quit and pass the buck + * to someone else. + */ +static int virNetClientIOEventLoop(virNetClientPtr client, + virNetClientCallPtr thiscall) +{ + struct pollfd fds[2]; + int ret; + + fds[0].fd = virNetSocketFD(client->sock); + fds[1].fd = client->wakeupReadFD; + + for (;;) { + virNetClientCallPtr tmp = client->waitDispatch; + virNetClientCallPtr prev; + char ignore; +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t oldmask, blockedsigs; +#endif + + fds[0].events = fds[0].revents = 0; + fds[1].events = fds[1].revents = 0; + + fds[1].events = POLLIN; + while (tmp) { + if (tmp->mode == VIR_NET_CLIENT_MODE_WAIT_RX) + fds[0].events |= POLLIN; + if (tmp->mode == VIR_NET_CLIENT_MODE_WAIT_TX) + fds[0].events |= POLLOUT; + + tmp = tmp->next; + } + +#if 0 + XXX + if (client->streams) + fds[0].events |= POLLIN; +#endif + + /* Release lock while poll'ing so other threads + * can stuff themselves on the queue */ + virNetClientUnlock(client); + + /* Block SIGWINCH from interrupting poll in curses programs, + * then restore the original signal mask again immediately + * after the call (RHBZ#567931). Same for SIGCHLD and SIGPIPE + * at the suggestion of Paolo Bonzini and Daniel Berrange. + */ +#ifdef HAVE_PTHREAD_SIGMASK + sigemptyset (&blockedsigs); + sigaddset (&blockedsigs, SIGWINCH); + sigaddset (&blockedsigs, SIGCHLD); + sigaddset (&blockedsigs, SIGPIPE); + ignore_value(pthread_sigmask(SIG_BLOCK, &blockedsigs, &oldmask)); +#endif + + repoll: + ret = poll(fds, ARRAY_CARDINALITY(fds), -1); + if (ret < 0 && errno == EAGAIN) + goto repoll; + +#ifdef HAVE_PTHREAD_SIGMASK + ignore_value(pthread_sigmask(SIG_SETMASK, &oldmask, NULL)); +#endif + + virNetClientLock(client); + + if (fds[1].revents) { + VIR_DEBUG0("Woken up from poll by other thread"); + if (saferead(client->wakeupReadFD, &ignore, sizeof(ignore)) != sizeof(ignore)) { + virReportSystemError(errno, "%s", + _("read on wakeup fd failed")); + goto error; + } + } + + if (ret < 0) { + if (errno == EWOULDBLOCK) + continue; + virReportSystemError(errno, + "%s", _("poll on socket failed")); + goto error; + } + + if (fds[0].revents & POLLOUT) { + if (virNetClientIOHandleOutput(client) < 0) + goto error; + } + + if (fds[0].revents & POLLIN) { + if (virNetClientIOHandleInput(client) < 0) + goto error; + } + + /* Iterate through waiting threads and if + * any are complete then tell 'em to wakeup + */ + tmp = client->waitDispatch; + prev = NULL; + while (tmp) { + if (tmp != thiscall && + tmp->mode == VIR_NET_CLIENT_MODE_COMPLETE) { + /* Take them out of the list */ + if (prev) + prev->next = tmp->next; + else + client->waitDispatch = tmp->next; + + /* And wake them up.... + * ...they won't actually wakeup until + * we release our mutex a short while + * later... + */ + VIR_DEBUG("Waking up sleep %p %p", tmp, client->waitDispatch); + virCondSignal(&tmp->cond); + } + prev = tmp; + tmp = tmp->next; + } + + /* Now see if *we* are done */ + if (thiscall->mode == VIR_NET_CLIENT_MODE_COMPLETE) { + /* We're at head of the list already, so + * remove us + */ + client->waitDispatch = thiscall->next; + VIR_DEBUG("Giving up the buck %p %p", thiscall, client->waitDispatch); + /* See if someone else is still waiting + * and if so, then pass the buck ! */ + if (client->waitDispatch) { + VIR_DEBUG("Passing the buck to %p", client->waitDispatch); + virCondSignal(&client->waitDispatch->cond); + } + return 0; + } + + + if (fds[0].revents & (POLLHUP | POLLERR)) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("received hangup / error event on socket")); + goto error; + } + } + + +error: + client->waitDispatch = thiscall->next; + VIR_DEBUG("Giving up the buck due to I/O error %p %p", thiscall, client->waitDispatch); + /* See if someone else is still waiting + * and if so, then pass the buck ! */ + if (client->waitDispatch) { + VIR_DEBUG("Passing the buck to %p", client->waitDispatch); + virCondSignal(&client->waitDispatch->cond); + } + return -1; +} + + +/* + * This function sends a message to remote server and awaits a reply + * + * NB. This does not free the args structure (not desirable, since you + * often want this allocated on the stack or else it contains strings + * which come from the user). It does however free any intermediate + * results, eg. the error structure if there is one. + * + * NB(2). Make sure to memset (&ret, 0, sizeof ret) before calling, + * else Bad Things will happen in the XDR code. + * + * NB(3) You must have the client lock before calling this + * + * NB(4) This is very complicated. Multiple threads are allowed to + * use the client for RPC at the same time. Obviously only one of + * them can. So if someone's using the socket, other threads are put + * to sleep on condition variables. The existing thread may completely + * send & receive their RPC call/reply while they're asleep. Or it + * may only get around to dealing with sending the call. Or it may + * get around to neither. So upon waking up from slumber, the other + * thread may or may not have more work todo. + * + * We call this dance 'passing the buck' + * + * http://en.wikipedia.org/wiki/Passing_the_buck + * + * "Buck passing or passing the buck is the action of transferring + * responsibility or blame unto another person. It is also used as + * a strategy in power politics when the actions of one country/ + * nation are blamed on another, providing an opportunity for war." + * + * NB(5) Don't Panic! + */ +static int virNetClientIO(virNetClientPtr client, + virNetClientCallPtr thiscall) +{ + int rv = -1; + + VIR_DEBUG("program=%u version=%u serial=%u proc=%d type=%d length=%d dispatach=%p", + thiscall->msg->header.prog, + thiscall->msg->header.vers, + thiscall->msg->header.serial, + thiscall->msg->header.proc, + thiscall->msg->header.type, + thiscall->msg->bufferLength, + client->waitDispatch); + + /* Check to see if another thread is dispatching */ + if (client->waitDispatch) { + /* Stick ourselves on the end of the wait queue */ + virNetClientCallPtr tmp = client->waitDispatch; + char ignore = 1; + while (tmp && tmp->next) + tmp = tmp->next; + if (tmp) + tmp->next = thiscall; + else + client->waitDispatch = thiscall; + + /* Force other thread to wakeup from poll */ + if (safewrite(client->wakeupSendFD, &ignore, sizeof(ignore)) != sizeof(ignore)) { + if (tmp) + tmp->next = NULL; + else + client->waitDispatch = NULL; + virReportSystemError(errno, "%s", + _("failed to wake up polling thread")); + return -1; + } + + VIR_DEBUG("Going to sleep %p %p", client->waitDispatch, thiscall); + /* Go to sleep while other thread is working... */ + if (virCondWait(&thiscall->cond, &client->lock) < 0) { + if (client->waitDispatch == thiscall) { + client->waitDispatch = thiscall->next; + } else { + tmp = client->waitDispatch; + while (tmp && tmp->next && + tmp->next != thiscall) { + tmp = tmp->next; + } + if (tmp && tmp->next == thiscall) + tmp->next = thiscall->next; + } + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to wait on condition")); + return -1; + } + + VIR_DEBUG("Wokeup from sleep %p %p", client->waitDispatch, thiscall); + /* Two reasons we can be woken up + * 1. Other thread has got our reply ready for us + * 2. Other thread is all done, and it is our turn to + * be the dispatcher to finish waiting for + * our reply + */ + if (thiscall->mode == VIR_NET_CLIENT_MODE_COMPLETE) { + rv = 0; + /* + * We avoided catching the buck and our reply is ready ! + * We've already had 'thiscall' removed from the list + * so just need to (maybe) handle errors & free it + */ + goto cleanup; + } + + /* Grr, someone passed the buck onto us ... */ + + } else { + /* We're first to catch the buck */ + client->waitDispatch = thiscall; + } + + VIR_DEBUG("We have the buck %p %p", client->waitDispatch, thiscall); + /* + * The buck stops here! + * + * At this point we're about to own the dispatch + * process... + */ + + /* + * Avoid needless wake-ups of the event loop in the + * case where this call is being made from a different + * thread than the event loop. These wake-ups would + * cause the event loop thread to be blocked on the + * mutex for the duration of the call + */ + virNetSocketUpdateIOCallback(client->sock, 0); + + rv = virNetClientIOEventLoop(client, thiscall); + + virNetSocketUpdateIOCallback(client->sock, VIR_EVENT_HANDLE_READABLE); + +cleanup: + VIR_DEBUG("All done with our call %p %p %d", client->waitDispatch, thiscall, rv); + return rv; +} + + +void virNetClientIncomingEvent(virNetSocketPtr sock, + int events, + void *opaque) +{ + virNetClientPtr client = opaque; + + virNetClientLock(client); + + /* This should be impossible, but it doesn't hurt to check */ + if (client->waitDispatch) + goto done; + + VIR_DEBUG("Event fired %p %d", sock, events); + + if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) { + VIR_DEBUG("%s : VIR_EVENT_HANDLE_HANGUP or " + "VIR_EVENT_HANDLE_ERROR encountered", __FUNCTION__); + virNetSocketRemoveIOCallback(sock); + goto done; + } + + if (virNetClientIOHandleInput(client) < 0) + VIR_DEBUG0("Something went wrong during async message processing"); + +done: + virNetClientUnlock(client); +} + + +int virNetClientSend(virNetClientPtr client, + virNetMessagePtr msg, + bool expectReply) +{ + virNetClientCallPtr call; + int ret = -1; + + if (VIR_ALLOC(call) < 0) { + virReportOOMError(); + return -1; + } + + virNetClientLock(client); + + if (virCondInit(&call->cond) < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot initialize condition variable")); + goto cleanup; + } + + call->mode = VIR_NET_CLIENT_MODE_WAIT_TX; + call->msg = msg; + call->expectReply = expectReply; + + ret = virNetClientIO(client, call); + +cleanup: + VIR_FREE(call); + virNetClientUnlock(client); + return ret; +} diff --git a/src/rpc/virnetclient.h b/src/rpc/virnetclient.h new file mode 100644 index 0000000..cd6a20f --- /dev/null +++ b/src/rpc/virnetclient.h @@ -0,0 +1,60 @@ + + +#ifndef __VIR_NET_CLIENT_H__ +#define __VIR_NET_CLIENT_H__ + +#include <stdbool.h> + +#include "virnettlscontext.h" +#include "virnetmessage.h" +#if HAVE_SASL +#include "virnetclientsaslcontext.h" +#endif +#include "virnetclientprogram.h" + + +virNetClientPtr virNetClientNewUNIX(const char *path, + bool spawnDaemon, + const char *daemon); + +virNetClientPtr virNetClientNewTCP(const char *nodename, + const char *service); + +virNetClientPtr virNetClientNewSSH(const char *nodename, + const char *service, + const char *binary, + const char *username, + bool noTTY, + const char *netcat, + const char *path); + +virNetClientPtr virNetClientNewCommand(const char **cmdargv, + const char **cmdenv); + +void virNetClientRef(virNetClientPtr client); + +int virNetClientAddProgram(virNetClientPtr client, + virNetClientProgramPtr prog); + +int virNetClientSend(virNetClientPtr client, + virNetMessagePtr msg, + bool expectReply); + +#if HAVE_SASL +void virNetClientSetSASLContext(virNetClientPtr client, + virNetClientSaslContextPtr ctxt); +#endif + +int virNetClientSetTLSSession(virNetClientPtr client, + virNetTLSContextPtr tls); + +bool virNetClientIsEncrypted(virNetClientPtr client); + +const char *virNetClientLocalAddrString(virNetClientPtr client); +const char *virNetClientRemoteAddrString(virNetClientPtr client); + +int virNetClientGetTLSKeySize(virNetClientPtr client); + +void virNetClientFree(virNetClientPtr client); + +#endif /* __VIR_NET_CLIENT_H__ */ diff --git a/src/rpc/virnetclientprogram.c b/src/rpc/virnetclientprogram.c new file mode 100644 index 0000000..eb918f0 --- /dev/null +++ b/src/rpc/virnetclientprogram.c @@ -0,0 +1,258 @@ + +#include <config.h> + +#include "virnetclientprogram.h" +#include "virnetclient.h" +#include "virnetprotocol.h" + +#include "memory.h" +#include "virterror_internal.h" +#include "logging.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +struct _virNetClientProgram { + int refs; + + unsigned program; + unsigned version; + virNetClientProgramEventPtr events; + size_t nevents; + void *eventOpaque; + virNetClientProgramErrorHanderPtr err; +}; + +virNetClientProgramPtr virNetClientProgramNew(unsigned program, + unsigned version, + virNetClientProgramEventPtr events, + size_t nevents, + void *eventOpaque, + virNetClientProgramErrorHanderPtr err) +{ + virNetClientProgramPtr prog; + + if (VIR_ALLOC(prog) < 0) { + virReportOOMError(); + return NULL; + } + + prog->refs = 1; + prog->program = program; + prog->version = version; + prog->events = events; + prog->nevents = nevents; + prog->eventOpaque = eventOpaque; + prog->err = err; + + return prog; +} + + +void virNetClientProgramRef(virNetClientProgramPtr prog) +{ + prog->refs++; +} + + +void virNetClientProgramFree(virNetClientProgramPtr prog) +{ + if (!prog) + return; + + prog->refs--; + if (prog->refs > 0) + return; + + VIR_FREE(prog); +} + + +int virNetClientProgramMatches(virNetClientProgramPtr prog, + virNetMessagePtr msg) +{ + if (prog->program == msg->header.prog && + prog->version == msg->header.vers) + return 1; + return 0; +} + + +static int virNetClientProgramDispatchError(virNetClientProgramPtr prog, + virNetMessagePtr msg) +{ + char *err; + int ret = -1; + + if (VIR_ALLOC_N(err, prog->err->len) < 0) { + virReportOOMError(); + return -1; + } + + if (virNetMessageDecodePayload(msg, prog->err->filter, err) < 0) + goto cleanup; + + prog->err->func(prog, err); + + ret = 0; + +cleanup: + VIR_FREE(err); + return ret; +} + +static virNetClientProgramEventPtr virNetClientProgramGetEvent(virNetClientProgramPtr prog, + int procedure) +{ + int i; + + for (i = 0 ; i < prog->nevents ; i++) { + if (prog->events[i].proc == procedure) + return &prog->events[i]; + } + + return NULL; +} + + +int virNetClientProgramDispatch(virNetClientProgramPtr prog, + virNetClientPtr client, + virNetMessagePtr msg) +{ + virNetClientProgramEventPtr event; + char *evdata; + + VIR_DEBUG("prog=%d ver=%d type=%d status=%d serial=%d proc=%d", + msg->header.prog, msg->header.vers, msg->header.type, + msg->header.status, msg->header.serial, msg->header.proc); + + /* Check version, etc. */ + if (msg->header.prog != prog->program) { + VIR_ERROR(_("program mismatch in event (actual %x, expected %x)"), + msg->header.prog, prog->program); + return -1; + } + + if (msg->header.vers != prog->version) { + VIR_ERROR(_("version mismatch in event (actual %x, expected %x)"), + msg->header.vers, prog->version); + return -1; + } + + if (msg->header.status != VIR_NET_OK) { + VIR_ERROR(_("status mismatch in event (actual %x, expected %x)"), + msg->header.status, VIR_NET_OK); + return -1; + } + + if (msg->header.type != VIR_NET_MESSAGE) { + VIR_ERROR(_("type mismatch in event (actual %x, expected %x)"), + msg->header.type, VIR_NET_MESSAGE); + return -1; + } + + event = virNetClientProgramGetEvent(prog, msg->header.proc); + + if (!event) { + VIR_ERROR(_("No event expected with procedure %x"), + msg->header.proc); + return -1; + } + + if (VIR_ALLOC_N(evdata, event->msg_len) < 0) { + virReportOOMError(); + return -1; + } + + if (virNetMessageDecodePayload(msg, event->msg_filter, evdata) < 0) + goto cleanup; + + event->func(prog, client, &evdata, prog->eventOpaque); + + xdr_free(event->msg_filter, evdata); + +cleanup: + VIR_FREE(evdata); + return 0; +} + + +int virNetClientProgramCall(virNetClientProgramPtr prog, + virNetClientPtr client, + unsigned serial, + int proc, + xdrproc_t args_filter, void *args, + xdrproc_t ret_filter, void *ret) +{ + virNetMessagePtr msg; + + if (VIR_ALLOC(msg) < 0) { + virReportOOMError(); + return -1; + } + + msg->header.prog = prog->program; + msg->header.vers = prog->version; + msg->header.status = VIR_NET_OK; + msg->header.type = VIR_NET_CALL; + msg->header.serial = serial; + msg->header.proc = proc; + + if (virNetMessageEncodeHeader(msg) < 0) + goto error; + + if (virNetMessageEncodePayload(msg, args_filter, args) < 0) + goto error; + + if (virNetClientSend(client, msg, true) < 0) + goto error; + + /* None of these 3 should ever happen here, because + * virNetClientSend should have validated the reply, + * but it doesn't hurt to check again. + */ + if (msg->header.type != VIR_NET_REPLY) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected message type %d"), msg->header.type); + goto error; + } + if (msg->header.proc != proc) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected message proc %d != %d"), + msg->header.proc, proc); + goto error; + } + if (msg->header.serial != serial) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Unexpected message serial %d != %d"), + msg->header.serial, serial); + goto error; + } + + switch (msg->header.status) { + case VIR_NET_OK: + if (virNetMessageDecodePayload(msg, ret_filter, ret) < 0) + goto error; + break; + + case VIR_NET_ERROR: + virNetClientProgramDispatchError(prog, msg); + goto error; + + default: + virNetError(VIR_ERR_RPC, + _("Unexpected message status %d"), msg->header.status); + goto error; + } + + VIR_FREE(msg); + + return 0; + +error: + VIR_FREE(msg); + return -1; +} diff --git a/src/rpc/virnetclientprogram.h b/src/rpc/virnetclientprogram.h new file mode 100644 index 0000000..5a5b937 --- /dev/null +++ b/src/rpc/virnetclientprogram.h @@ -0,0 +1,71 @@ + +#ifndef __VIR_NET_CLIENT_PROGRAM_H__ +#define __VIR_NET_CLIENT_PROGRAM_H__ + +#include <rpc/types.h> +#include <rpc/xdr.h> + +#include "virnetmessage.h" + +typedef struct _virNetClient virNetClient; +typedef virNetClient *virNetClientPtr; + +typedef struct _virNetClientProgram virNetClientProgram; +typedef virNetClientProgram *virNetClientProgramPtr; + +typedef struct _virNetClientProgramEvent virNetClientProgramEvent; +typedef virNetClientProgramEvent *virNetClientProgramEventPtr; + +typedef struct _virNetClientProgramErrorHandler virNetClientProgramErrorHander; +typedef virNetClientProgramErrorHander *virNetClientProgramErrorHanderPtr; + +typedef int (*virNetClientProgramErrorFunc)(virNetClientProgramPtr prog, + void *rerr); + +struct _virNetClientProgramErrorHandler { + virNetClientProgramErrorFunc func; + size_t len; + xdrproc_t filter; +}; + + +typedef void (*virNetClientProgramDispatchFunc)(virNetClientProgramPtr prog, + virNetClientPtr client, + void *msg, + void *opaque); + +struct _virNetClientProgramEvent { + int proc; + virNetClientProgramDispatchFunc func; + size_t msg_len; + xdrproc_t msg_filter; +}; + +virNetClientProgramPtr virNetClientProgramNew(unsigned program, + unsigned version, + virNetClientProgramEventPtr events, + size_t nevents, + void *eventOpaque, + virNetClientProgramErrorHanderPtr err); + +void virNetClientProgramRef(virNetClientProgramPtr prog); + +void virNetClientProgramFree(virNetClientProgramPtr prog); + +int virNetClientProgramMatches(virNetClientProgramPtr prog, + virNetMessagePtr msg); + +int virNetClientProgramDispatch(virNetClientProgramPtr prog, + virNetClientPtr client, + virNetMessagePtr msg); + +int virNetClientProgramCall(virNetClientProgramPtr prog, + virNetClientPtr client, + unsigned serial, + int proc, + xdrproc_t args_filter, void *args, + xdrproc_t ret_filter, void *ret); + + + +#endif /* __VIR_NET_CLIENT_PROGRAM_H__ */ diff --git a/src/rpc/virnetclientsaslcontext.c b/src/rpc/virnetclientsaslcontext.c new file mode 100644 index 0000000..757cd72 --- /dev/null +++ b/src/rpc/virnetclientsaslcontext.c @@ -0,0 +1,246 @@ + +#include <config.h> + +#include "virnetclientsaslcontext.h" + +#include "virterror_internal.h" +#include "memory.h" +#include "logging.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define virNetError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + + +struct _virNetClientSaslContext { + sasl_conn_t *conn; + int refs; +}; + +virNetClientSaslContextPtr virNetClientSaslContextNew(const char *service, + const char *hostname, + const char *localAddr, + const char *remoteAddr, + const sasl_callback_t *cbs) +{ + virNetClientSaslContextPtr sasl = NULL; + int err; + + err = sasl_client_init(NULL); + if (err != SASL_OK) { + virNetError(VIR_ERR_AUTH_FAILED, + _("failed to initialize SASL library: %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + goto cleanup; + } + + if (VIR_ALLOC(sasl) < 0) { + virReportOOMError(); + goto cleanup; + } + + sasl->refs = 1; + + err = sasl_client_new(service, + hostname, + localAddr, + remoteAddr, + cbs, + SASL_SUCCESS_DATA, + &sasl->conn); + if (err != SASL_OK) { + virNetError(VIR_ERR_AUTH_FAILED, + _("Failed to create SASL client context: %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + goto cleanup; + } + + return sasl; + +cleanup: + virNetClientSaslContextFree(sasl); + return NULL; +} + +void virNetClientSaslContextRef(virNetClientSaslContextPtr sasl) +{ + sasl->refs++; +} + +int virNetClientSaslContextExtKeySize(virNetClientSaslContextPtr sasl, + int ssf) +{ + int err; + + err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf); + if (err != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("cannot set external SSF %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + return 0; +} + +int virNetClientSaslContextGetKeySize(virNetClientSaslContextPtr sasl) +{ + int err; + int ssf; + const void *val; + err = sasl_getprop(sasl->conn, SASL_SSF, &val); + if (err != SASL_OK) { + virNetError(VIR_ERR_AUTH_FAILED, + _("cannot query SASL ssf on connection %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + ssf = *(const int *)val; + return ssf; +} + +int virNetClientSaslContextSecProps(virNetClientSaslContextPtr sasl, + int minSSF, + int maxSSF, + bool allowAnonymous) +{ + sasl_security_properties_t secprops; + int err; + + memset (&secprops, 0, sizeof secprops); + + secprops.min_ssf = minSSF; + secprops.max_ssf = maxSSF; + secprops.maxbufsize = 100000; + secprops.security_flags = allowAnonymous ? 0 : + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; + + err = sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops); + if (err != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("cannot set security props %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + + return 0; +} + + +int virNetClientSaslContextStart(virNetClientSaslContextPtr sasl, + const char *mechlist, + sasl_interact_t **prompt_need, + const char **clientout, + size_t *clientoutlen, + const char **mech) +{ + int err = sasl_client_start(sasl->conn, + mechlist, + prompt_need, + clientout, + (unsigned *)clientoutlen, + mech); + + switch (err) { + case SASL_OK: + return VIR_NET_CLIENT_SASL_COMPLETE; + case SASL_CONTINUE: + return VIR_NET_CLIENT_SASL_CONTINUE; + case SASL_INTERACT: + return VIR_NET_CLIENT_SASL_INTERACT; + + default: + virNetError(VIR_ERR_AUTH_FAILED, + _("Failed to start SASL negotiation: %d (%s)"), + err, sasl_errdetail(sasl->conn)); + return -1; + } +} + + +int virNetClientSaslContextStep(virNetClientSaslContextPtr sasl, + const char *serverin, + size_t serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + size_t *clientoutlen) +{ + int err = sasl_client_step(sasl->conn, + serverin, + (unsigned)serverinlen, + prompt_need, + clientout, + (unsigned *)clientoutlen); + + + switch (err) { + case SASL_OK: + return VIR_NET_CLIENT_SASL_COMPLETE; + case SASL_CONTINUE: + return VIR_NET_CLIENT_SASL_CONTINUE; + case SASL_INTERACT: + return VIR_NET_CLIENT_SASL_INTERACT; + + default: + virNetError(VIR_ERR_AUTH_FAILED, + _("Failed to start SASL negotiation: %d (%s)"), + err, sasl_errdetail(sasl->conn)); + return -1; + } +} + +ssize_t virNetClientSaslContextEncode(virNetClientSaslContextPtr sasl, + const char *input, + size_t inputLen, + const char **output, + size_t *outputlen) +{ + int err; + err = sasl_encode(sasl->conn, + input, (unsigned)inputLen, + output, (unsigned *)outputlen); + + if (err != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to encode SASL data: %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + return 0; +} + +ssize_t virNetClientSaslContextDecode(virNetClientSaslContextPtr sasl, + const char *input, + size_t inputLen, + const char **output, + size_t *outputlen) +{ + int err; + err = sasl_decode(sasl->conn, + input, (unsigned)inputLen, + output, (unsigned *)outputlen); + if (err != SASL_OK) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to decode SASL data: %d (%s)"), + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + return 0; +} + +void virNetClientSaslContextFree(virNetClientSaslContextPtr sasl) +{ + if (!sasl) + return; + + sasl->refs--; + if (sasl->refs > 0) + return; + + if (sasl->conn) + sasl_dispose(&sasl->conn); + + VIR_FREE(sasl); +} + diff --git a/src/rpc/virnetclientsaslcontext.h b/src/rpc/virnetclientsaslcontext.h new file mode 100644 index 0000000..043ae58 --- /dev/null +++ b/src/rpc/virnetclientsaslcontext.h @@ -0,0 +1,66 @@ + +#ifndef __VIR_NET_CLIENT_SASL_CONTEXT_H__ +# define __VIR_NET_CLIENT_SASL_CONTEXT_H__ + +# include <sasl/sasl.h> + +# include <sys/types.h> + +#include "virnetsocket.h" + +typedef struct _virNetClientSaslContext virNetClientSaslContext; +typedef virNetClientSaslContext *virNetClientSaslContextPtr; + +enum { + VIR_NET_CLIENT_SASL_COMPLETE, + VIR_NET_CLIENT_SASL_CONTINUE, + VIR_NET_CLIENT_SASL_INTERACT, +}; + +virNetClientSaslContextPtr virNetClientSaslContextNew(const char *service, + const char *hostname, + const char *localAddr, + const char *remoteAddr, + const sasl_callback_t *cbs); + +void virNetClientSaslContextRef(virNetClientSaslContextPtr sasl); + +int virNetClientSaslContextExtKeySize(virNetClientSaslContextPtr sasl, + int ssf); + +int virNetClientSaslContextGetKeySize(virNetClientSaslContextPtr sasl); + +int virNetClientSaslContextSecProps(virNetClientSaslContextPtr sasl, + int minSSF, + int maxSSF, + bool allowAnonymous); + +int virNetClientSaslContextStart(virNetClientSaslContextPtr sasl, + const char *mechlist, + sasl_interact_t **prompt_need, + const char **clientout, + size_t *clientoutlen, + const char **mech); + +int virNetClientSaslContextStep(virNetClientSaslContextPtr sasl, + const char *serverin, + size_t serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + size_t *clientoutlen); + +ssize_t virNetClientSaslContextEncode(virNetClientSaslContextPtr sasl, + const char *input, + size_t inputLen, + const char **output, + size_t *outputlen); + +ssize_t virNetClientSaslContextDecode(virNetClientSaslContextPtr sasl, + const char *input, + size_t inputLen, + const char **output, + size_t *outputlen); + +void virNetClientSaslContextFree(virNetClientSaslContextPtr sasl); + +#endif /* __VIR_NET_CLIENT_SASL_CONTEXT_H__ */ -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
To facilitate creation of new clients using XDR RPC services, pull alot of the remote driver code into a set of reusable
s/alot/a lot/
objects.
- virNetClient: Encapsulates a socket connection to a remote RPC server. Handles all the network I/O for reading/writing RPC messages. Delegates RPC encoding and decoding to the registered programs
- virNetClientProgram: Handles processing and dispatch of RPC messages for a single RPC (program,version). A program can register to receive async events from a client - virNetClientSASLContext: Handles everything todo with SASL authentication and encryption. The callers no longer need directly call any cyrus-sasl APIs, which means error handling is simpler & alternative SASL impls can be provided for Win32
Each new client program now merely needs to define the list of RPC procedures & events it wants and their handlers. It does not need to deal with any of the network I/O functionality at all.
+++ b/src/Makefile.am @@ -1117,7 +1117,7 @@ libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
-noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la +noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la libvirt-net-rpc-client.la
Wrap at 80 columns.
libvirt_net_rpc_la_SOURCES = \ ../daemon/event.c \ @@ -1153,6 +1153,18 @@ libvirt_net_server_la_LDFLAGS = \ libvirt_net_server_la_LIBADD = \ $(CYGWIN_EXTRA_LIBADD)
+libvirt_net_client_la_SOURCES = \ + rpc/virnetclientsaslcontext.h rpc/virnetclientsaslcontext.c \ + rpc/virnetclientprogram.h rpc/virnetclientprogram.c \ + rpc/virnetclient.h rpc/virnetclient.c +libvirt_net_client_la_CFLAGS = \ + $(AM_CFLAGS) +libvirt_net_client_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS)l
s/l$//
+++ b/src/rpc/virnetclient.c @@ -0,0 +1,1237 @@ + +
Copyright header? Probably affects multiple new files across multiple of these patches.
+ +#ifdef WIN32 +# define pipe(fds) _pipe(fds,4096, _O_BINARY) +#endif
Yuck. Gnulib should really take care of this for us. But for now, we have to keep it.
+ +virNetClientPtr virNetClientNewCommand(const char **cmdargv, + const char **cmdenv) +{
If virNetSocketNewConnectCommand is rewritten around virCommand, then this should be updated as well.
+ +static int virNetClientCallDispatchStream(virNetClientPtr client ATTRIBUTE_UNUSED) +{ +#if 0 + struct private_stream_data *privst;
You weren't kidding about this being an incomplete RFC series.
+#define __VIR_NET_CLIENT_H__
+ +#if HAVE_SASL
'make syntax-check' won't like this if you have cppi installed. Again, mostly okay; just copying existing code and renaming into new API. Overall, I'm liking the direction this series is heading. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

<...snip...>
diff --git a/src/Makefile.am b/src/Makefile.am index e78a0af..4c6efa8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1117,7 +1117,7 @@ libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE)
-noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la +noinst_LTLIBRARIES += libvirt-net-rpc.la libvirt-net-rpc-server.la libvirt-net-rpc-client.la
s/libvirt-net-rpc-client.la/libvirt-net-client.la/ to make it compile -- Thanks, Hu Tao

This guts the current remote driver, removing all its networking handling code. Instead it calls out to the new virClientPtr and virClientProgramPtr APIs for all RPC & networking work. --- src/Makefile.am | 3 +- src/remote/remote_driver.c | 2530 ++++++++------------------------------------ 2 files changed, 419 insertions(+), 2114 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 4c6efa8..8986f22 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -475,9 +475,10 @@ libvirt_driver_remote_la_CFLAGS = \ $(GNUTLS_CFLAGS) \ $(SASL_CFLAGS) \ -I@top_srcdir@/src/conf \ + -I@top_srcdir@/src/rpc \ $(AM_CFLAGS) libvirt_driver_remote_la_LDFLAGS = $(AM_LDFLAGS) -libvirt_driver_remote_la_LIBADD = $(GNUTLS_LIBS) $(SASL_LIBS) +libvirt_driver_remote_la_LIBADD = $(GNUTLS_LIBS) $(SASL_LIBS) libvirt-net-client.la libvirt-net-rpc.la if WITH_DRIVER_MODULES libvirt_driver_remote_la_LDFLAGS += -module -avoid-version endif diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e6eb9b5..8fd7949 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -23,51 +23,13 @@ #include <config.h> -#include <stdio.h> -#include <stdlib.h> #include <unistd.h> -#include <string.h> #include <assert.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <arpa/inet.h> -#include <sys/wait.h> - -/* Windows socket compatibility functions. */ -#include <errno.h> -#include <sys/socket.h> - -#ifndef HAVE_WINSOCK2_H /* Unix & Cygwin. */ -# include <sys/un.h> -# include <net/if.h> -# include <netinet/in.h> -# include <netinet/tcp.h> -#endif - -#ifdef HAVE_PWD_H -# include <pwd.h> -#endif - -#ifdef HAVE_PATHS_H -# include <paths.h> -#endif -#include <rpc/types.h> -#include <rpc/xdr.h> -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include "gnutls_1_0_compat.h" -#if HAVE_SASL -# include <sasl/sasl.h> -#endif #include <libxml/uri.h> -#include <netdb.h> - -#include <poll.h> - +#include "virnetclient.h" +#include "virnetclientprogram.h" #include "virterror_internal.h" #include "logging.h" #include "datatypes.h" @@ -86,106 +48,24 @@ #define VIR_FROM_THIS VIR_FROM_REMOTE -#ifdef WIN32 -# define pipe(fds) _pipe(fds,4096, _O_BINARY) -#endif - - static int inside_daemon = 0; -struct remote_thread_call; - - -enum { - REMOTE_MODE_WAIT_TX, - REMOTE_MODE_WAIT_RX, - REMOTE_MODE_COMPLETE, - REMOTE_MODE_ERROR, -}; - -struct remote_thread_call { - int mode; - - /* Buffer for outgoing data packet - * 4 byte length, followed by RPC message header+body */ - char buffer[4 + REMOTE_MESSAGE_MAX]; - unsigned int bufferLength; - unsigned int bufferOffset; - - unsigned int serial; - unsigned int proc_nr; - - virCond cond; - - int want_reply; - xdrproc_t ret_filter; - char *ret; - - remote_error err; - - struct remote_thread_call *next; -}; +struct private_data { + virMutex lock; -struct private_stream_data { - unsigned int has_error : 1; - remote_error err; - - unsigned int serial; - unsigned int proc_nr; - - virStreamEventCallback cb; - void *cbOpaque; - virFreeCallback cbFree; - int cbEvents; - int cbTimer; - int cbDispatch; - - /* XXX this is potentially unbounded if the client - * app has domain events registered, since packets - * may be read off wire, while app isn't ready to - * recv them. Figure out how to address this some - * time.... - */ - char *incoming; - unsigned int incomingOffset; - unsigned int incomingLength; + virNetClientPtr client; + virNetClientProgramPtr remoteProgram; + virNetClientProgramPtr qemuProgram; - struct private_stream_data *next; -}; + int counter; /* Serial number for RPC */ -struct private_data { - virMutex lock; + virNetTLSContextPtr tls; - int sock; /* Socket. */ - int errfd; /* File handle connected to remote stderr */ int watch; /* File handle watch */ - pid_t pid; /* PID of tunnel process */ - int uses_tls; /* TLS enabled on socket? */ int is_secure; /* Secure if TLS or SASL or UNIX sockets */ - gnutls_session_t session; /* GnuTLS session (if uses_tls != 0). */ char *type; /* Cached return from remoteType. */ - int counter; /* Generates serial numbers for RPC. */ int localUses; /* Ref count for private data */ char *hostname; /* Original hostname */ - FILE *debugLog; /* Debug remote protocol */ - -#if HAVE_SASL - sasl_conn_t *saslconn; /* SASL context */ - - const char *saslDecoded; - unsigned int saslDecodedLength; - unsigned int saslDecodedOffset; - - const char *saslEncoded; - unsigned int saslEncodedLength; - unsigned int saslEncodedOffset; -#endif - - /* Buffer for incoming data packets - * 4 byte length, followed by RPC message header+body */ - char buffer[4 + REMOTE_MESSAGE_MAX]; - unsigned int bufferLength; - unsigned int bufferOffset; /* The list of domain event callbacks */ virDomainEventCallbackListPtr callbackList; @@ -196,15 +76,6 @@ struct private_data { int eventFlushTimer; /* Flag if we're in process of dispatching */ int domainEventDispatching; - - /* Self-pipe to wakeup threads waiting in poll() */ - int wakeupSendFD; - int wakeupReadFD; - - /* List of threads currently waiting for dispatch */ - struct remote_thread_call *waitDispatch; - - struct private_stream_data *streams; }; enum { @@ -225,10 +96,6 @@ static void remoteDriverUnlock(struct private_data *driver) virMutexUnlock(&driver->lock); } -static int remoteIO(virConnectPtr conn, - struct private_data *priv, - int flags, - struct remote_thread_call *thiscall); static int call (virConnectPtr conn, struct private_data *priv, int flags, int proc_nr, xdrproc_t args_filter, char *args, @@ -272,10 +139,6 @@ void remoteDomainEventQueueFlush(int timer, void *opaque); /* Helper functions for remoteOpen. */ static char *get_transport_from_scheme (char *scheme); -/* GnuTLS functions used by remoteOpen. */ -static int initialize_gnutls(void); -static gnutls_session_t negotiate_gnutls_on_connection (virConnectPtr conn, struct private_data *priv, int no_verify); - #ifdef WITH_LIBVIRTD static int remoteStartup(int privileged ATTRIBUTE_UNUSED) @@ -290,7 +153,7 @@ remoteStartup(int privileged ATTRIBUTE_UNUSED) #ifndef WIN32 /** - * remoteFindServerPath: + * remoteFindDaemonPath: * * Tries to find the path to the libvirtd binary. * @@ -317,36 +180,68 @@ remoteFindDaemonPath(void) } return NULL; } +#endif -/** - * qemuForkDaemon: - * - * Forks and try to launch the libvirtd daemon - * - * Returns 0 in case of success or -1 in case of detected error. - */ -static int -remoteForkDaemon(void) -{ - const char *daemonPath = remoteFindDaemonPath(); - const char *const daemonargs[] = { daemonPath, "--timeout=30", NULL }; - pid_t pid; - - if (!daemonPath) { - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to find libvirtd binary")); - return -1; - } - - if (virExecDaemonize(daemonargs, NULL, NULL, - &pid, -1, NULL, NULL, - VIR_EXEC_CLEAR_CAPS, - NULL, NULL, NULL) < 0) - return -1; - return 0; -} -#endif +static void +remoteDomainBuildEventLifecycle(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque); +static void +remoteDomainBuildEventReboot(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque); +static void +remoteDomainBuildEventRTCChange(virNetClientProgramPtr prog, + virNetClientPtr client, + void *evdata, void *opaque); +static void +remoteDomainBuildEventWatchdog(virNetClientProgramPtr prog, + virNetClientPtr client, + void *evdata, void *opaque); +static void +remoteDomainBuildEventIOError(virNetClientProgramPtr prog, + virNetClientPtr client, + void *evdata, void *opaque); +static void +remoteDomainBuildEventIOErrorReason(virNetClientProgramPtr prog, + virNetClientPtr client, + void *evdata, void *opaque); +static void +remoteDomainBuildEventGraphics(virNetClientProgramPtr prog, + virNetClientPtr client, + void *evdata, void *opaque); + +static virNetClientProgramEvent remoteDomainEvents[] = { + { REMOTE_PROC_DOMAIN_EVENT_RTC_CHANGE, + remoteDomainBuildEventRTCChange, + sizeof(remote_domain_event_rtc_change_msg), + (xdrproc_t)xdr_remote_domain_event_rtc_change_msg }, + { REMOTE_PROC_DOMAIN_EVENT_REBOOT, + remoteDomainBuildEventReboot, + sizeof(remote_domain_event_reboot_msg), + (xdrproc_t)xdr_remote_domain_event_reboot_msg }, + { REMOTE_PROC_DOMAIN_EVENT_LIFECYCLE, + remoteDomainBuildEventLifecycle, + sizeof(remote_domain_event_lifecycle_msg), + (xdrproc_t)xdr_remote_domain_event_lifecycle_msg }, + { REMOTE_PROC_DOMAIN_EVENT_WATCHDOG, + remoteDomainBuildEventWatchdog, + sizeof(remote_domain_event_watchdog_msg), + (xdrproc_t)xdr_remote_domain_event_watchdog_msg}, + { REMOTE_PROC_DOMAIN_EVENT_IO_ERROR, + remoteDomainBuildEventIOError, + sizeof(remote_domain_event_io_error_msg), + (xdrproc_t)xdr_remote_domain_event_io_error_msg }, + { REMOTE_PROC_DOMAIN_EVENT_IO_ERROR_REASON, + remoteDomainBuildEventIOErrorReason, + sizeof(remote_domain_event_io_error_reason_msg), + (xdrproc_t)xdr_remote_domain_event_io_error_reason_msg }, + { REMOTE_PROC_DOMAIN_EVENT_GRAPHICS, + remoteDomainBuildEventGraphics, + sizeof(remote_domain_event_graphics_msg), + (xdrproc_t)xdr_remote_domain_event_graphics_address }, +}; enum virDrvOpenRemoteFlags { VIR_DRV_OPEN_REMOTE_RO = (1 << 0), @@ -379,7 +274,6 @@ doRemoteOpen (virConnectPtr conn, int flags) { struct qparam_set *vars = NULL; - int wakeupFD[2] = { -1, -1 }; char *transport_str = NULL; enum { trans_tls, @@ -508,15 +402,10 @@ doRemoteOpen (virConnectPtr conn, } else if (STRCASEEQ (var->name, "no_tty")) { no_tty = atoi (var->value); var->ignore = 1; - } else if (STRCASEEQ (var->name, "debug")) { - if (var->value && - STRCASEEQ (var->value, "stdout")) - priv->debugLog = stdout; - else - priv->debugLog = stderr; - } else + } else { DEBUG("passing through variable '%s' ('%s') to remote end", var->name, var->value); + } } /* Construct the original name. */ @@ -579,89 +468,35 @@ doRemoteOpen (virConnectPtr conn, goto failed; } + + VIR_DEBUG("Connecting with transport %d", transport); /* Connect to the remote service. */ switch (transport) { case trans_tls: - if (initialize_gnutls() == -1) goto failed; - priv->uses_tls = 1; + priv->tls = virNetTLSContextNewClient(LIBVIRT_CACERT, + LIBVIRT_CLIENTCERT, + LIBVIRT_CLIENTKEY, + no_verify ? false : true); + if (!priv->tls) + goto failed; priv->is_secure = 1; /*FALLTHROUGH*/ - case trans_tcp: { - // http://people.redhat.com/drepper/userapi-ipv6.html - struct addrinfo *res, *r; - struct addrinfo hints; - int saved_errno = EINVAL; - memset (&hints, 0, sizeof hints); - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG; - int e = getaddrinfo (priv->hostname, port, &hints, &res); - if (e != 0) { - remoteError(VIR_ERR_SYSTEM_ERROR, - _("unable to resolve hostname '%s': %s"), - priv->hostname, gai_strerror (e)); + case trans_tcp: + priv->client = virNetClientNewTCP(priv->hostname, port); + if (!priv->client) goto failed; - } - /* Try to connect to each returned address in turn. */ - /* XXX This loop contains a subtle problem. In the case - * where a host is accessible over IPv4 and IPv6, it will - * try the IPv4 and IPv6 addresses in turn. However it - * should be able to present different client certificates - * (because the commonName field in a client cert contains - * the client IP address, which is different for IPv4 and - * IPv6). At the moment we only have a single client - * certificate, and no way to specify what address family - * that certificate belongs to. - */ - for (r = res; r; r = r->ai_next) { - int no_slow_start = 1; - - priv->sock = socket (r->ai_family, SOCK_STREAM, 0); - if (priv->sock == -1) { - saved_errno = errno; - continue; - } - - /* Disable Nagle - Dan Berrange. */ - setsockopt (priv->sock, - IPPROTO_TCP, TCP_NODELAY, (void *)&no_slow_start, - sizeof no_slow_start); - - if (connect (priv->sock, r->ai_addr, r->ai_addrlen) == -1) { - saved_errno = errno; - VIR_FORCE_CLOSE(priv->sock); - continue; - } - - if (priv->uses_tls) { - priv->session = - negotiate_gnutls_on_connection - (conn, priv, no_verify); - if (!priv->session) { - VIR_FORCE_CLOSE(priv->sock); - goto failed; - } - } - goto tcp_connected; + if (priv->tls) { + VIR_DEBUG0("Starting TLS session"); + if (virNetClientSetTLSSession(priv->client, priv->tls) < 0) + goto failed; } - freeaddrinfo (res); - virReportSystemError(saved_errno, - _("unable to connect to libvirtd at '%s'"), - priv->hostname); - goto failed; - - tcp_connected: - freeaddrinfo (res); - - // NB. All versioning is done by the RPC headers, so we don't - // need to worry (at this point anyway) about versioning. break; - } #ifndef WIN32 - case trans_unix: { + case trans_unix: if (!sockname) { if (flags & VIR_DRV_OPEN_REMOTE_USER) { char *userdir = virGetUserDirectory(getuid()); @@ -676,153 +511,57 @@ doRemoteOpen (virConnectPtr conn, VIR_FREE(userdir); } else { if (flags & VIR_DRV_OPEN_REMOTE_RO) - sockname = strdup (LIBVIRTD_PRIV_UNIX_SOCKET_RO); + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET_RO); else - sockname = strdup (LIBVIRTD_PRIV_UNIX_SOCKET); + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET); if (sockname == NULL) goto out_of_memory; } + VIR_DEBUG("Proceeding with sockname %s", sockname); } -# ifndef UNIX_PATH_MAX -# define UNIX_PATH_MAX(addr) (sizeof (addr).sun_path) -# endif - struct sockaddr_un addr; - int trials = 0; - - memset (&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - if (virStrcpyStatic(addr.sun_path, sockname) == NULL) { - remoteError(VIR_ERR_INTERNAL_ERROR, - _("Socket %s too big for destination"), sockname); - goto failed; - } - if (addr.sun_path[0] == '@') - addr.sun_path[0] = '\0'; - - autostart_retry: - priv->is_secure = 1; - priv->sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (priv->sock == -1) { - virReportSystemError(errno, "%s", - _("unable to create socket")); - goto failed; - } - if (connect (priv->sock, (struct sockaddr *) &addr, sizeof addr) == -1) { - /* We might have to autostart the daemon in some cases.... - * It takes a short while for the daemon to startup, hence we - * have a number of retries, with a small sleep. This will - * sometimes cause multiple daemons to be started - this is - * ok because the duplicates will fail to bind to the socket - * and immediately exit, leaving just one daemon. - */ - if (errno == ECONNREFUSED && - flags & VIR_DRV_OPEN_REMOTE_AUTOSTART && - trials < 20) { - VIR_FORCE_CLOSE(priv->sock); - if (trials > 0 || - remoteForkDaemon() == 0) { - trials++; - usleep(1000 * 100 * trials); - goto autostart_retry; - } - } - virReportSystemError(errno, - _("unable to connect to '%s', libvirtd may need to be started"), - sockname); + if (!(priv->client = virNetClientNewUNIX(sockname, + flags & VIR_DRV_OPEN_REMOTE_AUTOSTART, + remoteFindDaemonPath()))) goto failed; - } break; - } - - case trans_ssh: { - int j, nr_args = 6; - - if (username) nr_args += 2; /* For -l username */ - if (no_tty) nr_args += 5; /* For -T -o BatchMode=yes -e none */ - if (port) nr_args += 2; /* For -p port */ + case trans_ssh: command = command ? command : strdup ("ssh"); if (command == NULL) goto out_of_memory; - // Generate the final command argv[] array. - // ssh [-p $port] [-l $username] $hostname $netcat -U $sockname [NULL] - if (VIR_ALLOC_N(cmd_argv, nr_args) < 0) - goto out_of_memory; - - j = 0; - cmd_argv[j++] = strdup (command); - if (port) { - cmd_argv[j++] = strdup ("-p"); - cmd_argv[j++] = strdup (port); - } - if (username) { - cmd_argv[j++] = strdup ("-l"); - cmd_argv[j++] = strdup (username); - } - if (no_tty) { - cmd_argv[j++] = strdup ("-T"); - cmd_argv[j++] = strdup ("-o"); - cmd_argv[j++] = strdup ("BatchMode=yes"); - cmd_argv[j++] = strdup ("-e"); - cmd_argv[j++] = strdup ("none"); - } - cmd_argv[j++] = strdup (priv->hostname); - cmd_argv[j++] = strdup (netcat ? netcat : "nc"); - cmd_argv[j++] = strdup ("-U"); - cmd_argv[j++] = strdup (sockname ? sockname : - (flags & VIR_CONNECT_RO - ? LIBVIRTD_PRIV_UNIX_SOCKET_RO - : LIBVIRTD_PRIV_UNIX_SOCKET)); - cmd_argv[j++] = 0; - assert (j == nr_args); - for (j = 0; j < (nr_args-1); j++) - if (cmd_argv[j] == NULL) + if (!sockname) { + if (flags & VIR_DRV_OPEN_REMOTE_RO) + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET_RO); + else + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET); + if (sockname == NULL) goto out_of_memory; - - priv->is_secure = 1; - } - - /*FALLTHROUGH*/ - case trans_ext: { - pid_t pid; - int sv[2]; - int errfd[2]; - - /* Fork off the external process. Use socketpair to create a private - * (unnamed) Unix domain socket to the child process so we don't have - * to faff around with two file descriptors (a la 'pipe(2)'). - */ - if (socketpair (PF_UNIX, SOCK_STREAM, 0, sv) == -1) { - virReportSystemError(errno, "%s", - _("unable to create socket pair")); - goto failed; } - if (pipe(errfd) == -1) { - virReportSystemError(errno, "%s", - _("unable to create socket pair")); + if (!(priv->client = virNetClientNewSSH(priv->hostname, + port, + command, + username, + no_tty, + netcat ? netcat : "nc", + sockname))) goto failed; - } - if (virExec((const char**)cmd_argv, NULL, NULL, - &pid, sv[1], &(sv[1]), &(errfd[1]), - VIR_EXEC_CLEAR_CAPS) < 0) - goto failed; + priv->is_secure = 1; + break; - /* Parent continues here. */ - VIR_FORCE_CLOSE(sv[1]); - VIR_FORCE_CLOSE(errfd[1]); - priv->sock = sv[0]; - priv->errfd = errfd[0]; - priv->pid = pid; + case trans_ext: + if (!(priv->client = virNetClientNewCommand((const char **)cmd_argv, NULL))) + goto failed; /* Do not set 'is_secure' flag since we can't guarentee * an external program is secure, and this flag must be * pessimistic */ - } + break; + #else /* WIN32 */ case trans_unix: @@ -834,36 +573,32 @@ doRemoteOpen (virConnectPtr conn, goto failed; #endif /* WIN32 */ - } /* switch (transport) */ - if (virSetNonBlock(priv->sock) < 0) { - virReportSystemError(errno, "%s", - _("unable to make socket non-blocking")); - goto failed; - } - - if ((priv->errfd != -1) && virSetNonBlock(priv->errfd) < 0) { - virReportSystemError(errno, "%s", - _("unable to make socket non-blocking")); + if (!(priv->remoteProgram = virNetClientProgramNew(REMOTE_PROGRAM, + REMOTE_PROTOCOL_VERSION, + remoteDomainEvents, + ARRAY_CARDINALITY(remoteDomainEvents), + conn, + NULL))) goto failed; - } - - if (pipe(wakeupFD) < 0) { - virReportSystemError(errno, "%s", - _("unable to make pipe")); + if (!(priv->qemuProgram = virNetClientProgramNew(QEMU_PROGRAM, + QEMU_PROTOCOL_VERSION, + NULL, + 0, + NULL, + NULL))) goto failed; - } - priv->wakeupReadFD = wakeupFD[0]; - priv->wakeupSendFD = wakeupFD[1]; /* Try and authenticate with server */ + VIR_DEBUG0("Trying authentication"); if (remoteAuthenticate(conn, priv, 1, auth, authtype) == -1) goto failed; /* Finally we can call the remote side's open function. */ remote_open_args args = { &name, flags }; + VIR_DEBUG("Trying to open URI %s", name); if (call (conn, priv, REMOTE_CALL_IN_OPEN, REMOTE_PROC_OPEN, (xdrproc_t) xdr_remote_open_args, (char *) &args, (xdrproc_t) xdr_void, (char *) NULL) == -1) @@ -874,6 +609,7 @@ doRemoteOpen (virConnectPtr conn, remote_get_uri_ret uriret; int urierr; + VIR_DEBUG0("Trying to query remote URI"); memset (&uriret, 0, sizeof uriret); urierr = call (conn, priv, REMOTE_CALL_IN_OPEN | REMOTE_CALL_QUIET_MISSING_RPC, @@ -902,36 +638,23 @@ doRemoteOpen (virConnectPtr conn, } } - if(VIR_ALLOC(priv->callbackList)<0) { + if (VIR_ALLOC(priv->callbackList)<0) { virReportOOMError(); goto failed; } - if(VIR_ALLOC(priv->domainEvents)<0) { + if (VIR_ALLOC(priv->domainEvents)<0) { virReportOOMError(); goto failed; } - DEBUG0("Adding Handler for remote events"); - /* Set up a callback to listen on the socket data */ - if ((priv->watch = virEventAddHandle(priv->sock, - VIR_EVENT_HANDLE_READABLE, - remoteDomainEventFired, - conn, NULL)) < 0) { - DEBUG0("virEventAddHandle failed: No addHandleImpl defined." - " continuing without events."); - } else { - - DEBUG0("Adding Timeout for remote event queue flushing"); - if ( (priv->eventFlushTimer = virEventAddTimeout(-1, - remoteDomainEventQueueFlush, - conn, NULL)) < 0) { - DEBUG0("virEventAddTimeout failed: No addTimeoutImpl defined. " - "continuing without events."); - virEventRemoveHandle(priv->watch); - priv->watch = -1; - } + DEBUG0("Adding Timeout for remote event queue flushing"); + if ((priv->eventFlushTimer = virEventAddTimeout(-1, + remoteDomainEventQueueFlush, + conn, NULL)) < 0) { + DEBUG0("Failed to add timer for dispatching events, disabling events"); } + /* Successful. */ retcode = VIR_DRV_OPEN_SUCCESS; @@ -961,30 +684,8 @@ doRemoteOpen (virConnectPtr conn, free_qparam_set (vars); failed: - /* Close the socket if we failed. */ - VIR_FORCE_CLOSE(priv->errfd); - - if (priv->sock >= 0) { - if (priv->uses_tls && priv->session) { - gnutls_bye (priv->session, GNUTLS_SHUT_RDWR); - gnutls_deinit (priv->session); - } - VIR_FORCE_CLOSE(priv->sock); -#ifndef WIN32 - if (priv->pid > 0) { - pid_t reap; - do { -retry: - reap = waitpid(priv->pid, NULL, 0); - if (reap == -1 && errno == EINTR) - goto retry; - } while (reap != -1 && reap != priv->pid); - } -#endif - } - - VIR_FORCE_CLOSE(wakeupFD[0]); - VIR_FORCE_CLOSE(wakeupFD[1]); + virNetClientFree(priv->client); + priv->client = NULL; VIR_FREE(priv->hostname); goto cleanup; @@ -1008,8 +709,6 @@ remoteAllocPrivateData(void) remoteDriverLock(priv); priv->localUses = 1; priv->watch = -1; - priv->sock = -1; - priv->errfd = -1; return priv; } @@ -1121,435 +820,98 @@ get_transport_from_scheme (char *scheme) return p ? p+1 : 0; } -/* GnuTLS functions used by remoteOpen. */ -static gnutls_certificate_credentials_t x509_cred; - - -static int -check_cert_file(const char *type, const char *file) -{ - struct stat sb; - if (stat(file, &sb) < 0) { - virReportSystemError(errno, - _("Cannot access %s '%s'"), - type, file); - return -1; - } - return 0; -} - +/*----------------------------------------------------------------------*/ -static void remote_debug_gnutls_log(int level, const char* str) { - DEBUG("%d %s", level, str); -} static int -initialize_gnutls(void) +doRemoteClose (virConnectPtr conn, struct private_data *priv) { - static int initialized = 0; - int err; - char *gnutlsdebug; - - if (initialized) return 0; - - gnutls_global_init (); - - if ((gnutlsdebug = getenv("LIBVIRT_GNUTLS_DEBUG")) != NULL) { - int val; - if (virStrToLong_i(gnutlsdebug, NULL, 10, &val) < 0) - val = 10; - gnutls_global_set_log_level(val); - gnutls_global_set_log_function(remote_debug_gnutls_log); - } - - /* X509 stuff */ - err = gnutls_certificate_allocate_credentials (&x509_cred); - if (err) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to allocate TLS credentials: %s"), - gnutls_strerror (err)); - return -1; + if (priv->eventFlushTimer >= 0) { + /* Remove timeout */ + virEventRemoveTimeout(priv->eventFlushTimer); + /* Remove handle for remote events */ + virEventRemoveHandle(priv->watch); + priv->watch = -1; } - - if (check_cert_file("CA certificate", LIBVIRT_CACERT) < 0) - return -1; - if (check_cert_file("client key", LIBVIRT_CLIENTKEY) < 0) - return -1; - if (check_cert_file("client certificate", LIBVIRT_CLIENTCERT) < 0) + if (call (conn, priv, 0, REMOTE_PROC_CLOSE, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_void, (char *) NULL) == -1) return -1; - /* Set the trusted CA cert. */ - DEBUG("loading CA file %s", LIBVIRT_CACERT); - err = - gnutls_certificate_set_x509_trust_file (x509_cred, LIBVIRT_CACERT, - GNUTLS_X509_FMT_PEM); - if (err < 0) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to load CA certificate: %s"), - gnutls_strerror (err)); - return -1; - } + virNetTLSContextFree(priv->tls); + priv->tls = NULL; + virNetClientFree(priv->client); + priv->client = NULL; + virNetClientProgramFree(priv->remoteProgram); + virNetClientProgramFree(priv->qemuProgram); + priv->remoteProgram = priv->qemuProgram = NULL; - /* Set the client certificate and private key. */ - DEBUG("loading client cert and key from files %s and %s", - LIBVIRT_CLIENTCERT, LIBVIRT_CLIENTKEY); - err = - gnutls_certificate_set_x509_key_file (x509_cred, - LIBVIRT_CLIENTCERT, - LIBVIRT_CLIENTKEY, - GNUTLS_X509_FMT_PEM); - if (err < 0) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to load private key/certificate: %s"), - gnutls_strerror (err)); - return -1; - } + /* Free hostname copy */ + VIR_FREE(priv->hostname); - initialized = 1; - return 0; -} + /* See comment for remoteType. */ + VIR_FREE(priv->type); -static int verify_certificate (virConnectPtr conn, struct private_data *priv, gnutls_session_t session); + /* Free callback list */ + virDomainEventCallbackListFree(priv->callbackList); -#if HAVE_WINSOCK2_H -static ssize_t -custom_gnutls_push(void *s, const void *buf, size_t len) -{ - return send((size_t)s, buf, len, 0); -} + /* Free queued events */ + virDomainEventQueueFree(priv->domainEvents); -static ssize_t -custom_gnutls_pull(void *s, void *buf, size_t len) -{ - return recv((size_t)s, buf, len, 0); + return 0; } -#endif -static gnutls_session_t -negotiate_gnutls_on_connection (virConnectPtr conn, - struct private_data *priv, - int no_verify) +static int +remoteClose (virConnectPtr conn) { - const int cert_type_priority[3] = { - GNUTLS_CRT_X509, - GNUTLS_CRT_OPENPGP, - 0 - }; - int err; - gnutls_session_t session; - - /* Initialize TLS session - */ - err = gnutls_init (&session, GNUTLS_CLIENT); - if (err) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to initialize TLS client: %s"), - gnutls_strerror (err)); - return NULL; - } + int ret = 0; + struct private_data *priv = conn->privateData; - /* Use default priorities */ - err = gnutls_set_default_priority (session); - if (err) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to set TLS algorithm priority: %s"), - gnutls_strerror (err)); - return NULL; - } - err = - gnutls_certificate_type_set_priority (session, - cert_type_priority); - if (err) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to set certificate priority: %s"), - gnutls_strerror (err)); - return NULL; + remoteDriverLock(priv); + priv->localUses--; + if (!priv->localUses) { + ret = doRemoteClose(conn, priv); + conn->privateData = NULL; + remoteDriverUnlock(priv); + virMutexDestroy(&priv->lock); + VIR_FREE (priv); } + if (priv) + remoteDriverUnlock(priv); - /* put the x509 credentials to the current session - */ - err = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509_cred); - if (err) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to set session credentials: %s"), - gnutls_strerror (err)); - return NULL; - } + return ret; +} - gnutls_transport_set_ptr (session, - (gnutls_transport_ptr_t) (long) priv->sock); +static int +remoteSupportsFeature (virConnectPtr conn, int feature) +{ + int rv = -1; + remote_supports_feature_args args; + remote_supports_feature_ret ret; + struct private_data *priv = conn->privateData; -#if HAVE_WINSOCK2_H - /* Make sure GnuTLS uses gnulib's replacment functions for send() and - * recv() on Windows */ - gnutls_transport_set_push_function(session, custom_gnutls_push); - gnutls_transport_set_pull_function(session, custom_gnutls_pull); -#endif + remoteDriverLock(priv); - /* Perform the TLS handshake. */ - again: - err = gnutls_handshake (session); - if (err < 0) { - if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) - goto again; - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to complete TLS handshake: %s"), - gnutls_strerror (err)); - return NULL; + /* VIR_DRV_FEATURE_REMOTE* features are handled directly. */ + if (feature == VIR_DRV_FEATURE_REMOTE) { + rv = 1; + goto done; } - /* Verify certificate. */ - if (verify_certificate (conn, priv, session) == -1) { - DEBUG0("failed to verify peer's certificate"); - if (!no_verify) return NULL; - } + args.feature = feature; - /* At this point, the server is verifying _our_ certificate, IP address, - * etc. If we make the grade, it will send us a '\1' byte. - */ - char buf[1]; - int len; - again_2: - len = gnutls_record_recv (session, buf, 1); - if (len < 0 && len != GNUTLS_E_UNEXPECTED_PACKET_LENGTH) { - if (len == GNUTLS_E_AGAIN || len == GNUTLS_E_INTERRUPTED) - goto again_2; - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to complete TLS initialization: %s"), - gnutls_strerror (len)); - return NULL; - } - if (len != 1 || buf[0] != '\1') { - remoteError(VIR_ERR_RPC, "%s", - _("server verification (of our certificate or IP " - "address) failed")); - return NULL; - } + memset (&ret, 0, sizeof ret); + if (call (conn, priv, 0, REMOTE_PROC_SUPPORTS_FEATURE, + (xdrproc_t) xdr_remote_supports_feature_args, (char *) &args, + (xdrproc_t) xdr_remote_supports_feature_ret, (char *) &ret) == -1) + goto done; -#if 0 - /* Print session info. */ - print_info (session); -#endif + rv = ret.supported; - return session; -} - -static int -verify_certificate (virConnectPtr conn ATTRIBUTE_UNUSED, - struct private_data *priv, - gnutls_session_t session) -{ - int ret; - unsigned int status; - const gnutls_datum_t *certs; - unsigned int nCerts, i; - time_t now; - - if ((ret = gnutls_certificate_verify_peers2 (session, &status)) < 0) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to verify server certificate: %s"), - gnutls_strerror (ret)); - return -1; - } - - if ((now = time(NULL)) == ((time_t)-1)) { - virReportSystemError(errno, "%s", - _("cannot get current time")); - return -1; - } - - if (status != 0) { - const char *reason = _("Invalid certificate"); - - if (status & GNUTLS_CERT_INVALID) - reason = _("The certificate is not trusted."); - - if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) - reason = _("The certificate hasn't got a known issuer."); - - if (status & GNUTLS_CERT_REVOKED) - reason = _("The certificate has been revoked."); - -#ifndef GNUTLS_1_0_COMPAT - if (status & GNUTLS_CERT_INSECURE_ALGORITHM) - reason = _("The certificate uses an insecure algorithm"); -#endif - - remoteError(VIR_ERR_RPC, - _("server certificate failed validation: %s"), - reason); - return -1; - } - - if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) { - remoteError(VIR_ERR_RPC, "%s",_("Certificate type is not X.509")); - return -1; - } - - if (!(certs = gnutls_certificate_get_peers(session, &nCerts))) { - remoteError(VIR_ERR_RPC, "%s",_("gnutls_certificate_get_peers failed")); - return -1; - } - - for (i = 0 ; i < nCerts ; i++) { - gnutls_x509_crt_t cert; - - ret = gnutls_x509_crt_init (&cert); - if (ret < 0) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to initialize certificate: %s"), - gnutls_strerror (ret)); - return -1; - } - - ret = gnutls_x509_crt_import (cert, &certs[i], GNUTLS_X509_FMT_DER); - if (ret < 0) { - remoteError(VIR_ERR_GNUTLS_ERROR, - _("unable to import certificate: %s"), - gnutls_strerror (ret)); - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (gnutls_x509_crt_get_expiration_time (cert) < now) { - remoteError(VIR_ERR_RPC, "%s", _("The certificate has expired")); - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (gnutls_x509_crt_get_activation_time (cert) > now) { - remoteError(VIR_ERR_RPC, "%s", - _("The certificate is not yet activated")); - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (i == 0) { - if (!gnutls_x509_crt_check_hostname (cert, priv->hostname)) { - remoteError(VIR_ERR_RPC, - _("Certificate's owner does not match the hostname (%s)"), - priv->hostname); - gnutls_x509_crt_deinit (cert); - return -1; - } - } - } - - return 0; -} - -/*----------------------------------------------------------------------*/ - - -static int -doRemoteClose (virConnectPtr conn, struct private_data *priv) -{ - if (priv->eventFlushTimer >= 0) { - /* Remove timeout */ - virEventRemoveTimeout(priv->eventFlushTimer); - /* Remove handle for remote events */ - virEventRemoveHandle(priv->watch); - priv->watch = -1; - } - - if (call (conn, priv, 0, REMOTE_PROC_CLOSE, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_void, (char *) NULL) == -1) - return -1; - - /* Close socket. */ - if (priv->uses_tls && priv->session) { - gnutls_bye (priv->session, GNUTLS_SHUT_RDWR); - gnutls_deinit (priv->session); - } -#if HAVE_SASL - if (priv->saslconn) - sasl_dispose (&priv->saslconn); -#endif - VIR_FORCE_CLOSE(priv->sock); - VIR_FORCE_CLOSE(priv->errfd); - -#ifndef WIN32 - if (priv->pid > 0) { - pid_t reap; - do { -retry: - reap = waitpid(priv->pid, NULL, 0); - if (reap == -1 && errno == EINTR) - goto retry; - } while (reap != -1 && reap != priv->pid); - } -#endif - VIR_FORCE_CLOSE(priv->wakeupReadFD); - VIR_FORCE_CLOSE(priv->wakeupSendFD); - - - /* Free hostname copy */ - VIR_FREE(priv->hostname); - - /* See comment for remoteType. */ - VIR_FREE(priv->type); - - /* Free callback list */ - virDomainEventCallbackListFree(priv->callbackList); - - /* Free queued events */ - virDomainEventQueueFree(priv->domainEvents); - - return 0; -} - -static int -remoteClose (virConnectPtr conn) -{ - int ret = 0; - struct private_data *priv = conn->privateData; - - remoteDriverLock(priv); - priv->localUses--; - if (!priv->localUses) { - ret = doRemoteClose(conn, priv); - conn->privateData = NULL; - remoteDriverUnlock(priv); - virMutexDestroy(&priv->lock); - VIR_FREE (priv); - } - if (priv) - remoteDriverUnlock(priv); - - return ret; -} - -static int -remoteSupportsFeature (virConnectPtr conn, int feature) -{ - int rv = -1; - remote_supports_feature_args args; - remote_supports_feature_ret ret; - struct private_data *priv = conn->privateData; - - remoteDriverLock(priv); - - /* VIR_DRV_FEATURE_REMOTE* features are handled directly. */ - if (feature == VIR_DRV_FEATURE_REMOTE) { - rv = 1; - goto done; - } - - args.feature = feature; - - memset (&ret, 0, sizeof ret); - if (call (conn, priv, 0, REMOTE_PROC_SUPPORTS_FEATURE, - (xdrproc_t) xdr_remote_supports_feature_args, (char *) &args, - (xdrproc_t) xdr_remote_supports_feature_ret, (char *) &ret) == -1) - goto done; - - rv = ret.supported; - -done: - remoteDriverUnlock(priv); - return rv; +done: + remoteDriverUnlock(priv); + return rv; } /* Unfortunately this function is defined to return a static string. @@ -1701,13 +1063,8 @@ static int remoteIsEncrypted(virConnectPtr conn) (xdrproc_t) xdr_remote_is_secure_ret, (char *) &ret) == -1) goto done; - if (priv->uses_tls) + if (virNetClientIsEncrypted(priv->client)) encrypted = 1; -#if HAVE_SASL - else if (priv->saslconn) - encrypted = 1; -#endif - /* We claim to be encrypted, if the remote driver * transport itself is encrypted, and the remote @@ -7103,8 +6460,6 @@ static int remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, virConnectAuthPtr auth, const char *wantmech) { - sasl_conn_t *saslconn = NULL; - sasl_security_properties_t secprops; remote_auth_sasl_init_ret iret; remote_auth_sasl_start_args sargs; remote_auth_sasl_start_ret sret; @@ -7112,49 +6467,19 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, remote_auth_sasl_step_ret pret; const char *clientout; char *serverin = NULL; - unsigned int clientoutlen, serverinlen; + size_t clientoutlen, serverinlen; const char *mech; int err, complete; - virSocketAddr sa; - char *localAddr = NULL, *remoteAddr = NULL; - const void *val; - sasl_ssf_t ssf; + int ssf; sasl_callback_t *saslcb = NULL; sasl_interact_t *interact = NULL; virConnectCredentialPtr cred = NULL; int ncred = 0; int ret = -1; const char *mechlist; + virNetClientSaslContextPtr sasl; DEBUG0("Client initialize SASL authentication"); - /* Sets up the SASL library as a whole */ - err = sasl_client_init(NULL); - if (err != SASL_OK) { - remoteError(VIR_ERR_AUTH_FAILED, - _("failed to initialize SASL library: %d (%s)"), - err, sasl_errstring(err, NULL, NULL)); - goto cleanup; - } - - /* Get local address in form IPADDR:PORT */ - sa.len = sizeof(sa.data.stor); - if (getsockname(priv->sock, &sa.data.sa, &sa.len) < 0) { - virReportSystemError(errno, "%s", - _("failed to get sock address")); - goto cleanup; - } - if ((localAddr = virSocketFormatAddrFull(&sa, true, ";")) == NULL) - goto cleanup; - - /* Get remote address in form IPADDR:PORT */ - sa.len = sizeof(sa.data.stor); - if (getpeername(priv->sock, &sa.data.sa, &sa.len) < 0) { - virReportSystemError(errno, "%s", - _("failed to get peer address")); - goto cleanup; - } - if ((remoteAddr = virSocketFormatAddrFull(&sa, true, ";")) == NULL) - goto cleanup; if (auth) { if ((saslcb = remoteAuthMakeCallbacks(auth->credtype, auth->ncredtype)) == NULL) @@ -7164,59 +6489,32 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, } /* Setup a handle for being a client */ - err = sasl_client_new("libvirt", - priv->hostname, - localAddr, - remoteAddr, - saslcb, - SASL_SUCCESS_DATA, - &saslconn); - - if (err != SASL_OK) { - remoteError(VIR_ERR_AUTH_FAILED, - _("Failed to create SASL client context: %d (%s)"), - err, sasl_errstring(err, NULL, NULL)); + if (!(sasl = virNetClientSaslContextNew("libvirt", + priv->hostname, + virNetClientLocalAddrString(priv->client), + virNetClientRemoteAddrString(priv->client), + saslcb))) goto cleanup; - } /* Initialize some connection props we care about */ - if (priv->uses_tls) { - gnutls_cipher_algorithm_t cipher; - - cipher = gnutls_cipher_get(priv->session); - if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("invalid cipher size for TLS session")); + if (priv->tls) { + if ((ssf = virNetClientGetTLSKeySize(priv->client)) < 0) goto cleanup; - } + ssf *= 8; /* key size is bytes, sasl wants bits */ DEBUG("Setting external SSF %d", ssf); - err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf); - if (err != SASL_OK) { - remoteError(VIR_ERR_INTERNAL_ERROR, - _("cannot set external SSF %d (%s)"), - err, sasl_errstring(err, NULL, NULL)); + if (virNetClientSaslContextExtKeySize(sasl, ssf) < 0) goto cleanup; - } } - memset (&secprops, 0, sizeof secprops); /* If we've got a secure channel (TLS or UNIX sock), we don't care about SSF */ - secprops.min_ssf = priv->is_secure ? 0 : 56; /* Equiv to DES supported by all Kerberos */ - secprops.max_ssf = priv->is_secure ? 0 : 100000; /* Very strong ! AES == 256 */ - secprops.maxbufsize = 100000; /* If we're not secure, then forbid any anonymous or trivially crackable auth */ - secprops.security_flags = priv->is_secure ? 0 : - SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; - - err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops); - if (err != SASL_OK) { - remoteError(VIR_ERR_INTERNAL_ERROR, - _("cannot set security props %d (%s)"), - err, sasl_errstring(err, NULL, NULL)); + if (virNetClientSaslContextSecProps(sasl, + priv->is_secure ? 0 : 56, /* Equiv to DES supported by all Kerberos */ + priv->is_secure ? 0 : 100000, /* Very strong ! AES == 256 */ + priv->is_secure ? true : false) < 0) goto cleanup; - } /* First call is to inquire about supported mechanisms in the server */ memset (&iret, 0, sizeof iret); @@ -7240,22 +6538,16 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, restart: /* Start the auth negotiation on the client end first */ DEBUG("Client start negotiation mechlist '%s'", mechlist); - err = sasl_client_start(saslconn, - mechlist, - &interact, - &clientout, - &clientoutlen, - &mech); - if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { - remoteError(VIR_ERR_AUTH_FAILED, - _("Failed to start SASL negotiation: %d (%s)"), - err, sasl_errdetail(saslconn)); - VIR_FREE(iret.mechlist); + if ((err = virNetClientSaslContextStart(sasl, + mechlist, + &interact, + &clientout, + &clientoutlen, + &mech)) < 0) goto cleanup; - } /* Need to gather some credentials from the client */ - if (err == SASL_INTERACT) { + if (err == VIR_NET_CLIENT_SASL_INTERACT) { const char *msg; if (cred) { remoteAuthFreeCredentials(cred, ncred); @@ -7285,7 +6577,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, if (clientoutlen > REMOTE_AUTH_SASL_DATA_MAX) { remoteError(VIR_ERR_AUTH_FAILED, _("SASL negotiation data too long: %d bytes"), - clientoutlen); + (int)clientoutlen); goto cleanup; } /* NB, distinction of NULL vs "" is *critical* in SASL */ @@ -7294,7 +6586,8 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, sargs.data.data_val = (char*)clientout; sargs.data.data_len = clientoutlen; sargs.mech = (char*)mech; - DEBUG("Server start negotiation with mech %s. Data %d bytes %p", mech, clientoutlen, clientout); + DEBUG("Server start negotiation with mech %s. Data %d bytes %p", + mech, (int)clientoutlen, clientout); /* Now send the initial auth data to the server */ memset (&sret, 0, sizeof sret); @@ -7308,27 +6601,23 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, serverin = sret.nil ? NULL : sret.data.data_val; serverinlen = sret.data.data_len; DEBUG("Client step result complete: %d. Data %d bytes %p", - complete, serverinlen, serverin); + complete, (int)serverinlen, serverin); /* Loop-the-loop... * Even if the server has completed, the client must *always* do at least one step * in this loop to verify the server isn't lying about something. Mutual auth */ for (;;) { restep: - err = sasl_client_step(saslconn, - serverin, - serverinlen, - &interact, - &clientout, - &clientoutlen); - if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { - remoteError(VIR_ERR_AUTH_FAILED, - _("Failed SASL step: %d (%s)"), - err, sasl_errdetail(saslconn)); + if ((err = virNetClientSaslContextStep(sasl, + serverin, + serverinlen, + &interact, + &clientout, + &clientoutlen)) < 0) goto cleanup; - } + /* Need to gather some credentials from the client */ - if (err == SASL_INTERACT) { + if (err == VIR_NET_CLIENT_SASL_INTERACT) { const char *msg; if (cred) { remoteAuthFreeCredentials(cred, ncred); @@ -7354,10 +6643,11 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, } VIR_FREE(serverin); - DEBUG("Client step result %d. Data %d bytes %p", err, clientoutlen, clientout); + DEBUG("Client step result %d. Data %d bytes %p", + err, (int)clientoutlen, clientout); /* Previous server call showed completion & we're now locally complete too */ - if (complete && err == SASL_OK) + if (complete && err == VIR_NET_CLIENT_SASL_COMPLETE) break; /* Not done, prepare to talk with the server for another iteration */ @@ -7366,7 +6656,8 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, pargs.nil = clientout ? 0 : 1; pargs.data.data_val = (char*)clientout; pargs.data.data_len = clientoutlen; - DEBUG("Server step with %d bytes %p", clientoutlen, clientout); + DEBUG("Server step with %d bytes %p", + (int)clientoutlen, clientout); memset (&pret, 0, sizeof pret); if (call (conn, priv, in_open, REMOTE_PROC_AUTH_SASL_STEP, @@ -7380,10 +6671,10 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, serverinlen = pret.data.data_len; DEBUG("Client step result complete: %d. Data %d bytes %p", - complete, serverinlen, serverin); + complete, (int)serverinlen, serverin); /* This server call shows complete, and earlier client step was OK */ - if (complete && err == SASL_OK) { + if (complete && err == VIR_NET_CLIENT_SASL_COMPLETE) { VIR_FREE(serverin); break; } @@ -7391,14 +6682,9 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, /* Check for suitable SSF if not already secure (TLS or UNIX sock) */ if (!priv->is_secure) { - err = sasl_getprop(saslconn, SASL_SSF, &val); - if (err != SASL_OK) { - remoteError(VIR_ERR_AUTH_FAILED, - _("cannot query SASL ssf on connection %d (%s)"), - err, sasl_errstring(err, NULL, NULL)); + if ((ssf = virNetClientSaslContextGetKeySize(sasl)) < 0) goto cleanup; - } - ssf = *(const int *)val; + DEBUG("SASL SSF value %d", ssf); if (ssf < 56) { /* 56 == DES level, good for Kerberos */ remoteError(VIR_ERR_AUTH_FAILED, @@ -7409,18 +6695,15 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, } DEBUG0("SASL authentication complete"); - priv->saslconn = saslconn; + virNetClientSetSASLContext(priv->client, sasl); ret = 0; cleanup: - VIR_FREE(localAddr); - VIR_FREE(remoteAddr); VIR_FREE(serverin); VIR_FREE(saslcb); remoteAuthFreeCredentials(cred, ncred); - if (ret != 0 && saslconn) - sasl_dispose(&saslconn); + virNetClientSaslContextFree(sasl); return ret; } @@ -7573,184 +6856,187 @@ done: return rv; } + +static int remoteDomainEventQueuePush(struct private_data *priv, + virDomainEventPtr event) +{ + int ret = -1; + remoteDriverLock(priv); + + if (virDomainEventQueuePush(priv->domainEvents, + event) < 0) { + DEBUG0("Error adding event to queue"); + goto cleanup; + } + + ret = 0; + virEventUpdateTimeout(priv->eventFlushTimer, 0); + +cleanup: + remoteDriverUnlock(priv); + return ret; +} + + /** * remoteDomainReadEventLifecycle * * Read the domain lifecycle event data off the wire */ -static virDomainEventPtr -remoteDomainReadEventLifecycle(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventLifecycle(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_lifecycle_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_lifecycle_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_lifecycle_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall lifecycle event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; - - event = virDomainEventNewFromDom(dom, msg.event, msg.detail); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_lifecycle_msg, (char *) &msg); + return; + event = virDomainEventNewFromDom(dom, msg->event, msg->detail); virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventReboot(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventReboot(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_reboot_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_reboot_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_reboot_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; + return; event = virDomainEventRebootNewFromDom(dom); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_reboot_msg, (char *) &msg); - virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventRTCChange(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventRTCChange(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_rtc_change_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_rtc_change_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_rtc_change_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; - - event = virDomainEventRTCChangeNewFromDom(dom, msg.offset); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_rtc_change_msg, (char *) &msg); + return; + event = virDomainEventRTCChangeNewFromDom(dom, msg->offset); virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventWatchdog(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventWatchdog(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_watchdog_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_watchdog_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_watchdog_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; - - event = virDomainEventWatchdogNewFromDom(dom, msg.action); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_watchdog_msg, (char *) &msg); + return; + event = virDomainEventWatchdogNewFromDom(dom, msg->action); virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventIOError(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventIOError(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_io_error_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_io_error_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_io_error_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; + return; event = virDomainEventIOErrorNewFromDom(dom, - msg.srcPath, - msg.devAlias, - msg.action); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_io_error_msg, (char *) &msg); - + msg->srcPath, + msg->devAlias, + msg->action); virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventIOErrorReason(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventIOErrorReason(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_io_error_reason_msg msg; - virDomainPtr dom; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_io_error_reason_msg *msg = evdata; + virDomainPtr dom; virDomainEventPtr event = NULL; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_io_error_reason_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn,msg->dom); if (!dom) - return NULL; + return; event = virDomainEventIOErrorReasonNewFromDom(dom, - msg.srcPath, - msg.devAlias, - msg.action, - msg.reason); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_io_error_reason_msg, (char *) &msg); + msg->srcPath, + msg->devAlias, + msg->action, + msg->reason); virDomainFree(dom); - return event; + + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); } -static virDomainEventPtr -remoteDomainReadEventGraphics(virConnectPtr conn, XDR *xdr) +static void +remoteDomainBuildEventGraphics(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) { - remote_domain_event_graphics_msg msg; + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + remote_domain_event_graphics_msg *msg = evdata; virDomainPtr dom; virDomainEventPtr event = NULL; virDomainEventGraphicsAddressPtr localAddr = NULL; @@ -7758,58 +7044,49 @@ remoteDomainReadEventGraphics(virConnectPtr conn, XDR *xdr) virDomainEventGraphicsSubjectPtr subject = NULL; int i; - memset (&msg, 0, sizeof msg); - - /* unmarshall parameters, and process it*/ - if (! xdr_remote_domain_event_graphics_msg(xdr, &msg) ) { - remoteError(VIR_ERR_RPC, "%s", - _("unable to demarshall reboot event")); - return NULL; - } - - dom = get_nonnull_domain(conn,msg.dom); + dom = get_nonnull_domain(conn, msg->dom); if (!dom) - return NULL; + return; if (VIR_ALLOC(localAddr) < 0) goto no_memory; - localAddr->family = msg.local.family; - if (!(localAddr->service = strdup(msg.local.service)) || - !(localAddr->node = strdup(msg.local.node))) + localAddr->family = msg->local.family; + if (!(localAddr->service = strdup(msg->local.service)) || + !(localAddr->node = strdup(msg->local.node))) goto no_memory; if (VIR_ALLOC(remoteAddr) < 0) goto no_memory; - remoteAddr->family = msg.remote.family; - if (!(remoteAddr->service = strdup(msg.remote.service)) || - !(remoteAddr->node = strdup(msg.remote.node))) + remoteAddr->family = msg->remote.family; + if (!(remoteAddr->service = strdup(msg->remote.service)) || + !(remoteAddr->node = strdup(msg->remote.node))) goto no_memory; if (VIR_ALLOC(subject) < 0) goto no_memory; - if (VIR_ALLOC_N(subject->identities, msg.subject.subject_len) < 0) + if (VIR_ALLOC_N(subject->identities, msg->subject.subject_len) < 0) goto no_memory; - subject->nidentity = msg.subject.subject_len; + subject->nidentity = msg->subject.subject_len; for (i = 0 ; i < subject->nidentity ; i++) { - if (!(subject->identities[i].type = strdup(msg.subject.subject_val[i].type)) || - !(subject->identities[i].name = strdup(msg.subject.subject_val[i].name))) + if (!(subject->identities[i].type = strdup(msg->subject.subject_val[i].type)) || + !(subject->identities[i].name = strdup(msg->subject.subject_val[i].name))) goto no_memory; } event = virDomainEventGraphicsNewFromDom(dom, - msg.phase, + msg->phase, localAddr, remoteAddr, - msg.authScheme, + msg->authScheme, subject); - xdr_free ((xdrproc_t) &xdr_remote_domain_event_graphics_msg, (char *) &msg); virDomainFree(dom); - return event; -no_memory: - xdr_free ((xdrproc_t) &xdr_remote_domain_event_graphics_msg, (char *) &msg); + if (remoteDomainEventQueuePush(priv, event) < 0) + virDomainEventFree(event); + return; +no_memory: if (localAddr) { VIR_FREE(localAddr->service); VIR_FREE(localAddr->node); @@ -7828,7 +7105,7 @@ no_memory: VIR_FREE(subject->identities); VIR_FREE(subject); } - return NULL; + return; } @@ -8165,7 +7442,7 @@ done: return rv; } - +#if 0 static struct private_stream_data * remoteStreamOpen(virStreamPtr st, int output ATTRIBUTE_UNUSED, @@ -8712,7 +7989,7 @@ done: return rv; } - +#endif static int remoteCPUCompare(virConnectPtr conn, const char *xmlDesc, @@ -9244,7 +8521,7 @@ done: return rv; } - +#if 0 static int remoteDomainOpenConsole(virDomainPtr dom, const char *devname, @@ -9283,6 +8560,7 @@ done: return rv; } +#endif /*----------------------------------------------------------------------*/ @@ -9325,534 +8603,7 @@ done: return rv; } -/*----------------------------------------------------------------------*/ - -static struct remote_thread_call * -prepareCall(struct private_data *priv, - int flags, - int proc_nr, - xdrproc_t args_filter, char *args, - xdrproc_t ret_filter, char *ret) -{ - XDR xdr; - struct remote_message_header hdr; - struct remote_thread_call *rv; - - if (VIR_ALLOC(rv) < 0) { - virReportOOMError(); - return NULL; - } - - if (virCondInit(&rv->cond) < 0) { - VIR_FREE(rv); - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("cannot initialize mutex")); - return NULL; - } - - /* Get a unique serial number for this message. */ - rv->serial = priv->counter++; - rv->proc_nr = proc_nr; - rv->ret_filter = ret_filter; - rv->ret = ret; - rv->want_reply = 1; - - if (flags & REMOTE_CALL_QEMU) { - hdr.prog = QEMU_PROGRAM; - hdr.vers = QEMU_PROTOCOL_VERSION; - } - else { - hdr.prog = REMOTE_PROGRAM; - hdr.vers = REMOTE_PROTOCOL_VERSION; - } - hdr.proc = proc_nr; - hdr.type = REMOTE_CALL; - hdr.serial = rv->serial; - hdr.status = REMOTE_OK; - - /* Serialise header followed by args. */ - xdrmem_create (&xdr, rv->buffer+4, REMOTE_MESSAGE_MAX, XDR_ENCODE); - if (!xdr_remote_message_header (&xdr, &hdr)) { - remoteError(VIR_ERR_RPC, "%s", _("xdr_remote_message_header failed")); - goto error; - } - - if (!(*args_filter) (&xdr, args)) { - remoteError(VIR_ERR_RPC, "%s", _("marshalling args")); - goto error; - } - - /* Get the length stored in buffer. */ - rv->bufferLength = xdr_getpos (&xdr); - xdr_destroy (&xdr); - - /* Length must include the length word itself (always encoded in - * 4 bytes as per RFC 4506). - */ - rv->bufferLength += REMOTE_MESSAGE_HEADER_XDR_LEN; - - /* Encode the length word. */ - xdrmem_create (&xdr, rv->buffer, REMOTE_MESSAGE_HEADER_XDR_LEN, XDR_ENCODE); - if (!xdr_u_int (&xdr, &rv->bufferLength)) { - remoteError(VIR_ERR_RPC, "%s", _("xdr_u_int (length word)")); - goto error; - } - xdr_destroy (&xdr); - - return rv; - -error: - xdr_destroy (&xdr); - VIR_FREE(rv); - return NULL; -} - - - -static int -remoteIOWriteBuffer(struct private_data *priv, - const char *bytes, int len) -{ - int ret; - - if (priv->uses_tls) { - tls_resend: - ret = gnutls_record_send (priv->session, bytes, len); - if (ret < 0) { - if (ret == GNUTLS_E_INTERRUPTED) - goto tls_resend; - if (ret == GNUTLS_E_AGAIN) - return 0; - - remoteError(VIR_ERR_GNUTLS_ERROR, "%s", gnutls_strerror (ret)); - return -1; - } - } else { - resend: - ret = send (priv->sock, bytes, len, 0); - if (ret == -1) { - if (errno == EINTR) - goto resend; - if (errno == EWOULDBLOCK) - return 0; - - virReportSystemError(errno, "%s", _("cannot send data")); - return -1; - - } - } - - return ret; -} - - -static int -remoteIOReadBuffer(struct private_data *priv, - char *bytes, int len) -{ - int ret; - - if (priv->uses_tls) { - tls_resend: - ret = gnutls_record_recv (priv->session, bytes, len); - if (ret == GNUTLS_E_INTERRUPTED) - goto tls_resend; - if (ret == GNUTLS_E_AGAIN) - return 0; - - /* Treat 0 == EOF as an error */ - if (ret <= 0) { - if (ret < 0) - remoteError(VIR_ERR_GNUTLS_ERROR, - _("failed to read from TLS socket %s"), - gnutls_strerror (ret)); - else - remoteError(VIR_ERR_SYSTEM_ERROR, "%s", - _("server closed connection")); - return -1; - } - } else { - resend: - ret = recv (priv->sock, bytes, len, 0); - if (ret <= 0) { - if (ret == -1) { - if (errno == EINTR) - goto resend; - if (errno == EWOULDBLOCK) - return 0; - - char errout[1024] = "\0"; - if (priv->errfd != -1) { - if (saferead(priv->errfd, errout, sizeof(errout)) < 0) { - virReportSystemError(errno, "%s", - _("cannot recv data")); - return -1; - } - } - - virReportSystemError(errno, - _("cannot recv data: %s"), errout); - - } else { - char errout[1024] = "\0"; - if (priv->errfd != -1) { - if (saferead(priv->errfd, errout, sizeof(errout)) < 0) { - remoteError(VIR_ERR_SYSTEM_ERROR, - _("server closed connection: %s"), - virStrerror(errno, errout, sizeof errout)); - return -1; - } - } - - remoteError(VIR_ERR_SYSTEM_ERROR, - _("server closed connection: %s"), errout); - } - return -1; - } - } - - return ret; -} - - -static int -remoteIOWriteMessage(struct private_data *priv, - struct remote_thread_call *thecall) -{ -#if HAVE_SASL - if (priv->saslconn) { - const char *output; - unsigned int outputlen; - int err, ret; - - if (!priv->saslEncoded) { - err = sasl_encode(priv->saslconn, - thecall->buffer + thecall->bufferOffset, - thecall->bufferLength - thecall->bufferOffset, - &output, &outputlen); - if (err != SASL_OK) { - remoteError(VIR_ERR_INTERNAL_ERROR, - _("failed to encode SASL data: %s"), - sasl_errstring(err, NULL, NULL)); - return -1; - } - priv->saslEncoded = output; - priv->saslEncodedLength = outputlen; - priv->saslEncodedOffset = 0; - - thecall->bufferOffset = thecall->bufferLength; - } - - ret = remoteIOWriteBuffer(priv, - priv->saslEncoded + priv->saslEncodedOffset, - priv->saslEncodedLength - priv->saslEncodedOffset); - if (ret < 0) - return ret; - priv->saslEncodedOffset += ret; - - if (priv->saslEncodedOffset == priv->saslEncodedLength) { - priv->saslEncoded = NULL; - priv->saslEncodedOffset = priv->saslEncodedLength = 0; - if (thecall->want_reply) - thecall->mode = REMOTE_MODE_WAIT_RX; - else - thecall->mode = REMOTE_MODE_COMPLETE; - } - } else { -#endif - int ret; - ret = remoteIOWriteBuffer(priv, - thecall->buffer + thecall->bufferOffset, - thecall->bufferLength - thecall->bufferOffset); - if (ret < 0) - return ret; - thecall->bufferOffset += ret; - - if (thecall->bufferOffset == thecall->bufferLength) { - thecall->bufferOffset = thecall->bufferLength = 0; - if (thecall->want_reply) - thecall->mode = REMOTE_MODE_WAIT_RX; - else - thecall->mode = REMOTE_MODE_COMPLETE; - } -#if HAVE_SASL - } -#endif - return 0; -} - - -static int -remoteIOHandleOutput(struct private_data *priv) { - struct remote_thread_call *thecall = priv->waitDispatch; - - while (thecall && - thecall->mode != REMOTE_MODE_WAIT_TX) - thecall = thecall->next; - - if (!thecall) - return -1; /* Shouldn't happen, but you never know... */ - - while (thecall) { - int ret = remoteIOWriteMessage(priv, thecall); - if (ret < 0) - return ret; - - if (thecall->mode == REMOTE_MODE_WAIT_TX) - return 0; /* Blocking write, to back to event loop */ - - thecall = thecall->next; - } - - return 0; /* No more calls to send, all done */ -} - -static int -remoteIOReadMessage(struct private_data *priv) { - unsigned int wantData; - - /* Start by reading length word */ - if (priv->bufferLength == 0) - priv->bufferLength = 4; - - wantData = priv->bufferLength - priv->bufferOffset; - -#if HAVE_SASL - if (priv->saslconn) { - if (priv->saslDecoded == NULL) { - char encoded[8192]; - int ret, err; - ret = remoteIOReadBuffer(priv, encoded, sizeof(encoded)); - if (ret < 0) - return -1; - if (ret == 0) - return 0; - - err = sasl_decode(priv->saslconn, encoded, ret, - &priv->saslDecoded, &priv->saslDecodedLength); - if (err != SASL_OK) { - remoteError(VIR_ERR_INTERNAL_ERROR, - _("failed to decode SASL data: %s"), - sasl_errstring(err, NULL, NULL)); - return -1; - } - priv->saslDecodedOffset = 0; - } - - if ((priv->saslDecodedLength - priv->saslDecodedOffset) < wantData) - wantData = (priv->saslDecodedLength - priv->saslDecodedOffset); - - memcpy(priv->buffer + priv->bufferOffset, - priv->saslDecoded + priv->saslDecodedOffset, - wantData); - priv->saslDecodedOffset += wantData; - priv->bufferOffset += wantData; - if (priv->saslDecodedOffset == priv->saslDecodedLength) { - priv->saslDecodedOffset = priv->saslDecodedLength = 0; - priv->saslDecoded = NULL; - } - - return wantData; - } else { -#endif - int ret; - - ret = remoteIOReadBuffer(priv, - priv->buffer + priv->bufferOffset, - wantData); - if (ret < 0) - return -1; - if (ret == 0) - return 0; - - priv->bufferOffset += ret; - - return ret; -#if HAVE_SASL - } -#endif -} - - -static int -remoteIODecodeMessageLength(struct private_data *priv) { - XDR xdr; - unsigned int len; - - xdrmem_create (&xdr, priv->buffer, priv->bufferLength, XDR_DECODE); - if (!xdr_u_int (&xdr, &len)) { - remoteError(VIR_ERR_RPC, "%s", _("xdr_u_int (length word, reply)")); - return -1; - } - xdr_destroy (&xdr); - - if (len < REMOTE_MESSAGE_HEADER_XDR_LEN) { - remoteError(VIR_ERR_RPC, "%s", - _("packet received from server too small")); - return -1; - } - - /* Length includes length word - adjust to real length to read. */ - len -= REMOTE_MESSAGE_HEADER_XDR_LEN; - - if (len > REMOTE_MESSAGE_MAX) { - remoteError(VIR_ERR_RPC, "%s", - _("packet received from server too large")); - return -1; - } - - /* Extend our declared buffer length and carry - on reading the header + payload */ - priv->bufferLength += len; - DEBUG("Got length, now need %d total (%d more)", priv->bufferLength, len); - return 0; -} - - -static int -processCallDispatchReply(virConnectPtr conn, struct private_data *priv, - remote_message_header *hdr, - XDR *xdr); - -static int -processCallDispatchMessage(virConnectPtr conn, struct private_data *priv, - int in_open, - remote_message_header *hdr, - XDR *xdr); - -static int -processCallDispatchStream(virConnectPtr conn, struct private_data *priv, - remote_message_header *hdr, - XDR *xdr); - - -static int -processCallDispatch(virConnectPtr conn, struct private_data *priv, - int flags) { - XDR xdr; - struct remote_message_header hdr; - int len = priv->bufferLength - 4; - int rv = -1; - int expectedprog; - int expectedvers; - - /* Length word has already been read */ - priv->bufferOffset = 4; - - /* Deserialise reply header. */ - xdrmem_create (&xdr, priv->buffer + priv->bufferOffset, len, XDR_DECODE); - if (!xdr_remote_message_header (&xdr, &hdr)) { - remoteError(VIR_ERR_RPC, "%s", _("invalid header in reply")); - return -1; - } - - priv->bufferOffset += xdr_getpos(&xdr); - - expectedprog = REMOTE_PROGRAM; - expectedvers = REMOTE_PROTOCOL_VERSION; - if (flags & REMOTE_CALL_QEMU) { - expectedprog = QEMU_PROGRAM; - expectedvers = QEMU_PROTOCOL_VERSION; - } - - /* Check program, version, etc. are what we expect. */ - if (hdr.prog != expectedprog) { - remoteError(VIR_ERR_RPC, - _("unknown program (received %x, expected %x)"), - hdr.prog, expectedprog); - return -1; - } - if (hdr.vers != expectedvers) { - remoteError(VIR_ERR_RPC, - _("unknown protocol version (received %x, expected %x)"), - hdr.vers, expectedvers); - return -1; - } - - - switch (hdr.type) { - case REMOTE_REPLY: /* Normal RPC replies */ - rv = processCallDispatchReply(conn, priv, &hdr, &xdr); - break; - - case REMOTE_MESSAGE: /* Async notifications */ - rv = processCallDispatchMessage(conn, priv, flags & REMOTE_CALL_IN_OPEN, - &hdr, &xdr); - break; - - case REMOTE_STREAM: /* Stream protocol */ - rv = processCallDispatchStream(conn, priv, &hdr, &xdr); - break; - - default: - remoteError(VIR_ERR_RPC, - _("got unexpected RPC call %d from server"), - hdr.proc); - rv = -1; - break; - } - - xdr_destroy(&xdr); - return rv; -} - - -static int -processCallDispatchReply(virConnectPtr conn ATTRIBUTE_UNUSED, - struct private_data *priv, - remote_message_header *hdr, - XDR *xdr) { - struct remote_thread_call *thecall; - - /* Ok, definitely got an RPC reply now find - out who's been waiting for it */ - thecall = priv->waitDispatch; - while (thecall && - thecall->serial != hdr->serial) - thecall = thecall->next; - - if (!thecall) { - remoteError(VIR_ERR_RPC, - _("no call waiting for reply with serial %d"), - hdr->serial); - return -1; - } - - if (hdr->proc != thecall->proc_nr) { - remoteError(VIR_ERR_RPC, - _("unknown procedure (received %x, expected %x)"), - hdr->proc, thecall->proc_nr); - return -1; - } - - /* Status is either REMOTE_OK (meaning that what follows is a ret - * structure), or REMOTE_ERROR (and what follows is a remote_error - * structure). - */ - switch (hdr->status) { - case REMOTE_OK: - if (!(*thecall->ret_filter) (xdr, thecall->ret)) { - remoteError(VIR_ERR_RPC, "%s", _("unmarshalling ret")); - return -1; - } - thecall->mode = REMOTE_MODE_COMPLETE; - return 0; - - case REMOTE_ERROR: - memset (&thecall->err, 0, sizeof thecall->err); - if (!xdr_remote_error (xdr, &thecall->err)) { - remoteError(VIR_ERR_RPC, "%s", _("unmarshalling remote_error")); - return -1; - } - thecall->mode = REMOTE_MODE_ERROR; - return 0; - - default: - remoteError(VIR_ERR_RPC, _("unknown status (received %x)"), hdr->status); - return -1; - } -} - +#if 0 static int processCallDispatchMessage(virConnectPtr conn, struct private_data *priv, int in_open, @@ -9871,31 +8622,31 @@ processCallDispatchMessage(virConnectPtr conn, struct private_data *priv, switch (hdr->proc) { case REMOTE_PROC_DOMAIN_EVENT_LIFECYCLE: - event = remoteDomainReadEventLifecycle(conn, xdr); + event = remoteDomainBuildEventLifecycle(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_REBOOT: - event = remoteDomainReadEventReboot(conn, xdr); + event = remoteDomainBuildEventReboot(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_RTC_CHANGE: - event = remoteDomainReadEventRTCChange(conn, xdr); + event = remoteDomainBuildEventRTCChange(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_WATCHDOG: - event = remoteDomainReadEventWatchdog(conn, xdr); + event = remoteDomainBuildEventWatchdog(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_IO_ERROR: - event = remoteDomainReadEventIOError(conn, xdr); + event = remoteDomainBuildEventIOError(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_IO_ERROR_REASON: - event = remoteDomainReadEventIOErrorReason(conn, xdr); + event = remoteDomainBuildEventIOErrorReason(conn, xdr); break; case REMOTE_PROC_DOMAIN_EVENT_GRAPHICS: - event = remoteDomainReadEventGraphics(conn, xdr); + event = remoteDomainBuildEventGraphics(conn, xdr); break; default: @@ -9906,16 +8657,11 @@ processCallDispatchMessage(virConnectPtr conn, struct private_data *priv, if (!event) return -1; - if (virDomainEventQueuePush(priv->domainEvents, - event) < 0) { - DEBUG0("Error adding event to queue"); - virDomainEventFree(event); - } - virEventUpdateTimeout(priv->eventFlushTimer, 0); - return 0; } +#endif +#if 0 static int processCallDispatchStream(virConnectPtr conn ATTRIBUTE_UNUSED, struct private_data *priv, @@ -10022,485 +8768,43 @@ processCallDispatchStream(virConnectPtr conn ATTRIBUTE_UNUSED, return -1; } } - -static int -remoteIOHandleInput(virConnectPtr conn, struct private_data *priv, - int flags) -{ - /* Read as much data as is available, until we get - * EAGAIN - */ - for (;;) { - int ret = remoteIOReadMessage(priv); - - if (ret < 0) - return -1; - if (ret == 0) - return 0; /* Blocking on read */ - - /* Check for completion of our goal */ - if (priv->bufferOffset == priv->bufferLength) { - if (priv->bufferOffset == 4) { - ret = remoteIODecodeMessageLength(priv); - if (ret < 0) - return -1; - - /* - * We'll carry on around the loop to immediately - * process the message body, because it has probably - * already arrived. Worst case, we'll get EAGAIN on - * next iteration. - */ - } else { - ret = processCallDispatch(conn, priv, flags); - priv->bufferOffset = priv->bufferLength = 0; - /* - * We've completed one call, so return even - * though there might still be more data on - * the wire. We need to actually let the caller - * deal with this arrived message to keep good - * response, and also to correctly handle EOF. - */ - return ret; - } - } - } -} - -/* - * Process all calls pending dispatch/receive until we - * get a reply to our own call. Then quit and pass the buck - * to someone else. - */ -static int -remoteIOEventLoop(virConnectPtr conn, - struct private_data *priv, - int flags, - struct remote_thread_call *thiscall) -{ - struct pollfd fds[2]; - int ret; - - fds[0].fd = priv->sock; - fds[1].fd = priv->wakeupReadFD; - - for (;;) { - struct remote_thread_call *tmp = priv->waitDispatch; - struct remote_thread_call *prev; - char ignore; -#ifdef HAVE_PTHREAD_SIGMASK - sigset_t oldmask, blockedsigs; -#endif - - fds[0].events = fds[0].revents = 0; - fds[1].events = fds[1].revents = 0; - - fds[1].events = POLLIN; - while (tmp) { - if (tmp->mode == REMOTE_MODE_WAIT_RX) - fds[0].events |= POLLIN; - if (tmp->mode == REMOTE_MODE_WAIT_TX) - fds[0].events |= POLLOUT; - - tmp = tmp->next; - } - - if (priv->streams) - fds[0].events |= POLLIN; - - /* Release lock while poll'ing so other threads - * can stuff themselves on the queue */ - remoteDriverUnlock(priv); - - /* Block SIGWINCH from interrupting poll in curses programs, - * then restore the original signal mask again immediately - * after the call (RHBZ#567931). Same for SIGCHLD and SIGPIPE - * at the suggestion of Paolo Bonzini and Daniel Berrange. - */ -#ifdef HAVE_PTHREAD_SIGMASK - sigemptyset (&blockedsigs); - sigaddset (&blockedsigs, SIGWINCH); - sigaddset (&blockedsigs, SIGCHLD); - sigaddset (&blockedsigs, SIGPIPE); - ignore_value(pthread_sigmask(SIG_BLOCK, &blockedsigs, &oldmask)); #endif - repoll: - ret = poll(fds, ARRAY_CARDINALITY(fds), -1); - if (ret < 0 && errno == EAGAIN) - goto repoll; - -#ifdef HAVE_PTHREAD_SIGMASK - ignore_value(pthread_sigmask(SIG_SETMASK, &oldmask, NULL)); -#endif - - remoteDriverLock(priv); - - if (fds[1].revents) { - ssize_t s; - DEBUG0("Woken up from poll by other thread"); - s = saferead(priv->wakeupReadFD, &ignore, sizeof(ignore)); - if (s < 0) { - virReportSystemError(errno, "%s", - _("read on wakeup fd failed")); - goto error; - } else if (s != sizeof(ignore)) { - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("read on wakeup fd failed")); - goto error; - } - } - - if (ret < 0) { - if (errno == EWOULDBLOCK) - continue; - virReportSystemError(errno, - "%s", _("poll on socket failed")); - goto error; - } - - if (fds[0].revents & POLLOUT) { - if (remoteIOHandleOutput(priv) < 0) - goto error; - } - - if (fds[0].revents & POLLIN) { - if (remoteIOHandleInput(conn, priv, flags) < 0) - goto error; - } - - /* Iterate through waiting threads and if - * any are complete then tell 'em to wakeup - */ - tmp = priv->waitDispatch; - prev = NULL; - while (tmp) { - if (tmp != thiscall && - (tmp->mode == REMOTE_MODE_COMPLETE || - tmp->mode == REMOTE_MODE_ERROR)) { - /* Take them out of the list */ - if (prev) - prev->next = tmp->next; - else - priv->waitDispatch = tmp->next; - - /* And wake them up.... - * ...they won't actually wakeup until - * we release our mutex a short while - * later... - */ - DEBUG("Waking up sleep %d %p %p", tmp->proc_nr, tmp, priv->waitDispatch); - virCondSignal(&tmp->cond); - } - prev = tmp; - tmp = tmp->next; - } - - /* Now see if *we* are done */ - if (thiscall->mode == REMOTE_MODE_COMPLETE || - thiscall->mode == REMOTE_MODE_ERROR) { - /* We're at head of the list already, so - * remove us - */ - priv->waitDispatch = thiscall->next; - DEBUG("Giving up the buck %d %p %p", thiscall->proc_nr, thiscall, priv->waitDispatch); - /* See if someone else is still waiting - * and if so, then pass the buck ! */ - if (priv->waitDispatch) { - DEBUG("Passing the buck to %d %p", priv->waitDispatch->proc_nr, priv->waitDispatch); - virCondSignal(&priv->waitDispatch->cond); - } - return 0; - } - - - if (fds[0].revents & (POLLHUP | POLLERR)) { - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("received hangup / error event on socket")); - goto error; - } - } - - -error: - priv->waitDispatch = thiscall->next; - DEBUG("Giving up the buck due to I/O error %d %p %p", thiscall->proc_nr, thiscall, priv->waitDispatch); - /* See if someone else is still waiting - * and if so, then pass the buck ! */ - if (priv->waitDispatch) { - DEBUG("Passing the buck to %d %p", priv->waitDispatch->proc_nr, priv->waitDispatch); - virCondSignal(&priv->waitDispatch->cond); - } - return -1; -} - -/* - * This function sends a message to remote server and awaits a reply - * - * NB. This does not free the args structure (not desirable, since you - * often want this allocated on the stack or else it contains strings - * which come from the user). It does however free any intermediate - * results, eg. the error structure if there is one. - * - * NB(2). Make sure to memset (&ret, 0, sizeof ret) before calling, - * else Bad Things will happen in the XDR code. - * - * NB(3) You must have the private_data lock before calling this - * - * NB(4) This is very complicated. Due to connection cloning, multiple - * threads can want to use the socket at once. Obviously only one of - * them can. So if someone's using the socket, other threads are put - * to sleep on condition variables. The existing thread may completely - * send & receive their RPC call/reply while they're asleep. Or it - * may only get around to dealing with sending the call. Or it may - * get around to neither. So upon waking up from slumber, the other - * thread may or may not have more work todo. - * - * We call this dance 'passing the buck' - * - * http://en.wikipedia.org/wiki/Passing_the_buck - * - * "Buck passing or passing the buck is the action of transferring - * responsibility or blame unto another person. It is also used as - * a strategy in power politics when the actions of one country/ - * nation are blamed on another, providing an opportunity for war." - * - * NB(5) Don't Panic! - */ -static int -remoteIO(virConnectPtr conn, - struct private_data *priv, - int flags, - struct remote_thread_call *thiscall) -{ - int rv; - - DEBUG("Do proc=%d serial=%d length=%d wait=%p", - thiscall->proc_nr, thiscall->serial, - thiscall->bufferLength, priv->waitDispatch); - - /* Check to see if another thread is dispatching */ - if (priv->waitDispatch) { - /* Stick ourselves on the end of the wait queue */ - struct remote_thread_call *tmp = priv->waitDispatch; - char ignore = 1; - ssize_t s; - while (tmp && tmp->next) - tmp = tmp->next; - if (tmp) - tmp->next = thiscall; - else - priv->waitDispatch = thiscall; - - /* Force other thread to wakeup from poll */ - s = safewrite(priv->wakeupSendFD, &ignore, sizeof(ignore)); - if (s < 0) { - char errout[1024]; - remoteError(VIR_ERR_INTERNAL_ERROR, - _("failed to wake up polling thread: %s"), - virStrerror(errno, errout, sizeof errout)); - return -1; - } else if (s != sizeof(ignore)) { - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to wake up polling thread")); - return -1; - } - - DEBUG("Going to sleep %d %p %p", thiscall->proc_nr, priv->waitDispatch, thiscall); - /* Go to sleep while other thread is working... */ - if (virCondWait(&thiscall->cond, &priv->lock) < 0) { - if (priv->waitDispatch == thiscall) { - priv->waitDispatch = thiscall->next; - } else { - tmp = priv->waitDispatch; - while (tmp && tmp->next && - tmp->next != thiscall) { - tmp = tmp->next; - } - if (tmp && tmp->next == thiscall) - tmp->next = thiscall->next; - } - remoteError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to wait on condition")); - return -1; - } - - DEBUG("Wokeup from sleep %d %p %p", thiscall->proc_nr, priv->waitDispatch, thiscall); - /* Two reasons we can be woken up - * 1. Other thread has got our reply ready for us - * 2. Other thread is all done, and it is our turn to - * be the dispatcher to finish waiting for - * our reply - */ - if (thiscall->mode == REMOTE_MODE_COMPLETE || - thiscall->mode == REMOTE_MODE_ERROR) { - /* - * We avoided catching the buck and our reply is ready ! - * We've already had 'thiscall' removed from the list - * so just need to (maybe) handle errors & free it - */ - goto cleanup; - } - - /* Grr, someone passed the buck onto us ... */ - - } else { - /* We're first to catch the buck */ - priv->waitDispatch = thiscall; - } - - DEBUG("We have the buck %d %p %p", thiscall->proc_nr, priv->waitDispatch, thiscall); - /* - * The buck stops here! - * - * At this point we're about to own the dispatch - * process... - */ - - /* - * Avoid needless wake-ups of the event loop in the - * case where this call is being made from a different - * thread than the event loop. These wake-ups would - * cause the event loop thread to be blocked on the - * mutex for the duration of the call - */ - if (priv->watch >= 0) - virEventUpdateHandle(priv->watch, 0); - - rv = remoteIOEventLoop(conn, priv, flags, thiscall); - - if (priv->watch >= 0) - virEventUpdateHandle(priv->watch, VIR_EVENT_HANDLE_READABLE); - - if (rv < 0) - return -1; - -cleanup: - DEBUG("All done with our call %d %p %p", thiscall->proc_nr, priv->waitDispatch, thiscall); - if (thiscall->mode == REMOTE_MODE_ERROR) { - /* See if caller asked us to keep quiet about missing RPCs - * eg for interop with older servers */ - if (flags & REMOTE_CALL_QUIET_MISSING_RPC && - thiscall->err.domain == VIR_FROM_REMOTE && - thiscall->err.code == VIR_ERR_RPC && - thiscall->err.level == VIR_ERR_ERROR && - thiscall->err.message && - STRPREFIX(*thiscall->err.message, "unknown procedure")) { - rv = -2; - } else if (thiscall->err.domain == VIR_FROM_REMOTE && - thiscall->err.code == VIR_ERR_RPC && - thiscall->err.level == VIR_ERR_ERROR && - thiscall->err.message && - STRPREFIX(*thiscall->err.message, "unknown procedure")) { - /* - * convert missing remote entry points into the unsupported - * feature error - */ - virRaiseErrorFull(flags & REMOTE_CALL_IN_OPEN ? NULL : conn, - __FILE__, __FUNCTION__, __LINE__, - thiscall->err.domain, - VIR_ERR_NO_SUPPORT, - thiscall->err.level, - thiscall->err.str1 ? *thiscall->err.str1 : NULL, - thiscall->err.str2 ? *thiscall->err.str2 : NULL, - thiscall->err.str3 ? *thiscall->err.str3 : NULL, - thiscall->err.int1, - thiscall->err.int2, - "%s", *thiscall->err.message); - rv = -1; - } else { - virRaiseErrorFull(flags & REMOTE_CALL_IN_OPEN ? NULL : conn, - __FILE__, __FUNCTION__, __LINE__, - thiscall->err.domain, - thiscall->err.code, - thiscall->err.level, - thiscall->err.str1 ? *thiscall->err.str1 : NULL, - thiscall->err.str2 ? *thiscall->err.str2 : NULL, - thiscall->err.str3 ? *thiscall->err.str3 : NULL, - thiscall->err.int1, - thiscall->err.int2, - "%s", thiscall->err.message ? *thiscall->err.message : "unknown"); - rv = -1; - } - xdr_free((xdrproc_t)xdr_remote_error, (char *)&thiscall->err); - } else { - rv = 0; - } - return rv; -} - /* * Serial a set of arguments into a method call message, * send that to the server and wait for reply */ static int -call (virConnectPtr conn, struct private_data *priv, +call (virConnectPtr conn ATTRIBUTE_UNUSED, + struct private_data *priv, int flags, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret) { - struct remote_thread_call *thiscall; int rv; + virNetClientProgramPtr prog = flags & REMOTE_CALL_QEMU ? priv->qemuProgram : priv->remoteProgram; + int counter = priv->counter++; + priv->localUses++; - thiscall = prepareCall(priv, flags, proc_nr, args_filter, args, - ret_filter, ret); - - if (!thiscall) { - virReportOOMError(); - return -1; - } - - rv = remoteIO(conn, priv, flags, thiscall); - VIR_FREE(thiscall); + /* Unlock, so that if we get any async events/stream data + * while processing the RPC, we don't deadlock when our + * callbacks for those are invoked + */ + remoteDriverUnlock(priv); + rv = virNetClientProgramCall(prog, + priv->client, + counter, + proc_nr, + args_filter, args, + ret_filter, ret); + remoteDriverLock(priv); + priv->localUses--; return rv; } -/** remoteDomainEventFired: - * - * The callback for monitoring the remote socket - * for event data - */ -void -remoteDomainEventFired(int watch, - int fd, - int event, - void *opaque) -{ - virConnectPtr conn = opaque; - struct private_data *priv = conn->privateData; - - remoteDriverLock(priv); - - /* This should be impossible, but it doesn't hurt to check */ - if (priv->waitDispatch) - goto done; - - DEBUG("Event fired %d %d %d %X", watch, fd, event, event); - - if (event & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) { - DEBUG("%s : VIR_EVENT_HANDLE_HANGUP or " - "VIR_EVENT_HANDLE_ERROR encountered", __FUNCTION__); - virEventRemoveHandle(watch); - priv->watch = -1; - goto done; - } - - if (fd != priv->sock) { - virEventRemoveHandle(watch); - priv->watch = -1; - goto done; - } - - if (remoteIOHandleInput(conn, priv, 0) < 0) - DEBUG0("Something went wrong during async message processing"); - -done: - remoteDriverUnlock(priv); -} - static void remoteDomainEventDispatchFunc(virConnectPtr conn, virDomainEventPtr event, virConnectDomainEventGenericCallback cb, @@ -10753,7 +9057,7 @@ static virDriver remote_driver = { remoteNodeDeviceDettach, /* nodeDeviceDettach */ remoteNodeDeviceReAttach, /* nodeDeviceReAttach */ remoteNodeDeviceReset, /* nodeDeviceReset */ - remoteDomainMigratePrepareTunnel, /* domainMigratePrepareTunnel */ + NULL, //remoteDomainMigratePrepareTunnel, /* domainMigratePrepareTunnel */ remoteIsEncrypted, /* isEncrypted */ remoteIsSecure, /* isSecure */ remoteDomainIsActive, /* domainIsActive */ @@ -10781,7 +9085,7 @@ static virDriver remote_driver = { remoteQemuDomainMonitorCommand, /* qemuDomainMonitorCommand */ remoteDomainSetMemoryParameters, /* domainSetMemoryParameters */ remoteDomainGetMemoryParameters, /* domainGetMemoryParameters */ - remoteDomainOpenConsole, /* domainOpenConsole */ + NULL, //remoteDomainOpenConsole, /* domainOpenConsole */ }; static virNetworkDriver network_driver = { -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
This guts the current remote driver, removing all its networking handling code. Instead it calls out to the new virClientPtr and virClientProgramPtr APIs for all RPC & networking work. --- src/Makefile.am | 3 +- src/remote/remote_driver.c | 2530 ++++++++------------------------------------ 2 files changed, 419 insertions(+), 2114 deletions(-)
And here we start to see the real payoff of this series.
- if(VIR_ALLOC(priv->callbackList)<0) { + if (VIR_ALLOC(priv->callbackList)<0) { virReportOOMError(); goto failed; }
- if(VIR_ALLOC(priv->domainEvents)<0) { + if (VIR_ALLOC(priv->domainEvents)<0) {
Can you separate some of this formatting touchups into an independent patch?
@@ -8165,7 +7442,7 @@ done: return rv; }
- +#if 0 static struct private_stream_data * remoteStreamOpen(virStreamPtr st, int output ATTRIBUTE_UNUSED, @@ -8712,7 +7989,7 @@ done:
I see - another case where stream support is missing. But looking like a nice start. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

This starts the basic framework for a lock manager daemon to provide guarenteed isolation between VMs using disk images. It is a simple demo of how the generic RPC server APIs are to be used --- src/Makefile.am | 16 ++ src/virtlockd.c | 620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 636 insertions(+), 0 deletions(-) create mode 100644 src/virtlockd.c diff --git a/src/Makefile.am b/src/Makefile.am index 8986f22..4c9cc79 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1167,6 +1167,22 @@ libvirt_net_client_la_LDFLAGS = \ libvirt_net_client_la_LIBADD = \ $(CYGWIN_EXTRA_LIBADD) +sbin_PROGRAMS = virtlockd + +virtlockd_SOURCES = virtlockd.c +virtlockd_CFLAGS = \ + $(AM_CFLAGS) +virtlockd_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS) +virtlockd_LDADD = \ + ../gnulib/lib/libgnu.la \ + libvirt-net-server.la \ + libvirt-net-rpc.la \ + libvirt_util.la \ + $(CYGWIN_EXTRA_LIBADD) + libexec_PROGRAMS = if WITH_STORAGE_DISK diff --git a/src/virtlockd.c b/src/virtlockd.c new file mode 100644 index 0000000..ec7dd5d --- /dev/null +++ b/src/virtlockd.c @@ -0,0 +1,620 @@ +/* + * virtlockd.c: lock management daemon + * + * Copyright (C) 2006-2010 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <getopt.h> +#include <stdlib.h> + + +#include "util.h" +#include "files.h" +#include "virterror_internal.h" +#include "logging.h" +#include "memory.h" +#include "conf.h" +#include "rpc/virnetserver.h" +#include "threads.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +#include "configmake.h" + +enum { + VIR_DAEMON_ERR_NONE = 0, + VIR_DAEMON_ERR_PIDFILE, + VIR_DAEMON_ERR_RUNDIR, + VIR_DAEMON_ERR_INIT, + VIR_DAEMON_ERR_SIGNAL, + VIR_DAEMON_ERR_PRIVS, + VIR_DAEMON_ERR_NETWORK, + VIR_DAEMON_ERR_CONFIG, + VIR_DAEMON_ERR_HOOKS, + + VIR_DAEMON_ERR_LAST +}; + +VIR_ENUM_DECL(virDaemonErr) +VIR_ENUM_IMPL(virDaemonErr, VIR_DAEMON_ERR_LAST, + "Initialization successful", + "Unable to obtain pidfile", + "Unable to create rundir", + "Unable to initialize libvirt", + "Unable to setup signal handlers", + "Unable to drop privileges", + "Unable to initialize network sockets", + "Unable to load configuration file", + "Unable to look for hook scripts"); + + +static int daemonForkIntoBackground(const char *argv0) +{ + int statuspipe[2]; + if (pipe(statuspipe) < 0) + return -1; + + int pid = fork(); + switch (pid) { + case 0: + { + int stdinfd = -1; + int stdoutfd = -1; + int nextpid; + + VIR_FORCE_CLOSE(statuspipe[0]); + + if ((stdinfd = open("/dev/null", O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open("/dev/null", O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (VIR_CLOSE(stdinfd) < 0) + goto cleanup; + if (VIR_CLOSE(stdoutfd) < 0) + goto cleanup; + + if (setsid() < 0) + goto cleanup; + + nextpid = fork(); + switch (nextpid) { + case 0: + return statuspipe[1]; + case -1: + return -1; + default: + _exit(0); + } + + cleanup: + VIR_FORCE_CLOSE(stdoutfd); + VIR_FORCE_CLOSE(stdinfd); + return -1; + + } + + case -1: + return -1; + + default: + { + int got, exitstatus = 0; + int ret; + char status; + + VIR_FORCE_CLOSE(statuspipe[1]); + + /* We wait to make sure the first child forked successfully */ + if ((got = waitpid(pid, &exitstatus, 0)) < 0 || + got != pid || + exitstatus != 0) { + return -1; + } + + /* Now block until the second child initializes successfully */ + again: + ret = read(statuspipe[0], &status, 1); + if (ret == -1 && errno == EINTR) + goto again; + + if (ret == 1 && status != 0) { + fprintf(stderr, + _("%s: error: %s. Check /var/log/messages or run without " + "--daemon for more info.\n"), argv0, + virDaemonErrTypeToString(status)); + } + _exit(ret == 1 && status == 0 ? 0 : 1); + } + } +} + +static int daemonWritePidFile(const char *argv0, const char *pidFile) { + int fd; + FILE *fh; + char ebuf[1024]; + + if (pidFile[0] == '\0') + return 0; + + if ((fd = open(pidFile, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0) { + VIR_ERROR(_("Failed to open pid file '%s' : %s"), + pidFile, virStrerror(errno, ebuf, sizeof ebuf)); + return -1; + } + + if (!(fh = VIR_FDOPEN(fd, "w"))) { + VIR_ERROR(_("Failed to fdopen pid file '%s' : %s"), + pidFile, virStrerror(errno, ebuf, sizeof ebuf)); + VIR_FORCE_CLOSE(fd); + return -1; + } + + if (fprintf(fh, "%lu\n", (unsigned long)getpid()) < 0) { + VIR_ERROR(_("%s: Failed to write to pid file '%s' : %s"), + argv0, pidFile, virStrerror(errno, ebuf, sizeof ebuf)); + VIR_FORCE_FCLOSE(fh); + return -1; + } + + if (VIR_FCLOSE(fh) == EOF) { + VIR_ERROR(_("%s: Failed to close pid file '%s' : %s"), + argv0, pidFile, virStrerror(errno, ebuf, sizeof ebuf)); + return -1; + } + + return 0; +} + + +static int daemonMakePaths(char **statedir, + char **pidfile, + char **sockfile) +{ + char *userdir = NULL; + uid_t uid = geteuid(); + + if (uid == 0) { + if (!(*statedir = strdup(LOCALSTATEDIR "/run"))) + goto no_memory; + if (!(*pidfile = strdup(LOCALSTATEDIR "/run/virtlockd.pid"))) + goto no_memory; + if (!(*sockfile = strdup(LOCALSTATEDIR "/run/virtlockd.sock"))) + goto no_memory; + } else { + if (!(userdir = virGetUserDirectory(uid))) + goto error; + + if (virAsprintf(statedir, "%s/.libvirt", userdir) < 0) + goto no_memory; + if (virAsprintf(pidfile, "%s/.libvirt/virtlockd.pid", userdir) < 0) + goto no_memory; + if (virAsprintf(sockfile, "%s/.libvirt/virtlockd.sock", userdir) < 0) + goto no_memory; + } + VIR_FREE(userdir); + return 0; + +no_memory: + VIR_FREE(*pidfile); + VIR_FREE(*sockfile); +error: + VIR_FREE(userdir); + return -1; +} + +static void daemonErrorHandler(void *opaque ATTRIBUTE_UNUSED, + virErrorPtr err ATTRIBUTE_UNUSED) +{ + /* Don't do anything, since logging infrastructure already + * took care of reporting the error */ +} + + +/* + * Set up the logging environment + * By default if daemonized all errors go to syslog and the logging + * is also saved onto the logfile libvird.log, but if verbose or error + * debugging is asked for then output informations or debug. + */ +static int +daemonSetLogging(virConfPtr conf ATTRIBUTE_UNUSED, + const char *filename ATTRIBUTE_UNUSED, + int godaemon, int verbose) +{ + //int log_level = 0; + char *log_filters = NULL; + char *log_outputs = NULL; + int ret = -1; + + virLogReset(); +#if 0 + /* + * Libvirtd's order of precedence is: + * cmdline > environment > config + * + * In order to achieve this, we must process configuration in + * different order for the log level versus the filters and + * outputs. Because filters and outputs append, we have to look at + * the environment first and then only check the config file if + * there was no result from the environment. The default output is + * then applied only if there was no setting from either of the + * first two. Because we don't have a way to determine if the log + * level has been set, we must process variables in the opposite + * order, each one overriding the previous. + */ + GET_CONF_INT (conf, filename, log_level); + if (log_level != 0) + virLogSetDefaultPriority(log_level); + + if (virLogGetNbFilters() == 0) { + GET_CONF_STR (conf, filename, log_filters); + virLogParseFilters(log_filters); + } + + if (virLogGetNbOutputs() == 0) { + GET_CONF_STR (conf, filename, log_outputs); + virLogParseOutputs(log_outputs); + } +#endif + + virLogSetFromEnv(); + + /* + * If no defined outputs, then direct to syslog when running + * as daemon. Otherwise the default output is stderr. + */ + if (virLogGetNbOutputs() == 0) { + char *tmp = NULL; + if (godaemon) { + if (virAsprintf (&tmp, "%d:syslog:libvirtd", + virLogGetDefaultPriority()) < 0) + goto free_and_fail; + } else { + if (virAsprintf (&tmp, "%d:stderr", + virLogGetDefaultPriority()) < 0) + goto free_and_fail; + } + virLogParseOutputs(tmp); + VIR_FREE(tmp); + } + + /* + * Command line override for --verbose + */ + if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO)) + virLogSetDefaultPriority(VIR_LOG_INFO); + + ret = 0; + +free_and_fail: + VIR_FREE(log_filters); + VIR_FREE(log_outputs); + return(ret); +} + +/* Read the config file if it exists. + * Only used in the remote case, hence the name. + */ +static int daemonReadConfigFile(const char *filename, + int godaemon, int verbose) +{ + virConfPtr conf; + + if (!(conf = virConfReadFile (filename, 0))) + goto error; + + if (daemonSetLogging(conf, filename, godaemon, verbose) < 0) + goto error; + + virConfFree (conf); + return 0; + +error: + virConfFree (conf); + + return -1; +} + +/* Display version information. */ +static void daemonVersion(const char *argv0) +{ + printf ("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION); +} + +static void daemonShutdownHandler(virNetServerPtr srv, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virNetServerQuit(srv); +} + +static int daemonSetupSignals(virNetServerPtr srv) +{ + if (virNetServerAddSignalHandler(srv, SIGINT, daemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGQUIT, daemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGTERM, daemonShutdownHandler, NULL) < 0) + return -1; + return 0; +} + +static int daemonSetupNetworking(virNetServerPtr srv, const char *sock_path) +{ + virNetServerServicePtr svc; + + if (!(svc = virNetServerServiceNewUNIX(sock_path, 0700, 0, 0, false))) + return -1; + + if (virNetServerAddService(srv, svc) < 0) { + virNetServerServiceFree(svc); + return -1; + } + return 0; +} + + +static void daemonUsage(const char *argv0) +{ + fprintf (stderr, + _("\n\ +Usage:\n\ + %s [options]\n\ +\n\ +Options:\n\ + -v | --verbose Verbose messages.\n\ + -d | --daemon Run as a daemon & write PID file.\n\ + -t | --timeout <secs> Exit after timeout period.\n\ + -f | --config <file> Configuration file.\n\ + | --version Display version information.\n\ + -p | --pid-file <file> Change name of PID file.\n\ +\n\ +libvirt lock management daemon:\n\ +\n\ + Default paths:\n\ +\n\ + Configuration file (unless overridden by -f):\n\ + %s/libvirt/libvirtd.conf\n\ +\n\ + Sockets (as root):\n\ + %s/run/virtlockd.sock\n\ +\n\ + Sockets (as non-root):\n\ + $HOME/.libvirt/virtlockd.sock (in UNIX abstract namespace)\n\ +\n\ + Default PID file (as root):\ + %s/run/virtlockd.pid\n\ +\n\ + Default PID file (as non-root):\ + $HOME/.libvirt/virtlockd.pid\n\ +\n"), + argv0, + SYSCONFDIR, + LOCALSTATEDIR, + LOCALSTATEDIR); +} + +enum { + OPT_VERSION = 129 +}; + +#define MAX_LISTEN 5 +int main(int argc, char **argv) { + virNetServerPtr srv = NULL; + const char *remote_config_file = NULL; + int statuswrite = -1; + int ret = 1; + int verbose = 0; + int godaemon = 0; + int timeout = 0; + char *state_dir = NULL; + char *pid_file = NULL; + char *sock_file = NULL; + + struct option opts[] = { + { "verbose", no_argument, &verbose, 1}, + { "daemon", no_argument, &godaemon, 1}, + { "config", required_argument, NULL, 'f'}, + { "timeout", required_argument, NULL, 't'}, + { "pid-file", required_argument, NULL, 'p'}, + { "version", no_argument, NULL, OPT_VERSION }, + { "help", no_argument, NULL, '?' }, + {0, 0, 0, 0} + }; + + if (setlocale (LC_ALL, "") == NULL || + bindtextdomain (PACKAGE, LOCALEDIR) == NULL || + textdomain(PACKAGE) == NULL || + virThreadInitialize() < 0 || + virErrorInitialize() < 0 || + virRandomInitialize(time(NULL) ^ getpid())) { + fprintf(stderr, _("%s: initialization failed\n"), argv[0]); + exit(EXIT_FAILURE); + } + + if (daemonMakePaths(&state_dir, &pid_file, &sock_file) < 0) + exit(EXIT_FAILURE); + + while (1) { + int optidx = 0; + int c; + char *tmp; + + c = getopt_long(argc, argv, "ldf:p:t:v", opts, &optidx); + + if (c == -1) { + break; + } + + switch (c) { + case 0: + /* Got one of the flags */ + break; + case 'v': + verbose = 1; + break; + case 'd': + godaemon = 1; + break; + + case 't': + if (virStrToLong_i(optarg, &tmp, 10, &timeout) != 0 + || timeout <= 0 + /* Ensure that we can multiply by 1000 without overflowing. */ + || timeout > INT_MAX / 1000) + timeout = -1; + break; + + case 'p': + VIR_FREE(pid_file); + if (!(pid_file = strdup(optarg))) + exit(EXIT_FAILURE); + break; + + case 'f': + remote_config_file = optarg; + break; + + case OPT_VERSION: + daemonVersion(argv[0]); + return 0; + + case '?': + daemonUsage(argv[0]); + return 2; + + default: + fprintf (stderr, _("%s: internal error: unknown flag: %c\n"), + argv[0], c); + exit (EXIT_FAILURE); + } + } + + if (remote_config_file == NULL) { + static const char *default_config_file + = SYSCONFDIR "/libvirt/libvirtd.conf"; + remote_config_file = + (access(default_config_file, R_OK) == 0 + ? default_config_file + : "/dev/null"); + } + + if (godaemon) { + char ebuf[1024]; + if ((statuswrite = daemonForkIntoBackground(argv[0])) < 0) { + VIR_ERROR(_("Failed to fork as daemon: %s"), + virStrerror(errno, ebuf, sizeof ebuf)); + goto cleanup; + } + } + + if (!(srv = virNetServerNew(1, 1, 20))) { + ret = VIR_DAEMON_ERR_INIT; + goto cleanup; + } + + if ((daemonSetupSignals(srv)) < 0) { + ret = VIR_DAEMON_ERR_SIGNAL; + goto cleanup; + } + + /* If we have a pidfile set, claim it now, exiting if already taken */ + if (daemonWritePidFile(argv[0], pid_file) < 0) { + pid_file = NULL; /* Prevent unlinking of someone else's pid ! */ + ret = VIR_DAEMON_ERR_PIDFILE; + goto cleanup; + } + + /* Ensure the rundir exists (on tmpfs on some systems) */ + if (mkdir(state_dir, 0755)) { + if (errno != EEXIST) { + char ebuf[1024]; + VIR_ERROR(_("unable to create rundir %s: %s"), state_dir, + virStrerror(errno, ebuf, sizeof(ebuf))); + ret = VIR_DAEMON_ERR_RUNDIR; + goto cleanup; + } + } + + /* Read the config file (if it exists). */ + if (daemonReadConfigFile(remote_config_file, godaemon, verbose) < 0) { + ret = VIR_DAEMON_ERR_CONFIG; + goto cleanup; + } + + if (daemonSetupNetworking(srv, sock_file) < 0) { + ret = VIR_DAEMON_ERR_NETWORK; + goto cleanup; + } + + /* Disable error func, now logging is setup */ + virSetErrorFunc(NULL, daemonErrorHandler); + + + /* Tell parent of daemon that basic initialization is complete + * In particular we're ready to accept net connections & have + * written the pidfile + */ + if (statuswrite != -1) { + char status = 0; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + VIR_FORCE_CLOSE(statuswrite); + } + + /* Start accepting new clients from network */ + + virNetServerUpdateServices(srv, true); + virNetServerRun(srv); + ret = 0; + +cleanup: + virNetServerFree(srv); + if (statuswrite != -1) { + if (ret != 0) { + /* Tell parent of daemon what failed */ + char status = ret; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + } + VIR_FORCE_CLOSE(statuswrite); + } + if (pid_file) + unlink(pid_file); + virLogShutdown(); + VIR_FREE(pid_file); + VIR_FREE(sock_file); + VIR_FREE(state_dir); + return ret; +} -- 1.7.2.3

On 12/01/2010 10:26 AM, Daniel P. Berrange wrote:
This starts the basic framework for a lock manager daemon to provide guarenteed isolation between VMs using disk images. It is a simple demo of how the generic RPC server APIs are to be used --- src/Makefile.am | 16 ++ src/virtlockd.c | 620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 636 insertions(+), 0 deletions(-) create mode 100644 src/virtlockd.c
Not really part of the XDR refactoring series.
+ + +static int daemonForkIntoBackground(const char *argv0) +{ + int statuspipe[2]; + if (pipe(statuspipe) < 0) + return -1;
Does this overlap with virCommand? I didn't really review this one too closely. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Hu Tao