/* * aprs485 - RS485 access point/bridge/hub */ #include "aprs485.h" #include char version[] = "@(#) aprs485 0.09 20100514"; typedef struct { /* log entry */ tmv_t tim; char buf[256-8]; } sle_t; void slog(char *fmt, ...); void slog_dump(); typedef struct { tmv_t tim; /* pwr on time */ int pwr; soa_t adr; int pks; u08_t pkb[UDPSIZ]; } tab_t; struct { int exit; tmv_t tboot; char bus[128]; /* bus adapter (tty or TCP socket) */ int esel; /* consecutive error count select() */ int esin; /* consecutive error count soc_in() */ int dbug; char *ldir; /* log directory */ int lprd; /* log partial reads on bus */ int nrcv; /* bus receive buffer */ u08_t brcv[UDPSIZ]; tab_t tabs[8]; /* tab clients */ tab_t *tsnd; /* tab waiting to send */ int nsle; /* log mechanism */ sle_t sles[16]; } gl; /* * log mechanism */ static int tm2yymmdd(struct tm *tm) { return (tm->tm_year%100)*10000+(tm->tm_mon+1)*100+tm->tm_mday; } void slog(char *fmt, ...) { sle_t *se; int *a, i; se = &gl.sles[gl.nsle++]; if (gl.nsle >= NEL(gl.sles)) return; gettimeofday(&se->tim,0); a = (int *)&fmt; a++; i = snprintf(se->buf,NEL(se->buf)-1,fmt,a[0],a[1],a[2],a[3],a[4],a[5]); se->buf[i] = 0; } void slog_dump() { sle_t *se; int ymd, lf, fd, k; char *p, buf[BUFSIZ], lfn[BUFSIZ]; struct tm tm; fd = lf = -1; for (se = gl.sles, k = MIN(NEL(gl.sles),gl.nsle); --k >= 0; se++) { p = buf; localtime_r(&se->tim.tv_sec,&tm); ymd = tm2yymmdd(&tm); p += sprintf(p,"%06d",ymd); p += sprintf(p," %02d:%02d:%02d.%03lu",tm.tm_hour,tm.tm_min,tm.tm_sec,se->tim.tv_usec/1000); p += sprintf(p," %.*s",NEL(se->buf),se->buf); if (p[-1] != '\n' && p[-1] != '\r') *p++ = '\n'; if (gl.ldir) { if (fd < 0 || lf != ymd) { if (fd >= 0) close(fd); lf = ymd; sprintf(lfn,"%s/%06d.log",gl.ldir,lf); fd = open(lfn,O_WRONLY|O_CREAT|O_APPEND,0644); } if (fd >= 0) write(fd,buf,p-buf); } if (gl.dbug) *p++ = '\r', write(1,buf,p-buf); } if (fd >= 0) close(fd); gl.nsle = 0; } /* * tab management */ tab_t *soa2tab(soa_t *sa) { tab_t *t; int n; for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) if (t->pwr != 1) continue; else if (t->adr.sin_addr == sa->sin_addr && t->adr.sin_port == sa->sin_port) return t; return 0; } /* * tab[] client/server communication * messages are 2 bytes: * * client server * BEL ACK client request to attach to hub tab * NAK <#tabs> error, no tab available * * DEL ACK client detach request * NAK error, not on a tab * * EOT 0 server exit */ int tab_man(soa_t *sa, u08_t *pkb, int pks) { tab_t *t; int n; if (pks != 2) return 0; switch (pkb[0]) { case BEL: /* request tab */ if ((t = soa2tab(sa)) == 0) { for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) if (t->pwr == 0) { t->pwr = 1; t->adr = *sa; t->pks = 0; gettimeofday(&t->tim,0); break; } if (n < 0) t = 0; else slog("T __ add [%d]",t-gl.tabs); } pkb[0] = t ? ACK : NAK; pkb[1] = t ? (t-gl.tabs) : NEL(gl.tabs); return 2; break; case DEL: /* drop tab */ if ((t = soa2tab(sa))) { if (t == gl.tsnd) gl.tsnd = 0; t->pwr = 0; pkb[0] = ACK; slog("T __ del [%d]",t-gl.tabs); } else pkb[0] = NAK; return 2; break; } return 0; } void snd2tabs(int sd, tab_t *ts, u08_t *pkb, int pks) { tab_t *t; int n, k; if (pks <= 0) return; for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) if (t->pwr == 1 && t != ts) { k = sendto(sd,pkb,pks,0,&t->adr.sa,sizeof(t->adr.sa)); if (k != pks) { t->pwr = 0; slog("T __ drop [%d] %s",t-gl.tabs,k<0?strerror(errno):""); } } } char *hex(u08_t *b, int nb, char *hb, int nh) { char *h = hb; int i; for (i = MIN((nh/2-1),nb); --i >= 0; b++) { *h++ = "0123456789abcdef"[(*b>>4)&0x0f]; *h++ = "0123456789abcdef"[(*b>>0)&0x0f]; } *h = 0; return hb; } /* * debug I/O */ void usr_in(fd) { int k; char buf[32]; if ((k = read(fd,buf,sizeof(buf))) != 1) { slog("E __ term read()=%d %s",k,k<0?strerror(errno):""); gl.exit = 1; return; } switch (buf[0]) { default: slog("D __ hit to exit"); break; case ESC: gl.exit = 1; break; case 'D': gl.lprd ^= 1; slog("D __ log partial read %d now",gl.lprd); break; } } /* * bus I/O */ void bus_in(int bd) { int k; char hb[128]; if ((k = NEL(gl.brcv) - gl.nrcv) <= 0) { slog("E __ bus buffer overrun drop %d bytes",gl.nrcv); gl.nrcv = 0; /* drop everything */ k = NEL(gl.brcv); } if ((k = read(bd,&gl.brcv[gl.nrcv],k)) <= 0) { slog("E __ bus read()=%d %s",k,k<0?strerror(errno):"disconnect!"); gl.exit = 1; return; } if (gl.lprd) slog("r %2d %s",k,hex(&gl.brcv[gl.nrcv],k,hb,sizeof(hb))); gl.nrcv += k; } void bus_out(int sd, int bd) { tab_t *t; int k; char hb[UDPSIZ*2+4]; if ((t = gl.tsnd) == 0) return; slog("S %2d %s",t->pks,hex(t->pkb,t->pks,hb,sizeof(hb))); if ((k = write(bd,t->pkb,t->pks)) != t->pks) { slog("E __ bus write(%d)=%d %s",t->pks,k,k<0?strerror(errno):""); if (k < 0) gl.exit = 1; } else snd2tabs(sd,t,t->pkb,t->pks); t->pks = 0; gl.tsnd = 0; } /* * client interface */ int str2av(char *line, char **av, int nav) { char *b, *s, *d, del; int ac; for (b = line; *b && *b != '\n' && *b != '\r'; b++); *b = 0; for (b = line, ac = 0; ac < nav; ) { while (*b == ' ' || *b == '\t') b++; if (*b == 0 || *b == '#') break; if (*b == '\"') { del = *b++; av[ac] = b; while (*b != 0 && (*b != del || b[-1] == '\\')) b++; if (*b == del) *b++ = 0; else av[ac] -= 1; for (s = d = av[ac]; *s && s < b; s++) if (s > av[ac] && *s == del && d[-1] == '\\') d[-1] = del; else if (s == d) d++; else *d++ = *s; *d = 0; ac++; } else { av[ac++] = b; while (*b != 0 && *b != ' ' && *b != '\t') b++; } if (*b == 0) break; *b++ = 0; } return ac; } void srv_client(int sd, soa_t *fa, int na, char **av) { tab_t *t; int n; u08_t *u; char *r, *a, ans[UDPSIZ]; struct tm tm; a = ans; if (na <= 0 || !strcmp(av[0],"stat")) { a += sprintf(a,"%s bus:%s",version+5,gl.bus); localtime_r(&gl.tboot.tv_sec,&tm); a += sprintf(a,"\nboot: %04d/%02d/%02d %02d:%02d:%02d", tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec); for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) { if (t->pwr == 0) continue; a += sprintf(a,"\ntab[%d]",t-gl.tabs); localtime_r(&t->tim.tv_sec,&tm); a += sprintf(a," %04d/%02d/%02d %02d:%02d:%02d", tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec); u = (u08_t *)&t->adr.sin_addr; a += sprintf(a," %d.%d.%d.%d:%d",u[0],u[1],u[2],u[3],ntohs(t->adr.sin_port)); } } else if (!strcmp(av[0],"kill")) { if (na != 2 || (n = strtol(av[1],&r,0)) < 0 || n >= NEL(gl.tabs) || r == av[1] || *r) a += sprintf(a,"which one?"); else { t = &gl.tabs[n]; if (t->pwr == 0) a += sprintf(a,"tab[%d] is not active",n); else { sendto(sd,EOT_STR,2,0,&t->adr.sa,sizeof(t->adr.sa)); t->pwr = 0; slog("T __ kill [%d]",t-gl.tabs); a += sprintf(a,"done"); } } } else if (!strcmp(av[0],"exit")) { a += sprintf(a,"bye!"); gl.exit = 1; } else if (!strcmp(av[0],"l") || !strcmp(av[0],"logdir")) { a += sprintf(a,"%s",gl.ldir?gl.ldir:"none"); } else a += sprintf(a,"what?"); if (a > ans) sendto(sd,ans,a-ans,0,&fa->sa,sizeof(fa->sa)); } /* * socket I/O */ void soc_in(int sd) { tab_t *t; soa_t fa; int pks, k; u08_t pkb[UDPSIZ+4]; char *av[32]; socklen_t len; len = sizeof(fa.sa); pks = recvfrom(sd,pkb,sizeof(pkb)-4,0,&fa.sa,&len); if (pks <= 0) { if (++gl.esin >= 3) gl.exit = 1; slog("E __ socket read()=%d %s",pks,pks<0?strerror(errno):""); return; } gl.esin = 0; if (pks <= 2 && (k = tab_man(&fa,pkb,pks)) > 0) { sendto(sd,pkb,k,0,&fa.sa,len); return; } if (pkb[0] == 'c' && pks >= 6 && !strncmp((char *)pkb,"client",6)) { pkb[pks] = 0; if ((k = str2av((char *)pkb+6,av,NEL(av))) > 0) srv_client(sd,&fa,k,av); return; } if (gl.tsnd) return; /* busy, drop it */ if ((t = soa2tab(&fa)) == 0) return; /* not registered */ bcopy(pkb,t->pkb,t->pks=pks); gl.tsnd = t; } int a5end(u08_t *pk, int np) { pa5_t *p; u08_t *b, *e; for (e = (b = pk) + np; b < e && *b != 0xa5; b++); if (b >= e) return np; p = (pa5_t *)b; b = &p->dat[p->len+2]; if (b >= e || *b != 0xff) return np; return b - pk; } void soc_out(int sd) { int n, i, k; char hb[UDPSIZ*2+4]; if ((n = gl.nrcv) > 2) { for (i = 0; i < n; i += k) { k = a5end(gl.brcv+i,n-i); slog("R %2d %s",k,hex(gl.brcv+i,k,hb,sizeof(hb))); snd2tabs(sd,0,gl.brcv+i,k); } } else slog("E %2d %s noise?",n,hex(gl.brcv,n,hb,sizeof(hb))); gl.nrcv = 0; } /* * bridge server */ #define MTICK (15*60) #define TBRTO 20000 /* bus read timeout (end of package) [microseconds] */ int bridge(int bd, int sd) { fd_set pfds, rfds; tmv_t tc, tv; int md, ct, lt, n; FD_ZERO(&pfds); if (gl.dbug) FD_SET(0,&pfds); FD_SET(bd,&pfds); FD_SET(sd,&pfds); md = MAX(bd,sd) + 1; ct = lt = -1; slog("B __ %s bus:%s",version+5,gl.bus); while (!gl.exit) { gettimeofday(&tc,0); if ((ct = (tc.tv_sec%(60*60))/MTICK) != lt) { /* mark tick */ if (lt >= 0) slog("M __ %s bus:%s",version+5,gl.bus); lt = ct; } slog_dump(); tv.tv_sec = tv.tv_usec = 0; if (gl.nrcv <= 0 && gl.tsnd == 0) { tv.tv_sec = ((tc.tv_sec/MTICK)+1)*MTICK; timersub(&tv,&tc,&tv); } if (tv.tv_sec == 0 && tv.tv_usec < TBRTO) tv.tv_usec = TBRTO; rfds = pfds; if ((n = select(md,&rfds,0,0,&tv)) < 0) { if (++gl.esel >= 3) gl.exit = 1; slog("E __ select() %s",strerror(errno)); continue; } gl.esel = 0; if (n == 0) { if (gl.nrcv > 0) soc_out(sd); else if (gl.tsnd) bus_out(sd,bd); } else { if (gl.dbug && FD_ISSET(0,&rfds)) usr_in(0); if (FD_ISSET(bd,&rfds)) bus_in(bd); if (FD_ISSET(sd,&rfds)) soc_in(sd); } } snd2tabs(sd,0,(u08_t *)EOT_STR,2); slog("X __ %s bus:%s",version+5,gl.bus); slog_dump(); return 0; } int open_aptty(char *dev) { int bd, eno; struct termios tio; if ((bd = open(dev,O_RDWR|O_NOCTTY|O_NDELAY)) < 0) return -1; while (1) { tcflush(bd,TCIOFLUSH); if (tcgetattr(bd,&tio) < 0) break; tio.c_iflag = IGNPAR; tio.c_oflag = 0; tio.c_cflag = CREAD|CLOCAL|B9600|CS8; tio.c_lflag = 0; tio.c_line = 0; tio.c_cc[VMIN] = 0; tio.c_cc[VTIME] = 0; if (tcsetattr(bd,TCSANOW,&tio) < 0) break; return bd; } eno = errno; close(bd); errno = eno; return -1; } int open_apsoc(char *ip, char *err) { soa_t sa; int bd, i; char *p, *r, buf[BUFSIZ]; struct hostent *h; if (ip == 0 || strlen(ip) >= NEL(buf)) { if (err) sprintf(err,"name too long"); return -1; } strcpy(buf,ip); bzero(&sa,sizeof(sa)); sa.sin_fmly = AF_INET; if ((p = strrchr(buf,':')) == 0) { if (err) sprintf(err,"port not specified"); return -1; } *p++ = 0; i = strtol(p,&r,10); if (r <= p || *r || i <= 0 || i >= 0xffff) { if (err) sprintf(err,"bad port '%s'",p); return -1; } sa.sin_port = htons(i); if (*(p = buf) == 0) p = "localhost"; if ((h = gethostbyname(p)) == 0) { if (err) sprintf(err,"%s",hstrerror(h_errno)); return -1; } bcopy(h->h_addr_list[0],&sa.sin_addr,h->h_length); if ((bd = socket(sa.sin_fmly,SOCK_STREAM,0)) < 0) { if (err) sprintf(err,"%s",strerror(errno)); return -1; } if (connect(bd,&sa.sa,sizeof(sa.sa))) { if (err) sprintf(err,"%s",strerror(errno)); close(bd); return -1; } fcntl(bd,F_SETFL,O_NONBLOCK); return bd; } #include void sighandler(int sig) { switch (sig) { case SIGHUP: case SIGTERM: gl.exit++; break; } } void sigsetup() { struct sigaction action; sigaction(SIGHUP,NULL,&action); action.sa_handler = sighandler; action.sa_flags = 0; sigaction(SIGHUP, &action,0); sigaction(SIGTERM,&action,0); sigaction(SIGALRM,&action,0); } int av2str(int na, char **av, char *str, int sob) { char *s, *d, *e; int i; if ((d = str) && sob > 0) { for (e = d + sob - 1, i = 0; i < na && d < e; i++) { if (i) *d++ = ' '; for (s = av[i]; d < e && (*d = *s++); d++); } *d = 0; } return d - str; } int client(int sd, soa_t *sa, int ac, char **av) { fd_set rds; tmv_t tv; int n; char buf[UDPSIZ]; n = sprintf(buf,"client "); if (ac <= 0) n += sprintf(buf+n,"stat"); else n += av2str(ac,av,buf+n,sizeof(buf)-1-n); if (sendto(sd,buf,n,0,&sa->sa,sizeof(sa->sa)) != n) return errno; FD_ZERO(&rds); FD_SET(sd,&rds); tv.tv_sec = 1; tv.tv_usec = 0; if ((n = select(sd+1,&rds,0,0,&tv)) < 0) return errno; if (n == 0) return ETIME; if ((n = read(sd,buf,sizeof(buf))) < 0) return errno; printf("%.*s\n",n,buf); return 0; } int main(int ac, char **av) { char *p; soa_t sa; int bd, sd, k, eno; struct termios tio[2]; bzero(&gl,sizeof(gl)); gettimeofday(&gl.tboot,0); bzero(&sa,sizeof(sa)); sa.sin_fmly = AF_INET; sa.sin_port = htons(APRS485_PORT); for (k = 0; ++k < ac; ) if (*(p = av[k]) == '-') switch (*(++p)) { default: USAGE: printf("usage: [-] \n"); printf("-p - server port (default: %d)\n",APRS485_PORT); printf("-l - log directory\n"); printf("-d - debug, don't fork\n"); printf("-D - log partial reads from \n"); printf(" - or \n"); printf("-c - send to running server\n"); return EINVAL; break; case 'd': gl.dbug++; break; case 'D': gl.lprd++; break; case 'c': if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) < 0) { printf("socket: %s\n",strerror(eno=errno)); return eno; } k++; eno = client(sd,&sa,ac-k,av+k); close(sd); if (eno) printf("client: %s\n",strerror(eno)); return 0; break; case 'p': if (++k >= ac) goto USAGE; eno = strtol(av[k],0,0); if (eno <= 1024 || eno > 0xffff) goto USAGE; sa.sin_port = htons(eno); break; case 'l': if (++k >= ac) goto USAGE; gl.ldir = av[k]; break; } else if (gl.bus[0]) goto USAGE; else if (*p == 'S' || (*p >= '0' && *p <= '9')) sprintf(gl.bus,"/dev/tty%s",p); else sprintf(gl.bus,"%.*s",sizeof(gl.bus)-1,p); if (gl.bus[0] == 0) goto USAGE; if (tcgetattr(0,&tio[0]) < 0) { printf("tcgetattr: %s\n",strerror(eno=errno)); return eno; } if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) <= 0) { printf("socket: %s\n",strerror(eno=errno)); return eno; } if (fcntl(sd,F_SETFL,O_NONBLOCK)) { printf("fcntl(O_NONBLOCK): %s\n",strerror(eno=errno)); return eno; } if (bind(sd,&sa.sa,sizeof(sa.sa))) { printf("bind to port %d: %s\n",ntohs(sa.sin_port),strerror(eno=errno)); return eno; } k = 1; setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&k,sizeof(k)); if (strchr(gl.bus,':')) { char msg[128]; if ((bd = open_apsoc(gl.bus,msg)) <= 0) { printf("%s: %s\n",gl.bus,msg); close(sd); return EINVAL; } } else if ((bd = open_aptty(gl.bus)) <= 0) { printf("%s: %s\n",gl.bus,strerror(eno=errno)); close(sd); return eno; } if (!gl.dbug) { if ((k = fork()) < 0) { printf("fork: %s\n",strerror(eno=errno)); close(bd); close(sd); return eno; } if (k > 0) return 0; /* parent, go away */ /* child continues */ setsid(); close(0); close(1); close(2); } else { tio[1] = tio[0]; cfmakeraw(&tio[1]); tcsetattr(0,TCSANOW,&tio[1]); printf("hit to exit...\r\n"); } sigsetup(); eno = bridge(bd,sd); if (gl.dbug) tcsetattr(0,TCSANOW,tio); close(bd); close(sd); return eno; }