/*
* Copyright (c) 2010-2014 University of Utah and the Flux Group.
*
* {{{EMULAB-LICENSE
*
* This file is part of the Emulab network testbed software.
*
* This file is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* This file 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this file. If not, see .
*
* }}}
*/
/*
* Frisbee master server. Listen for GET/PUT requests and fork off
* handler processes.
*
* TODO:
* - timeouts for child frisbee processes
* - record the state of running frisbeeds in persistant store so that
* they can be restarted if we die and restart
* - related: make sure we don't leave orphans when we die!
* - handle signals: INT/TERM should kill all frisbeeds and remove our pid
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef WITH_IGMP
#include
#endif
#include "decls.h"
#include "utils.h"
#include "configdefs.h"
#define FRISBEE_SERVER "/usr/testbed/sbin/frisbeed"
#define FRISBEE_CLIENT "/usr/testbed/sbin/frisbee"
#define FRISBEE_UPLOAD "/usr/testbed/sbin/frisuploadd"
#define FRISBEE_RETRIES 5
static void get_options(int argc, char **argv);
static int makesocket(int portnum, struct in_addr *ifip, int *tcpsockp);
static void handle_request(int sock);
static int reapchildren(int apid, int *status);
#ifdef WITH_IGMP
static void handle_igmp(void);
#endif
static char * gidstr(int ngids, gid_t gids[]);
static int daemonize = 1;
int debug = 0;
static int dumpconfig = 0;
static int onlymethods = (MS_METHOD_UNICAST|MS_METHOD_MULTICAST);
static int parentmethods = (MS_METHOD_UNICAST|MS_METHOD_MULTICAST);
static int myuid = NOUID;
static int mygid = NOUID;
/*
* For recursively GETing images:
* parentip/parentport address of a parent master server (-S,-P)
* fetchfromabove true if a parent has been specified
* canredirect if true, redirect our clients to our parent (-R)
* usechildauth if true, pass client's authinfo to our parent (-A)
*/
static struct in_addr parentip;
static int parentport = MS_PORTNUM;
static int fetchfromabove = 0;
static int canredirect = 0;
static int usechildauth = 0;
static int mirrormode = 0;
static char *configstyle = "null";
static char *configopts = "";
static struct in_addr ifaceip;
static int igmpqueryinterval = 0;
/* XXX the following just keep network.c happy */
int portnum = MS_PORTNUM;
int sockbufsize = SOCKBUFSIZE;
struct in_addr mcastaddr;
struct in_addr mcastif;
int
main(int argc, char **argv)
{
int tcpsock;
FILE *fp;
char buf[BUFSIZ];
char *pidfile = (char *) NULL;
struct timeval tv;
fd_set ready;
#ifdef USE_LOCALHOST_PROXY
int localsock = -1;
#endif
get_options(argc, argv);
myuid = geteuid();
mygid = getegid();
if (daemonize && debug) {
int odebug = debug;
debug = 0;
MasterServerLogInit();
debug = odebug;
} else
MasterServerLogInit();
FrisLog("mfrisbeed daemon starting as %d/%d, methods=%s (debug level %d)",
myuid, mygid, GetMSMethods(onlymethods), debug);
if (fetchfromabove)
FrisLog(" using parent %s:%d%s, methods=%s",
inet_ntoa(parentip), parentport,
mirrormode ? " in mirror mode" : "",
GetMSMethods(parentmethods));
#ifdef WITH_IGMP
if (igmpqueryinterval)
FrisLog(" acting as IGMP querier on %s with %d second interval",
inet_ntoa(ifaceip), igmpqueryinterval);
#endif
config_init(configstyle, 1, configopts);
/* Just dump the config to stdout in human readable form and exit. */
if (dumpconfig) {
config_dump(stdout);
exit(0);
}
/*
* Create TCP server.
*/
if (makesocket(portnum, &ifaceip, &tcpsock) < 0) {
FrisError("Could not make primary tcp socket!");
exit(1);
}
#ifdef USE_LOCALHOST_PROXY
/*
* Listen on localhost too for proxy requests.
*/
if (ifaceip.s_addr != htonl(INADDR_ANY) &&
ifaceip.s_addr != htonl(INADDR_LOOPBACK)) {
struct in_addr localip;
localip.s_addr = htonl(INADDR_LOOPBACK);
if (makesocket(portnum, &localip, &localsock) < 0) {
FrisError("Could not create localhost tcp socket!");
exit(1);
}
}
#endif
#ifdef WITH_IGMP
if (igmpqueryinterval)
IGMPInit(&ifaceip, NULL);
#endif
/* Now become a daemon */
if (daemonize)
daemon(0, 0);
/*
* Stash the pid away.
*/
if (!geteuid()) {
if (!pidfile) {
sprintf(buf, "%s/mfrisbeed.pid", _PATH_VARRUN);
pidfile = buf;
}
fp = fopen(pidfile, "w");
if (fp != NULL) {
fprintf(fp, "%d\n", getpid());
(void) fclose(fp);
}
}
/*
* Handle connections
*/
FD_ZERO(&ready);
while (1) {
struct sockaddr_in client;
socklen_t length;
int newsock, rv, maxsock = 0;
#ifdef WITH_IGMP
/* Do the querier thing */
if (igmpqueryinterval)
handle_igmp();
#endif
FD_SET(tcpsock, &ready);
maxsock = tcpsock + 1;
#ifdef USE_LOCALHOST_PROXY
if (localsock >= 0) {
FD_SET(localsock, &ready);
if (localsock > tcpsock)
maxsock = localsock + 1;
}
#endif
tv.tv_sec = 0;
tv.tv_usec = 100000;
rv = select(maxsock, &ready, NULL, NULL, &tv);
if (rv < 0) {
if (errno == EINTR)
continue;
FrisPfatal("select failed");
}
if (rv) {
int sock = tcpsock;
#ifdef USE_LOCALHOST_PROXY
if (localsock >= 0 && FD_ISSET(localsock, &ready))
sock = localsock;
#endif
length = sizeof(client);
newsock = accept(sock, (struct sockaddr *)&client,
&length);
if (newsock < 0)
FrisPwarning("accepting TCP connection");
else {
fcntl(newsock, F_SETFD, FD_CLOEXEC);
handle_request(newsock);
close(newsock);
}
}
(void) reapchildren(0, NULL);
}
close(tcpsock);
#ifdef USE_LOCALHOST_PROXY
if (localsock >= 0)
close(localsock);
#endif
FrisLog("daemon terminating");
exit(0);
}
struct childinfo {
struct childinfo *next;
struct config_imageinfo *imageinfo;
int ptype;
int method;
int pid;
char *pidfile;
int uid; /* UID to run child as */
gid_t gids[MAXGIDS]; /* GID to run child as */
int ngids; /* number of GIDs */
int retries; /* # times to try starting up child */
int timeout; /* max runtime (sec) for child */
in_addr_t servaddr; /* -S arg */
in_addr_t ifaceaddr; /* -i arg */
in_addr_t addr; /* -m arg */
in_port_t port; /* -p arg */
void (*done)(struct childinfo *, int);
void *extra;
};
#define PTYPE_CLIENT 1
#define PTYPE_SERVER 2
#define PTYPE_UPLOADER 4
struct clientextra {
char *realname;
char *resolvedname;
/* info from our parent */
uint16_t sigtype;
uint8_t signature[MS_MAXSIGLEN];
uint32_t hisize;
uint32_t losize;
};
struct uploadextra {
char *realname;
uint64_t isize;
uint32_t mtime;
int itimeout;
};
static struct childinfo *findchild(char *, int, int);
static int startchild(struct childinfo *);
static struct childinfo *startserver(struct config_imageinfo *,
in_addr_t, in_addr_t, int, int *);
static struct childinfo *startclient(struct config_imageinfo *,
in_addr_t, in_addr_t, in_addr_t,
in_port_t, int, int *);
static struct childinfo *startuploader(struct config_imageinfo *,
in_addr_t, in_addr_t, uint64_t,
uint32_t, int, int *);
static void
free_imageinfo(struct config_imageinfo *ii)
{
if (ii) {
if (ii->imageid)
free(ii->imageid);
if (ii->dir)
free(ii->dir);
if (ii->path)
free(ii->path);
if (ii->sig)
free(ii->sig);
if (ii->get_options)
free(ii->get_options);
if (ii->put_oldversion)
free(ii->put_oldversion);
if (ii->put_options)
free(ii->put_options);
free(ii);
}
}
/*
* (Deep) copy an imageinfo structure.
* Returns pointer or null on error.
*/
static struct config_imageinfo *
copy_imageinfo(struct config_imageinfo *ii)
{
struct config_imageinfo *nii;
int i;
if ((nii = calloc(1, sizeof *nii)) == NULL)
goto fail;
if (ii->imageid && (nii->imageid = strdup(ii->imageid)) == NULL)
goto fail;
if (ii->dir && (nii->dir = strdup(ii->dir)) == NULL)
goto fail;
if (ii->path && (nii->path = strdup(ii->path)) == NULL)
goto fail;
if (ii->sig) {
int sz = 0;
if (ii->flags & CONFIG_SIG_ISMTIME)
sz = sizeof(time_t);
if (sz) {
if ((nii->sig = malloc(sz)) == NULL)
goto fail;
}
if (nii->sig)
memcpy(nii->sig, ii->sig, sz);
}
nii->flags = ii->flags;
nii->uid = ii->uid;
for (i = 0; i < ii->ngids; i++)
nii->gids[i] = ii->gids[i];
nii->ngids = ii->ngids;
if (ii->get_options &&
(nii->get_options = strdup(ii->get_options)) == NULL)
goto fail;
nii->get_methods = ii->get_methods;
nii->get_timeout = ii->get_timeout;
if (ii->put_oldversion &&
(nii->put_oldversion = strdup(ii->put_oldversion)) == NULL)
goto fail;
if (ii->put_options &&
(nii->put_options = strdup(ii->put_options)) == NULL)
goto fail;
nii->put_maxsize = ii->put_maxsize;
nii->put_timeout = ii->put_timeout;
nii->put_itimeout = ii->put_itimeout;
/* XXX don't care about extra right now */
return nii;
fail:
free_imageinfo(nii);
return NULL;
}
/*
* Fetch an image from our parent by getting image info from it and firing
* off a frisbee. If canredirect is non-zero, we also hook our child up
* directly with our parent so that it can fetch in parallel. If statusonly
* is non-zero, we just request info from our parent and return that.
* Returns zero if Reply struct contains the desired info, TRYAGAIN if we
* have started up a frisbee to fetch from our parent, or an error otherwise.
* Status is returned in host order.
*/
int
fetch_parent(struct in_addr *myip, struct in_addr *hostip,
struct in_addr *pip, in_port_t pport,
struct config_imageinfo *ii, int statusonly, GetReply *replyp)
{
struct childinfo *ci;
struct clientextra *ce;
struct in_addr pif;
in_addr_t authip;
GetReply reply;
int rv, methods;
/*
* If usechildauth is set, we pass the child host IP to our parent
* for authentication, otherwise we use our own.
*/
authip = usechildauth ? ntohl(hostip->s_addr) : 0;
/*
* Allowed image methods are constrained by any parent methods
*/
methods = ii->get_methods & parentmethods;
if (methods == 0)
return MS_ERROR_NOMETHOD;
/*
* See if a fetch is already in progress.
* If so we will either return "try again later" or point them to
* our parent.
*/
ci = findchild(ii->imageid, PTYPE_CLIENT, methods);
if (ci != NULL) {
if (debug)
FrisInfo("%s: fetch from %s in progress",
ii->imageid, inet_ntoa(*pip));
/*
* Since a download is in progress we don't normally need
* to revalidate since we wouldn't be downloading the image
* if we didn't have access.
*
* However, when acting for a child, we do need to validate
* every time since the child making the current request
* might not have the same access as the child we are
* currently downloading for.
*/
if (authip) {
if (!ClientNetFindServer(ntohl(pip->s_addr),
pport, authip, ii->imageid,
methods, 1, 5,
&reply, &pif))
return MS_ERROR_NOIMAGE;
if (reply.error)
return reply.error;
}
/*
* We return the info we got from our parent.
*/
assert(ci->extra != NULL);
ce = ci->extra;
memset(replyp, 0, sizeof *replyp);
replyp->sigtype = ce->sigtype;
memcpy(replyp->signature, ce->signature, MS_MAXSIGLEN);
replyp->hisize = ce->hisize;
replyp->losize = ce->losize;
if (statusonly) {
if (debug)
FrisInfo("%s: parent status: "
"sigtype=%d, sig=0x%08x..., size=%x/%x",
ii->imageid,
ce->sigtype, *(uint32_t *)ce->signature,
ce->hisize, ce->losize);
return 0;
}
/*
* A real get request.
* We either tell them to try again later or redirect them
* to our parent.
*/
goto done;
}
if (debug)
FrisInfo("%s: requesting %simage from %s:%d",
ii->imageid, (statusonly ? "status of ": ""),
inet_ntoa(*pip), pport);
/*
* Image fetch is not in progress.
* Send our parent a GET request and see what it says.
*/
if (!ClientNetFindServer(ntohl(pip->s_addr), pport, authip,
ii->imageid, methods, statusonly, 5,
&reply, &pif))
return MS_ERROR_NOIMAGE;
if (reply.error)
return reply.error;
/*
* Return the image size and signature from our parent
*/
memset(replyp, 0, sizeof *replyp);
replyp->sigtype = reply.sigtype;
memcpy(replyp->signature, reply.signature, MS_MAXSIGLEN);
replyp->hisize = reply.hisize;
replyp->losize = reply.losize;
if (statusonly) {
if (debug)
FrisInfo("%s: parent status: "
"sigtype=%d, sig=0x%08x..., size=%x/%x",
ii->imageid,
reply.sigtype, *(uint32_t *)reply.signature,
reply.hisize, reply.losize);
return 0;
}
/*
* Parent has started up a frisbeed, spawn a frisbee to capture it.
*
* Note that servaddr from the reply might not be our parent (pip),
* if our parent in turn had to ask its parent and redirect was in
* effect. Unfortunately, in this case, 'pif' might not be correct
* since it is always the interface from which our parent responded.
* It will work as long as our parent's ancestor(s) reach us via
* the same interface.
*/
ci = startclient(ii, ntohl(pif.s_addr), reply.servaddr,
reply.addr, reply.port, reply.method, &rv);
if (ci == NULL)
return rv;
/*
* Cache the size/signature info for use in future calls when
* we are busy (the case above).
*/
assert(ci->extra != NULL);
ce = ci->extra;
ce->sigtype = replyp->sigtype;
memcpy(ce->signature, replyp->signature, MS_MAXSIGLEN);
ce->hisize = replyp->hisize;
ce->losize = replyp->losize;
if (debug)
FrisInfo("%s cache status: "
"sigtype=%d, sig=0x%08x..., size=%x/%x",
ii->imageid,
ce->sigtype, *(uint32_t *)ce->signature,
ce->hisize, ce->losize);
done:
if (!canredirect)
return MS_ERROR_TRYAGAIN;
/*
* XXX more unicast "fer now" hackary...if we are talking unicast with
* our parent, we cannot redirect the child there as well.
*/
if (ci->method == MS_METHOD_UNICAST)
return MS_ERROR_TRYAGAIN;
memset(replyp, 0, sizeof *replyp);
replyp->method = ci->method;
replyp->servaddr = ci->servaddr;
replyp->addr = ci->addr;
replyp->port = ci->port;
return 0;
}
/*
* Handle a GET request.
*
* This can be either a request to get the actual image (status==0)
* or a request to get just the status of the image (status==1).
*
* A request for the actual image causes us to start up a frisbee server
* for the image if one is not already running. If we are configured with
* a parent, we may need to first fetch the image from our parent with a
* GET request. In this case, we may return a TRYAGAIN status while the
* image transfer is in progress. Alternatively, if we are configured to
* allow redirection, we could just point our client to our parent and allow
* them to download the image along side us.
*
* A request for status will not fire up a server, but may require a status
* call on our parent. A status call will never return TRYAGAIN, it will
* always return the most recent status for an image or some other error.
* If we have a parent, we will always get its status for the image and
* return that if "newer" than ours. Thus status is a synchronous call that
* will return the attributes of the image that will ultimately be transferred
* to the client upon a GET request.
*/
void
handle_get(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip,
MasterMsg_t *msg)
{
struct in_addr host;
char imageid[MS_MAXIDLEN+1], *cimageid;
char clientip[sizeof("XXX.XXX.XXX.XXX")+1];
int len;
struct config_host_authinfo *ai;
struct config_imageinfo *ii = NULL;
struct childinfo *ci;
struct stat sb;
uint64_t isize;
int rv, methods, wantstatus;
int getfromparent;
GetReply reply;
char *op;
/*
* If an explicit host was listed, use that as the host we are
* authenticating, otherwise use the caller's IP. config_auth_by_IP
* will reject the former if the caller is not allowed to proxy for
* the node in question.
*/
if (msg->body.getrequest.hostip)
host.s_addr = msg->body.getrequest.hostip;
else
host.s_addr = cip->sin_addr.s_addr;
len = ntohs(msg->body.getrequest.idlen);
memcpy(imageid, msg->body.getrequest.imageid, len);
imageid[len] = '\0';
cimageid = config_canonicalize_imageid(imageid);
if (cimageid != NULL && strcmp(cimageid, imageid) == 0) {
free(cimageid);
cimageid = NULL;
}
methods = msg->body.getrequest.methods;
wantstatus = msg->body.getrequest.status;
op = wantstatus ? "GETSTATUS" : "GET";
strncpy(clientip, inet_ntoa(cip->sin_addr), sizeof clientip);
if (cimageid == NULL) {
if (host.s_addr != cip->sin_addr.s_addr)
FrisLog("%s: %s from %s (for %s), methods: 0x%x",
imageid, op, clientip, inet_ntoa(host),
methods);
else
FrisLog("%s: %s from %s, (methods: 0x%x)",
imageid, op, clientip, methods);
} else {
if (host.s_addr != cip->sin_addr.s_addr)
FrisLog("%s (%s): %s from %s (for %s), methods: 0x%x",
imageid, cimageid, op, clientip,
inet_ntoa(host), methods);
else
FrisLog("%s (%s): %s from %s, (methods: 0x%x)",
imageid, cimageid, op, clientip, methods);
}
memset(msg, 0, sizeof *msg);
msg->hdr.type = htonl(MS_MSGTYPE_GETREPLY);
strncpy((char *)msg->hdr.version, MS_MSGVERS_1,
sizeof(msg->hdr.version));
/*
* If they request a method we don't support, reject them before
* we do any other work. XXX maybe the method should not matter
* for a status-only call?
*/
methods &= onlymethods;
if (methods == 0)
goto badmethod;
/*
* Use the canonical name from here on out.
*/
if (cimageid != NULL) {
strcpy(imageid, cimageid);
free(cimageid);
cimageid = NULL;
}
/*
* In mirrormode, we first validate access with our parent
* before doing anything locally.
*/
if (mirrormode) {
struct in_addr pif;
in_addr_t authip;
authip = usechildauth ? ntohl(host.s_addr) : 0;
if (!ClientNetFindServer(ntohl(parentip.s_addr),
parentport, authip, imageid,
methods, 1, 5,
&reply, &pif))
reply.error = MS_ERROR_NOIMAGE;
if (reply.error) {
msg->body.getreply.error = reply.error;
FrisLog("%s: client %s authentication with parent failed: %s",
imageid, clientip, GetMSError(reply.error));
goto reply;
}
}
#ifdef USE_LOCALHOST_PROXY
/*
* XXX this is really an Emulab special case for a boss node.
*
* We allow localhost access to any image that has a daemon
* currently running. This is a hack to allow localhost to get
* info for a running daemon so it can kill it.
*
* Perhaps localhost should be able to access ANY valid image
* or image directory (running or not), but I don't want to go
* there right now...
*/
if (wantstatus &&
cip->sin_addr.s_addr == htonl(INADDR_LOOPBACK) &&
host.s_addr == htonl(INADDR_LOOPBACK) &&
(ci = findchild(imageid, PTYPE_SERVER, methods)) != NULL) {
/* XXX only fill in the info that the caller cares about */
msg->body.getreply.method = ci->method;
msg->body.getreply.isrunning = 1;
msg->body.getreply.addr = htonl(ci->addr);
msg->body.getreply.port = htons(ci->port);
goto reply;
}
#endif
/*
* See if node has access to the image.
* If not, return an error code immediately.
*/
rv = config_auth_by_IP(1, &cip->sin_addr, &host, imageid, &ai);
if (rv) {
FrisWarning("%s: client %s %s failed: %s",
imageid, clientip, op, GetMSError(rv));
msg->body.getreply.error = rv;
goto reply;
}
if (debug > 1)
config_dump_host_authinfo(ai);
if (ai->numimages > 1) {
rv = MS_ERROR_INVALID;
FrisWarning("%s: client %s %s failed: "
"lookup returned multiple (%d) images",
imageid, clientip, op, ai->numimages);
msg->body.getreply.error = rv;
goto reply;
}
ii = copy_imageinfo(&ai->imageinfo[0]);
config_free_host_authinfo(ai);
assert((ii->flags & CONFIG_PATH_ISFILE) != 0);
/*
* If the image is currently being uploaded, return TRYAGAIN.
*/
if (findchild(ii->imageid, PTYPE_UPLOADER, MS_METHOD_UNICAST)) {
rv = MS_ERROR_TRYAGAIN;
FrisLog("%s: %s currently being uploaded", imageid, op);
msg->body.getreply.error = rv;
goto reply;
}
/*
* See if image actually exists.
*
* If the file exists but is not a regular file, we return an error.
*
* If the file does not exist (it is possible to request an image
* that doesn't exist if the authentication check allows access to
* the containing directory) and we have a parent, we request it
* from the parent.
*/
isize = 0;
getfromparent = 0;
if ((ii->flags & CONFIG_PATH_EXISTS) != 0 &&
stat(ii->path, &sb) == 0) {
if (!S_ISREG(sb.st_mode)) {
rv = MS_ERROR_INVALID;
FrisWarning("%s: client %s %s failed: "
"not a regular file",
imageid, clientip, op);
msg->body.getreply.error = rv;
goto reply;
}
isize = sb.st_size;
/*
* If the file exists and we have a parent, get the signature
* from the parent and see if we need to update our copy.
* We only do this for mirror mode.
*/
if (mirrormode) {
FrisLog("%s: have local copy", imageid);
/*
* See if the signature is out of date.
* Since this is mirror mode, we can use the status
* info we got from the earlier call.
*
* Note that we actually check for a different date,
* not just a newer date. People often roll back
* faulty images to previous versions.
*
* XXX need checks for other signature types.
*/
if ((reply.sigtype == MS_SIGTYPE_MTIME &&
*(time_t *)reply.signature != sb.st_mtime)) {
uint32_t mt = *(uint32_t *)reply.signature;
msg->body.getreply.sigtype =
htons(reply.sigtype);
if (reply.sigtype == MS_SIGTYPE_MTIME)
*(uint32_t *)reply.signature =
htonl(mt);
memcpy(msg->body.getreply.signature,
reply.signature, MS_MAXSIGLEN);
msg->body.getreply.hisize =
htonl(reply.hisize);
msg->body.getreply.losize =
htonl(reply.losize);
if (wantstatus)
goto reply;
FrisLog("%s: local copy (sig=%x) "
"is out of date (sig=%x), GET from parent",
imageid, sb.st_mtime, mt);
getfromparent = 1;
}
}
} else if (fetchfromabove) {
FrisLog("%s: no local copy, %s from parent", imageid, op);
/*
* We don't have the image, but we have a parent,
* request the status as we did above. Again, in mirrormode,
* we have already fetched the status so no additional call
* is needed.
*
* Any error is reflected to our caller.
*/
if (!mirrormode) {
rv = fetch_parent(&sip->sin_addr, &host, &parentip,
parentport, ii, 1, &reply);
if (rv) {
FrisLog("%s: failed getting parent status: %s, "
"failing",
imageid, GetMSError(rv));
msg->body.getreply.error = rv;
goto reply;
}
}
/*
* And we must always fetch from the parent.
* Return the attributes we got via the check above.
*/
msg->body.getreply.sigtype = htons(reply.sigtype);
if (reply.sigtype == MS_SIGTYPE_MTIME) {
uint32_t mt;
mt = *(uint32_t *)reply.signature;
*(uint32_t *)reply.signature = htonl(mt);
}
memcpy(msg->body.getreply.signature, reply.signature,
MS_MAXSIGLEN);
msg->body.getreply.hisize = htonl(reply.hisize);
msg->body.getreply.losize = htonl(reply.losize);
if (wantstatus)
goto reply;
getfromparent = 1;
} else {
/*
* No image and no parent, just flat fail.
*/
rv = (errno == ENOENT) ? MS_ERROR_NOIMAGE : MS_ERROR_INVALID;
FrisWarning("%s: client %s %s failed: %s",
imageid, clientip, op, GetMSError(rv));
msg->body.getreply.error = rv;
goto reply;
}
/*
* Either we did not have the image or our copy is out of date,
* attempt to fetch from our parent.
*/
if (getfromparent) {
rv = fetch_parent(&sip->sin_addr, &host,
&parentip, parentport, ii, 0, &reply);
/*
* Redirecting to parent.
* Can only do this if our parents method is compatible
* with the client's request.
*/
if (rv == 0) {
if ((reply.method & methods) != 0) {
msg->body.getreply.method = reply.method;
msg->body.getreply.isrunning = 1;
msg->body.getreply.servaddr =
htonl(reply.servaddr);
msg->body.getreply.addr =
htonl(reply.addr);
msg->body.getreply.port =
htons(reply.port);
FrisLog("%s: redirecting %s to our parent",
imageid, clientip);
goto reply;
}
FrisLog("%s: cannot redirect %s to parent; "
"incompatible transfer methods",
imageid, clientip);
rv = MS_ERROR_TRYAGAIN;
}
/*
* If parent callout failed, but we have a copy
* use our stale version.
*/
if (rv != MS_ERROR_TRYAGAIN && isize > 0) {
FrisWarning("%s: client %s %s from parent failed: %s, "
"using our stale copy",
imageid, clientip, op, GetMSError(rv));
}
/*
* Otherwise, we are busy fetching the new copy (TRYAGAIN),
* or we had a real failure and we don't have a copy.
*/
else {
FrisWarning("%s: client %s %s from parent failed: %s",
imageid, clientip, op, GetMSError(rv));
msg->body.getreply.error = rv;
goto reply;
}
}
/*
* Figure out what transfer method to use.
*/
if (debug)
FrisInfo(" request methods: 0x%x, image methods: 0x%x",
methods, ii->get_methods);
methods &= ii->get_methods;
if (methods == 0) {
badmethod:
rv = MS_ERROR_NOMETHOD;
FrisWarning("%s: client %s %s failed: %s",
imageid, clientip, op, GetMSError(rv));
msg->body.getreply.error = rv;
goto reply;
}
/*
* Otherwise see if there is a frisbeed already running, starting
* one if not. Then construct a reply with the available info.
*/
ci = findchild(ii->imageid, PTYPE_SERVER, methods);
/*
* XXX right now frisbeed doesn't support mutiple clients
* on the same unicast address. We could do multiple servers
* for the same image, but we don't.
*/
if (!wantstatus && ci != NULL && ci->method == MS_METHOD_UNICAST) {
FrisWarning("%s: client %s %s failed: "
"unicast server already running",
imageid, clientip, op);
msg->body.getreply.error = MS_ERROR_TRYAGAIN;
goto reply;
}
if (ci == NULL) {
struct in_addr in;
in_addr_t myaddr;
int stat;
if (ii->sig != NULL) {
int32_t mt = *(int32_t *)ii->sig;
assert((ii->flags & CONFIG_SIG_ISMTIME) != 0);
msg->body.getreply.sigtype = htons(MS_SIGTYPE_MTIME);
*(int32_t *)msg->body.getreply.signature = htonl(mt);
}
if (wantstatus) {
msg->body.getreply.method = methods;
msg->body.getreply.isrunning = 0;
msg->body.getreply.hisize = htonl(isize >> 32);
msg->body.getreply.losize = htonl(isize);
FrisLog("%s: STATUS is not running", imageid);
goto reply;
}
myaddr = ntohl(sip->sin_addr.s_addr);
#ifdef USE_LOCALHOST_PROXY
/*
* If this was a proxy request from localhost,
* we need to start the server on the real interface
* instead. If none was specified, we don't know what
* interface to use, so just fail.
*/
if (myaddr == INADDR_LOOPBACK) {
myaddr = ntohl(ifaceip.s_addr);
if (myaddr == INADDR_ANY) {
FrisWarning("%s: cannot start server on behalf "
"of %s", imageid, clientip);
msg->body.getreply.error = MS_ERROR_FAILED;
goto reply;
}
}
#endif
ci = startserver(ii, myaddr, ntohl(cip->sin_addr.s_addr),
methods, &rv);
if (ci == NULL) {
msg->body.getreply.error = rv;
goto reply;
}
in.s_addr = htonl(ci->addr);
FrisLog("%s: started %s server on %s:%d (pid %d, timo %ds)",
imageid, GetMSMethods(ci->method),
inet_ntoa(in), ci->port, ci->pid, ci->timeout);
if (debug) {
FrisLog(" uid: %d, gids: %s",
ci->uid, gidstr(ci->ngids, ci->gids));
}
/*
* Watch for an immediate death so we don't tell our client
* a server is running when it really isn't.
*
* XXX what is the right response? Right now we just tell
* them to try again and hope the problem is transient.
*/
sleep(2);
if (reapchildren(ci->pid, &stat)) {
msg->body.getreply.error = MS_ERROR_TRYAGAIN;
FrisLog("%s: server immediately exited (stat=0x%x), "
"telling client to try again!",
imageid, stat);
goto reply;
}
} else {
struct in_addr in;
if (ii->sig != NULL) {
int32_t mt = *(int32_t *)ii->sig;
assert((ii->flags & CONFIG_SIG_ISMTIME) != 0);
msg->body.getreply.sigtype = htons(MS_SIGTYPE_MTIME);
*(int32_t *)msg->body.getreply.signature = htonl(mt);
}
in.s_addr = htonl(ci->addr);
if (wantstatus)
FrisLog("%s: STATUS is running %s on %s:%d (pid %d)",
imageid, GetMSMethods(ci->method),
inet_ntoa(in), ci->port, ci->pid);
else
FrisLog("%s: %s server already running on %s:%d (pid %d)",
imageid, GetMSMethods(ci->method),
inet_ntoa(in), ci->port, ci->pid);
if (debug)
FrisLog(" uid: %d, gids: %s",
ci->uid, gidstr(ci->ngids, ci->gids));
}
msg->body.getreply.hisize = htonl(isize >> 32);
msg->body.getreply.losize = htonl(isize);
msg->body.getreply.method = ci->method;
msg->body.getreply.isrunning = 1;
msg->body.getreply.servaddr = htonl(ci->servaddr);
/*
* XXX tmp hack. Currently, if we are unicasting, startserver
* returns the addr field set to the address of the client
* (see the XXX there for details). However, we need to return our
* address to the client.
*
* When frisbeed is changed to support more than one unicast client,
* this will change.
*/
if (ci->method == MS_METHOD_UNICAST)
msg->body.getreply.addr = msg->body.getreply.servaddr;
else
msg->body.getreply.addr = htonl(ci->addr);
msg->body.getreply.port = htons(ci->port);
reply:
msg->body.getreply.error = htons(msg->body.getreply.error);
if (debug) {
FrisInfo("%s reply: sigtype=%d, sig=0x%08x..., size=%x/%x",
op, ntohs(msg->body.getreply.sigtype),
ntohl(*(uint32_t *)msg->body.getreply.signature),
ntohl(msg->body.getreply.hisize),
ntohl(msg->body.getreply.losize));
}
len = sizeof msg->hdr + sizeof msg->body.getreply;
if (!MsgSend(sock, msg, len, 10))
FrisError("%s: could not send reply",
inet_ntoa(cip->sin_addr));
if (ii)
free_imageinfo(ii);
}
void
handle_put(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip,
MasterMsg_t *msg)
{
struct in_addr host, in;
in_addr_t myaddr;
char imageid[MS_MAXIDLEN+1];
char clientip[sizeof("XXX.XXX.XXX.XXX")+1];
int len;
struct config_host_authinfo *ai;
struct config_imageinfo *ii = NULL;
struct childinfo *ci;
struct stat sb;
uint64_t isize;
uint32_t mtime, timo;
int rv, wantstatus;
char *op;
/*
* If an explicit host was listed, use that as the host we are
* authenticating, otherwise use the caller's IP. config_auth_by_IP
* will reject the former if the caller is not allowed to proxy for
* the node in question.
*/
if (msg->body.putrequest.hostip)
host.s_addr = msg->body.putrequest.hostip;
else
host.s_addr = cip->sin_addr.s_addr;
len = ntohs(msg->body.putrequest.idlen);
memcpy(imageid, msg->body.putrequest.imageid, len);
imageid[len] = '\0';
wantstatus = msg->body.putrequest.status;
op = wantstatus ? "PUTSTATUS" : "PUT";
isize = ((uint64_t)ntohl(msg->body.putrequest.hisize) << 32) |
ntohl(msg->body.putrequest.losize);
mtime = ntohl(msg->body.putrequest.mtime);
timo = ntohl(msg->body.putrequest.timeout);
strncpy(clientip, inet_ntoa(cip->sin_addr), sizeof clientip);
if (host.s_addr != cip->sin_addr.s_addr)
FrisLog("%s: %s from %s (for %s), size=%llu",
imageid, op, clientip, inet_ntoa(host), isize);
else
FrisLog("%s: %s from %s, size=%llu",
imageid, op, clientip, isize);
memset(msg, 0, sizeof *msg);
msg->hdr.type = htonl(MS_MSGTYPE_PUTREPLY);
strncpy((char *)msg->hdr.version, MS_MSGVERS_1,
sizeof(msg->hdr.version));
/*
* XXX we don't handle mirror mode right now.
*/
if (mirrormode) {
rv = MS_ERROR_NOTIMPL;
FrisWarning("%s: client %s %s failed: "
"upload not supported in mirror mode",
imageid, clientip, op);
msg->body.putreply.error = rv;
goto reply;
}
/*
* See if node has access to the image.
* If not, return an error code immediately.
*/
rv = config_auth_by_IP(0, &cip->sin_addr, &host, imageid, &ai);
if (rv) {
FrisWarning("%s: client %s %s failed: %s",
imageid, clientip, op, GetMSError(rv));
msg->body.putreply.error = rv;
goto reply;
}
if (debug > 1)
config_dump_host_authinfo(ai);
if (ai->numimages > 1) {
rv = MS_ERROR_INVALID;
FrisWarning("%s: client %s %s failed: "
"lookup returned multiple (%d) images",
imageid, clientip, op, ai->numimages);
msg->body.putreply.error = rv;
goto reply;
}
ii = copy_imageinfo(&ai->imageinfo[0]);
config_free_host_authinfo(ai);
assert((ii->flags & CONFIG_PATH_ISFILE) != 0);
/*
* If they gave us a size and it exceeds the maxsize, return an error.
* We do this even for a status-only request; they can specify a size
* of zero if they want to get all the image attributes.
*/
if (isize > ii->put_maxsize) {
rv = MS_ERROR_TOOBIG;
FrisWarning("%s: client %s %s failed: "
"upload size (%llu) exceeds maximum for image (%llu)",
imageid, clientip, op, isize, ii->put_maxsize);
/*
* We return the max size here along with the error so that
* the client doesn't have to make a second, wantstatus call.
*/
msg->body.putreply.himaxsize = htonl(ii->put_maxsize >> 32);
msg->body.putreply.lomaxsize = htonl(ii->put_maxsize);
msg->body.putreply.error = rv;
goto reply;
}
/*
* They gave us an mtime and it is "bad", return an error.
* XXX somewhat arbitrary: cannot set a time in the future.
* XXX cut them some slack on the future time thing, up to 10s okay.
*/
if (mtime) {
struct timeval now;
gettimeofday(&now, NULL);
if (mtime > (now.tv_sec + 10)) {
rv = MS_ERROR_BADMTIME;
FrisWarning("%s: client %s %s failed: "
"attempt to set mtime in the future "
"(%u > %u)\n",
imageid, clientip, op, mtime, now.tv_sec);
msg->body.putreply.error = rv;
goto reply;
}
}
/*
* If the image is being served, fetched from our parent,
* or uploaded--return TRYAGAIN.
*/
if ((ci = findchild(ii->imageid, PTYPE_SERVER, onlymethods)) ||
(ci = findchild(ii->imageid, PTYPE_CLIENT, onlymethods)) ||
(ci = findchild(ii->imageid, PTYPE_UPLOADER, MS_METHOD_UNICAST))) {
msg->body.putreply.error = MS_ERROR_TRYAGAIN;
FrisLog("%s: %s currently being %s", imageid, op,
(ci->ptype == PTYPE_SERVER) ? "served" :
(ci->ptype == PTYPE_CLIENT) ? "downloaded from parent" :
"uploaded");
goto reply;
}
/*
* See if image actually exists.
*
* If the file exists but is not a regular file, we return an error.
* (maybe we should just remove it?)
*
* If it is a regular file, we return its current signature.
*
* If the file does not exist, that is okay (the authentication check
* has verified that the node can create the image).
*/
if ((ii->flags & CONFIG_PATH_EXISTS) != 0 &&
stat(ii->path, &sb) == 0) {
if (!S_ISREG(sb.st_mode)) {
rv = MS_ERROR_INVALID;
FrisWarning("%s: client %s %s failed: "
"existing target is not a regular file",
imageid, clientip, op);
msg->body.putreply.error = rv;
goto reply;
}
msg->body.putreply.exists = 1;
/*
* Return the current size and signature info
*/
msg->body.putreply.hisize = htonl((uint64_t)sb.st_size >> 32);
msg->body.putreply.losize = htonl(sb.st_size);
if (ii->sig != NULL) {
int32_t mt = *(int32_t *)ii->sig;
assert((ii->flags & CONFIG_SIG_ISMTIME) != 0);
msg->body.putreply.sigtype = htons(MS_SIGTYPE_MTIME);
*(int32_t *)msg->body.putreply.signature = htonl(mt);
}
} else {
msg->body.putreply.exists = 0;
}
/*
* At this point we know that there is no conflicting use of the
* image in progress, so either return status or fire off an uploader
* and return the contact info to the client.
*/
msg->body.putreply.himaxsize = htonl(ii->put_maxsize >> 32);
msg->body.putreply.lomaxsize = htonl(ii->put_maxsize);
if (wantstatus) {
FrisLog("%s: PUTSTATUS upload allowed", imageid);
goto reply;
}
myaddr = ntohl(sip->sin_addr.s_addr);
ci = startuploader(ii, myaddr, ntohl(cip->sin_addr.s_addr),
isize, mtime, (int)timo, &rv);
if (ci == NULL) {
msg->body.putreply.error = rv;
goto reply;
}
in.s_addr = htonl(ci->addr);
FrisLog("%s: started uploader on %s:%d (pid %d, timo %ds)",
imageid, inet_ntoa(in), ci->port, ci->pid, ci->timeout);
if (debug)
FrisLog(" uid: %d, gids: %s",
ci->uid, gidstr(ci->ngids, ci->gids));
/*
* Watch for an immediate death so we don't tell our client
* an uploader is running when it really isn't.
*
* XXX what is the right response? Right now we just tell
* them to try again and hope the problem is transient.
*/
sleep(2);
if (reapchildren(ci->pid, &rv)) {
msg->body.putreply.error = MS_ERROR_TRYAGAIN;
FrisLog("%s: uploader immediately exited (stat=0x%x), "
"telling client to try again!",
imageid, rv);
goto reply;
}
msg->body.putreply.addr = htonl(ci->servaddr);
msg->body.putreply.port = htons(ci->port);
reply:
msg->body.putreply.error = htons(msg->body.putreply.error);
if (debug) {
FrisInfo("%s reply: sigtype=%d, sig=0x%08x..., "
"size=%x/%x, maxsize=%x/%x",
op, ntohs(msg->body.putreply.sigtype),
ntohl(*(uint32_t *)msg->body.putreply.signature),
ntohl(msg->body.putreply.hisize),
ntohl(msg->body.putreply.losize),
ntohl(msg->body.putreply.himaxsize),
ntohl(msg->body.putreply.lomaxsize));
}
len = sizeof msg->hdr + sizeof msg->body.putreply;
if (!MsgSend(sock, msg, len, 10))
FrisError("%s: could not send reply",
inet_ntoa(cip->sin_addr));
if (ii)
free_imageinfo(ii);
}
static void
handle_request(int sock)
{
MasterMsg_t msg;
int cc;
struct sockaddr_in me, you;
socklen_t len;
len = sizeof me;
if (getsockname(sock, (struct sockaddr *)&me, &len) < 0) {
perror("getsockname");
return;
}
len = sizeof you;
if (getpeername(sock, (struct sockaddr *)&you, &len) < 0) {
perror("getpeername");
return;
}
cc = read(sock, &msg, sizeof msg);
if (cc < sizeof msg.hdr) {
if (cc < 0)
perror("request message failed");
else
FrisError("request message too small");
return;
}
switch (ntohl(msg.hdr.type)) {
case MS_MSGTYPE_GETREQUEST:
if (cc < sizeof msg.hdr + sizeof msg.body.getrequest)
FrisError("GET request message too small");
else
handle_get(sock, &me, &you, &msg);
break;
case MS_MSGTYPE_PUTREQUEST:
if (cc < sizeof msg.hdr + sizeof msg.body.putrequest)
FrisError("PUT request message too small");
else
handle_put(sock, &me, &you, &msg);
break;
default:
FrisError("unrecognized message type %d", ntohl(msg.hdr.type));
break;
}
}
static void
usage(void)
{
fprintf(stderr, "mfrisbeed [-ADRd] [-X method] [-I imagedir] [-S parentIP] [-P parentport] [-p port]\n");
fprintf(stderr, "Basic:\n");
fprintf(stderr, " -C