serval-dna/dna_helper.c
gardners 02ca21a601 CHANGED DNA REPLY PACKET FORMAT BREAKING BACKWARDS COMPATIBILITY
... as part of working on work on DNA helper callout facility.
(DNA replies now contain a token that can be used to match them
against DNA requests as a happy side-effect).
2012-07-03 16:45:03 +09:30

213 lines
6.8 KiB
C

/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2012 Paul Gardner-Stephen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "serval.h"
/*
The challenge with making an interface for calling an external program to
resolve a DID into a URI is that it really should be asynchronous, so that
servald can't pause due to delays in looking up DIDs by helper applications.
This can be partially mitigated by having a cache, so that at least for repeated
requests the helper doesn't need to be called each time. This is very important
because the DNA protocol relies on pre-emptive retries to ensure reception of
a request over a lossy network.
The second part of the solution is to create an asynchronous queue for requests,
by passing them via file descriptor to a single persistent instance of the DNA
helper application, and polling the output of that application for results, and
then passing them out to their destinations. This ensures that the process is
asynchronous and non-blocking, regardless of how much time the helper application
requires. Then the helper will just be another file descriptor to poll in the
main loop.
*/
static int dna_helper_stdin = -1;
static int dna_helper_stdout = -1;
int
parseDnaReply(unsigned char *bytes, int count,
char *sidhex, char *did, char *name, char *uri) {
bzero(sidhex, SID_SIZE*2+1);
bzero(did, SID_SIZE);
bzero(name,64);
bzero(uri,512);
int i,l,maxlen;
l=0;
/* Replies look like: TOKEN|URI|DID|CALLERID| */
maxlen=SID_SIZE*2+1;
for(i=0;l<maxlen&&i<count&&bytes[i]!='|';i++)
sidhex[l++]=bytes[i];
sidhex[l]=0;
if (l>=count) return WHY("DNA helper response does not contain URI field");
if (l>=maxlen) return WHY("DNA helper response SID field too long");
l=0; i++; maxlen=511;
for(;l<maxlen&&i<count&&bytes[i]!='|';i++)
uri[l++]=bytes[i];
uri[l]=0;
if (l>=count) return WHY("DNA helper response does not contain DID field");
if (l>=maxlen) return WHY("DNA helper response URI field too long");
l=0; i++; maxlen=SID_SIZE;
for(;l<maxlen&&i<count&&bytes[i]!='|';i++)
did[l++]=bytes[i];
did[l]=0;
if (l>=count) return WHY("DNA helper response does not contain CALLERID field");
if (l>=maxlen) return WHY("DNA helper response DID field too long");
l=0; i++; maxlen=SID_SIZE;
for(;l<maxlen&&i<count&&bytes[i]!='|';i++)
name[l++]=bytes[i];
name[l]=0;
if (l>=count) return WHY("DNA helper response does not contain terminator");
if (l>=maxlen) return WHY("DNA helper response CALLERID field too long");
/* DEBUGF("did='%s', name='%s', uri='%s'",did,name,uri); */
return 0;
}
static int
dna_helper_start(const char *command, const char *arg) {
int stdin_fds[2], stdout_fds[2];
pid_t pid;
if (pipe(stdin_fds))
return WHY_perror("pipe");
if (pipe(stdout_fds)) {
close(stdin_fds[0]);
close(stdin_fds[1]);
return WHY_perror("pipe");
}
if ((pid = fork()) != 0) {
/* Child, should exec() to become helper after installing file descriptors. */
if (dup2(stdin_fds[1], 0)) /* replace stdin */
exit(-1);
if (dup2(stdout_fds[0], 1)) /* replace stdout */
exit(-1);
if (dup2(stdout_fds[0], 2)) /* replace stderr */
exit(-1);
execl(command, command, arg, NULL);
abort(); /* Can't get here */
} else {
if (pid == -1) {
/* fork failed */
WHY_perror("fork");
close(stdin_fds[0]);
close(stdin_fds[1]);
close(stdout_fds[0]);
close(stdout_fds[1]);
return -1;
} else {
/* Parent, should put file descriptors into place for use */
dna_helper_stdin = stdin_fds[0];
dna_helper_stdout = stdout_fds[1];
return 0;
}
}
return -1;
}
int
dna_helper_enqueue(char *did, unsigned char *requestorSid) {
const char *dna_helper, *dna_helper_arg;
char buffer[1024];
if (dna_helper_stdin == -2)
return -1;
if (dna_helper_stdin == -1) {
dna_helper = confValueGet("dna.helper", NULL);
if (!dna_helper || !dna_helper[0]) {
/* Check if we have a helper configured. If not, then set
dna_helper_stdin to magic value of -2 so that we don't waste time
in future looking up the dna helper configuration value. */
dna_helper_stdin = -2;
return -1;
}
/* Look for optional argument */
dna_helper_arg = confValueGet("dna.helperarg", NULL);
/* Okay, so we have a helper configured.
Run it */
if (dna_helper_start(dna_helper, dna_helper_arg) < 0)
/* Something broke, bail out */
return -1;
}
/* Write request to dna helper.
Request takes form: SID-of-Requestor|DID|\n
By passing the requestor's SID to the helper, we don't need to maintain
any state, as all we have to do is wait for responses from the helper,
which will include the requestor's SID.
*/
bzero(buffer, sizeof(buffer));
if (snprintf(buffer, sizeof(buffer) - 1, "%s|%s|\n", overlay_render_sid(requestorSid), did) >
sizeof(buffer) - 1)
return WHY("Command to helper is too long");
sigPipeFlag = 0;
WRITE_STR(dna_helper_stdin, buffer);
if (sigPipeFlag) {
/* Assume broken pipe due to dead helper.
Next request will cause it to be restarted.
(Losing the current request is not a big problem, because
DNA preemptively retries, anyway.
XXX In fact, we should probably have a limit to the number of restarts
in quick succession so that we don't waste lots of time with a buggy or
suicidal helper.
*/
close(dna_helper_stdin);
close(dna_helper_stdout);
dna_helper_stdin = -1;
dna_helper_stdout = -1;
return -1;
}
return 0;
}
int dna_return_resolution(overlay_mdp_frame *mdp, unsigned char *fromSid,
const char *did,const char *name, const char *uri) {
/* copy SID out into source address of frame */
bcopy(fromSid,&mdp->out.src.sid[0],SID_SIZE);
/* and build reply as did\nname\nURI<NUL> */
snprintf((char *)&mdp->out.payload[0],512,"%s\n%s\n%s",
did,name,uri);
mdp->out.payload_length=strlen((char *)mdp->out.payload)+1;
/* Dispatch response */
mdp->packetTypeAndFlags&=MDP_FLAG_MASK;
mdp->packetTypeAndFlags|=MDP_TX;
overlay_mdp_dispatch(mdp,0 /* system generated */,
NULL,0);
return 0;
}