Index: head/sbin/bsdlabel/bsdlabel.c =================================================================== --- head/sbin/bsdlabel/bsdlabel.c (revision 229777) +++ head/sbin/bsdlabel/bsdlabel.c (revision 229778) @@ -1,1572 +1,1572 @@ /* * Copyright (c) 1994, 1995 Gordon W. Ross * Copyright (c) 1994 Theo de Raadt * All rights reserved. * Copyright (c) 1987, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Symmetric Computer Systems. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * This product includes software developed by Theo de Raadt. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: $NetBSD: disksubr.c,v 1.13 2000/12/17 22:39:18 pk $ */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1987, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)disklabel.c 8.2 (Berkeley) 1/7/94"; /* from static char sccsid[] = "@(#)disklabel.c 1.2 (Symmetric) 11/28/85"; */ #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #define DKTYPENAMES #define FSTYPENAMES #define MAXPARTITIONS 20 #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" static void makelabel(const char *, struct disklabel *); static int geom_class_available(const char *); static int writelabel(void); static int readlabel(int flag); static void display(FILE *, const struct disklabel *); static int edit(void); static int editit(void); static void fixlabel(struct disklabel *); static char *skip(char *); static char *word(char *); static int getasciilabel(FILE *, struct disklabel *); static int getasciipartspec(char *, struct disklabel *, int, int); static int checklabel(struct disklabel *); static void usage(void); static struct disklabel *getvirginlabel(void); #define DEFEDITOR _PATH_VI #define DEFPARTITIONS 8 static char *specname; static char *pname; static char tmpfil[] = PATH_TMPFILE; static struct disklabel lab; static u_char bootarea[BBSIZE]; static off_t mediasize; static ssize_t secsize; static char blank[] = ""; static char unknown[] = "unknown"; #define MAX_PART ('z') #define MAX_NUM_PARTS (1 + MAX_PART - 'a') static char part_size_type[MAX_NUM_PARTS]; static char part_offset_type[MAX_NUM_PARTS]; static int part_set[MAX_NUM_PARTS]; static int installboot; /* non-zero if we should install a boot program */ static int allfields; /* present all fields in edit */ static char const *xxboot; /* primary boot */ static uint32_t lba_offset; #ifndef LABELSECTOR #define LABELSECTOR -1 #endif #ifndef LABELOFFSET #define LABELOFFSET -1 #endif static int labelsoffset = LABELSECTOR; static int labeloffset = LABELOFFSET; static int bbsize = BBSIZE; static enum { UNSPEC, EDIT, READ, RESTORE, WRITE, WRITEBOOT } op = UNSPEC; static int disable_write; /* set to disable writing to disk label */ static int is_file; /* work on a file (abs. pathname), "-f" opt. */ int main(int argc, char *argv[]) { FILE *t; int ch, error, fd; const char *name; error = 0; name = NULL; while ((ch = getopt(argc, argv, "ABb:efm:nRrw")) != -1) switch (ch) { case 'A': allfields = 1; break; case 'B': ++installboot; break; case 'b': xxboot = optarg; break; case 'f': is_file=1; break; case 'm': if (!strcmp(optarg, "i386") || !strcmp(optarg, "amd64") || !strcmp(optarg, "ia64") || !strcmp(optarg, "pc98")) { labelsoffset = 1; labeloffset = 0; bbsize = 8192; } else { errx(1, "Unsupported architecture"); } break; case 'n': disable_write = 1; break; case 'R': if (op != UNSPEC) usage(); op = RESTORE; break; case 'e': if (op != UNSPEC) usage(); op = EDIT; break; case 'r': /* - * We accept and ignode -r for compatibility with - * historically disklabel usage. + * We accept and ignore -r for compatibility with + * historical disklabel usage. */ break; case 'w': if (op != UNSPEC) usage(); op = WRITE; break; case '?': default: usage(); } argc -= optind; argv += optind; if (argc < 1) usage(); if (labelsoffset < 0 || labeloffset < 0) errx(1, "a -m option must be specified"); /* Figure out the names of the thing we're working on */ if (is_file) { specname = argv[0]; } else { specname = g_device_path(argv[0]); if (specname == NULL) { warn("unable to get correct path for %s", argv[0]); return(1); } fd = open(specname, O_RDONLY); if (fd < 0) { warn("error opening %s", specname); return(1); } pname = g_providername(fd); if (pname == NULL) { warn("error getting providername for %s", specname); close(fd); return(1); } close(fd); } if (installboot && op == UNSPEC) op = WRITEBOOT; else if (op == UNSPEC) op = READ; switch(op) { case UNSPEC: break; case EDIT: if (argc != 1) usage(); readlabel(1); fixlabel(&lab); error = edit(); break; case READ: if (argc != 1) usage(); readlabel(1); display(stdout, NULL); error = checklabel(NULL); break; case RESTORE: if (argc != 2) usage(); if (!(t = fopen(argv[1], "r"))) err(4, "fopen %s", argv[1]); readlabel(0); if (!getasciilabel(t, &lab)) exit(1); error = writelabel(); break; case WRITE: if (argc == 2) name = argv[1]; else if (argc == 1) name = "auto"; else usage(); readlabel(0); makelabel(name, &lab); fixlabel(&lab); if (checklabel(NULL) == 0) error = writelabel(); break; case WRITEBOOT: readlabel(1); fixlabel(&lab); if (argc == 2) makelabel(argv[1], &lab); if (checklabel(NULL) == 0) error = writelabel(); break; } exit(error); } static void fixlabel(struct disklabel *lp) { struct partition *dp; int i; for (i = 0; i < lp->d_npartitions; i++) { if (i == RAW_PART) continue; if (lp->d_partitions[i].p_size) return; } dp = &lp->d_partitions[0]; dp->p_offset = BBSIZE / secsize; dp->p_size = lp->d_secperunit - dp->p_offset; } /* * Construct a prototype disklabel from /etc/disktab. */ static void makelabel(const char *type, struct disklabel *lp) { struct disklabel *dp; if (strcmp(type, "auto") == 0) dp = getvirginlabel(); else dp = getdiskbyname(type); if (dp == NULL) errx(1, "%s: unknown disk type", type); *lp = *dp; bzero(lp->d_packname, sizeof(lp->d_packname)); } static void readboot(void) { int fd; struct stat st; if (xxboot == NULL) xxboot = "/boot/boot"; fd = open(xxboot, O_RDONLY); if (fd < 0) err(1, "cannot open %s", xxboot); fstat(fd, &st); if (st.st_size <= BBSIZE) { if (read(fd, bootarea, st.st_size) != st.st_size) err(1, "read error %s", xxboot); close(fd); return; } errx(1, "boot code %s is wrong size", xxboot); } static int geom_class_available(const char *name) { struct gclass *class; struct gmesh mesh; int error; error = geom_gettree(&mesh); if (error != 0) errc(1, error, "Cannot get GEOM tree"); LIST_FOREACH(class, &mesh.lg_class, lg_class) { if (strcmp(class->lg_name, name) == 0) { geom_deletetree(&mesh); return (1); } } geom_deletetree(&mesh); return (0); } static int writelabel(void) { int i, fd, serrno; struct gctl_req *grq; char const *errstr; struct disklabel *lp = &lab; if (disable_write) { - warnx("write to disk label supressed - label was as follows:"); + warnx("write to disk label suppressed - label was as follows:"); display(stdout, NULL); return (0); } lp->d_magic = DISKMAGIC; lp->d_magic2 = DISKMAGIC; lp->d_checksum = 0; lp->d_checksum = dkcksum(lp); if (installboot) readboot(); for (i = 0; i < lab.d_npartitions; i++) if (lab.d_partitions[i].p_size) lab.d_partitions[i].p_offset += lba_offset; bsd_disklabel_le_enc(bootarea + labeloffset + labelsoffset * lab.d_secsize, lp); fd = open(specname, O_RDWR); if (fd < 0) { if (is_file) { warn("cannot open file %s for writing label", specname); return(1); } else serrno = errno; if (geom_class_available("PART") != 0) { /* * Since we weren't able open provider for * writing, then recommend user to use gpart(8). */ warnc(serrno, "cannot open provider %s for writing label", specname); warnx("Try to use gpart(8)."); return (1); } /* Give up if GEOM_BSD is not available. */ if (geom_class_available("BSD") == 0) { warnc(serrno, "%s", specname); return (1); } grq = gctl_get_handle(); gctl_ro_param(grq, "verb", -1, "write label"); gctl_ro_param(grq, "class", -1, "BSD"); gctl_ro_param(grq, "geom", -1, pname); gctl_ro_param(grq, "label", 148+16*8, bootarea + labeloffset + labelsoffset * lab.d_secsize); errstr = gctl_issue(grq); if (errstr != NULL) { warnx("%s", errstr); gctl_free(grq); return(1); } gctl_free(grq); if (installboot) { grq = gctl_get_handle(); gctl_ro_param(grq, "verb", -1, "write bootcode"); gctl_ro_param(grq, "class", -1, "BSD"); gctl_ro_param(grq, "geom", -1, pname); gctl_ro_param(grq, "bootcode", BBSIZE, bootarea); errstr = gctl_issue(grq); if (errstr != NULL) { warnx("%s", errstr); gctl_free(grq); return (1); } gctl_free(grq); } } else { if (write(fd, bootarea, bbsize) != bbsize) { warn("write %s", specname); close (fd); return (1); } close (fd); } return (0); } static void get_file_parms(int f) { int i; struct stat sb; if (fstat(f, &sb) != 0) err(4, "fstat failed"); i = sb.st_mode & S_IFMT; if (i != S_IFREG && i != S_IFLNK) errx(4, "%s is not a valid file or link", specname); secsize = DEV_BSIZE; mediasize = sb.st_size; } /* * Fetch disklabel for disk. */ static int readlabel(int flag) { ssize_t nbytes; uint32_t lba; int f, i; int error; f = open(specname, O_RDONLY); if (f < 0) err(1, "%s", specname); if (is_file) get_file_parms(f); else { mediasize = g_mediasize(f); secsize = g_sectorsize(f); if (secsize < 0 || mediasize < 0) err(4, "cannot get disk geometry"); } if (mediasize > (off_t)0xffffffff * secsize) errx(1, "disks with more than 2^32-1 sectors are not supported"); (void)lseek(f, (off_t)0, SEEK_SET); nbytes = read(f, bootarea, BBSIZE); if (nbytes == -1) err(4, "%s read", specname); if (nbytes != BBSIZE) errx(4, "couldn't read %d bytes from %s", BBSIZE, specname); close (f); error = bsd_disklabel_le_dec( bootarea + (labeloffset + labelsoffset * secsize), &lab, MAXPARTITIONS); if (flag && error) errx(1, "%s: no valid label found", specname); if (is_file) return(0); /* * Compensate for absolute block addressing by finding the * smallest partition offset and if the offset of the 'c' * partition is equal to that, subtract it from all offsets. */ lba = ~0; for (i = 0; i < lab.d_npartitions; i++) { if (lab.d_partitions[i].p_size) lba = MIN(lba, lab.d_partitions[i].p_offset); } if (lba != 0 && lab.d_partitions[RAW_PART].p_offset == lba) { for (i = 0; i < lab.d_npartitions; i++) { if (lab.d_partitions[i].p_size) lab.d_partitions[i].p_offset -= lba; } /* * Save the offset so that we can write the label * back with absolute block addresses. */ lba_offset = lba; } return (error); } static void display(FILE *f, const struct disklabel *lp) { int i, j; const struct partition *pp; if (lp == NULL) lp = &lab; fprintf(f, "# %s:\n", specname); if (allfields) { if (lp->d_type < DKMAXTYPES) fprintf(f, "type: %s\n", dktypenames[lp->d_type]); else fprintf(f, "type: %u\n", lp->d_type); fprintf(f, "disk: %.*s\n", (int)sizeof(lp->d_typename), lp->d_typename); fprintf(f, "label: %.*s\n", (int)sizeof(lp->d_packname), lp->d_packname); fprintf(f, "flags:"); if (lp->d_flags & D_REMOVABLE) fprintf(f, " removeable"); if (lp->d_flags & D_ECC) fprintf(f, " ecc"); if (lp->d_flags & D_BADSECT) fprintf(f, " badsect"); fprintf(f, "\n"); fprintf(f, "bytes/sector: %lu\n", (u_long)lp->d_secsize); fprintf(f, "sectors/track: %lu\n", (u_long)lp->d_nsectors); fprintf(f, "tracks/cylinder: %lu\n", (u_long)lp->d_ntracks); fprintf(f, "sectors/cylinder: %lu\n", (u_long)lp->d_secpercyl); fprintf(f, "cylinders: %lu\n", (u_long)lp->d_ncylinders); fprintf(f, "sectors/unit: %lu\n", (u_long)lp->d_secperunit); fprintf(f, "rpm: %u\n", lp->d_rpm); fprintf(f, "interleave: %u\n", lp->d_interleave); fprintf(f, "trackskew: %u\n", lp->d_trackskew); fprintf(f, "cylinderskew: %u\n", lp->d_cylskew); fprintf(f, "headswitch: %lu\t\t# milliseconds\n", (u_long)lp->d_headswitch); fprintf(f, "track-to-track seek: %ld\t# milliseconds\n", (u_long)lp->d_trkseek); fprintf(f, "drivedata: "); for (i = NDDATA - 1; i >= 0; i--) if (lp->d_drivedata[i]) break; if (i < 0) i = 0; for (j = 0; j <= i; j++) fprintf(f, "%lu ", (u_long)lp->d_drivedata[j]); fprintf(f, "\n\n"); } fprintf(f, "%u partitions:\n", lp->d_npartitions); fprintf(f, "# size offset fstype [fsize bsize bps/cpg]\n"); pp = lp->d_partitions; for (i = 0; i < lp->d_npartitions; i++, pp++) { if (pp->p_size) { fprintf(f, " %c: %10lu %10lu ", 'a' + i, (u_long)pp->p_size, (u_long)pp->p_offset); if (pp->p_fstype < FSMAXTYPES) fprintf(f, "%8.8s", fstypenames[pp->p_fstype]); else fprintf(f, "%8d", pp->p_fstype); switch (pp->p_fstype) { case FS_UNUSED: /* XXX */ fprintf(f, " %5lu %5lu %2s", (u_long)pp->p_fsize, (u_long)(pp->p_fsize * pp->p_frag), ""); break; case FS_BSDFFS: fprintf(f, " %5lu %5lu %5u", (u_long)pp->p_fsize, (u_long)(pp->p_fsize * pp->p_frag), pp->p_cpg); break; case FS_BSDLFS: fprintf(f, " %5lu %5lu %5d", (u_long)pp->p_fsize, (u_long)(pp->p_fsize * pp->p_frag), pp->p_cpg); break; default: fprintf(f, "%20.20s", ""); break; } if (i == RAW_PART) { fprintf(f, " # \"raw\" part, don't edit"); } fprintf(f, "\n"); } } fflush(f); } static int edit(void) { int c, fd; struct disklabel label; FILE *fp; if ((fd = mkstemp(tmpfil)) == -1 || (fp = fdopen(fd, "w")) == NULL) { warnx("can't create %s", tmpfil); return (1); } display(fp, NULL); fclose(fp); for (;;) { if (!editit()) break; fp = fopen(tmpfil, "r"); if (fp == NULL) { warnx("can't reopen %s for reading", tmpfil); break; } bzero((char *)&label, sizeof(label)); c = getasciilabel(fp, &label); fclose(fp); if (c) { lab = label; if (writelabel() == 0) { (void) unlink(tmpfil); return (0); } } printf("re-edit the label? [y]: "); fflush(stdout); c = getchar(); if (c != EOF && c != (int)'\n') while (getchar() != (int)'\n') ; if (c == (int)'n') break; } (void) unlink(tmpfil); return (1); } static int editit(void) { int pid, xpid; int locstat, omask; const char *ed; uid_t uid; gid_t gid; omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); while ((pid = fork()) < 0) { if (errno == EPROCLIM) { warnx("you have too many processes"); return(0); } if (errno != EAGAIN) { warn("fork"); return(0); } sleep(1); } if (pid == 0) { sigsetmask(omask); gid = getgid(); if (setresgid(gid, gid, gid) == -1) err(1, "setresgid"); uid = getuid(); if (setresuid(uid, uid, uid) == -1) err(1, "setresuid"); if ((ed = getenv("EDITOR")) == (char *)0) ed = DEFEDITOR; execlp(ed, ed, tmpfil, (char *)0); err(1, "%s", ed); } while ((xpid = wait(&locstat)) >= 0) if (xpid == pid) break; sigsetmask(omask); return(!locstat); } static char * skip(char *cp) { while (*cp != '\0' && isspace(*cp)) cp++; if (*cp == '\0' || *cp == '#') return (NULL); return (cp); } static char * word(char *cp) { char c; while (*cp != '\0' && !isspace(*cp) && *cp != '#') cp++; if ((c = *cp) != '\0') { *cp++ = '\0'; if (c != '#') return (skip(cp)); } return (NULL); } /* * Read an ascii label in from fd f, * in the same format as that put out by display(), * and fill in lp. */ static int getasciilabel(FILE *f, struct disklabel *lp) { char *cp, *endp; const char **cpp; u_int part; char *tp, line[BUFSIZ]; u_long v; int lineno = 0, errors = 0; int i; makelabel("auto", lp); bzero(&part_set, sizeof(part_set)); bzero(&part_size_type, sizeof(part_size_type)); bzero(&part_offset_type, sizeof(part_offset_type)); lp->d_bbsize = BBSIZE; /* XXX */ lp->d_sbsize = 0; /* XXX */ while (fgets(line, sizeof(line) - 1, f)) { lineno++; if ((cp = strchr(line,'\n')) != 0) *cp = '\0'; cp = skip(line); if (cp == NULL) continue; tp = strchr(cp, ':'); if (tp == NULL) { fprintf(stderr, "line %d: syntax error\n", lineno); errors++; continue; } *tp++ = '\0', tp = skip(tp); if (!strcmp(cp, "type")) { if (tp == NULL) tp = unknown; cpp = dktypenames; for (; cpp < &dktypenames[DKMAXTYPES]; cpp++) if (*cpp && !strcmp(*cpp, tp)) { lp->d_type = cpp - dktypenames; break; } if (cpp < &dktypenames[DKMAXTYPES]) continue; errno = 0; v = strtoul(tp, &endp, 10); if (errno != 0 || *endp != '\0') v = DKMAXTYPES; if (v >= DKMAXTYPES) fprintf(stderr, "line %d:%s %lu\n", lineno, "Warning, unknown disk type", v); else lp->d_type = v; continue; } if (!strcmp(cp, "flags")) { for (v = 0; (cp = tp) && *cp != '\0';) { tp = word(cp); if (!strcmp(cp, "removeable")) v |= D_REMOVABLE; else if (!strcmp(cp, "ecc")) v |= D_ECC; else if (!strcmp(cp, "badsect")) v |= D_BADSECT; else { fprintf(stderr, "line %d: %s: bad flag\n", lineno, cp); errors++; } } lp->d_flags = v; continue; } if (!strcmp(cp, "drivedata")) { for (i = 0; (cp = tp) && *cp != '\0' && i < NDDATA;) { lp->d_drivedata[i++] = strtoul(cp, NULL, 10); tp = word(cp); } continue; } if (sscanf(cp, "%lu partitions", &v) == 1) { if (v > MAXPARTITIONS) { fprintf(stderr, "line %d: bad # of partitions\n", lineno); lp->d_npartitions = MAXPARTITIONS; errors++; } else if (v < DEFPARTITIONS) { fprintf(stderr, "line %d: bad # of partitions\n", lineno); lp->d_npartitions = DEFPARTITIONS; errors++; } else lp->d_npartitions = v; continue; } if (tp == NULL) tp = blank; if (!strcmp(cp, "disk")) { strncpy(lp->d_typename, tp, sizeof (lp->d_typename)); continue; } if (!strcmp(cp, "label")) { strncpy(lp->d_packname, tp, sizeof (lp->d_packname)); continue; } if (!strcmp(cp, "bytes/sector")) { v = strtoul(tp, NULL, 10); if (v == 0 || (v % DEV_BSIZE) != 0) { fprintf(stderr, "line %d: %s: bad sector size\n", lineno, tp); errors++; } else lp->d_secsize = v; continue; } if (!strcmp(cp, "sectors/track")) { v = strtoul(tp, NULL, 10); #if (ULONG_MAX != 0xffffffffUL) if (v == 0 || v > 0xffffffff) #else if (v == 0) #endif { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_nsectors = v; continue; } if (!strcmp(cp, "sectors/cylinder")) { v = strtoul(tp, NULL, 10); if (v == 0) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_secpercyl = v; continue; } if (!strcmp(cp, "tracks/cylinder")) { v = strtoul(tp, NULL, 10); if (v == 0) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_ntracks = v; continue; } if (!strcmp(cp, "cylinders")) { v = strtoul(tp, NULL, 10); if (v == 0) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_ncylinders = v; continue; } if (!strcmp(cp, "sectors/unit")) { v = strtoul(tp, NULL, 10); if (v == 0) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_secperunit = v; continue; } if (!strcmp(cp, "rpm")) { v = strtoul(tp, NULL, 10); if (v == 0 || v > USHRT_MAX) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_rpm = v; continue; } if (!strcmp(cp, "interleave")) { v = strtoul(tp, NULL, 10); if (v == 0 || v > USHRT_MAX) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_interleave = v; continue; } if (!strcmp(cp, "trackskew")) { v = strtoul(tp, NULL, 10); if (v > USHRT_MAX) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_trackskew = v; continue; } if (!strcmp(cp, "cylinderskew")) { v = strtoul(tp, NULL, 10); if (v > USHRT_MAX) { fprintf(stderr, "line %d: %s: bad %s\n", lineno, tp, cp); errors++; } else lp->d_cylskew = v; continue; } if (!strcmp(cp, "headswitch")) { v = strtoul(tp, NULL, 10); lp->d_headswitch = v; continue; } if (!strcmp(cp, "track-to-track seek")) { v = strtoul(tp, NULL, 10); lp->d_trkseek = v; continue; } /* the ':' was removed above */ if (*cp < 'a' || *cp > MAX_PART || cp[1] != '\0') { fprintf(stderr, "line %d: %s: Unknown disklabel field\n", lineno, cp); errors++; continue; } /* Process a partition specification line. */ part = *cp - 'a'; if (part >= lp->d_npartitions) { fprintf(stderr, "line %d: partition name out of range a-%c: %s\n", lineno, 'a' + lp->d_npartitions - 1, cp); errors++; continue; } part_set[part] = 1; if (getasciipartspec(tp, lp, part, lineno) != 0) { errors++; break; } } errors += checklabel(lp); return (errors == 0); } #define NXTNUM(n) do { \ if (tp == NULL) { \ fprintf(stderr, "line %d: too few numeric fields\n", lineno); \ return (1); \ } else { \ cp = tp, tp = word(cp); \ (n) = strtoul(cp, NULL, 10); \ } \ } while (0) /* retain 1 character following number */ #define NXTWORD(w,n) do { \ if (tp == NULL) { \ fprintf(stderr, "line %d: too few numeric fields\n", lineno); \ return (1); \ } else { \ char *tmp; \ cp = tp, tp = word(cp); \ (n) = strtoul(cp, &tmp, 10); \ if (tmp) (w) = *tmp; \ } \ } while (0) /* * Read a partition line into partition `part' in the specified disklabel. * Return 0 on success, 1 on failure. */ static int getasciipartspec(char *tp, struct disklabel *lp, int part, int lineno) { struct partition *pp; char *cp, *endp; const char **cpp; u_long v; pp = &lp->d_partitions[part]; cp = NULL; v = 0; NXTWORD(part_size_type[part],v); if (v == 0 && part_size_type[part] != '*') { fprintf(stderr, "line %d: %s: bad partition size\n", lineno, cp); return (1); } pp->p_size = v; v = 0; NXTWORD(part_offset_type[part],v); if (v == 0 && part_offset_type[part] != '*' && part_offset_type[part] != '\0') { fprintf(stderr, "line %d: %s: bad partition offset\n", lineno, cp); return (1); } pp->p_offset = v; if (tp == NULL) { fprintf(stderr, "line %d: missing file system type\n", lineno); return (1); } cp = tp, tp = word(cp); for (cpp = fstypenames; cpp < &fstypenames[FSMAXTYPES]; cpp++) if (*cpp && !strcmp(*cpp, cp)) break; if (*cpp != NULL) { pp->p_fstype = cpp - fstypenames; } else { if (isdigit(*cp)) { errno = 0; v = strtoul(cp, &endp, 10); if (errno != 0 || *endp != '\0') v = FSMAXTYPES; } else v = FSMAXTYPES; if (v >= FSMAXTYPES) { fprintf(stderr, "line %d: Warning, unknown file system type %s\n", lineno, cp); v = FS_UNUSED; } pp->p_fstype = v; } switch (pp->p_fstype) { case FS_UNUSED: case FS_BSDFFS: case FS_BSDLFS: /* accept defaults for fsize/frag/cpg */ if (tp) { NXTNUM(pp->p_fsize); if (pp->p_fsize == 0) break; NXTNUM(v); pp->p_frag = v / pp->p_fsize; if (tp != NULL) NXTNUM(pp->p_cpg); } /* else default to 0's */ break; default: break; } return (0); } /* * Check disklabel for errors and fill in * derived fields according to supplied values. */ static int checklabel(struct disklabel *lp) { struct partition *pp; int i, errors = 0; char part; u_long base_offset, needed, total_size, total_percent, current_offset; long free_space; int seen_default_offset; int hog_part; int j; struct partition *pp2; if (lp == NULL) lp = &lab; if (allfields) { if (lp->d_secsize == 0) { fprintf(stderr, "sector size 0\n"); return (1); } if (lp->d_nsectors == 0) { fprintf(stderr, "sectors/track 0\n"); return (1); } if (lp->d_ntracks == 0) { fprintf(stderr, "tracks/cylinder 0\n"); return (1); } if (lp->d_ncylinders == 0) { fprintf(stderr, "cylinders/unit 0\n"); errors++; } if (lp->d_rpm == 0) warnx("revolutions/minute 0"); if (lp->d_secpercyl == 0) lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks; if (lp->d_secperunit == 0) lp->d_secperunit = lp->d_secpercyl * lp->d_ncylinders; if (lp->d_bbsize == 0) { fprintf(stderr, "boot block size 0\n"); errors++; } else if (lp->d_bbsize % lp->d_secsize) warnx("boot block size %% sector-size != 0"); if (lp->d_npartitions > MAXPARTITIONS) { warnx("number of partitions (%lu) > MAXPARTITIONS (%d)", (u_long)lp->d_npartitions, MAXPARTITIONS); errors++; } if (lp->d_npartitions < DEFPARTITIONS) { warnx("number of partitions (%lu) < DEFPARTITIONS (%d)", (u_long)lp->d_npartitions, DEFPARTITIONS); errors++; } } else { struct disklabel *vl; vl = getvirginlabel(); if (lp->d_secsize == 0) lp->d_secsize = vl->d_secsize; if (lp->d_nsectors == 0) lp->d_nsectors = vl->d_nsectors; if (lp->d_ntracks == 0) lp->d_ntracks = vl->d_ntracks; if (lp->d_ncylinders == 0) lp->d_ncylinders = vl->d_ncylinders; if (lp->d_rpm == 0) lp->d_rpm = vl->d_rpm; if (lp->d_interleave == 0) lp->d_interleave = vl->d_interleave; if (lp->d_secpercyl == 0) lp->d_secpercyl = vl->d_secpercyl; if (lp->d_secperunit == 0) lp->d_secperunit = vl->d_secperunit; if (lp->d_bbsize == 0) lp->d_bbsize = vl->d_bbsize; if (lp->d_npartitions < DEFPARTITIONS || lp->d_npartitions > MAXPARTITIONS) lp->d_npartitions = vl->d_npartitions; } /* first allocate space to the partitions, then offsets */ total_size = 0; /* in sectors */ total_percent = 0; /* in percent */ hog_part = -1; /* find all fixed partitions */ for (i = 0; i < lp->d_npartitions; i++) { pp = &lp->d_partitions[i]; if (part_set[i]) { if (part_size_type[i] == '*') { if (i == RAW_PART) { pp->p_size = lp->d_secperunit; } else { if (hog_part != -1) warnx("Too many '*' partitions (%c and %c)", hog_part + 'a',i + 'a'); else hog_part = i; } } else { off_t size; size = pp->p_size; switch (part_size_type[i]) { case '%': total_percent += size; break; case 't': case 'T': size *= 1024ULL; /* FALLTHROUGH */ case 'g': case 'G': size *= 1024ULL; /* FALLTHROUGH */ case 'm': case 'M': size *= 1024ULL; /* FALLTHROUGH */ case 'k': case 'K': size *= 1024ULL; break; case '\0': break; default: warnx("unknown multiplier suffix '%c' for partition %c (should be K, M, G or T)", part_size_type[i], i + 'a'); break; } /* don't count %'s yet */ if (part_size_type[i] != '%') { /* * for all not in sectors, convert to * sectors */ if (part_size_type[i] != '\0') { if (size % lp->d_secsize != 0) warnx("partition %c not an integer number of sectors", i + 'a'); size /= lp->d_secsize; pp->p_size = size; } /* else already in sectors */ if (i != RAW_PART) total_size += size; } } } } /* Find out the total free space, excluding the boot block area. */ base_offset = BBSIZE / secsize; free_space = 0; for (i = 0; i < lp->d_npartitions; i++) { pp = &lp->d_partitions[i]; if (!part_set[i] || i == RAW_PART || part_size_type[i] == '%' || part_size_type[i] == '*') continue; if (pp->p_offset > base_offset) free_space += pp->p_offset - base_offset; if (pp->p_offset + pp->p_size > base_offset) base_offset = pp->p_offset + pp->p_size; } if (base_offset < lp->d_secperunit) free_space += lp->d_secperunit - base_offset; /* handle % partitions - note %'s don't need to add up to 100! */ if (total_percent != 0) { if (total_percent > 100) { fprintf(stderr,"total percentage %lu is greater than 100\n", total_percent); errors++; } if (free_space > 0) { for (i = 0; i < lp->d_npartitions; i++) { pp = &lp->d_partitions[i]; if (part_set[i] && part_size_type[i] == '%') { /* careful of overflows! and integer roundoff */ pp->p_size = ((double)pp->p_size/100) * free_space; total_size += pp->p_size; /* FIX we can lose a sector or so due to roundoff per partition. A more complex algorithm could avoid that */ } } } else { fprintf(stderr, "%ld sectors available to give to '*' and '%%' partitions\n", free_space); errors++; /* fix? set all % partitions to size 0? */ } } /* give anything remaining to the hog partition */ if (hog_part != -1) { /* * Find the range of offsets usable by '*' partitions around * the hog partition and how much space they need. */ needed = 0; base_offset = BBSIZE / secsize; for (i = hog_part - 1; i >= 0; i--) { pp = &lp->d_partitions[i]; if (!part_set[i] || i == RAW_PART) continue; if (part_offset_type[i] == '*') { needed += pp->p_size; continue; } base_offset = pp->p_offset + pp->p_size; break; } current_offset = lp->d_secperunit; for (i = lp->d_npartitions - 1; i > hog_part; i--) { pp = &lp->d_partitions[i]; if (!part_set[i] || i == RAW_PART) continue; if (part_offset_type[i] == '*') { needed += pp->p_size; continue; } current_offset = pp->p_offset; } if (current_offset - base_offset <= needed) { fprintf(stderr, "Cannot find space for partition %c\n", hog_part + 'a'); fprintf(stderr, "Need more than %lu sectors between %lu and %lu\n", needed, base_offset, current_offset); errors++; lp->d_partitions[hog_part].p_size = 0; } else { lp->d_partitions[hog_part].p_size = current_offset - base_offset - needed; total_size += lp->d_partitions[hog_part].p_size; } } /* Now set the offsets for each partition */ current_offset = BBSIZE / secsize; /* in sectors */ seen_default_offset = 0; for (i = 0; i < lp->d_npartitions; i++) { part = 'a' + i; pp = &lp->d_partitions[i]; if (part_set[i]) { if (part_offset_type[i] == '*') { if (i == RAW_PART) { pp->p_offset = 0; } else { pp->p_offset = current_offset; seen_default_offset = 1; } } else { /* allow them to be out of order for old-style tables */ if (pp->p_offset < current_offset && seen_default_offset && i != RAW_PART && pp->p_fstype != FS_VINUM) { fprintf(stderr, "Offset %ld for partition %c overlaps previous partition which ends at %lu\n", (long)pp->p_offset,i+'a',current_offset); fprintf(stderr, "Labels with any *'s for offset must be in ascending order by sector\n"); errors++; } else if (pp->p_offset != current_offset && i != RAW_PART && seen_default_offset) { /* * this may give unneeded warnings if * partitions are out-of-order */ warnx( "Offset %ld for partition %c doesn't match expected value %ld", (long)pp->p_offset, i + 'a', current_offset); } } if (i != RAW_PART) current_offset = pp->p_offset + pp->p_size; } } for (i = 0; i < lp->d_npartitions; i++) { part = 'a' + i; pp = &lp->d_partitions[i]; if (pp->p_size == 0 && pp->p_offset != 0) warnx("partition %c: size 0, but offset %lu", part, (u_long)pp->p_offset); #ifdef notdef if (pp->p_size % lp->d_secpercyl) warnx("partition %c: size %% cylinder-size != 0", part); if (pp->p_offset % lp->d_secpercyl) warnx("partition %c: offset %% cylinder-size != 0", part); #endif if (pp->p_offset > lp->d_secperunit) { fprintf(stderr, "partition %c: offset past end of unit\n", part); errors++; } if (pp->p_offset + pp->p_size > lp->d_secperunit) { fprintf(stderr, "partition %c: partition extends past end of unit\n", part); errors++; } if (i == RAW_PART) { if (pp->p_fstype != FS_UNUSED) warnx("partition %c is not marked as unused!",part); if (pp->p_offset != 0) warnx("partition %c doesn't start at 0!",part); if (pp->p_size != lp->d_secperunit) warnx("partition %c doesn't cover the whole unit!",part); if ((pp->p_fstype != FS_UNUSED) || (pp->p_offset != 0) || (pp->p_size != lp->d_secperunit)) { warnx("An incorrect partition %c may cause problems for " "standard system utilities",part); } } /* check for overlaps */ /* this will check for all possible overlaps once and only once */ for (j = 0; j < i; j++) { pp2 = &lp->d_partitions[j]; if (j != RAW_PART && i != RAW_PART && pp->p_fstype != FS_VINUM && pp2->p_fstype != FS_VINUM && part_set[i] && part_set[j]) { if (pp2->p_offset < pp->p_offset + pp->p_size && (pp2->p_offset + pp2->p_size > pp->p_offset || pp2->p_offset >= pp->p_offset)) { fprintf(stderr,"partitions %c and %c overlap!\n", j + 'a', i + 'a'); errors++; } } } } for (; i < lp->d_npartitions; i++) { part = 'a' + i; pp = &lp->d_partitions[i]; if (pp->p_size || pp->p_offset) warnx("unused partition %c: size %d offset %lu", 'a' + i, pp->p_size, (u_long)pp->p_offset); } return (errors); } /* * When operating on a "virgin" disk, try getting an initial label * from the associated device driver. This might work for all device * drivers that are able to fetch some initial device parameters * without even having access to a (BSD) disklabel, like SCSI disks, * most IDE drives, or vn devices. * * The device name must be given in its "canonical" form. */ static struct disklabel * getvirginlabel(void) { static struct disklabel loclab; struct partition *dp; int f; u_int u; if ((f = open(specname, O_RDONLY)) == -1) { warn("cannot open %s", specname); return (NULL); } if (is_file) get_file_parms(f); else { mediasize = g_mediasize(f); secsize = g_sectorsize(f); if (secsize < 0 || mediasize < 0) { close (f); return (NULL); } } memset(&loclab, 0, sizeof loclab); loclab.d_magic = DISKMAGIC; loclab.d_magic2 = DISKMAGIC; loclab.d_secsize = secsize; loclab.d_secperunit = mediasize / secsize; /* - * Nobody in these enligthened days uses the CHS geometry for - * anything, but nontheless try to get it right. If we fail + * Nobody in these enlightened days uses the CHS geometry for + * anything, but nonetheless try to get it right. If we fail * to get any good ideas from the device, construct something * which is IBM-PC friendly. */ if (ioctl(f, DIOCGFWSECTORS, &u) == 0) loclab.d_nsectors = u; else loclab.d_nsectors = 63; if (ioctl(f, DIOCGFWHEADS, &u) == 0) loclab.d_ntracks = u; else if (loclab.d_secperunit <= 63*1*1024) loclab.d_ntracks = 1; else if (loclab.d_secperunit <= 63*16*1024) loclab.d_ntracks = 16; else loclab.d_ntracks = 255; loclab.d_secpercyl = loclab.d_ntracks * loclab.d_nsectors; loclab.d_ncylinders = loclab.d_secperunit / loclab.d_secpercyl; loclab.d_npartitions = DEFPARTITIONS; /* Various (unneeded) compat stuff */ loclab.d_rpm = 3600; loclab.d_bbsize = BBSIZE; loclab.d_interleave = 1; strncpy(loclab.d_typename, "amnesiac", sizeof(loclab.d_typename)); dp = &loclab.d_partitions[RAW_PART]; dp->p_size = loclab.d_secperunit; loclab.d_checksum = dkcksum(&loclab); close (f); return (&loclab); } static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", "usage: bsdlabel disk", "\t\t(to read label)", " bsdlabel -w [-n] [-m machine] disk [type]", "\t\t(to write label with existing boot program)", " bsdlabel -e [-n] [-m machine] disk", "\t\t(to edit label)", " bsdlabel -R [-n] [-m machine] disk protofile", "\t\t(to restore label with existing boot program)", " bsdlabel -B [-b boot] [-m machine] disk", "\t\t(to install boot program with existing on-disk label)", " bsdlabel -w -B [-n] [-b boot] [-m machine] disk [type]", "\t\t(to write label and install boot program)", " bsdlabel -R -B [-n] [-b boot] [-m machine] disk protofile", "\t\t(to restore label and install boot program)" ); exit(1); } Index: head/sbin/camcontrol/camcontrol.c =================================================================== --- head/sbin/camcontrol/camcontrol.c (revision 229777) +++ head/sbin/camcontrol/camcontrol.c (revision 229778) @@ -1,6143 +1,6143 @@ /* * Copyright (c) 1997-2007 Kenneth D. Merry * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "camcontrol.h" typedef enum { CAM_CMD_NONE = 0x00000000, CAM_CMD_DEVLIST = 0x00000001, CAM_CMD_TUR = 0x00000002, CAM_CMD_INQUIRY = 0x00000003, CAM_CMD_STARTSTOP = 0x00000004, CAM_CMD_RESCAN = 0x00000005, CAM_CMD_READ_DEFECTS = 0x00000006, CAM_CMD_MODE_PAGE = 0x00000007, CAM_CMD_SCSI_CMD = 0x00000008, CAM_CMD_DEVTREE = 0x00000009, CAM_CMD_USAGE = 0x0000000a, CAM_CMD_DEBUG = 0x0000000b, CAM_CMD_RESET = 0x0000000c, CAM_CMD_FORMAT = 0x0000000d, CAM_CMD_TAG = 0x0000000e, CAM_CMD_RATE = 0x0000000f, CAM_CMD_DETACH = 0x00000010, CAM_CMD_REPORTLUNS = 0x00000011, CAM_CMD_READCAP = 0x00000012, CAM_CMD_IDENTIFY = 0x00000013, CAM_CMD_IDLE = 0x00000014, CAM_CMD_STANDBY = 0x00000015, CAM_CMD_SLEEP = 0x00000016, CAM_CMD_SMP_CMD = 0x00000017, CAM_CMD_SMP_RG = 0x00000018, CAM_CMD_SMP_PC = 0x00000019, CAM_CMD_SMP_PHYLIST = 0x0000001a, CAM_CMD_SMP_MANINFO = 0x0000001b, CAM_CMD_DOWNLOAD_FW = 0x0000001c } cam_cmdmask; typedef enum { CAM_ARG_NONE = 0x00000000, CAM_ARG_VERBOSE = 0x00000001, CAM_ARG_DEVICE = 0x00000002, CAM_ARG_BUS = 0x00000004, CAM_ARG_TARGET = 0x00000008, CAM_ARG_LUN = 0x00000010, CAM_ARG_EJECT = 0x00000020, CAM_ARG_UNIT = 0x00000040, CAM_ARG_FORMAT_BLOCK = 0x00000080, CAM_ARG_FORMAT_BFI = 0x00000100, CAM_ARG_FORMAT_PHYS = 0x00000200, CAM_ARG_PLIST = 0x00000400, CAM_ARG_GLIST = 0x00000800, CAM_ARG_GET_SERIAL = 0x00001000, CAM_ARG_GET_STDINQ = 0x00002000, CAM_ARG_GET_XFERRATE = 0x00004000, CAM_ARG_INQ_MASK = 0x00007000, CAM_ARG_MODE_EDIT = 0x00008000, CAM_ARG_PAGE_CNTL = 0x00010000, CAM_ARG_TIMEOUT = 0x00020000, CAM_ARG_CMD_IN = 0x00040000, CAM_ARG_CMD_OUT = 0x00080000, CAM_ARG_DBD = 0x00100000, CAM_ARG_ERR_RECOVER = 0x00200000, CAM_ARG_RETRIES = 0x00400000, CAM_ARG_START_UNIT = 0x00800000, CAM_ARG_DEBUG_INFO = 0x01000000, CAM_ARG_DEBUG_TRACE = 0x02000000, CAM_ARG_DEBUG_SUBTRACE = 0x04000000, CAM_ARG_DEBUG_CDB = 0x08000000, CAM_ARG_DEBUG_XPT = 0x10000000, CAM_ARG_DEBUG_PERIPH = 0x20000000, } cam_argmask; struct camcontrol_opts { const char *optname; uint32_t cmdnum; cam_argmask argnum; const char *subopt; }; #ifndef MINIMALISTIC static const char scsicmd_opts[] = "a:c:dfi:o:r"; static const char readdefect_opts[] = "f:GP"; static const char negotiate_opts[] = "acD:M:O:qR:T:UW:"; static const char smprg_opts[] = "l"; static const char smppc_opts[] = "a:A:d:lm:M:o:p:s:S:T:"; static const char smpphylist_opts[] = "lq"; #endif static struct camcontrol_opts option_table[] = { #ifndef MINIMALISTIC {"tur", CAM_CMD_TUR, CAM_ARG_NONE, NULL}, {"inquiry", CAM_CMD_INQUIRY, CAM_ARG_NONE, "DSR"}, {"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, NULL}, {"start", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT, NULL}, {"stop", CAM_CMD_STARTSTOP, CAM_ARG_NONE, NULL}, {"load", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT | CAM_ARG_EJECT, NULL}, {"eject", CAM_CMD_STARTSTOP, CAM_ARG_EJECT, NULL}, {"reportluns", CAM_CMD_REPORTLUNS, CAM_ARG_NONE, "clr:"}, {"readcapacity", CAM_CMD_READCAP, CAM_ARG_NONE, "bhHNqs"}, #endif /* MINIMALISTIC */ {"rescan", CAM_CMD_RESCAN, CAM_ARG_NONE, NULL}, {"reset", CAM_CMD_RESET, CAM_ARG_NONE, NULL}, #ifndef MINIMALISTIC {"cmd", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts}, {"command", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts}, {"smpcmd", CAM_CMD_SMP_CMD, CAM_ARG_NONE, "r:R:"}, {"smprg", CAM_CMD_SMP_RG, CAM_ARG_NONE, smprg_opts}, {"smpreportgeneral", CAM_CMD_SMP_RG, CAM_ARG_NONE, smprg_opts}, {"smppc", CAM_CMD_SMP_PC, CAM_ARG_NONE, smppc_opts}, {"smpphycontrol", CAM_CMD_SMP_PC, CAM_ARG_NONE, smppc_opts}, {"smpplist", CAM_CMD_SMP_PHYLIST, CAM_ARG_NONE, smpphylist_opts}, {"smpphylist", CAM_CMD_SMP_PHYLIST, CAM_ARG_NONE, smpphylist_opts}, {"smpmaninfo", CAM_CMD_SMP_MANINFO, CAM_ARG_NONE, "l"}, {"defects", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts}, {"defectlist", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts}, #endif /* MINIMALISTIC */ {"devlist", CAM_CMD_DEVTREE, CAM_ARG_NONE, NULL}, #ifndef MINIMALISTIC {"periphlist", CAM_CMD_DEVLIST, CAM_ARG_NONE, NULL}, {"modepage", CAM_CMD_MODE_PAGE, CAM_ARG_NONE, "bdelm:P:"}, {"tags", CAM_CMD_TAG, CAM_ARG_NONE, "N:q"}, {"negotiate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts}, {"rate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts}, {"debug", CAM_CMD_DEBUG, CAM_ARG_NONE, "IPTSXc"}, {"format", CAM_CMD_FORMAT, CAM_ARG_NONE, "qrwy"}, {"idle", CAM_CMD_IDLE, CAM_ARG_NONE, "t:"}, {"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:"}, {"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""}, {"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:ys"}, #endif /* MINIMALISTIC */ {"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {"-h", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {NULL, 0, 0, NULL} }; typedef enum { CC_OR_NOT_FOUND, CC_OR_AMBIGUOUS, CC_OR_FOUND } camcontrol_optret; struct cam_devitem { struct device_match_result dev_match; int num_periphs; struct periph_match_result *periph_matches; struct scsi_vpd_device_id *device_id; int device_id_len; STAILQ_ENTRY(cam_devitem) links; }; struct cam_devlist { STAILQ_HEAD(, cam_devitem) dev_queue; path_id_t path_id; }; static cam_cmdmask cmdlist; static cam_argmask arglist; camcontrol_optret getoption(struct camcontrol_opts *table, char *arg, uint32_t *cmdnum, cam_argmask *argnum, const char **subopt); #ifndef MINIMALISTIC static int getdevlist(struct cam_device *device); #endif /* MINIMALISTIC */ static int getdevtree(void); #ifndef MINIMALISTIC static int testunitready(struct cam_device *device, int retry_count, int timeout, int quiet); static int scsistart(struct cam_device *device, int startstop, int loadeject, int retry_count, int timeout); static int scsiinquiry(struct cam_device *device, int retry_count, int timeout); static int scsiserial(struct cam_device *device, int retry_count, int timeout); static int camxferrate(struct cam_device *device); #endif /* MINIMALISTIC */ static int parse_btl(char *tstr, int *bus, int *target, int *lun, cam_argmask *arglst); static int dorescan_or_reset(int argc, char **argv, int rescan); static int rescan_or_reset_bus(int bus, int rescan); static int scanlun_or_reset_dev(int bus, int target, int lun, int scan); #ifndef MINIMALISTIC static int readdefects(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static void modepage(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int scsicmd(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int smpcmd(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int smpreportgeneral(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int smpphycontrol(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int smpmaninfo(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int getdevid(struct cam_devitem *item); static int buildbusdevlist(struct cam_devlist *devlist); static void freebusdevlist(struct cam_devlist *devlist); static struct cam_devitem *findsasdevice(struct cam_devlist *devlist, uint64_t sasaddr); static int smpphylist(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int tagcontrol(struct cam_device *device, int argc, char **argv, char *combinedopt); static void cts_print(struct cam_device *device, struct ccb_trans_settings *cts); static void cpi_print(struct ccb_pathinq *cpi); static int get_cpi(struct cam_device *device, struct ccb_pathinq *cpi); static int get_cgd(struct cam_device *device, struct ccb_getdev *cgd); static int get_print_cts(struct cam_device *device, int user_settings, int quiet, struct ccb_trans_settings *cts); static int ratecontrol(struct cam_device *device, int retry_count, int timeout, int argc, char **argv, char *combinedopt); static int scsiformat(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int scsireportluns(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int scsireadcapacity(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); static int atapm(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); #endif /* MINIMALISTIC */ #ifndef min #define min(a,b) (((a)<(b))?(a):(b)) #endif #ifndef max #define max(a,b) (((a)>(b))?(a):(b)) #endif camcontrol_optret getoption(struct camcontrol_opts *table, char *arg, uint32_t *cmdnum, cam_argmask *argnum, const char **subopt) { struct camcontrol_opts *opts; int num_matches = 0; for (opts = table; (opts != NULL) && (opts->optname != NULL); opts++) { if (strncmp(opts->optname, arg, strlen(arg)) == 0) { *cmdnum = opts->cmdnum; *argnum = opts->argnum; *subopt = opts->subopt; if (++num_matches > 1) return(CC_OR_AMBIGUOUS); } } if (num_matches > 0) return(CC_OR_FOUND); else return(CC_OR_NOT_FOUND); } #ifndef MINIMALISTIC static int getdevlist(struct cam_device *device) { union ccb *ccb; char status[32]; int error = 0; ccb = cam_getccb(device); ccb->ccb_h.func_code = XPT_GDEVLIST; ccb->ccb_h.flags = CAM_DIR_NONE; ccb->ccb_h.retry_count = 1; ccb->cgdl.index = 0; ccb->cgdl.status = CAM_GDEVLIST_MORE_DEVS; while (ccb->cgdl.status == CAM_GDEVLIST_MORE_DEVS) { if (cam_send_ccb(device, ccb) < 0) { perror("error getting device list"); cam_freeccb(ccb); return(1); } status[0] = '\0'; switch (ccb->cgdl.status) { case CAM_GDEVLIST_MORE_DEVS: strcpy(status, "MORE"); break; case CAM_GDEVLIST_LAST_DEVICE: strcpy(status, "LAST"); break; case CAM_GDEVLIST_LIST_CHANGED: strcpy(status, "CHANGED"); break; case CAM_GDEVLIST_ERROR: strcpy(status, "ERROR"); error = 1; break; } fprintf(stdout, "%s%d: generation: %d index: %d status: %s\n", ccb->cgdl.periph_name, ccb->cgdl.unit_number, ccb->cgdl.generation, ccb->cgdl.index, status); /* * If the list has changed, we need to start over from the * beginning. */ if (ccb->cgdl.status == CAM_GDEVLIST_LIST_CHANGED) ccb->cgdl.index = 0; } cam_freeccb(ccb); return(error); } #endif /* MINIMALISTIC */ static int getdevtree(void) { union ccb ccb; int bufsize, fd; unsigned int i; int need_close = 0; int error = 0; int skip_device = 0; if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { warn("couldn't open %s", XPT_DEVICE); return(1); } bzero(&ccb, sizeof(union ccb)); ccb.ccb_h.path_id = CAM_XPT_PATH_ID; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; ccb.ccb_h.func_code = XPT_DEV_MATCH; bufsize = sizeof(struct dev_match_result) * 100; ccb.cdm.match_buf_len = bufsize; ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); if (ccb.cdm.matches == NULL) { warnx("can't malloc memory for matches"); close(fd); return(1); } ccb.cdm.num_matches = 0; /* * We fetch all nodes, since we display most of them in the default * case, and all in the verbose case. */ ccb.cdm.num_patterns = 0; ccb.cdm.pattern_buf_len = 0; /* * We do the ioctl multiple times if necessary, in case there are * more than 100 nodes in the EDT. */ do { if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { warn("error sending CAMIOCOMMAND ioctl"); error = 1; break; } if ((ccb.ccb_h.status != CAM_REQ_CMP) || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { warnx("got CAM error %#x, CDM error %d\n", ccb.ccb_h.status, ccb.cdm.status); error = 1; break; } for (i = 0; i < ccb.cdm.num_matches; i++) { switch (ccb.cdm.matches[i].type) { case DEV_MATCH_BUS: { struct bus_match_result *bus_result; /* * Only print the bus information if the * user turns on the verbose flag. */ if ((arglist & CAM_ARG_VERBOSE) == 0) break; bus_result = &ccb.cdm.matches[i].result.bus_result; if (need_close) { fprintf(stdout, ")\n"); need_close = 0; } fprintf(stdout, "scbus%d on %s%d bus %d:\n", bus_result->path_id, bus_result->dev_name, bus_result->unit_number, bus_result->bus_id); break; } case DEV_MATCH_DEVICE: { struct device_match_result *dev_result; char vendor[16], product[48], revision[16]; char tmpstr[256]; dev_result = &ccb.cdm.matches[i].result.device_result; if ((dev_result->flags & DEV_RESULT_UNCONFIGURED) && ((arglist & CAM_ARG_VERBOSE) == 0)) { skip_device = 1; break; } else skip_device = 0; if (dev_result->protocol == PROTO_SCSI) { cam_strvis(vendor, dev_result->inq_data.vendor, sizeof(dev_result->inq_data.vendor), sizeof(vendor)); cam_strvis(product, dev_result->inq_data.product, sizeof(dev_result->inq_data.product), sizeof(product)); cam_strvis(revision, dev_result->inq_data.revision, sizeof(dev_result->inq_data.revision), sizeof(revision)); sprintf(tmpstr, "<%s %s %s>", vendor, product, revision); } else if (dev_result->protocol == PROTO_ATA || dev_result->protocol == PROTO_SATAPM) { cam_strvis(product, dev_result->ident_data.model, sizeof(dev_result->ident_data.model), sizeof(product)); cam_strvis(revision, dev_result->ident_data.revision, sizeof(dev_result->ident_data.revision), sizeof(revision)); sprintf(tmpstr, "<%s %s>", product, revision); } else { sprintf(tmpstr, "<>"); } if (need_close) { fprintf(stdout, ")\n"); need_close = 0; } fprintf(stdout, "%-33s at scbus%d " "target %d lun %d (", tmpstr, dev_result->path_id, dev_result->target_id, dev_result->target_lun); need_close = 1; break; } case DEV_MATCH_PERIPH: { struct periph_match_result *periph_result; periph_result = &ccb.cdm.matches[i].result.periph_result; if (skip_device != 0) break; if (need_close > 1) fprintf(stdout, ","); fprintf(stdout, "%s%d", periph_result->periph_name, periph_result->unit_number); need_close++; break; } default: fprintf(stdout, "unknown match type\n"); break; } } } while ((ccb.ccb_h.status == CAM_REQ_CMP) && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); if (need_close) fprintf(stdout, ")\n"); close(fd); return(error); } #ifndef MINIMALISTIC static int testunitready(struct cam_device *device, int retry_count, int timeout, int quiet) { int error = 0; union ccb *ccb; ccb = cam_getccb(device); scsi_test_unit_ready(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { if (quiet == 0) perror("error sending test unit ready"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); return(1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { if (quiet == 0) fprintf(stdout, "Unit is ready\n"); } else { if (quiet == 0) fprintf(stdout, "Unit is not ready\n"); error = 1; if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } cam_freeccb(ccb); return(error); } static int scsistart(struct cam_device *device, int startstop, int loadeject, int retry_count, int timeout) { union ccb *ccb; int error = 0; ccb = cam_getccb(device); /* * If we're stopping, send an ordered tag so the drive in question * will finish any previously queued writes before stopping. If * the device isn't capable of tagged queueing, or if tagged * queueing is turned off, the tag action is a no-op. */ scsi_start_stop(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ startstop ? MSG_SIMPLE_Q_TAG : MSG_ORDERED_Q_TAG, /* start/stop */ startstop, /* load_eject */ loadeject, /* immediate */ 0, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 120000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { perror("error sending start unit"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); return(1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) if (startstop) { fprintf(stdout, "Unit started successfully"); if (loadeject) fprintf(stdout,", Media loaded\n"); else fprintf(stdout,"\n"); } else { fprintf(stdout, "Unit stopped successfully"); if (loadeject) fprintf(stdout, ", Media ejected\n"); else fprintf(stdout, "\n"); } else { error = 1; if (startstop) fprintf(stdout, "Error received from start unit command\n"); else fprintf(stdout, "Error received from stop unit command\n"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } cam_freeccb(ccb); return(error); } int scsidoinquiry(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { int c; int error = 0; while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'D': arglist |= CAM_ARG_GET_STDINQ; break; case 'R': arglist |= CAM_ARG_GET_XFERRATE; break; case 'S': arglist |= CAM_ARG_GET_SERIAL; break; default: break; } } /* * If the user didn't specify any inquiry options, he wants all of * them. */ if ((arglist & CAM_ARG_INQ_MASK) == 0) arglist |= CAM_ARG_INQ_MASK; if (arglist & CAM_ARG_GET_STDINQ) error = scsiinquiry(device, retry_count, timeout); if (error != 0) return(error); if (arglist & CAM_ARG_GET_SERIAL) scsiserial(device, retry_count, timeout); if (error != 0) return(error); if (arglist & CAM_ARG_GET_XFERRATE) error = camxferrate(device); return(error); } static int scsiinquiry(struct cam_device *device, int retry_count, int timeout) { union ccb *ccb; struct scsi_inquiry_data *inq_buf; int error = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return(1); } /* cam_getccb cleans up the header, caller has to zero the payload */ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); inq_buf = (struct scsi_inquiry_data *)malloc( sizeof(struct scsi_inquiry_data)); if (inq_buf == NULL) { cam_freeccb(ccb); warnx("can't malloc memory for inquiry\n"); return(1); } bzero(inq_buf, sizeof(*inq_buf)); /* * Note that although the size of the inquiry buffer is the full * 256 bytes specified in the SCSI spec, we only tell the device * that we have allocated SHORT_INQUIRY_LENGTH bytes. There are * two reasons for this: * * - The SCSI spec says that when a length field is only 1 byte, * a value of 0 will be interpreted as 256. Therefore * scsi_inquiry() will convert an inq_len (which is passed in as * a u_int32_t, but the field in the CDB is only 1 byte) of 256 * to 0. Evidently, very few devices meet the spec in that * regard. Some devices, like many Seagate disks, take the 0 as * 0, and don't return any data. One Pioneer DVD-R drive * returns more data than the command asked for. * * So, since there are numerous devices that just don't work * right with the full inquiry size, we don't send the full size. * * - The second reason not to use the full inquiry data length is * that we don't need it here. The only reason we issue a * standard inquiry is to get the vendor name, device name, * and revision so scsi_print_inquiry() can print them. * * If, at some point in the future, more inquiry data is needed for * some reason, this code should use a procedure similar to the * probe code. i.e., issue a short inquiry, and determine from * the additional length passed back from the device how much * inquiry data the device supports. Once the amount the device * supports is determined, issue an inquiry for that amount and no * more. * * KDM, 2/18/2000 */ scsi_inquiry(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* inq_buf */ (u_int8_t *)inq_buf, /* inq_len */ SHORT_INQUIRY_LENGTH, /* evpd */ 0, /* page_code */ 0, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { perror("error sending SCSI inquiry"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); return(1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } cam_freeccb(ccb); if (error != 0) { free(inq_buf); return(error); } fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num); scsi_print_inquiry(inq_buf); free(inq_buf); return(0); } static int scsiserial(struct cam_device *device, int retry_count, int timeout) { union ccb *ccb; struct scsi_vpd_unit_serial_number *serial_buf; char serial_num[SVPD_SERIAL_NUM_SIZE + 1]; int error = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return(1); } /* cam_getccb cleans up the header, caller has to zero the payload */ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); serial_buf = (struct scsi_vpd_unit_serial_number *) malloc(sizeof(*serial_buf)); if (serial_buf == NULL) { cam_freeccb(ccb); warnx("can't malloc memory for serial number"); return(1); } scsi_inquiry(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* inq_buf */ (u_int8_t *)serial_buf, /* inq_len */ sizeof(*serial_buf), /* evpd */ 1, /* page_code */ SVPD_UNIT_SERIAL_NUMBER, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { warn("error getting serial number"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); free(serial_buf); return(1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } cam_freeccb(ccb); if (error != 0) { free(serial_buf); return(error); } bcopy(serial_buf->serial_num, serial_num, serial_buf->length); serial_num[serial_buf->length] = '\0'; if ((arglist & CAM_ARG_GET_STDINQ) || (arglist & CAM_ARG_GET_XFERRATE)) fprintf(stdout, "%s%d: Serial Number ", device->device_name, device->dev_unit_num); fprintf(stdout, "%.60s\n", serial_num); free(serial_buf); return(0); } static int camxferrate(struct cam_device *device) { struct ccb_pathinq cpi; u_int32_t freq = 0; u_int32_t speed = 0; union ccb *ccb; u_int mb; int retval = 0; if ((retval = get_cpi(device, &cpi)) != 0) return (1); ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS; ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char error_string[] = "error getting transfer settings"; if (retval < 0) warn(error_string); else warnx(error_string); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto xferrate_bailout; } speed = cpi.base_transfer_speed; freq = 0; if (ccb->cts.transport == XPORT_SPI) { struct ccb_trans_settings_spi *spi = &ccb->cts.xport_specific.spi; if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) { freq = scsi_calc_syncsrate(spi->sync_period); speed = freq; } if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) { speed *= (0x01 << spi->bus_width); } } else if (ccb->cts.transport == XPORT_FC) { struct ccb_trans_settings_fc *fc = &ccb->cts.xport_specific.fc; if (fc->valid & CTS_FC_VALID_SPEED) speed = fc->bitrate; } else if (ccb->cts.transport == XPORT_SAS) { struct ccb_trans_settings_sas *sas = &ccb->cts.xport_specific.sas; if (sas->valid & CTS_SAS_VALID_SPEED) speed = sas->bitrate; } else if (ccb->cts.transport == XPORT_ATA) { struct ccb_trans_settings_ata *ata = &ccb->cts.xport_specific.ata; if (ata->valid & CTS_ATA_VALID_MODE) speed = ata_mode2speed(ata->mode); } else if (ccb->cts.transport == XPORT_SATA) { struct ccb_trans_settings_sata *sata = &ccb->cts.xport_specific.sata; if (sata->valid & CTS_SATA_VALID_REVISION) speed = ata_revision2speed(sata->revision); } mb = speed / 1000; if (mb > 0) { fprintf(stdout, "%s%d: %d.%03dMB/s transfers", device->device_name, device->dev_unit_num, mb, speed % 1000); } else { fprintf(stdout, "%s%d: %dKB/s transfers", device->device_name, device->dev_unit_num, speed); } if (ccb->cts.transport == XPORT_SPI) { struct ccb_trans_settings_spi *spi = &ccb->cts.xport_specific.spi; if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) && (spi->sync_offset != 0)) fprintf(stdout, " (%d.%03dMHz, offset %d", freq / 1000, freq % 1000, spi->sync_offset); if (((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) && (spi->bus_width > 0)) { if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) && (spi->sync_offset != 0)) { fprintf(stdout, ", "); } else { fprintf(stdout, " ("); } fprintf(stdout, "%dbit)", 8 * (0x01 << spi->bus_width)); } else if (((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) && (spi->sync_offset != 0)) { fprintf(stdout, ")"); } } else if (ccb->cts.transport == XPORT_ATA) { struct ccb_trans_settings_ata *ata = &ccb->cts.xport_specific.ata; printf(" ("); if (ata->valid & CTS_ATA_VALID_MODE) printf("%s, ", ata_mode2string(ata->mode)); if ((ata->valid & CTS_ATA_VALID_ATAPI) && ata->atapi != 0) printf("ATAPI %dbytes, ", ata->atapi); if (ata->valid & CTS_ATA_VALID_BYTECOUNT) printf("PIO %dbytes", ata->bytecount); printf(")"); } else if (ccb->cts.transport == XPORT_SATA) { struct ccb_trans_settings_sata *sata = &ccb->cts.xport_specific.sata; printf(" ("); if (sata->valid & CTS_SATA_VALID_REVISION) printf("SATA %d.x, ", sata->revision); else printf("SATA, "); if (sata->valid & CTS_SATA_VALID_MODE) printf("%s, ", ata_mode2string(sata->mode)); if ((sata->valid & CTS_SATA_VALID_ATAPI) && sata->atapi != 0) printf("ATAPI %dbytes, ", sata->atapi); if (sata->valid & CTS_SATA_VALID_BYTECOUNT) printf("PIO %dbytes", sata->bytecount); printf(")"); } if (ccb->cts.protocol == PROTO_SCSI) { struct ccb_trans_settings_scsi *scsi = &ccb->cts.proto_specific.scsi; if (scsi->valid & CTS_SCSI_VALID_TQ) { if (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) { fprintf(stdout, ", Command Queueing Enabled"); } } } fprintf(stdout, "\n"); xferrate_bailout: cam_freeccb(ccb); return(retval); } static void atacapprint(struct ata_params *parm) { u_int32_t lbasize = (u_int32_t)parm->lba_size_1 | ((u_int32_t)parm->lba_size_2 << 16); u_int64_t lbasize48 = ((u_int64_t)parm->lba_size48_1) | ((u_int64_t)parm->lba_size48_2 << 16) | ((u_int64_t)parm->lba_size48_3 << 32) | ((u_int64_t)parm->lba_size48_4 << 48); printf("\n"); printf("protocol "); printf("ATA/ATAPI-%d", ata_version(parm->version_major)); if (parm->satacapabilities && parm->satacapabilities != 0xffff) { if (parm->satacapabilities & ATA_SATA_GEN3) printf(" SATA 3.x\n"); else if (parm->satacapabilities & ATA_SATA_GEN2) printf(" SATA 2.x\n"); else if (parm->satacapabilities & ATA_SATA_GEN1) printf(" SATA 1.x\n"); else printf(" SATA\n"); } else printf("\n"); printf("device model %.40s\n", parm->model); printf("firmware revision %.8s\n", parm->revision); printf("serial number %.20s\n", parm->serial); if (parm->enabled.extension & ATA_SUPPORT_64BITWWN) { printf("WWN %04x%04x%04x%04x\n", parm->wwn[0], parm->wwn[1], parm->wwn[2], parm->wwn[3]); } if (parm->enabled.extension & ATA_SUPPORT_MEDIASN) { printf("media serial number %.30s\n", parm->media_serial); } printf("cylinders %d\n", parm->cylinders); printf("heads %d\n", parm->heads); printf("sectors/track %d\n", parm->sectors); printf("sector size logical %u, physical %lu, offset %lu\n", ata_logical_sector_size(parm), (unsigned long)ata_physical_sector_size(parm), (unsigned long)ata_logical_sector_offset(parm)); if (parm->config == ATA_PROTO_CFA || (parm->support.command2 & ATA_SUPPORT_CFA)) printf("CFA supported\n"); printf("LBA%ssupported ", parm->capabilities1 & ATA_SUPPORT_LBA ? " " : " not "); if (lbasize) printf("%d sectors\n", lbasize); else printf("\n"); printf("LBA48%ssupported ", parm->support.command2 & ATA_SUPPORT_ADDRESS48 ? " " : " not "); if (lbasize48) printf("%ju sectors\n", (uintmax_t)lbasize48); else printf("\n"); printf("PIO supported PIO"); switch (ata_max_pmode(parm)) { case ATA_PIO4: printf("4"); break; case ATA_PIO3: printf("3"); break; case ATA_PIO2: printf("2"); break; case ATA_PIO1: printf("1"); break; default: printf("0"); } if ((parm->capabilities1 & ATA_SUPPORT_IORDY) == 0) printf(" w/o IORDY"); printf("\n"); printf("DMA%ssupported ", parm->capabilities1 & ATA_SUPPORT_DMA ? " " : " not "); if (parm->capabilities1 & ATA_SUPPORT_DMA) { if (parm->mwdmamodes & 0xff) { printf("WDMA"); if (parm->mwdmamodes & 0x04) printf("2"); else if (parm->mwdmamodes & 0x02) printf("1"); else if (parm->mwdmamodes & 0x01) printf("0"); printf(" "); } if ((parm->atavalid & ATA_FLAG_88) && (parm->udmamodes & 0xff)) { printf("UDMA"); if (parm->udmamodes & 0x40) printf("6"); else if (parm->udmamodes & 0x20) printf("5"); else if (parm->udmamodes & 0x10) printf("4"); else if (parm->udmamodes & 0x08) printf("3"); else if (parm->udmamodes & 0x04) printf("2"); else if (parm->udmamodes & 0x02) printf("1"); else if (parm->udmamodes & 0x01) printf("0"); printf(" "); } } printf("\n"); if (parm->media_rotation_rate == 1) { printf("media RPM non-rotating\n"); } else if (parm->media_rotation_rate >= 0x0401 && parm->media_rotation_rate <= 0xFFFE) { printf("media RPM %d\n", parm->media_rotation_rate); } printf("\nFeature " "Support Enabled Value Vendor\n"); printf("read ahead %s %s\n", parm->support.command1 & ATA_SUPPORT_LOOKAHEAD ? "yes" : "no", parm->enabled.command1 & ATA_SUPPORT_LOOKAHEAD ? "yes" : "no"); printf("write cache %s %s\n", parm->support.command1 & ATA_SUPPORT_WRITECACHE ? "yes" : "no", parm->enabled.command1 & ATA_SUPPORT_WRITECACHE ? "yes" : "no"); printf("flush cache %s %s\n", parm->support.command2 & ATA_SUPPORT_FLUSHCACHE ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_FLUSHCACHE ? "yes" : "no"); printf("overlap %s\n", parm->capabilities1 & ATA_SUPPORT_OVERLAP ? "yes" : "no"); printf("Tagged Command Queuing (TCQ) %s %s", parm->support.command2 & ATA_SUPPORT_QUEUED ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_QUEUED ? "yes" : "no"); if (parm->support.command2 & ATA_SUPPORT_QUEUED) { printf(" %d tags\n", ATA_QUEUE_LEN(parm->queue) + 1); } else printf("\n"); printf("Native Command Queuing (NCQ) "); if (parm->satacapabilities != 0xffff && (parm->satacapabilities & ATA_SUPPORT_NCQ)) { printf("yes %d tags\n", ATA_QUEUE_LEN(parm->queue) + 1); } else printf("no\n"); printf("SMART %s %s\n", parm->support.command1 & ATA_SUPPORT_SMART ? "yes" : "no", parm->enabled.command1 & ATA_SUPPORT_SMART ? "yes" : "no"); printf("microcode download %s %s\n", parm->support.command2 & ATA_SUPPORT_MICROCODE ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_MICROCODE ? "yes" : "no"); printf("security %s %s\n", parm->support.command1 & ATA_SUPPORT_SECURITY ? "yes" : "no", parm->enabled.command1 & ATA_SUPPORT_SECURITY ? "yes" : "no"); printf("power management %s %s\n", parm->support.command1 & ATA_SUPPORT_POWERMGT ? "yes" : "no", parm->enabled.command1 & ATA_SUPPORT_POWERMGT ? "yes" : "no"); printf("advanced power management %s %s", parm->support.command2 & ATA_SUPPORT_APM ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_APM ? "yes" : "no"); if (parm->support.command2 & ATA_SUPPORT_APM) { printf(" %d/0x%02X\n", parm->apm_value, parm->apm_value); } else printf("\n"); printf("automatic acoustic management %s %s", parm->support.command2 & ATA_SUPPORT_AUTOACOUSTIC ? "yes" :"no", parm->enabled.command2 & ATA_SUPPORT_AUTOACOUSTIC ? "yes" :"no"); if (parm->support.command2 & ATA_SUPPORT_AUTOACOUSTIC) { printf(" %d/0x%02X %d/0x%02X\n", ATA_ACOUSTIC_CURRENT(parm->acoustic), ATA_ACOUSTIC_CURRENT(parm->acoustic), ATA_ACOUSTIC_VENDOR(parm->acoustic), ATA_ACOUSTIC_VENDOR(parm->acoustic)); } else printf("\n"); printf("media status notification %s %s\n", parm->support.command2 & ATA_SUPPORT_NOTIFY ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_NOTIFY ? "yes" : "no"); printf("power-up in Standby %s %s\n", parm->support.command2 & ATA_SUPPORT_STANDBY ? "yes" : "no", parm->enabled.command2 & ATA_SUPPORT_STANDBY ? "yes" : "no"); printf("write-read-verify %s %s", parm->support2 & ATA_SUPPORT_WRITEREADVERIFY ? "yes" : "no", parm->enabled2 & ATA_SUPPORT_WRITEREADVERIFY ? "yes" : "no"); if (parm->support2 & ATA_SUPPORT_WRITEREADVERIFY) { printf(" %d/0x%x\n", parm->wrv_mode, parm->wrv_mode); } else printf("\n"); printf("unload %s %s\n", parm->support.extension & ATA_SUPPORT_UNLOAD ? "yes" : "no", parm->enabled.extension & ATA_SUPPORT_UNLOAD ? "yes" : "no"); printf("free-fall %s %s\n", parm->support2 & ATA_SUPPORT_FREEFALL ? "yes" : "no", parm->enabled2 & ATA_SUPPORT_FREEFALL ? "yes" : "no"); printf("data set management (TRIM) %s\n", parm->support_dsm & ATA_SUPPORT_DSM_TRIM ? "yes" : "no"); } static int ataidentify(struct cam_device *device, int retry_count, int timeout) { union ccb *ccb; struct ata_params *ident_buf; struct ccb_getdev cgd; u_int i, error = 0; int16_t *ptr; if (get_cgd(device, &cgd) != 0) { warnx("couldn't get CGD"); return(1); } ccb = cam_getccb(device); if (ccb == NULL) { warnx("couldn't allocate CCB"); return(1); } /* cam_getccb cleans up the header, caller has to zero the payload */ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr)); ptr = (uint16_t *)malloc(sizeof(struct ata_params)); if (ptr == NULL) { cam_freeccb(ccb); warnx("can't malloc memory for identify\n"); return(1); } bzero(ptr, sizeof(struct ata_params)); cam_fill_ataio(&ccb->ataio, retry_count, NULL, /*flags*/CAM_DIR_IN, MSG_SIMPLE_Q_TAG, /*data_ptr*/(u_int8_t *)ptr, /*dxfer_len*/sizeof(struct ata_params), timeout ? timeout : 30 * 1000); if (cgd.protocol == PROTO_ATA) ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0); else ata_28bit_cmd(&ccb->ataio, ATA_ATAPI_IDENTIFY, 0, 0, 0); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { perror("error sending ATA identify"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } free(ptr); cam_freeccb(ccb); return(1); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } cam_freeccb(ccb); if (error != 0) { free(ptr); return(error); } for (i = 0; i < sizeof(struct ata_params) / 2; i++) ptr[i] = le16toh(ptr[i]); if (arglist & CAM_ARG_VERBOSE) { fprintf(stdout, "%s%d: Raw identify data:\n", device->device_name, device->dev_unit_num); for (i = 0; i < sizeof(struct ata_params) / 2; i++) { if ((i % 8) == 0) fprintf(stdout, " %3d: ", i); fprintf(stdout, "%04x ", (uint16_t)ptr[i]); if ((i % 8) == 7) fprintf(stdout, "\n"); } } ident_buf = (struct ata_params *)ptr; if (strncmp(ident_buf->model, "FX", 2) && strncmp(ident_buf->model, "NEC", 3) && strncmp(ident_buf->model, "Pioneer", 7) && strncmp(ident_buf->model, "SHARP", 5)) { ata_bswap(ident_buf->model, sizeof(ident_buf->model)); ata_bswap(ident_buf->revision, sizeof(ident_buf->revision)); ata_bswap(ident_buf->serial, sizeof(ident_buf->serial)); ata_bswap(ident_buf->media_serial, sizeof(ident_buf->media_serial)); } ata_btrim(ident_buf->model, sizeof(ident_buf->model)); ata_bpack(ident_buf->model, ident_buf->model, sizeof(ident_buf->model)); ata_btrim(ident_buf->revision, sizeof(ident_buf->revision)); ata_bpack(ident_buf->revision, ident_buf->revision, sizeof(ident_buf->revision)); ata_btrim(ident_buf->serial, sizeof(ident_buf->serial)); ata_bpack(ident_buf->serial, ident_buf->serial, sizeof(ident_buf->serial)); ata_btrim(ident_buf->media_serial, sizeof(ident_buf->media_serial)); ata_bpack(ident_buf->media_serial, ident_buf->media_serial, sizeof(ident_buf->media_serial)); fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num); ata_print_ident(ident_buf); camxferrate(device); atacapprint(ident_buf); free(ident_buf); return(0); } #endif /* MINIMALISTIC */ /* * Parse out a bus, or a bus, target and lun in the following * format: * bus * bus:target * bus:target:lun * * Returns the number of parsed components, or 0. */ static int parse_btl(char *tstr, int *bus, int *target, int *lun, cam_argmask *arglst) { char *tmpstr; int convs = 0; while (isspace(*tstr) && (*tstr != '\0')) tstr++; tmpstr = (char *)strtok(tstr, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')) { *bus = strtol(tmpstr, NULL, 0); *arglst |= CAM_ARG_BUS; convs++; tmpstr = (char *)strtok(NULL, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')) { *target = strtol(tmpstr, NULL, 0); *arglst |= CAM_ARG_TARGET; convs++; tmpstr = (char *)strtok(NULL, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')) { *lun = strtol(tmpstr, NULL, 0); *arglst |= CAM_ARG_LUN; convs++; } } } return convs; } static int dorescan_or_reset(int argc, char **argv, int rescan) { static const char must[] = "you must specify \"all\", a bus, or a bus:target:lun to %s"; int rv, error = 0; int bus = -1, target = -1, lun = -1; char *tstr; if (argc < 3) { warnx(must, rescan? "rescan" : "reset"); return(1); } tstr = argv[optind]; while (isspace(*tstr) && (*tstr != '\0')) tstr++; if (strncasecmp(tstr, "all", strlen("all")) == 0) arglist |= CAM_ARG_BUS; else { rv = parse_btl(argv[optind], &bus, &target, &lun, &arglist); if (rv != 1 && rv != 3) { warnx(must, rescan? "rescan" : "reset"); return(1); } } if ((arglist & CAM_ARG_BUS) && (arglist & CAM_ARG_TARGET) && (arglist & CAM_ARG_LUN)) error = scanlun_or_reset_dev(bus, target, lun, rescan); else error = rescan_or_reset_bus(bus, rescan); return(error); } static int rescan_or_reset_bus(int bus, int rescan) { union ccb ccb, matchccb; int fd, retval; int bufsize; retval = 0; if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { warnx("error opening transport layer device %s", XPT_DEVICE); warn("%s", XPT_DEVICE); return(1); } if (bus != -1) { ccb.ccb_h.func_code = rescan ? XPT_SCAN_BUS : XPT_RESET_BUS; ccb.ccb_h.path_id = bus; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; ccb.crcn.flags = CAM_FLAG_NONE; /* run this at a low priority */ ccb.ccb_h.pinfo.priority = 5; if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { warn("CAMIOCOMMAND ioctl failed"); close(fd); return(1); } if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { fprintf(stdout, "%s of bus %d was successful\n", rescan ? "Re-scan" : "Reset", bus); } else { fprintf(stdout, "%s of bus %d returned error %#x\n", rescan ? "Re-scan" : "Reset", bus, ccb.ccb_h.status & CAM_STATUS_MASK); retval = 1; } close(fd); return(retval); } /* * The right way to handle this is to modify the xpt so that it can * handle a wildcarded bus in a rescan or reset CCB. At the moment * that isn't implemented, so instead we enumerate the busses and * send the rescan or reset to those busses in the case where the * given bus is -1 (wildcard). We don't send a rescan or reset * to the xpt bus; sending a rescan to the xpt bus is effectively a * no-op, sending a rescan to the xpt bus would result in a status of * CAM_REQ_INVALID. */ bzero(&(&matchccb.ccb_h)[1], sizeof(struct ccb_dev_match) - sizeof(struct ccb_hdr)); matchccb.ccb_h.func_code = XPT_DEV_MATCH; matchccb.ccb_h.path_id = CAM_BUS_WILDCARD; bufsize = sizeof(struct dev_match_result) * 20; matchccb.cdm.match_buf_len = bufsize; matchccb.cdm.matches=(struct dev_match_result *)malloc(bufsize); if (matchccb.cdm.matches == NULL) { warnx("can't malloc memory for matches"); retval = 1; goto bailout; } matchccb.cdm.num_matches = 0; matchccb.cdm.num_patterns = 1; matchccb.cdm.pattern_buf_len = sizeof(struct dev_match_pattern); matchccb.cdm.patterns = (struct dev_match_pattern *)malloc( matchccb.cdm.pattern_buf_len); if (matchccb.cdm.patterns == NULL) { warnx("can't malloc memory for patterns"); retval = 1; goto bailout; } matchccb.cdm.patterns[0].type = DEV_MATCH_BUS; matchccb.cdm.patterns[0].pattern.bus_pattern.flags = BUS_MATCH_ANY; do { unsigned int i; if (ioctl(fd, CAMIOCOMMAND, &matchccb) == -1) { warn("CAMIOCOMMAND ioctl failed"); retval = 1; goto bailout; } if ((matchccb.ccb_h.status != CAM_REQ_CMP) || ((matchccb.cdm.status != CAM_DEV_MATCH_LAST) && (matchccb.cdm.status != CAM_DEV_MATCH_MORE))) { warnx("got CAM error %#x, CDM error %d\n", matchccb.ccb_h.status, matchccb.cdm.status); retval = 1; goto bailout; } for (i = 0; i < matchccb.cdm.num_matches; i++) { struct bus_match_result *bus_result; /* This shouldn't happen. */ if (matchccb.cdm.matches[i].type != DEV_MATCH_BUS) continue; bus_result = &matchccb.cdm.matches[i].result.bus_result; /* * We don't want to rescan or reset the xpt bus. * See above. */ if ((int)bus_result->path_id == -1) continue; ccb.ccb_h.func_code = rescan ? XPT_SCAN_BUS : XPT_RESET_BUS; ccb.ccb_h.path_id = bus_result->path_id; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; ccb.crcn.flags = CAM_FLAG_NONE; /* run this at a low priority */ ccb.ccb_h.pinfo.priority = 5; if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { warn("CAMIOCOMMAND ioctl failed"); retval = 1; goto bailout; } if ((ccb.ccb_h.status & CAM_STATUS_MASK) ==CAM_REQ_CMP){ fprintf(stdout, "%s of bus %d was successful\n", rescan? "Re-scan" : "Reset", bus_result->path_id); } else { /* * Don't bail out just yet, maybe the other * rescan or reset commands will complete * successfully. */ fprintf(stderr, "%s of bus %d returned error " "%#x\n", rescan? "Re-scan" : "Reset", bus_result->path_id, ccb.ccb_h.status & CAM_STATUS_MASK); retval = 1; } } } while ((matchccb.ccb_h.status == CAM_REQ_CMP) && (matchccb.cdm.status == CAM_DEV_MATCH_MORE)); bailout: if (fd != -1) close(fd); if (matchccb.cdm.patterns != NULL) free(matchccb.cdm.patterns); if (matchccb.cdm.matches != NULL) free(matchccb.cdm.matches); return(retval); } static int scanlun_or_reset_dev(int bus, int target, int lun, int scan) { union ccb ccb; struct cam_device *device; int fd; device = NULL; if (bus < 0) { warnx("invalid bus number %d", bus); return(1); } if (target < 0) { warnx("invalid target number %d", target); return(1); } if (lun < 0) { warnx("invalid lun number %d", lun); return(1); } fd = -1; bzero(&ccb, sizeof(union ccb)); if (scan) { if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { warnx("error opening transport layer device %s\n", XPT_DEVICE); warn("%s", XPT_DEVICE); return(1); } } else { device = cam_open_btl(bus, target, lun, O_RDWR, NULL); if (device == NULL) { warnx("%s", cam_errbuf); return(1); } } ccb.ccb_h.func_code = (scan)? XPT_SCAN_LUN : XPT_RESET_DEV; ccb.ccb_h.path_id = bus; ccb.ccb_h.target_id = target; ccb.ccb_h.target_lun = lun; ccb.ccb_h.timeout = 5000; ccb.crcn.flags = CAM_FLAG_NONE; /* run this at a low priority */ ccb.ccb_h.pinfo.priority = 5; if (scan) { if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) { warn("CAMIOCOMMAND ioctl failed"); close(fd); return(1); } } else { if (cam_send_ccb(device, &ccb) < 0) { warn("error sending XPT_RESET_DEV CCB"); cam_close_device(device); return(1); } } if (scan) close(fd); else cam_close_device(device); /* * An error code of CAM_BDR_SENT is normal for a BDR request. */ if (((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) || ((!scan) && ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_BDR_SENT))) { fprintf(stdout, "%s of %d:%d:%d was successful\n", scan? "Re-scan" : "Reset", bus, target, lun); return(0); } else { fprintf(stdout, "%s of %d:%d:%d returned error %#x\n", scan? "Re-scan" : "Reset", bus, target, lun, ccb.ccb_h.status & CAM_STATUS_MASK); return(1); } } #ifndef MINIMALISTIC static int readdefects(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb = NULL; struct scsi_read_defect_data_10 *rdd_cdb; u_int8_t *defect_list = NULL; u_int32_t dlist_length = 65000; u_int32_t returned_length = 0; u_int32_t num_returned = 0; u_int8_t returned_format; unsigned int i; int c, error = 0; int lists_specified = 0; while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c){ case 'f': { char *tstr; tstr = optarg; while (isspace(*tstr) && (*tstr != '\0')) tstr++; if (strcmp(tstr, "block") == 0) arglist |= CAM_ARG_FORMAT_BLOCK; else if (strcmp(tstr, "bfi") == 0) arglist |= CAM_ARG_FORMAT_BFI; else if (strcmp(tstr, "phys") == 0) arglist |= CAM_ARG_FORMAT_PHYS; else { error = 1; warnx("invalid defect format %s", tstr); goto defect_bailout; } break; } case 'G': arglist |= CAM_ARG_GLIST; break; case 'P': arglist |= CAM_ARG_PLIST; break; default: break; } } ccb = cam_getccb(device); /* * Hopefully 65000 bytes is enough to hold the defect list. If it * isn't, the disk is probably dead already. We'd have to go with * 12 byte command (i.e. alloc_length is 32 bits instead of 16) * to hold them all. */ defect_list = malloc(dlist_length); if (defect_list == NULL) { warnx("can't malloc memory for defect list"); error = 1; goto defect_bailout; } rdd_cdb =(struct scsi_read_defect_data_10 *)&ccb->csio.cdb_io.cdb_bytes; /* * cam_getccb() zeros the CCB header only. So we need to zero the * payload portion of the ccb. */ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); cam_fill_csio(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_IN | ((arglist & CAM_ARG_ERR_RECOVER) ? CAM_PASS_ERR_RECOVER : 0), /*tag_action*/ MSG_SIMPLE_Q_TAG, /*data_ptr*/ defect_list, /*dxfer_len*/ dlist_length, /*sense_len*/ SSD_FULL_SIZE, /*cdb_len*/ sizeof(struct scsi_read_defect_data_10), /*timeout*/ timeout ? timeout : 5000); rdd_cdb->opcode = READ_DEFECT_DATA_10; if (arglist & CAM_ARG_FORMAT_BLOCK) rdd_cdb->format = SRDD10_BLOCK_FORMAT; else if (arglist & CAM_ARG_FORMAT_BFI) rdd_cdb->format = SRDD10_BYTES_FROM_INDEX_FORMAT; else if (arglist & CAM_ARG_FORMAT_PHYS) rdd_cdb->format = SRDD10_PHYSICAL_SECTOR_FORMAT; else { error = 1; warnx("no defect list format specified"); goto defect_bailout; } if (arglist & CAM_ARG_PLIST) { rdd_cdb->format |= SRDD10_PLIST; lists_specified++; } if (arglist & CAM_ARG_GLIST) { rdd_cdb->format |= SRDD10_GLIST; lists_specified++; } scsi_ulto2b(dlist_length, rdd_cdb->alloc_length); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (cam_send_ccb(device, ccb) < 0) { perror("error reading defect list"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } error = 1; goto defect_bailout; } returned_length = scsi_2btoul(((struct scsi_read_defect_data_hdr_10 *)defect_list)->length); returned_format = ((struct scsi_read_defect_data_hdr_10 *) defect_list)->format; if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR) && (ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND) && ((ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0)) { struct scsi_sense_data *sense; int error_code, sense_key, asc, ascq; sense = &ccb->csio.sense_data; scsi_extract_sense_len(sense, ccb->csio.sense_len - ccb->csio.sense_resid, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); /* * According to the SCSI spec, if the disk doesn't support * the requested format, it will generally return a sense * key of RECOVERED ERROR, and an additional sense code * of "DEFECT LIST NOT FOUND". So, we check for that, and * also check to make sure that the returned length is * greater than 0, and then print out whatever format the * disk gave us. */ if ((sense_key == SSD_KEY_RECOVERED_ERROR) && (asc == 0x1c) && (ascq == 0x00) && (returned_length > 0)) { warnx("requested defect format not available"); switch(returned_format & SRDDH10_DLIST_FORMAT_MASK) { case SRDD10_BLOCK_FORMAT: warnx("Device returned block format"); break; case SRDD10_BYTES_FROM_INDEX_FORMAT: warnx("Device returned bytes from index" " format"); break; case SRDD10_PHYSICAL_SECTOR_FORMAT: warnx("Device returned physical sector format"); break; default: error = 1; warnx("Device returned unknown defect" " data format %#x", returned_format); goto defect_bailout; break; /* NOTREACHED */ } } else { error = 1; warnx("Error returned from read defect data command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); goto defect_bailout; } } else if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = 1; warnx("Error returned from read defect data command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); goto defect_bailout; } /* * XXX KDM I should probably clean up the printout format for the * disk defects. */ switch (returned_format & SRDDH10_DLIST_FORMAT_MASK){ case SRDDH10_PHYSICAL_SECTOR_FORMAT: { struct scsi_defect_desc_phys_sector *dlist; dlist = (struct scsi_defect_desc_phys_sector *) (defect_list + sizeof(struct scsi_read_defect_data_hdr_10)); num_returned = returned_length / sizeof(struct scsi_defect_desc_phys_sector); fprintf(stderr, "Got %d defect", num_returned); if ((lists_specified == 0) || (num_returned == 0)) { fprintf(stderr, "s.\n"); break; } else if (num_returned == 1) fprintf(stderr, ":\n"); else fprintf(stderr, "s:\n"); for (i = 0; i < num_returned; i++) { fprintf(stdout, "%d:%d:%d\n", scsi_3btoul(dlist[i].cylinder), dlist[i].head, scsi_4btoul(dlist[i].sector)); } break; } case SRDDH10_BYTES_FROM_INDEX_FORMAT: { struct scsi_defect_desc_bytes_from_index *dlist; dlist = (struct scsi_defect_desc_bytes_from_index *) (defect_list + sizeof(struct scsi_read_defect_data_hdr_10)); num_returned = returned_length / sizeof(struct scsi_defect_desc_bytes_from_index); fprintf(stderr, "Got %d defect", num_returned); if ((lists_specified == 0) || (num_returned == 0)) { fprintf(stderr, "s.\n"); break; } else if (num_returned == 1) fprintf(stderr, ":\n"); else fprintf(stderr, "s:\n"); for (i = 0; i < num_returned; i++) { fprintf(stdout, "%d:%d:%d\n", scsi_3btoul(dlist[i].cylinder), dlist[i].head, scsi_4btoul(dlist[i].bytes_from_index)); } break; } case SRDDH10_BLOCK_FORMAT: { struct scsi_defect_desc_block *dlist; dlist = (struct scsi_defect_desc_block *)(defect_list + sizeof(struct scsi_read_defect_data_hdr_10)); num_returned = returned_length / sizeof(struct scsi_defect_desc_block); fprintf(stderr, "Got %d defect", num_returned); if ((lists_specified == 0) || (num_returned == 0)) { fprintf(stderr, "s.\n"); break; } else if (num_returned == 1) fprintf(stderr, ":\n"); else fprintf(stderr, "s:\n"); for (i = 0; i < num_returned; i++) fprintf(stdout, "%u\n", scsi_4btoul(dlist[i].address)); break; } default: fprintf(stderr, "Unknown defect format %d\n", returned_format & SRDDH10_DLIST_FORMAT_MASK); error = 1; break; } defect_bailout: if (defect_list != NULL) free(defect_list); if (ccb != NULL) cam_freeccb(ccb); return(error); } #endif /* MINIMALISTIC */ #if 0 void reassignblocks(struct cam_device *device, u_int32_t *blocks, int num_blocks) { union ccb *ccb; ccb = cam_getccb(device); cam_freeccb(ccb); } #endif #ifndef MINIMALISTIC void mode_sense(struct cam_device *device, int mode_page, int page_control, int dbd, int retry_count, int timeout, u_int8_t *data, int datalen) { union ccb *ccb; int retval; ccb = cam_getccb(device); if (ccb == NULL) errx(1, "mode_sense: couldn't allocate CCB"); bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); scsi_mode_sense(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* dbd */ dbd, /* page_code */ page_control << 6, /* page */ mode_page, /* param_buf */ data, /* param_len */ datalen, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); cam_close_device(device); if (retval < 0) err(1, "error sending mode sense command"); else errx(1, "error sending mode sense command"); } cam_freeccb(ccb); } void mode_select(struct cam_device *device, int save_pages, int retry_count, int timeout, u_int8_t *data, int datalen) { union ccb *ccb; int retval; ccb = cam_getccb(device); if (ccb == NULL) errx(1, "mode_select: couldn't allocate CCB"); bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); scsi_mode_select(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* scsi_page_fmt */ 1, /* save_pages */ save_pages, /* param_buf */ data, /* param_len */ datalen, /* sense_len */ SSD_FULL_SIZE, /* timeout */ timeout ? timeout : 5000); if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } cam_freeccb(ccb); cam_close_device(device); if (retval < 0) err(1, "error sending mode select command"); else errx(1, "error sending mode select command"); } cam_freeccb(ccb); } void modepage(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { int c, mode_page = -1, page_control = 0; int binary = 0, list = 0; while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'b': binary = 1; break; case 'd': arglist |= CAM_ARG_DBD; break; case 'e': arglist |= CAM_ARG_MODE_EDIT; break; case 'l': list = 1; break; case 'm': mode_page = strtol(optarg, NULL, 0); if (mode_page < 0) errx(1, "invalid mode page %d", mode_page); break; case 'P': page_control = strtol(optarg, NULL, 0); if ((page_control < 0) || (page_control > 3)) errx(1, "invalid page control field %d", page_control); arglist |= CAM_ARG_PAGE_CNTL; break; default: break; } } if (mode_page == -1 && list == 0) errx(1, "you must specify a mode page!"); if (list) { mode_list(device, page_control, arglist & CAM_ARG_DBD, retry_count, timeout); } else { mode_edit(device, mode_page, page_control, arglist & CAM_ARG_DBD, arglist & CAM_ARG_MODE_EDIT, binary, retry_count, timeout); } } static int scsicmd(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; u_int32_t flags = CAM_DIR_NONE; u_int8_t *data_ptr = NULL; u_int8_t cdb[20]; u_int8_t atacmd[12]; struct get_hook hook; int c, data_bytes = 0; int cdb_len = 0; int atacmd_len = 0; int dmacmd = 0; int fpdmacmd = 0; int need_res = 0; char *datastr = NULL, *tstr, *resstr = NULL; int error = 0; int fd_data = 0, fd_res = 0; int retval; ccb = cam_getccb(device); if (ccb == NULL) { warnx("scsicmd: error allocating ccb"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'a': tstr = optarg; while (isspace(*tstr) && (*tstr != '\0')) tstr++; hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; atacmd_len = buff_encode_visit(atacmd, sizeof(atacmd), tstr, iget, &hook); /* * Increment optind by the number of arguments the * encoding routine processed. After each call to * getopt(3), optind points to the argument that * getopt should process _next_. In this case, * that means it points to the first command string * argument, if there is one. Once we increment * this, it should point to either the next command * line argument, or it should be past the end of * the list. */ optind += hook.got; break; case 'c': tstr = optarg; while (isspace(*tstr) && (*tstr != '\0')) tstr++; hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; cdb_len = buff_encode_visit(cdb, sizeof(cdb), tstr, iget, &hook); /* * Increment optind by the number of arguments the * encoding routine processed. After each call to * getopt(3), optind points to the argument that * getopt should process _next_. In this case, * that means it points to the first command string * argument, if there is one. Once we increment * this, it should point to either the next command * line argument, or it should be past the end of * the list. */ optind += hook.got; break; case 'd': dmacmd = 1; break; case 'f': fpdmacmd = 1; break; case 'i': if (arglist & CAM_ARG_CMD_OUT) { warnx("command must either be " "read or write, not both"); error = 1; goto scsicmd_bailout; } arglist |= CAM_ARG_CMD_IN; flags = CAM_DIR_IN; data_bytes = strtol(optarg, NULL, 0); if (data_bytes <= 0) { warnx("invalid number of input bytes %d", data_bytes); error = 1; goto scsicmd_bailout; } hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; optind++; datastr = cget(&hook, NULL); /* * If the user supplied "-" instead of a format, he * wants the data to be written to stdout. */ if ((datastr != NULL) && (datastr[0] == '-')) fd_data = 1; data_ptr = (u_int8_t *)malloc(data_bytes); if (data_ptr == NULL) { warnx("can't malloc memory for data_ptr"); error = 1; goto scsicmd_bailout; } break; case 'o': if (arglist & CAM_ARG_CMD_IN) { warnx("command must either be " "read or write, not both"); error = 1; goto scsicmd_bailout; } arglist |= CAM_ARG_CMD_OUT; flags = CAM_DIR_OUT; data_bytes = strtol(optarg, NULL, 0); if (data_bytes <= 0) { warnx("invalid number of output bytes %d", data_bytes); error = 1; goto scsicmd_bailout; } hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; datastr = cget(&hook, NULL); data_ptr = (u_int8_t *)malloc(data_bytes); if (data_ptr == NULL) { warnx("can't malloc memory for data_ptr"); error = 1; goto scsicmd_bailout; } bzero(data_ptr, data_bytes); /* * If the user supplied "-" instead of a format, he * wants the data to be read from stdin. */ if ((datastr != NULL) && (datastr[0] == '-')) fd_data = 1; else buff_encode_visit(data_ptr, data_bytes, datastr, iget, &hook); optind += hook.got; break; case 'r': need_res = 1; hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; resstr = cget(&hook, NULL); if ((resstr != NULL) && (resstr[0] == '-')) fd_res = 1; optind += hook.got; break; default: break; } } /* * If fd_data is set, and we're writing to the device, we need to * read the data the user wants written from stdin. */ if ((fd_data == 1) && (arglist & CAM_ARG_CMD_OUT)) { ssize_t amt_read; int amt_to_read = data_bytes; u_int8_t *buf_ptr = data_ptr; for (amt_read = 0; amt_to_read > 0; amt_read = read(STDIN_FILENO, buf_ptr, amt_to_read)) { if (amt_read == -1) { warn("error reading data from stdin"); error = 1; goto scsicmd_bailout; } amt_to_read -= amt_read; buf_ptr += amt_read; } } if (arglist & CAM_ARG_ERR_RECOVER) flags |= CAM_PASS_ERR_RECOVER; /* Disable freezing the device queue */ flags |= CAM_DEV_QFRZDIS; if (cdb_len) { /* * This is taken from the SCSI-3 draft spec. * (T10/1157D revision 0.3) * The top 3 bits of an opcode are the group code. * The next 5 bits are the command code. * Group 0: six byte commands * Group 1: ten byte commands * Group 2: ten byte commands * Group 3: reserved * Group 4: sixteen byte commands * Group 5: twelve byte commands * Group 6: vendor specific * Group 7: vendor specific */ switch((cdb[0] >> 5) & 0x7) { case 0: cdb_len = 6; break; case 1: case 2: cdb_len = 10; break; case 3: case 6: case 7: /* computed by buff_encode_visit */ break; case 4: cdb_len = 16; break; case 5: cdb_len = 12; break; } /* * We should probably use csio_build_visit or something like that * here, but it's easier to encode arguments as you go. The * alternative would be skipping the CDB argument and then encoding * it here, since we've got the data buffer argument by now. */ bcopy(cdb, &ccb->csio.cdb_io.cdb_bytes, cdb_len); cam_fill_csio(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*flags*/ flags, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*data_ptr*/ data_ptr, /*dxfer_len*/ data_bytes, /*sense_len*/ SSD_FULL_SIZE, /*cdb_len*/ cdb_len, /*timeout*/ timeout ? timeout : 5000); } else { atacmd_len = 12; bcopy(atacmd, &ccb->ataio.cmd.command, atacmd_len); if (need_res) ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT; if (dmacmd) ccb->ataio.cmd.flags |= CAM_ATAIO_DMA; if (fpdmacmd) ccb->ataio.cmd.flags |= CAM_ATAIO_FPDMA; cam_fill_ataio(&ccb->ataio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*flags*/ flags, /*tag_action*/ 0, /*data_ptr*/ data_ptr, /*dxfer_len*/ data_bytes, /*timeout*/ timeout ? timeout : 5000); } if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } error = 1; goto scsicmd_bailout; } if (atacmd_len && need_res) { if (fd_res == 0) { buff_decode_visit(&ccb->ataio.res.status, 11, resstr, arg_put, NULL); fprintf(stdout, "\n"); } else { fprintf(stdout, "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", ccb->ataio.res.status, ccb->ataio.res.error, ccb->ataio.res.lba_low, ccb->ataio.res.lba_mid, ccb->ataio.res.lba_high, ccb->ataio.res.device, ccb->ataio.res.lba_low_exp, ccb->ataio.res.lba_mid_exp, ccb->ataio.res.lba_high_exp, ccb->ataio.res.sector_count, ccb->ataio.res.sector_count_exp); fflush(stdout); } } if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) && (arglist & CAM_ARG_CMD_IN) && (data_bytes > 0)) { if (fd_data == 0) { buff_decode_visit(data_ptr, data_bytes, datastr, arg_put, NULL); fprintf(stdout, "\n"); } else { ssize_t amt_written; int amt_to_write = data_bytes; u_int8_t *buf_ptr = data_ptr; for (amt_written = 0; (amt_to_write > 0) && (amt_written =write(1, buf_ptr,amt_to_write))> 0;){ amt_to_write -= amt_written; buf_ptr += amt_written; } if (amt_written == -1) { warn("error writing data to stdout"); error = 1; goto scsicmd_bailout; } else if ((amt_written == 0) && (amt_to_write > 0)) { warnx("only wrote %u bytes out of %u", data_bytes - amt_to_write, data_bytes); } } } scsicmd_bailout: if ((data_bytes > 0) && (data_ptr != NULL)) free(data_ptr); cam_freeccb(ccb); return(error); } static int camdebug(int argc, char **argv, char *combinedopt) { int c, fd; int bus = -1, target = -1, lun = -1; char *tstr, *tmpstr = NULL; union ccb ccb; int error = 0; bzero(&ccb, sizeof(union ccb)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'I': arglist |= CAM_ARG_DEBUG_INFO; ccb.cdbg.flags |= CAM_DEBUG_INFO; break; case 'P': arglist |= CAM_ARG_DEBUG_PERIPH; ccb.cdbg.flags |= CAM_DEBUG_PERIPH; break; case 'S': arglist |= CAM_ARG_DEBUG_SUBTRACE; ccb.cdbg.flags |= CAM_DEBUG_SUBTRACE; break; case 'T': arglist |= CAM_ARG_DEBUG_TRACE; ccb.cdbg.flags |= CAM_DEBUG_TRACE; break; case 'X': arglist |= CAM_ARG_DEBUG_XPT; ccb.cdbg.flags |= CAM_DEBUG_XPT; break; case 'c': arglist |= CAM_ARG_DEBUG_CDB; ccb.cdbg.flags |= CAM_DEBUG_CDB; break; default: break; } } if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { warnx("error opening transport layer device %s", XPT_DEVICE); warn("%s", XPT_DEVICE); return(1); } argc -= optind; argv += optind; if (argc <= 0) { warnx("you must specify \"off\", \"all\" or a bus,"); warnx("bus:target, or bus:target:lun"); close(fd); return(1); } tstr = *argv; while (isspace(*tstr) && (*tstr != '\0')) tstr++; if (strncmp(tstr, "off", 3) == 0) { ccb.cdbg.flags = CAM_DEBUG_NONE; arglist &= ~(CAM_ARG_DEBUG_INFO|CAM_ARG_DEBUG_PERIPH| CAM_ARG_DEBUG_TRACE|CAM_ARG_DEBUG_SUBTRACE| CAM_ARG_DEBUG_XPT); } else if (strncmp(tstr, "all", 3) != 0) { tmpstr = (char *)strtok(tstr, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')){ bus = strtol(tmpstr, NULL, 0); arglist |= CAM_ARG_BUS; tmpstr = (char *)strtok(NULL, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')){ target = strtol(tmpstr, NULL, 0); arglist |= CAM_ARG_TARGET; tmpstr = (char *)strtok(NULL, ":"); if ((tmpstr != NULL) && (*tmpstr != '\0')){ lun = strtol(tmpstr, NULL, 0); arglist |= CAM_ARG_LUN; } } } else { error = 1; warnx("you must specify \"all\", \"off\", or a bus,"); warnx("bus:target, or bus:target:lun to debug"); } } if (error == 0) { ccb.ccb_h.func_code = XPT_DEBUG; ccb.ccb_h.path_id = bus; ccb.ccb_h.target_id = target; ccb.ccb_h.target_lun = lun; if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { warn("CAMIOCOMMAND ioctl failed"); error = 1; } if (error == 0) { if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_FUNC_NOTAVAIL) { warnx("CAM debugging not available"); warnx("you need to put options CAMDEBUG in" " your kernel config file!"); error = 1; } else if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_DEBUG CCB failed with status %#x", ccb.ccb_h.status); error = 1; } else { if (ccb.cdbg.flags == CAM_DEBUG_NONE) { fprintf(stderr, "Debugging turned off\n"); } else { fprintf(stderr, "Debugging enabled for " "%d:%d:%d\n", bus, target, lun); } } } close(fd); } return(error); } static int tagcontrol(struct cam_device *device, int argc, char **argv, char *combinedopt) { int c; union ccb *ccb; int numtags = -1; int retval = 0; int quiet = 0; char pathstr[1024]; ccb = cam_getccb(device); if (ccb == NULL) { warnx("tagcontrol: error allocating ccb"); return(1); } while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'N': numtags = strtol(optarg, NULL, 0); if (numtags < 0) { warnx("tag count %d is < 0", numtags); retval = 1; goto tagcontrol_bailout; } break; case 'q': quiet++; break; default: break; } } cam_path_string(device, pathstr, sizeof(pathstr)); if (numtags >= 0) { bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_REL_SIMQ; ccb->ccb_h.flags = CAM_DEV_QFREEZE; ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS; ccb->crs.openings = numtags; if (cam_send_ccb(device, ccb) < 0) { perror("error sending XPT_REL_SIMQ CCB"); retval = 1; goto tagcontrol_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_REL_SIMQ CCB failed"); cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto tagcontrol_bailout; } if (quiet == 0) fprintf(stdout, "%stagged openings now %d\n", pathstr, ccb->crs.openings); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_getdevstats) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_GDEV_STATS; if (cam_send_ccb(device, ccb) < 0) { perror("error sending XPT_GDEV_STATS CCB"); retval = 1; goto tagcontrol_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_GDEV_STATS CCB failed"); cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto tagcontrol_bailout; } if (arglist & CAM_ARG_VERBOSE) { fprintf(stdout, "%s", pathstr); fprintf(stdout, "dev_openings %d\n", ccb->cgds.dev_openings); fprintf(stdout, "%s", pathstr); fprintf(stdout, "dev_active %d\n", ccb->cgds.dev_active); fprintf(stdout, "%s", pathstr); fprintf(stdout, "devq_openings %d\n", ccb->cgds.devq_openings); fprintf(stdout, "%s", pathstr); fprintf(stdout, "devq_queued %d\n", ccb->cgds.devq_queued); fprintf(stdout, "%s", pathstr); fprintf(stdout, "held %d\n", ccb->cgds.held); fprintf(stdout, "%s", pathstr); fprintf(stdout, "mintags %d\n", ccb->cgds.mintags); fprintf(stdout, "%s", pathstr); fprintf(stdout, "maxtags %d\n", ccb->cgds.maxtags); } else { if (quiet == 0) { fprintf(stdout, "%s", pathstr); fprintf(stdout, "device openings: "); } fprintf(stdout, "%d\n", ccb->cgds.dev_openings + ccb->cgds.dev_active); } tagcontrol_bailout: cam_freeccb(ccb); return(retval); } static void cts_print(struct cam_device *device, struct ccb_trans_settings *cts) { char pathstr[1024]; cam_path_string(device, pathstr, sizeof(pathstr)); if (cts->transport == XPORT_SPI) { struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) { fprintf(stdout, "%ssync parameter: %d\n", pathstr, spi->sync_period); if (spi->sync_offset != 0) { u_int freq; freq = scsi_calc_syncsrate(spi->sync_period); fprintf(stdout, "%sfrequency: %d.%03dMHz\n", pathstr, freq / 1000, freq % 1000); } } if (spi->valid & CTS_SPI_VALID_SYNC_OFFSET) { fprintf(stdout, "%soffset: %d\n", pathstr, spi->sync_offset); } if (spi->valid & CTS_SPI_VALID_BUS_WIDTH) { fprintf(stdout, "%sbus width: %d bits\n", pathstr, (0x01 << spi->bus_width) * 8); } if (spi->valid & CTS_SPI_VALID_DISC) { fprintf(stdout, "%sdisconnection is %s\n", pathstr, (spi->flags & CTS_SPI_FLAGS_DISC_ENB) ? "enabled" : "disabled"); } } if (cts->transport == XPORT_ATA) { struct ccb_trans_settings_ata *ata = &cts->xport_specific.ata; if ((ata->valid & CTS_ATA_VALID_MODE) != 0) { fprintf(stdout, "%sATA mode: %s\n", pathstr, ata_mode2string(ata->mode)); } if ((ata->valid & CTS_ATA_VALID_ATAPI) != 0) { fprintf(stdout, "%sATAPI packet length: %d\n", pathstr, ata->atapi); } if ((ata->valid & CTS_ATA_VALID_BYTECOUNT) != 0) { fprintf(stdout, "%sPIO transaction length: %d\n", pathstr, ata->bytecount); } } if (cts->transport == XPORT_SATA) { struct ccb_trans_settings_sata *sata = &cts->xport_specific.sata; if ((sata->valid & CTS_SATA_VALID_REVISION) != 0) { fprintf(stdout, "%sSATA revision: %d.x\n", pathstr, sata->revision); } if ((sata->valid & CTS_SATA_VALID_MODE) != 0) { fprintf(stdout, "%sATA mode: %s\n", pathstr, ata_mode2string(sata->mode)); } if ((sata->valid & CTS_SATA_VALID_ATAPI) != 0) { fprintf(stdout, "%sATAPI packet length: %d\n", pathstr, sata->atapi); } if ((sata->valid & CTS_SATA_VALID_BYTECOUNT) != 0) { fprintf(stdout, "%sPIO transaction length: %d\n", pathstr, sata->bytecount); } if ((sata->valid & CTS_SATA_VALID_PM) != 0) { fprintf(stdout, "%sPMP presence: %d\n", pathstr, sata->pm_present); } if ((sata->valid & CTS_SATA_VALID_TAGS) != 0) { fprintf(stdout, "%sNumber of tags: %d\n", pathstr, sata->tags); } if ((sata->valid & CTS_SATA_VALID_CAPS) != 0) { fprintf(stdout, "%sSATA capabilities: %08x\n", pathstr, sata->caps); } } if (cts->protocol == PROTO_SCSI) { struct ccb_trans_settings_scsi *scsi= &cts->proto_specific.scsi; if (scsi->valid & CTS_SCSI_VALID_TQ) { fprintf(stdout, "%stagged queueing is %s\n", pathstr, (scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) ? "enabled" : "disabled"); } } } /* * Get a path inquiry CCB for the specified device. */ static int get_cpi(struct cam_device *device, struct ccb_pathinq *cpi) { union ccb *ccb; int retval = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("get_cpi: couldn't allocate CCB"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_PATH_INQ; if (cam_send_ccb(device, ccb) < 0) { warn("get_cpi: error sending Path Inquiry CCB"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_cpi_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_cpi_bailout; } bcopy(&ccb->cpi, cpi, sizeof(struct ccb_pathinq)); get_cpi_bailout: cam_freeccb(ccb); return(retval); } /* * Get a get device CCB for the specified device. */ static int get_cgd(struct cam_device *device, struct ccb_getdev *cgd) { union ccb *ccb; int retval = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("get_cgd: couldn't allocate CCB"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_GDEV_TYPE; if (cam_send_ccb(device, ccb) < 0) { warn("get_cgd: error sending Path Inquiry CCB"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_cgd_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_cgd_bailout; } bcopy(&ccb->cgd, cgd, sizeof(struct ccb_getdev)); get_cgd_bailout: cam_freeccb(ccb); return(retval); } static void cpi_print(struct ccb_pathinq *cpi) { char adapter_str[1024]; int i; snprintf(adapter_str, sizeof(adapter_str), "%s%d:", cpi->dev_name, cpi->unit_number); fprintf(stdout, "%s SIM/HBA version: %d\n", adapter_str, cpi->version_num); for (i = 1; i < 0xff; i = i << 1) { const char *str; if ((i & cpi->hba_inquiry) == 0) continue; fprintf(stdout, "%s supports ", adapter_str); switch(i) { case PI_MDP_ABLE: str = "MDP message"; break; case PI_WIDE_32: str = "32 bit wide SCSI"; break; case PI_WIDE_16: str = "16 bit wide SCSI"; break; case PI_SDTR_ABLE: str = "SDTR message"; break; case PI_LINKED_CDB: str = "linked CDBs"; break; case PI_TAG_ABLE: str = "tag queue messages"; break; case PI_SOFT_RST: str = "soft reset alternative"; break; case PI_SATAPM: str = "SATA Port Multiplier"; break; default: str = "unknown PI bit set"; break; } fprintf(stdout, "%s\n", str); } for (i = 1; i < 0xff; i = i << 1) { const char *str; if ((i & cpi->hba_misc) == 0) continue; fprintf(stdout, "%s ", adapter_str); switch(i) { case PIM_SCANHILO: str = "bus scans from high ID to low ID"; break; case PIM_NOREMOVE: str = "removable devices not included in scan"; break; case PIM_NOINITIATOR: str = "initiator role not supported"; break; case PIM_NOBUSRESET: str = "user has disabled initial BUS RESET or" " controller is in target/mixed mode"; break; case PIM_NO_6_BYTE: str = "do not send 6-byte commands"; break; case PIM_SEQSCAN: str = "scan bus sequentially"; break; default: str = "unknown PIM bit set"; break; } fprintf(stdout, "%s\n", str); } for (i = 1; i < 0xff; i = i << 1) { const char *str; if ((i & cpi->target_sprt) == 0) continue; fprintf(stdout, "%s supports ", adapter_str); switch(i) { case PIT_PROCESSOR: str = "target mode processor mode"; break; case PIT_PHASE: str = "target mode phase cog. mode"; break; case PIT_DISCONNECT: str = "disconnects in target mode"; break; case PIT_TERM_IO: str = "terminate I/O message in target mode"; break; case PIT_GRP_6: str = "group 6 commands in target mode"; break; case PIT_GRP_7: str = "group 7 commands in target mode"; break; default: str = "unknown PIT bit set"; break; } fprintf(stdout, "%s\n", str); } fprintf(stdout, "%s HBA engine count: %d\n", adapter_str, cpi->hba_eng_cnt); fprintf(stdout, "%s maximum target: %d\n", adapter_str, cpi->max_target); fprintf(stdout, "%s maximum LUN: %d\n", adapter_str, cpi->max_lun); fprintf(stdout, "%s highest path ID in subsystem: %d\n", adapter_str, cpi->hpath_id); fprintf(stdout, "%s initiator ID: %d\n", adapter_str, cpi->initiator_id); fprintf(stdout, "%s SIM vendor: %s\n", adapter_str, cpi->sim_vid); fprintf(stdout, "%s HBA vendor: %s\n", adapter_str, cpi->hba_vid); fprintf(stdout, "%s HBA vendor ID: 0x%04x\n", adapter_str, cpi->hba_vendor); fprintf(stdout, "%s HBA device ID: 0x%04x\n", adapter_str, cpi->hba_device); fprintf(stdout, "%s HBA subvendor ID: 0x%04x\n", adapter_str, cpi->hba_subvendor); fprintf(stdout, "%s HBA subdevice ID: 0x%04x\n", adapter_str, cpi->hba_subdevice); fprintf(stdout, "%s bus ID: %d\n", adapter_str, cpi->bus_id); fprintf(stdout, "%s base transfer speed: ", adapter_str); if (cpi->base_transfer_speed > 1000) fprintf(stdout, "%d.%03dMB/sec\n", cpi->base_transfer_speed / 1000, cpi->base_transfer_speed % 1000); else fprintf(stdout, "%dKB/sec\n", (cpi->base_transfer_speed % 1000) * 1000); fprintf(stdout, "%s maximum transfer size: %u bytes\n", adapter_str, cpi->maxio); } static int get_print_cts(struct cam_device *device, int user_settings, int quiet, struct ccb_trans_settings *cts) { int retval; union ccb *ccb; retval = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("get_print_cts: error allocating ccb"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr)); ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS; if (user_settings == 0) ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS; else ccb->cts.type = CTS_TYPE_USER_SETTINGS; if (cam_send_ccb(device, ccb) < 0) { perror("error sending XPT_GET_TRAN_SETTINGS CCB"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_print_cts_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_GET_TRANS_SETTINGS CCB failed"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto get_print_cts_bailout; } if (quiet == 0) cts_print(device, &ccb->cts); if (cts != NULL) bcopy(&ccb->cts, cts, sizeof(struct ccb_trans_settings)); get_print_cts_bailout: cam_freeccb(ccb); return(retval); } static int ratecontrol(struct cam_device *device, int retry_count, int timeout, int argc, char **argv, char *combinedopt) { int c; union ccb *ccb; int user_settings = 0; int retval = 0; int disc_enable = -1, tag_enable = -1; int mode = -1; int offset = -1; double syncrate = -1; int bus_width = -1; int quiet = 0; int change_settings = 0, send_tur = 0; struct ccb_pathinq cpi; ccb = cam_getccb(device); if (ccb == NULL) { warnx("ratecontrol: error allocating ccb"); return(1); } while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c){ case 'a': send_tur = 1; break; case 'c': user_settings = 0; break; case 'D': if (strncasecmp(optarg, "enable", 6) == 0) disc_enable = 1; else if (strncasecmp(optarg, "disable", 7) == 0) disc_enable = 0; else { warnx("-D argument \"%s\" is unknown", optarg); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; case 'M': mode = ata_string2mode(optarg); if (mode < 0) { warnx("unknown mode '%s'", optarg); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; case 'O': offset = strtol(optarg, NULL, 0); if (offset < 0) { warnx("offset value %d is < 0", offset); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; case 'q': quiet++; break; case 'R': syncrate = atof(optarg); if (syncrate < 0) { warnx("sync rate %f is < 0", syncrate); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; case 'T': if (strncasecmp(optarg, "enable", 6) == 0) tag_enable = 1; else if (strncasecmp(optarg, "disable", 7) == 0) tag_enable = 0; else { warnx("-T argument \"%s\" is unknown", optarg); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; case 'U': user_settings = 1; break; case 'W': bus_width = strtol(optarg, NULL, 0); if (bus_width < 0) { warnx("bus width %d is < 0", bus_width); retval = 1; goto ratecontrol_bailout; } change_settings = 1; break; default: break; } } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_pathinq) - sizeof(struct ccb_hdr)); /* * Grab path inquiry information, so we can determine whether * or not the initiator is capable of the things that the user * requests. */ ccb->ccb_h.func_code = XPT_PATH_INQ; if (cam_send_ccb(device, ccb) < 0) { perror("error sending XPT_PATH_INQ CCB"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto ratecontrol_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_PATH_INQ CCB failed"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto ratecontrol_bailout; } bcopy(&ccb->cpi, &cpi, sizeof(struct ccb_pathinq)); bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_trans_settings) - sizeof(struct ccb_hdr)); if (quiet == 0) { fprintf(stdout, "%s parameters:\n", user_settings ? "User" : "Current"); } retval = get_print_cts(device, user_settings, quiet, &ccb->cts); if (retval != 0) goto ratecontrol_bailout; if (arglist & CAM_ARG_VERBOSE) cpi_print(&cpi); if (change_settings) { int didsettings = 0; struct ccb_trans_settings_spi *spi = NULL; struct ccb_trans_settings_ata *ata = NULL; struct ccb_trans_settings_sata *sata = NULL; struct ccb_trans_settings_scsi *scsi = NULL; if (ccb->cts.transport == XPORT_SPI) spi = &ccb->cts.xport_specific.spi; if (ccb->cts.transport == XPORT_ATA) ata = &ccb->cts.xport_specific.ata; if (ccb->cts.transport == XPORT_SATA) sata = &ccb->cts.xport_specific.sata; if (ccb->cts.protocol == PROTO_SCSI) scsi = &ccb->cts.proto_specific.scsi; ccb->cts.xport_specific.valid = 0; ccb->cts.proto_specific.valid = 0; if (spi && disc_enable != -1) { spi->valid |= CTS_SPI_VALID_DISC; if (disc_enable == 0) spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB; else spi->flags |= CTS_SPI_FLAGS_DISC_ENB; } if (scsi && tag_enable != -1) { if ((cpi.hba_inquiry & PI_TAG_ABLE) == 0) { warnx("HBA does not support tagged queueing, " "so you cannot modify tag settings"); retval = 1; goto ratecontrol_bailout; } scsi->valid |= CTS_SCSI_VALID_TQ; if (tag_enable == 0) scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; else scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; didsettings++; } if (spi && offset != -1) { if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) { warnx("HBA is not capable of changing offset"); retval = 1; goto ratecontrol_bailout; } spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->sync_offset = offset; didsettings++; } if (spi && syncrate != -1) { int prelim_sync_period; u_int freq; if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) { warnx("HBA is not capable of changing " "transfer rates"); retval = 1; goto ratecontrol_bailout; } spi->valid |= CTS_SPI_VALID_SYNC_RATE; /* * The sync rate the user gives us is in MHz. * We need to translate it into KHz for this * calculation. */ syncrate *= 1000; /* * Next, we calculate a "preliminary" sync period * in tenths of a nanosecond. */ if (syncrate == 0) prelim_sync_period = 0; else prelim_sync_period = 10000000 / syncrate; spi->sync_period = scsi_calc_syncparam(prelim_sync_period); freq = scsi_calc_syncsrate(spi->sync_period); didsettings++; } if (sata && syncrate != -1) { if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) { warnx("HBA is not capable of changing " "transfer rates"); retval = 1; goto ratecontrol_bailout; } sata->revision = ata_speed2revision(syncrate * 100); if (sata->revision < 0) { warnx("Invalid rate %f", syncrate); retval = 1; goto ratecontrol_bailout; } sata->valid |= CTS_SATA_VALID_REVISION; didsettings++; } if ((ata || sata) && mode != -1) { if ((cpi.hba_inquiry & PI_SDTR_ABLE) == 0) { warnx("HBA is not capable of changing " "transfer rates"); retval = 1; goto ratecontrol_bailout; } if (ata) { ata->mode = mode; ata->valid |= CTS_ATA_VALID_MODE; } else { sata->mode = mode; sata->valid |= CTS_SATA_VALID_MODE; } didsettings++; } /* * The bus_width argument goes like this: * 0 == 8 bit * 1 == 16 bit * 2 == 32 bit * Therefore, if you shift the number of bits given on the * command line right by 4, you should get the correct * number. */ if (spi && bus_width != -1) { /* * We might as well validate things here with a * decipherable error message, rather than what * will probably be an indecipherable error message * by the time it gets back to us. */ if ((bus_width == 16) && ((cpi.hba_inquiry & PI_WIDE_16) == 0)) { warnx("HBA does not support 16 bit bus width"); retval = 1; goto ratecontrol_bailout; } else if ((bus_width == 32) && ((cpi.hba_inquiry & PI_WIDE_32) == 0)) { warnx("HBA does not support 32 bit bus width"); retval = 1; goto ratecontrol_bailout; } else if ((bus_width != 8) && (bus_width != 16) && (bus_width != 32)) { warnx("Invalid bus width %d", bus_width); retval = 1; goto ratecontrol_bailout; } spi->valid |= CTS_SPI_VALID_BUS_WIDTH; spi->bus_width = bus_width >> 4; didsettings++; } if (didsettings == 0) { goto ratecontrol_bailout; } if (!user_settings && (ata || sata)) { warnx("You can modify only user settings for ATA/SATA"); retval = 1; goto ratecontrol_bailout; } ccb->ccb_h.func_code = XPT_SET_TRAN_SETTINGS; if (cam_send_ccb(device, ccb) < 0) { perror("error sending XPT_SET_TRAN_SETTINGS CCB"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto ratecontrol_bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("XPT_SET_TRANS_SETTINGS CCB failed"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto ratecontrol_bailout; } } if (send_tur) { retval = testunitready(device, retry_count, timeout, (arglist & CAM_ARG_VERBOSE) ? 0 : 1); /* * If the TUR didn't succeed, just bail. */ if (retval != 0) { if (quiet == 0) fprintf(stderr, "Test Unit Ready failed\n"); goto ratecontrol_bailout; } /* * If the user wants things quiet, there's no sense in * getting the transfer settings, if we're not going * to print them. */ if (quiet != 0) goto ratecontrol_bailout; fprintf(stdout, "New parameters:\n"); retval = get_print_cts(device, user_settings, 0, NULL); } ratecontrol_bailout: cam_freeccb(ccb); return(retval); } static int scsiformat(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; int c; int ycount = 0, quiet = 0; int error = 0, retval = 0; int use_timeout = 10800 * 1000; int immediate = 1; struct format_defect_list_header fh; u_int8_t *data_ptr = NULL; u_int32_t dxfer_len = 0; u_int8_t byte2 = 0; int num_warnings = 0; int reportonly = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("scsiformat: error allocating ccb"); return(1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch(c) { case 'q': quiet++; break; case 'r': reportonly = 1; break; case 'w': immediate = 0; break; case 'y': ycount++; break; } } if (reportonly) goto doreport; if (quiet == 0) { fprintf(stdout, "You are about to REMOVE ALL DATA from the " "following device:\n"); error = scsidoinquiry(device, argc, argv, combinedopt, retry_count, timeout); if (error != 0) { warnx("scsiformat: error sending inquiry"); goto scsiformat_bailout; } } if (ycount == 0) { if (!get_confirmation()) { error = 1; goto scsiformat_bailout; } } if (timeout != 0) use_timeout = timeout; if (quiet == 0) { fprintf(stdout, "Current format timeout is %d seconds\n", use_timeout / 1000); } /* * If the user hasn't disabled questions and didn't specify a * timeout on the command line, ask them if they want the current * timeout. */ if ((ycount == 0) && (timeout == 0)) { char str[1024]; int new_timeout = 0; fprintf(stdout, "Enter new timeout in seconds or press\n" "return to keep the current timeout [%d] ", use_timeout / 1000); if (fgets(str, sizeof(str), stdin) != NULL) { if (str[0] != '\0') new_timeout = atoi(str); } if (new_timeout != 0) { use_timeout = new_timeout * 1000; fprintf(stdout, "Using new timeout value %d\n", use_timeout / 1000); } } /* * Keep this outside the if block below to silence any unused * variable warnings. */ bzero(&fh, sizeof(fh)); /* * If we're in immediate mode, we've got to include the format * header */ if (immediate != 0) { fh.byte2 = FU_DLH_IMMED; data_ptr = (u_int8_t *)&fh; dxfer_len = sizeof(fh); byte2 = FU_FMT_DATA; } else if (quiet == 0) { fprintf(stdout, "Formatting..."); fflush(stdout); } scsi_format_unit(&ccb->csio, /* retries */ retry_count, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* byte2 */ byte2, /* ileave */ 0, /* data_ptr */ data_ptr, /* dxfer_len */ dxfer_len, /* sense_len */ SSD_FULL_SIZE, /* timeout */ use_timeout); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((immediate == 0) && ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP))) { const char errstr[] = "error sending format command"; if (retval < 0) warn(errstr); else warnx(errstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } error = 1; goto scsiformat_bailout; } /* * If we ran in non-immediate mode, we already checked for errors * above and printed out any necessary information. If we're in * immediate mode, we need to loop through and get status * information periodically. */ if (immediate == 0) { if (quiet == 0) { fprintf(stdout, "Format Complete\n"); } goto scsiformat_bailout; } doreport: do { cam_status status; bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); /* * There's really no need to do error recovery or * retries here, since we're just going to sit in a * loop and wait for the device to finish formatting. */ scsi_test_unit_ready(&ccb->csio, /* retries */ 0, /* cbfcnp */ NULL, /* tag_action */ MSG_SIMPLE_Q_TAG, /* sense_len */ SSD_FULL_SIZE, /* timeout */ 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; retval = cam_send_ccb(device, ccb); /* * If we get an error from the ioctl, bail out. SCSI * errors are expected. */ if (retval < 0) { warn("error sending CAMIOCOMMAND ioctl"); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } error = 1; goto scsiformat_bailout; } status = ccb->ccb_h.status & CAM_STATUS_MASK; if ((status != CAM_REQ_CMP) && (status == CAM_SCSI_STATUS_ERROR) && ((ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0)) { struct scsi_sense_data *sense; int error_code, sense_key, asc, ascq; sense = &ccb->csio.sense_data; scsi_extract_sense_len(sense, ccb->csio.sense_len - ccb->csio.sense_resid, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); /* * According to the SCSI-2 and SCSI-3 specs, a * drive that is in the middle of a format should * return NOT READY with an ASC of "logical unit * not ready, format in progress". The sense key * specific bytes will then be a progress indicator. */ if ((sense_key == SSD_KEY_NOT_READY) && (asc == 0x04) && (ascq == 0x04)) { uint8_t sks[3]; if ((scsi_get_sks(sense, ccb->csio.sense_len - ccb->csio.sense_resid, sks) == 0) && (quiet == 0)) { int val; u_int64_t percentage; val = scsi_2btoul(&sks[1]); percentage = 10000 * val; fprintf(stdout, "\rFormatting: %ju.%02u %% " "(%d/%d) done", (uintmax_t)(percentage / (0x10000 * 100)), (unsigned)((percentage / 0x10000) % 100), val, 0x10000); fflush(stdout); } else if ((quiet == 0) && (++num_warnings <= 1)) { warnx("Unexpected SCSI Sense Key " "Specific value returned " "during format:"); scsi_sense_print(device, &ccb->csio, stderr); warnx("Unable to print status " "information, but format will " "proceed."); warnx("will exit when format is " "complete"); } sleep(1); } else { warnx("Unexpected SCSI error during format"); cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); error = 1; goto scsiformat_bailout; } } else if (status != CAM_REQ_CMP) { warnx("Unexpected CAM status %#x", status); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); error = 1; goto scsiformat_bailout; } } while((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP); if (quiet == 0) fprintf(stdout, "\nFormat Complete\n"); scsiformat_bailout: cam_freeccb(ccb); return(error); } static int scsireportluns(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; int c, countonly, lunsonly; struct scsi_report_luns_data *lundata; int alloc_len; uint8_t report_type; uint32_t list_len, i, j; int retval; retval = 0; lundata = NULL; report_type = RPL_REPORT_DEFAULT; ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating ccb", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); countonly = 0; lunsonly = 0; while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'c': countonly++; break; case 'l': lunsonly++; break; case 'r': if (strcasecmp(optarg, "default") == 0) report_type = RPL_REPORT_DEFAULT; else if (strcasecmp(optarg, "wellknown") == 0) report_type = RPL_REPORT_WELLKNOWN; else if (strcasecmp(optarg, "all") == 0) report_type = RPL_REPORT_ALL; else { warnx("%s: invalid report type \"%s\"", __func__, optarg); retval = 1; goto bailout; } break; default: break; } } if ((countonly != 0) && (lunsonly != 0)) { warnx("%s: you can only specify one of -c or -l", __func__); retval = 1; goto bailout; } /* * According to SPC-4, the allocation length must be at least 16 * bytes -- enough for the header and one LUN. */ alloc_len = sizeof(*lundata) + 8; retry: lundata = malloc(alloc_len); if (lundata == NULL) { warn("%s: error mallocing %d bytes", __func__, alloc_len); retval = 1; goto bailout; } scsi_report_luns(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*select_report*/ report_type, /*rpl_buf*/ lundata, /*alloc_len*/ alloc_len, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { warn("error sending REPORT LUNS command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } list_len = scsi_4btoul(lundata->length); /* * If we need to list the LUNs, and our allocation * length was too short, reallocate and retry. */ if ((countonly == 0) && (list_len > (alloc_len - sizeof(*lundata)))) { alloc_len = list_len + sizeof(*lundata); free(lundata); goto retry; } if (lunsonly == 0) fprintf(stdout, "%u LUN%s found\n", list_len / 8, ((list_len / 8) > 1) ? "s" : ""); if (countonly != 0) goto bailout; for (i = 0; i < (list_len / 8); i++) { int no_more; no_more = 0; for (j = 0; j < sizeof(lundata->luns[i].lundata); j += 2) { if (j != 0) fprintf(stdout, ","); switch (lundata->luns[i].lundata[j] & RPL_LUNDATA_ATYP_MASK) { case RPL_LUNDATA_ATYP_PERIPH: if ((lundata->luns[i].lundata[j] & RPL_LUNDATA_PERIPH_BUS_MASK) != 0) fprintf(stdout, "%d:", lundata->luns[i].lundata[j] & RPL_LUNDATA_PERIPH_BUS_MASK); else if ((j == 0) && ((lundata->luns[i].lundata[j+2] & RPL_LUNDATA_PERIPH_BUS_MASK) == 0)) no_more = 1; fprintf(stdout, "%d", lundata->luns[i].lundata[j+1]); break; case RPL_LUNDATA_ATYP_FLAT: { uint8_t tmplun[2]; tmplun[0] = lundata->luns[i].lundata[j] & RPL_LUNDATA_FLAT_LUN_MASK; tmplun[1] = lundata->luns[i].lundata[j+1]; fprintf(stdout, "%d", scsi_2btoul(tmplun)); no_more = 1; break; } case RPL_LUNDATA_ATYP_LUN: fprintf(stdout, "%d:%d:%d", (lundata->luns[i].lundata[j+1] & RPL_LUNDATA_LUN_BUS_MASK) >> 5, lundata->luns[i].lundata[j] & RPL_LUNDATA_LUN_TARG_MASK, lundata->luns[i].lundata[j+1] & RPL_LUNDATA_LUN_LUN_MASK); break; case RPL_LUNDATA_ATYP_EXTLUN: { int field_len, field_len_code, eam_code; eam_code = lundata->luns[i].lundata[j] & RPL_LUNDATA_EXT_EAM_MASK; field_len_code = (lundata->luns[i].lundata[j] & RPL_LUNDATA_EXT_LEN_MASK) >> 4; field_len = field_len_code * 2; if ((eam_code == RPL_LUNDATA_EXT_EAM_WK) && (field_len_code == 0x00)) { fprintf(stdout, "%d", lundata->luns[i].lundata[j+1]); } else if ((eam_code == RPL_LUNDATA_EXT_EAM_NOT_SPEC) && (field_len_code == 0x03)) { uint8_t tmp_lun[8]; /* * This format takes up all 8 bytes. * If we aren't starting at offset 0, * that's a bug. */ if (j != 0) { fprintf(stdout, "Invalid " "offset %d for " "Extended LUN not " "specified format", j); no_more = 1; break; } bzero(tmp_lun, sizeof(tmp_lun)); bcopy(&lundata->luns[i].lundata[j+1], &tmp_lun[1], sizeof(tmp_lun) - 1); fprintf(stdout, "%#jx", (intmax_t)scsi_8btou64(tmp_lun)); no_more = 1; } else { fprintf(stderr, "Unknown Extended LUN" "Address method %#x, length " "code %#x", eam_code, field_len_code); no_more = 1; } break; } default: fprintf(stderr, "Unknown LUN address method " "%#x\n", lundata->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK); break; } /* * For the flat addressing method, there are no * other levels after it. */ if (no_more != 0) break; } fprintf(stdout, "\n"); } bailout: cam_freeccb(ccb); free(lundata); return (retval); } static int scsireadcapacity(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; int blocksizeonly, humanize, numblocks, quiet, sizeonly, baseten; struct scsi_read_capacity_data rcap; struct scsi_read_capacity_data_long rcaplong; uint64_t maxsector; uint32_t block_len; int retval; int c; blocksizeonly = 0; humanize = 0; numblocks = 0; quiet = 0; sizeonly = 0; baseten = 0; retval = 0; ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating ccb", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'b': blocksizeonly++; break; case 'h': humanize++; baseten = 0; break; case 'H': humanize++; baseten++; break; case 'N': numblocks++; break; case 'q': quiet++; break; case 's': sizeonly++; break; default: break; } } if ((blocksizeonly != 0) && (numblocks != 0)) { warnx("%s: you can only specify one of -b or -N", __func__); retval = 1; goto bailout; } if ((blocksizeonly != 0) && (sizeonly != 0)) { warnx("%s: you can only specify one of -b or -s", __func__); retval = 1; goto bailout; } if ((humanize != 0) && (quiet != 0)) { warnx("%s: you can only specify one of -h/-H or -q", __func__); retval = 1; goto bailout; } if ((humanize != 0) && (blocksizeonly != 0)) { warnx("%s: you can only specify one of -h/-H or -b", __func__); retval = 1; goto bailout; } scsi_read_capacity(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*tag_action*/ MSG_SIMPLE_Q_TAG, &rcap, SSD_FULL_SIZE, /*timeout*/ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { warn("error sending READ CAPACITY command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } maxsector = scsi_4btoul(rcap.addr); block_len = scsi_4btoul(rcap.length); /* * A last block of 2^32-1 means that the true capacity is over 2TB, * and we need to issue the long READ CAPACITY to get the real * capacity. Otherwise, we're all set. */ if (maxsector != 0xffffffff) goto do_print; scsi_read_capacity_16(&ccb->csio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*tag_action*/ MSG_SIMPLE_Q_TAG, /*lba*/ 0, /*reladdr*/ 0, /*pmi*/ 0, &rcaplong, /*sense_len*/ SSD_FULL_SIZE, /*timeout*/ timeout ? timeout : 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { warn("error sending READ CAPACITY (16) command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } maxsector = scsi_8btou64(rcaplong.addr); block_len = scsi_4btoul(rcaplong.length); do_print: if (blocksizeonly == 0) { /* * Humanize implies !quiet, and also implies numblocks. */ if (humanize != 0) { char tmpstr[6]; int64_t tmpbytes; int ret; tmpbytes = (maxsector + 1) * block_len; ret = humanize_number(tmpstr, sizeof(tmpstr), tmpbytes, "", HN_AUTOSCALE, HN_B | HN_DECIMAL | ((baseten != 0) ? HN_DIVISOR_1000 : 0)); if (ret == -1) { warnx("%s: humanize_number failed!", __func__); retval = 1; goto bailout; } fprintf(stdout, "Device Size: %s%s", tmpstr, (sizeonly == 0) ? ", " : "\n"); } else if (numblocks != 0) { fprintf(stdout, "%s%ju%s", (quiet == 0) ? "Blocks: " : "", (uintmax_t)maxsector + 1, (sizeonly == 0) ? ", " : "\n"); } else { fprintf(stdout, "%s%ju%s", (quiet == 0) ? "Last Block: " : "", (uintmax_t)maxsector, (sizeonly == 0) ? ", " : "\n"); } } if (sizeonly == 0) fprintf(stdout, "%s%u%s\n", (quiet == 0) ? "Block Length: " : "", block_len, (quiet == 0) ? " bytes" : ""); bailout: cam_freeccb(ccb); return (retval); } static int smpcmd(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { int c, error; union ccb *ccb; uint8_t *smp_request = NULL, *smp_response = NULL; int request_size = 0, response_size = 0; int fd_request = 0, fd_response = 0; char *datastr = NULL; struct get_hook hook; int retval; int flags = 0; /* * Note that at the moment we don't support sending SMP CCBs to * devices that aren't probed by CAM. */ ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'R': arglist |= CAM_ARG_CMD_IN; response_size = strtol(optarg, NULL, 0); if (response_size <= 0) { warnx("invalid number of response bytes %d", response_size); error = 1; goto smpcmd_bailout; } hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; optind++; datastr = cget(&hook, NULL); /* * If the user supplied "-" instead of a format, he * wants the data to be written to stdout. */ if ((datastr != NULL) && (datastr[0] == '-')) fd_response = 1; smp_response = (u_int8_t *)malloc(response_size); if (smp_response == NULL) { warn("can't malloc memory for SMP response"); error = 1; goto smpcmd_bailout; } break; case 'r': arglist |= CAM_ARG_CMD_OUT; request_size = strtol(optarg, NULL, 0); if (request_size <= 0) { warnx("invalid number of request bytes %d", request_size); error = 1; goto smpcmd_bailout; } hook.argc = argc - optind; hook.argv = argv + optind; hook.got = 0; datastr = cget(&hook, NULL); smp_request = (u_int8_t *)malloc(request_size); if (smp_request == NULL) { warn("can't malloc memory for SMP request"); error = 1; goto smpcmd_bailout; } bzero(smp_request, request_size); /* * If the user supplied "-" instead of a format, he * wants the data to be read from stdin. */ if ((datastr != NULL) && (datastr[0] == '-')) fd_request = 1; else buff_encode_visit(smp_request, request_size, datastr, iget, &hook); optind += hook.got; break; default: break; } } /* * If fd_data is set, and we're writing to the device, we need to * read the data the user wants written from stdin. */ if ((fd_request == 1) && (arglist & CAM_ARG_CMD_OUT)) { ssize_t amt_read; int amt_to_read = request_size; u_int8_t *buf_ptr = smp_request; for (amt_read = 0; amt_to_read > 0; amt_read = read(STDIN_FILENO, buf_ptr, amt_to_read)) { if (amt_read == -1) { warn("error reading data from stdin"); error = 1; goto smpcmd_bailout; } amt_to_read -= amt_read; buf_ptr += amt_read; } } if (((arglist & CAM_ARG_CMD_IN) == 0) || ((arglist & CAM_ARG_CMD_OUT) == 0)) { warnx("%s: need both the request (-r) and response (-R) " "arguments", __func__); error = 1; goto smpcmd_bailout; } flags |= CAM_DEV_QFRZDIS; cam_fill_smpio(&ccb->smpio, /*retries*/ retry_count, /*cbfcnp*/ NULL, /*flags*/ flags, /*smp_request*/ smp_request, /*smp_request_len*/ request_size, /*smp_response*/ smp_response, /*smp_response_len*/ response_size, /*timeout*/ timeout ? timeout : 5000); ccb->smpio.flags = SMP_FLAG_NONE; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } } if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) && (response_size > 0)) { if (fd_response == 0) { buff_decode_visit(smp_response, response_size, datastr, arg_put, NULL); fprintf(stdout, "\n"); } else { ssize_t amt_written; int amt_to_write = response_size; u_int8_t *buf_ptr = smp_response; for (amt_written = 0; (amt_to_write > 0) && (amt_written = write(STDOUT_FILENO, buf_ptr, amt_to_write)) > 0;){ amt_to_write -= amt_written; buf_ptr += amt_written; } if (amt_written == -1) { warn("error writing data to stdout"); error = 1; goto smpcmd_bailout; } else if ((amt_written == 0) && (amt_to_write > 0)) { warnx("only wrote %u bytes out of %u", response_size - amt_to_write, response_size); } } } smpcmd_bailout: if (ccb != NULL) cam_freeccb(ccb); if (smp_request != NULL) free(smp_request); if (smp_response != NULL) free(smp_response); return (error); } static int smpreportgeneral(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; struct smp_report_general_request *request = NULL; struct smp_report_general_response *response = NULL; struct sbuf *sb = NULL; int error = 0; int c, long_response = 0; int retval; /* * Note that at the moment we don't support sending SMP CCBs to * devices that aren't probed by CAM. */ ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'l': long_response = 1; break; default: break; } } request = malloc(sizeof(*request)); if (request == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*request)); error = 1; goto bailout; } response = malloc(sizeof(*response)); if (response == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*response)); error = 1; goto bailout; } try_long: smp_report_general(&ccb->smpio, retry_count, /*cbfcnp*/ NULL, request, /*request_len*/ sizeof(*request), (uint8_t *)response, /*response_len*/ sizeof(*response), /*long_response*/ long_response, timeout); if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } error = 1; goto bailout; } /* * If the device supports the long response bit, try again and see * if we can get all of the data. */ if ((response->long_response & SMP_RG_LONG_RESPONSE) && (long_response == 0)) { ccb->ccb_h.status = CAM_REQ_INPROG; bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); long_response = 1; goto try_long; } /* * XXX KDM detect and decode SMP errors here. */ sb = sbuf_new_auto(); if (sb == NULL) { warnx("%s: error allocating sbuf", __func__); goto bailout; } smp_report_general_sbuf(response, sizeof(*response), sb); sbuf_finish(sb); printf("%s", sbuf_data(sb)); bailout: if (ccb != NULL) cam_freeccb(ccb); if (request != NULL) free(request); if (response != NULL) free(response); if (sb != NULL) sbuf_delete(sb); return (error); } static struct camcontrol_opts phy_ops[] = { {"nop", SMP_PC_PHY_OP_NOP, CAM_ARG_NONE, NULL}, {"linkreset", SMP_PC_PHY_OP_LINK_RESET, CAM_ARG_NONE, NULL}, {"hardreset", SMP_PC_PHY_OP_HARD_RESET, CAM_ARG_NONE, NULL}, {"disable", SMP_PC_PHY_OP_DISABLE, CAM_ARG_NONE, NULL}, {"clearerrlog", SMP_PC_PHY_OP_CLEAR_ERR_LOG, CAM_ARG_NONE, NULL}, {"clearaffiliation", SMP_PC_PHY_OP_CLEAR_AFFILIATON, CAM_ARG_NONE,NULL}, {"sataportsel", SMP_PC_PHY_OP_TRANS_SATA_PSS, CAM_ARG_NONE, NULL}, {"clearitnl", SMP_PC_PHY_OP_CLEAR_STP_ITN_LS, CAM_ARG_NONE, NULL}, {"setdevname", SMP_PC_PHY_OP_SET_ATT_DEV_NAME, CAM_ARG_NONE, NULL}, {NULL, 0, 0, NULL} }; static int smpphycontrol(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; struct smp_phy_control_request *request = NULL; struct smp_phy_control_response *response = NULL; int long_response = 0; int retval = 0; int phy = -1; uint32_t phy_operation = SMP_PC_PHY_OP_NOP; int phy_op_set = 0; uint64_t attached_dev_name = 0; int dev_name_set = 0; uint32_t min_plr = 0, max_plr = 0; uint32_t pp_timeout_val = 0; int slumber_partial = 0; int set_pp_timeout_val = 0; int c; /* * Note that at the moment we don't support sending SMP CCBs to * devices that aren't probed by CAM. */ ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'a': case 'A': case 's': case 'S': { int enable = -1; if (strcasecmp(optarg, "enable") == 0) enable = 1; else if (strcasecmp(optarg, "disable") == 0) enable = 2; else { warnx("%s: Invalid argument %s", __func__, optarg); retval = 1; goto bailout; } switch (c) { case 's': slumber_partial |= enable << SMP_PC_SAS_SLUMBER_SHIFT; break; case 'S': slumber_partial |= enable << SMP_PC_SAS_PARTIAL_SHIFT; break; case 'a': slumber_partial |= enable << SMP_PC_SATA_SLUMBER_SHIFT; break; case 'A': slumber_partial |= enable << SMP_PC_SATA_PARTIAL_SHIFT; break; default: warnx("%s: programmer error", __func__); retval = 1; goto bailout; break; /*NOTREACHED*/ } break; } case 'd': attached_dev_name = (uintmax_t)strtoumax(optarg, NULL,0); dev_name_set = 1; break; case 'l': long_response = 1; break; case 'm': /* * We don't do extensive checking here, so this * will continue to work when new speeds come out. */ min_plr = strtoul(optarg, NULL, 0); if ((min_plr == 0) || (min_plr > 0xf)) { warnx("%s: invalid link rate %x", __func__, min_plr); retval = 1; goto bailout; } break; case 'M': /* * We don't do extensive checking here, so this * will continue to work when new speeds come out. */ max_plr = strtoul(optarg, NULL, 0); if ((max_plr == 0) || (max_plr > 0xf)) { warnx("%s: invalid link rate %x", __func__, max_plr); retval = 1; goto bailout; } break; case 'o': { camcontrol_optret optreturn; cam_argmask argnums; const char *subopt; if (phy_op_set != 0) { warnx("%s: only one phy operation argument " "(-o) allowed", __func__); retval = 1; goto bailout; } phy_op_set = 1; /* * Allow the user to specify the phy operation * numerically, as well as with a name. This will * future-proof it a bit, so options that are added * in future specs can be used. */ if (isdigit(optarg[0])) { phy_operation = strtoul(optarg, NULL, 0); if ((phy_operation == 0) || (phy_operation > 0xff)) { warnx("%s: invalid phy operation %#x", __func__, phy_operation); retval = 1; goto bailout; } break; } optreturn = getoption(phy_ops, optarg, &phy_operation, &argnums, &subopt); if (optreturn == CC_OR_AMBIGUOUS) { warnx("%s: ambiguous option %s", __func__, optarg); usage(0); retval = 1; goto bailout; } else if (optreturn == CC_OR_NOT_FOUND) { warnx("%s: option %s not found", __func__, optarg); usage(0); retval = 1; goto bailout; } break; } case 'p': phy = atoi(optarg); break; case 'T': pp_timeout_val = strtoul(optarg, NULL, 0); if (pp_timeout_val > 15) { warnx("%s: invalid partial pathway timeout " "value %u, need a value less than 16", __func__, pp_timeout_val); retval = 1; goto bailout; } set_pp_timeout_val = 1; break; default: break; } } if (phy == -1) { warnx("%s: a PHY (-p phy) argument is required",__func__); retval = 1; goto bailout; } if (((dev_name_set != 0) && (phy_operation != SMP_PC_PHY_OP_SET_ATT_DEV_NAME)) || ((phy_operation == SMP_PC_PHY_OP_SET_ATT_DEV_NAME) && (dev_name_set == 0))) { warnx("%s: -d name and -o setdevname arguments both " "required to set device name", __func__); retval = 1; goto bailout; } request = malloc(sizeof(*request)); if (request == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*request)); retval = 1; goto bailout; } response = malloc(sizeof(*response)); if (response == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*request)); retval = 1; goto bailout; } smp_phy_control(&ccb->smpio, retry_count, /*cbfcnp*/ NULL, request, sizeof(*request), (uint8_t *)response, sizeof(*response), long_response, /*expected_exp_change_count*/ 0, phy, phy_operation, (set_pp_timeout_val != 0) ? 1 : 0, attached_dev_name, min_plr, max_plr, slumber_partial, pp_timeout_val, timeout); if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { /* * Use CAM_EPF_NORMAL so we only get one line of * SMP command decoding. */ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_NORMAL, stderr); } retval = 1; goto bailout; } /* XXX KDM print out something here for success? */ bailout: if (ccb != NULL) cam_freeccb(ccb); if (request != NULL) free(request); if (response != NULL) free(response); return (retval); } static int smpmaninfo(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; struct smp_report_manuf_info_request request; struct smp_report_manuf_info_response response; struct sbuf *sb = NULL; int long_response = 0; int retval = 0; int c; /* * Note that at the moment we don't support sending SMP CCBs to * devices that aren't probed by CAM. */ ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'l': long_response = 1; break; default: break; } } bzero(&request, sizeof(request)); bzero(&response, sizeof(response)); smp_report_manuf_info(&ccb->smpio, retry_count, /*cbfcnp*/ NULL, &request, sizeof(request), (uint8_t *)&response, sizeof(response), long_response, timeout); if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto bailout; } sb = sbuf_new_auto(); if (sb == NULL) { warnx("%s: error allocating sbuf", __func__); goto bailout; } smp_report_manuf_info_sbuf(&response, sizeof(response), sb); sbuf_finish(sb); printf("%s", sbuf_data(sb)); bailout: if (ccb != NULL) cam_freeccb(ccb); if (sb != NULL) sbuf_delete(sb); return (retval); } static int getdevid(struct cam_devitem *item) { int retval = 0; union ccb *ccb = NULL; struct cam_device *dev; dev = cam_open_btl(item->dev_match.path_id, item->dev_match.target_id, item->dev_match.target_lun, O_RDWR, NULL); if (dev == NULL) { warnx("%s", cam_errbuf); retval = 1; goto bailout; } item->device_id_len = 0; ccb = cam_getccb(dev); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); retval = 1; goto bailout; } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); /* * On the first try, we just probe for the size of the data, and * then allocate that much memory and try again. */ retry: ccb->ccb_h.func_code = XPT_DEV_ADVINFO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->cdai.flags = 0; ccb->cdai.buftype = CDAI_TYPE_SCSI_DEVID; ccb->cdai.bufsiz = item->device_id_len; if (item->device_id_len != 0) ccb->cdai.buf = (uint8_t *)item->device_id; if (cam_send_ccb(dev, ccb) < 0) { warn("%s: error sending XPT_GDEV_ADVINFO CCB", __func__); retval = 1; goto bailout; } if (ccb->ccb_h.status != CAM_REQ_CMP) { warnx("%s: CAM status %#x", __func__, ccb->ccb_h.status); retval = 1; goto bailout; } if (item->device_id_len == 0) { /* * This is our first time through. Allocate the buffer, * and then go back to get the data. */ if (ccb->cdai.provsiz == 0) { warnx("%s: invalid .provsiz field returned with " "XPT_GDEV_ADVINFO CCB", __func__); retval = 1; goto bailout; } item->device_id_len = ccb->cdai.provsiz; item->device_id = malloc(item->device_id_len); if (item->device_id == NULL) { warn("%s: unable to allocate %d bytes", __func__, item->device_id_len); retval = 1; goto bailout; } ccb->ccb_h.status = CAM_REQ_INPROG; goto retry; } bailout: if (dev != NULL) cam_close_device(dev); if (ccb != NULL) cam_freeccb(ccb); return (retval); } /* * XXX KDM merge this code with getdevtree()? */ static int buildbusdevlist(struct cam_devlist *devlist) { union ccb ccb; int bufsize, fd = -1; struct dev_match_pattern *patterns; struct cam_devitem *item = NULL; int skip_device = 0; int retval = 0; if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { warn("couldn't open %s", XPT_DEVICE); return(1); } bzero(&ccb, sizeof(union ccb)); ccb.ccb_h.path_id = CAM_XPT_PATH_ID; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; ccb.ccb_h.func_code = XPT_DEV_MATCH; bufsize = sizeof(struct dev_match_result) * 100; ccb.cdm.match_buf_len = bufsize; ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); if (ccb.cdm.matches == NULL) { warnx("can't malloc memory for matches"); close(fd); return(1); } ccb.cdm.num_matches = 0; ccb.cdm.num_patterns = 2; ccb.cdm.pattern_buf_len = sizeof(struct dev_match_pattern) * ccb.cdm.num_patterns; patterns = (struct dev_match_pattern *)malloc(ccb.cdm.pattern_buf_len); if (patterns == NULL) { warnx("can't malloc memory for patterns"); retval = 1; goto bailout; } ccb.cdm.patterns = patterns; bzero(patterns, ccb.cdm.pattern_buf_len); patterns[0].type = DEV_MATCH_DEVICE; patterns[0].pattern.device_pattern.flags = DEV_MATCH_PATH; patterns[0].pattern.device_pattern.path_id = devlist->path_id; patterns[1].type = DEV_MATCH_PERIPH; patterns[1].pattern.periph_pattern.flags = PERIPH_MATCH_PATH; patterns[1].pattern.periph_pattern.path_id = devlist->path_id; /* * We do the ioctl multiple times if necessary, in case there are * more than 100 nodes in the EDT. */ do { unsigned int i; if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { warn("error sending CAMIOCOMMAND ioctl"); retval = 1; goto bailout; } if ((ccb.ccb_h.status != CAM_REQ_CMP) || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { warnx("got CAM error %#x, CDM error %d\n", ccb.ccb_h.status, ccb.cdm.status); retval = 1; goto bailout; } for (i = 0; i < ccb.cdm.num_matches; i++) { switch (ccb.cdm.matches[i].type) { case DEV_MATCH_DEVICE: { struct device_match_result *dev_result; dev_result = &ccb.cdm.matches[i].result.device_result; if (dev_result->flags & DEV_RESULT_UNCONFIGURED) { skip_device = 1; break; } else skip_device = 0; item = malloc(sizeof(*item)); if (item == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*item)); retval = 1; goto bailout; } bzero(item, sizeof(*item)); bcopy(dev_result, &item->dev_match, sizeof(*dev_result)); STAILQ_INSERT_TAIL(&devlist->dev_queue, item, links); if (getdevid(item) != 0) { retval = 1; goto bailout; } break; } case DEV_MATCH_PERIPH: { struct periph_match_result *periph_result; periph_result = &ccb.cdm.matches[i].result.periph_result; if (skip_device != 0) break; item->num_periphs++; item->periph_matches = realloc( item->periph_matches, item->num_periphs * sizeof(struct periph_match_result)); if (item->periph_matches == NULL) { warn("%s: error allocating periph " "list", __func__); retval = 1; goto bailout; } bcopy(periph_result, &item->periph_matches[ item->num_periphs - 1], sizeof(*periph_result)); break; } default: fprintf(stderr, "%s: unexpected match " "type %d\n", __func__, ccb.cdm.matches[i].type); retval = 1; goto bailout; break; /*NOTREACHED*/ } } } while ((ccb.ccb_h.status == CAM_REQ_CMP) && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); bailout: if (fd != -1) close(fd); free(patterns); free(ccb.cdm.matches); if (retval != 0) freebusdevlist(devlist); return (retval); } static void freebusdevlist(struct cam_devlist *devlist) { struct cam_devitem *item, *item2; STAILQ_FOREACH_SAFE(item, &devlist->dev_queue, links, item2) { STAILQ_REMOVE(&devlist->dev_queue, item, cam_devitem, links); free(item->device_id); free(item->periph_matches); free(item); } } static struct cam_devitem * findsasdevice(struct cam_devlist *devlist, uint64_t sasaddr) { struct cam_devitem *item; STAILQ_FOREACH(item, &devlist->dev_queue, links) { uint8_t *item_addr; /* * XXX KDM look for LUN IDs as well? */ item_addr = scsi_get_devid(item->device_id, item->device_id_len, scsi_devid_is_sas_target); if (item_addr == NULL) continue; if (scsi_8btou64(item_addr) == sasaddr) return (item); } return (NULL); } static int smpphylist(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { struct smp_report_general_request *rgrequest = NULL; struct smp_report_general_response *rgresponse = NULL; struct smp_discover_request *disrequest = NULL; struct smp_discover_response *disresponse = NULL; struct cam_devlist devlist; union ccb *ccb; int long_response = 0; int num_phys = 0; int quiet = 0; int retval; int i, c; /* * Note that at the moment we don't support sending SMP CCBs to * devices that aren't probed by CAM. */ ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating CCB", __func__); return (1); } bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); rgrequest = malloc(sizeof(*rgrequest)); if (rgrequest == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*rgrequest)); retval = 1; goto bailout; } rgresponse = malloc(sizeof(*rgresponse)); if (rgresponse == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*rgresponse)); retval = 1; goto bailout; } while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 'l': long_response = 1; break; case 'q': quiet = 1; break; default: break; } } smp_report_general(&ccb->smpio, retry_count, /*cbfcnp*/ NULL, rgrequest, /*request_len*/ sizeof(*rgrequest), (uint8_t *)rgresponse, /*response_len*/ sizeof(*rgresponse), /*long_response*/ long_response, timeout); ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (((retval = cam_send_ccb(device, ccb)) < 0) || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto bailout; } num_phys = rgresponse->num_phys; if (num_phys == 0) { if (quiet == 0) fprintf(stdout, "%s: No Phys reported\n", __func__); retval = 1; goto bailout; } STAILQ_INIT(&devlist.dev_queue); devlist.path_id = device->path_id; retval = buildbusdevlist(&devlist); if (retval != 0) goto bailout; if (quiet == 0) { fprintf(stdout, "%d PHYs:\n", num_phys); fprintf(stdout, "PHY Attached SAS Address\n"); } disrequest = malloc(sizeof(*disrequest)); if (disrequest == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*disrequest)); retval = 1; goto bailout; } disresponse = malloc(sizeof(*disresponse)); if (disresponse == NULL) { warn("%s: unable to allocate %zd bytes", __func__, sizeof(*disresponse)); retval = 1; goto bailout; } for (i = 0; i < num_phys; i++) { struct cam_devitem *item; struct device_match_result *dev_match; char vendor[16], product[48], revision[16]; char tmpstr[256]; int j; bzero(&(&ccb->ccb_h)[1], sizeof(union ccb) - sizeof(struct ccb_hdr)); ccb->ccb_h.status = CAM_REQ_INPROG; ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; smp_discover(&ccb->smpio, retry_count, /*cbfcnp*/ NULL, disrequest, sizeof(*disrequest), (uint8_t *)disresponse, sizeof(*disresponse), long_response, /*ignore_zone_group*/ 0, /*phy*/ i, timeout); if (((retval = cam_send_ccb(device, ccb)) < 0) || (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) && (disresponse->function_result != SMP_FR_PHY_VACANT))) { const char warnstr[] = "error sending command"; if (retval < 0) warn(warnstr); else warnx(warnstr); if (arglist & CAM_ARG_VERBOSE) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); } retval = 1; goto bailout; } if (disresponse->function_result == SMP_FR_PHY_VACANT) { if (quiet == 0) fprintf(stdout, "%3d \n", i); continue; } item = findsasdevice(&devlist, scsi_8btou64(disresponse->attached_sas_address)); if ((quiet == 0) || (item != NULL)) { fprintf(stdout, "%3d 0x%016jx", i, (uintmax_t)scsi_8btou64( disresponse->attached_sas_address)); if (item == NULL) { fprintf(stdout, "\n"); continue; } } else if (quiet != 0) continue; dev_match = &item->dev_match; if (dev_match->protocol == PROTO_SCSI) { cam_strvis(vendor, dev_match->inq_data.vendor, sizeof(dev_match->inq_data.vendor), sizeof(vendor)); cam_strvis(product, dev_match->inq_data.product, sizeof(dev_match->inq_data.product), sizeof(product)); cam_strvis(revision, dev_match->inq_data.revision, sizeof(dev_match->inq_data.revision), sizeof(revision)); sprintf(tmpstr, "<%s %s %s>", vendor, product, revision); } else if ((dev_match->protocol == PROTO_ATA) || (dev_match->protocol == PROTO_SATAPM)) { cam_strvis(product, dev_match->ident_data.model, sizeof(dev_match->ident_data.model), sizeof(product)); cam_strvis(revision, dev_match->ident_data.revision, sizeof(dev_match->ident_data.revision), sizeof(revision)); sprintf(tmpstr, "<%s %s>", product, revision); } else { sprintf(tmpstr, "<>"); } fprintf(stdout, " %-33s ", tmpstr); /* * If we have 0 periphs, that's a bug... */ if (item->num_periphs == 0) { fprintf(stdout, "\n"); continue; } fprintf(stdout, "("); for (j = 0; j < item->num_periphs; j++) { if (j > 0) fprintf(stdout, ","); fprintf(stdout, "%s%d", item->periph_matches[j].periph_name, item->periph_matches[j].unit_number); } fprintf(stdout, ")\n"); } bailout: if (ccb != NULL) cam_freeccb(ccb); free(rgrequest); free(rgresponse); free(disrequest); free(disresponse); freebusdevlist(&devlist); return (retval); } static int atapm(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { union ccb *ccb; int retval = 0; int t = -1; int c; u_char cmd, sc; ccb = cam_getccb(device); if (ccb == NULL) { warnx("%s: error allocating ccb", __func__); return (1); } while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { case 't': t = atoi(optarg); break; default: break; } } if (strcmp(argv[1], "idle") == 0) { if (t == -1) cmd = ATA_IDLE_IMMEDIATE; else cmd = ATA_IDLE_CMD; } else if (strcmp(argv[1], "standby") == 0) { if (t == -1) cmd = ATA_STANDBY_IMMEDIATE; else cmd = ATA_STANDBY_CMD; } else { cmd = ATA_SLEEP; t = -1; } if (t < 0) sc = 0; else if (t <= (240 * 5)) sc = (t + 4) / 5; else if (t <= (252 * 5)) /* special encoding for 21 minutes */ sc = 252; else if (t <= (11 * 30 * 60)) sc = (t - 1) / (30 * 60) + 241; else sc = 253; cam_fill_ataio(&ccb->ataio, retry_count, NULL, /*flags*/CAM_DIR_NONE, MSG_SIMPLE_Q_TAG, /*data_ptr*/NULL, /*dxfer_len*/0, timeout ? timeout : 30 * 1000); ata_28bit_cmd(&ccb->ataio, cmd, 0, 0, sc); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (arglist & CAM_ARG_ERR_RECOVER) ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; if (cam_send_ccb(device, ccb) < 0) { warn("error sending command"); if (arglist & CAM_ARG_VERBOSE) cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); retval = 1; goto bailout; } bailout: cam_freeccb(ccb); return (retval); } #endif /* MINIMALISTIC */ void usage(int verbose) { fprintf(verbose ? stdout : stderr, "usage: camcontrol [device id][generic args][command args]\n" " camcontrol devlist [-v]\n" #ifndef MINIMALISTIC " camcontrol periphlist [dev_id][-n dev_name] [-u unit]\n" " camcontrol tur [dev_id][generic args]\n" " camcontrol inquiry [dev_id][generic args] [-D] [-S] [-R]\n" " camcontrol identify [dev_id][generic args] [-v]\n" " camcontrol reportluns [dev_id][generic args] [-c] [-l] [-r report]\n" " camcontrol readcap [dev_id][generic args] [-b] [-h] [-H] [-N]\n" " [-q] [-s]\n" " camcontrol start [dev_id][generic args]\n" " camcontrol stop [dev_id][generic args]\n" " camcontrol load [dev_id][generic args]\n" " camcontrol eject [dev_id][generic args]\n" #endif /* MINIMALISTIC */ " camcontrol rescan \n" " camcontrol reset \n" #ifndef MINIMALISTIC " camcontrol defects [dev_id][generic args] <-f format> [-P][-G]\n" " camcontrol modepage [dev_id][generic args] <-m page | -l>\n" " [-P pagectl][-e | -b][-d]\n" " camcontrol cmd [dev_id][generic args]\n" " <-a cmd [args] | -c cmd [args]>\n" " [-d] [-f] [-i len fmt|-o len fmt [args]] [-r fmt]\n" " camcontrol smpcmd [dev_id][generic args]\n" " <-r len fmt [args]> <-R len fmt [args]>\n" " camcontrol smprg [dev_id][generic args][-l]\n" " camcontrol smppc [dev_id][generic args] <-p phy> [-l]\n" " [-o operation][-d name][-m rate][-M rate]\n" " [-T pp_timeout][-a enable|disable]\n" " [-A enable|disable][-s enable|disable]\n" " [-S enable|disable]\n" " camcontrol smpphylist [dev_id][generic args][-l][-q]\n" " camcontrol smpmaninfo [dev_id][generic args][-l]\n" " camcontrol debug [-I][-P][-T][-S][-X][-c]\n" " \n" " camcontrol tags [dev_id][generic args] [-N tags] [-q] [-v]\n" " camcontrol negotiate [dev_id][generic args] [-a][-c]\n" " [-D ][-M mode][-O offset]\n" " [-q][-R syncrate][-v][-T ]\n" " [-U][-W bus_width]\n" " camcontrol format [dev_id][generic args][-q][-r][-w][-y]\n" " camcontrol idle [dev_id][generic args][-t time]\n" " camcontrol standby [dev_id][generic args][-t time]\n" " camcontrol sleep [dev_id][generic args]\n" " camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-y][-s]\n" #endif /* MINIMALISTIC */ " camcontrol help\n"); if (!verbose) return; #ifndef MINIMALISTIC fprintf(stdout, "Specify one of the following options:\n" "devlist list all CAM devices\n" "periphlist list all CAM peripheral drivers attached to a device\n" "tur send a test unit ready to the named device\n" "inquiry send a SCSI inquiry command to the named device\n" "identify send a ATA identify command to the named device\n" "reportluns send a SCSI report luns command to the device\n" "readcap send a SCSI read capacity command to the device\n" "start send a Start Unit command to the device\n" "stop send a Stop Unit command to the device\n" "load send a Start Unit command to the device with the load bit set\n" "eject send a Stop Unit command to the device with the eject bit set\n" "rescan rescan all busses, the given bus, or bus:target:lun\n" "reset reset all busses, the given bus, or bus:target:lun\n" "defects read the defect list of the specified device\n" "modepage display or edit (-e) the given mode page\n" "cmd send the given SCSI command, may need -i or -o as well\n" "smpcmd send the given SMP command, requires -o and -i\n" "smprg send the SMP Report General command\n" "smppc send the SMP PHY Control command, requires -p\n" "smpphylist display phys attached to a SAS expander\n" "smpmaninfo send the SMP Report Manufacturer Info command\n" "debug turn debugging on/off for a bus, target, or lun, or all devices\n" "tags report or set the number of transaction slots for a device\n" "negotiate report or set device negotiation parameters\n" "format send the SCSI FORMAT UNIT command to the named device\n" "idle send the ATA IDLE command to the named device\n" "standby send the ATA STANDBY command to the named device\n" "sleep send the ATA SLEEP command to the named device\n" "fwdownload program firmware of the named device with the given image" "help this message\n" "Device Identifiers:\n" "bus:target specify the bus and target, lun defaults to 0\n" "bus:target:lun specify the bus, target and lun\n" "deviceUNIT specify the device name, like \"da4\" or \"cd2\"\n" "Generic arguments:\n" "-v be verbose, print out sense information\n" "-t timeout command timeout in seconds, overrides default timeout\n" "-n dev_name specify device name, e.g. \"da\", \"cd\"\n" "-u unit specify unit number, e.g. \"0\", \"5\"\n" "-E have the kernel attempt to perform SCSI error recovery\n" "-C count specify the SCSI command retry count (needs -E to work)\n" "modepage arguments:\n" "-l list all available mode pages\n" "-m page specify the mode page to view or edit\n" "-e edit the specified mode page\n" "-b force view to binary mode\n" "-d disable block descriptors for mode sense\n" "-P pgctl page control field 0-3\n" "defects arguments:\n" "-f format specify defect list format (block, bfi or phys)\n" "-G get the grown defect list\n" -"-P get the permanant defect list\n" +"-P get the permanent defect list\n" "inquiry arguments:\n" "-D get the standard inquiry data\n" "-S get the serial number\n" "-R get the transfer rate, etc.\n" "reportluns arguments:\n" "-c only report a count of available LUNs\n" "-l only print out luns, and not a count\n" "-r specify \"default\", \"wellknown\" or \"all\"\n" "readcap arguments\n" "-b only report the blocksize\n" "-h human readable device size, base 2\n" "-H human readable device size, base 10\n" "-N print the number of blocks instead of last block\n" "-q quiet, print numbers only\n" "-s only report the last block/device size\n" "cmd arguments:\n" "-c cdb [args] specify the SCSI CDB\n" "-i len fmt specify input data and input data format\n" "-o len fmt [args] specify output data and output data fmt\n" "smpcmd arguments:\n" "-r len fmt [args] specify the SMP command to be sent\n" "-R len fmt [args] specify SMP response format\n" "smprg arguments:\n" "-l specify the long response format\n" "smppc arguments:\n" "-p phy specify the PHY to operate on\n" "-l specify the long request/response format\n" "-o operation specify the phy control operation\n" "-d name set the attached device name\n" "-m rate set the minimum physical link rate\n" "-M rate set the maximum physical link rate\n" "-T pp_timeout set the partial pathway timeout value\n" "-a enable|disable enable or disable SATA slumber\n" "-A enable|disable enable or disable SATA partial phy power\n" "-s enable|disable enable or disable SAS slumber\n" "-S enable|disable enable or disable SAS partial phy power\n" "smpphylist arguments:\n" "-l specify the long response format\n" "-q only print phys with attached devices\n" "smpmaninfo arguments:\n" "-l specify the long response format\n" "debug arguments:\n" "-I CAM_DEBUG_INFO -- scsi commands, errors, data\n" "-T CAM_DEBUG_TRACE -- routine flow tracking\n" "-S CAM_DEBUG_SUBTRACE -- internal routine command flow\n" "-c CAM_DEBUG_CDB -- print out SCSI CDBs only\n" "tags arguments:\n" "-N tags specify the number of tags to use for this device\n" "-q be quiet, don't report the number of tags\n" "-v report a number of tag-related parameters\n" "negotiate arguments:\n" "-a send a test unit ready after negotiation\n" "-c report/set current negotiation settings\n" "-D \"enable\" or \"disable\" disconnection\n" "-M mode set ATA mode\n" "-O offset set command delay offset\n" "-q be quiet, don't report anything\n" "-R syncrate synchronization rate in MHz\n" "-T \"enable\" or \"disable\" tagged queueing\n" "-U report/set user negotiation settings\n" "-W bus_width set the bus width in bits (8, 16 or 32)\n" "-v also print a Path Inquiry CCB for the controller\n" "format arguments:\n" "-q be quiet, don't print status messages\n" "-r run in report only mode\n" "-w don't send immediate format command\n" "-y don't ask any questions\n" "idle/standby arguments:\n" "-t number of seconds before respective state.\n" "fwdownload arguments:\n" "-f fw_image path to firmware image file\n" "-y don't ask any questions\n" "-s run in simulation mode\n" "-v print info for every firmware segment sent to device\n"); #endif /* MINIMALISTIC */ } int main(int argc, char **argv) { int c; char *device = NULL; int unit = 0; struct cam_device *cam_dev = NULL; int timeout = 0, retry_count = 1; camcontrol_optret optreturn; char *tstr; const char *mainopt = "C:En:t:u:v"; const char *subopt = NULL; char combinedopt[256]; int error = 0, optstart = 2; int devopen = 1; #ifndef MINIMALISTIC int bus, target, lun; #endif /* MINIMALISTIC */ cmdlist = CAM_CMD_NONE; arglist = CAM_ARG_NONE; if (argc < 2) { usage(0); exit(1); } /* * Get the base option. */ optreturn = getoption(option_table,argv[1], &cmdlist, &arglist,&subopt); if (optreturn == CC_OR_AMBIGUOUS) { warnx("ambiguous option %s", argv[1]); usage(0); exit(1); } else if (optreturn == CC_OR_NOT_FOUND) { warnx("option %s not found", argv[1]); usage(0); exit(1); } /* * Ahh, getopt(3) is a pain. * * This is a gross hack. There really aren't many other good * options (excuse the pun) for parsing options in a situation like * this. getopt is kinda braindead, so you end up having to run * through the options twice, and give each invocation of getopt * the option string for the other invocation. * * You would think that you could just have two groups of options. * The first group would get parsed by the first invocation of * getopt, and the second group would get parsed by the second * invocation of getopt. It doesn't quite work out that way. When * the first invocation of getopt finishes, it leaves optind pointing * to the argument _after_ the first argument in the second group. * So when the second invocation of getopt comes around, it doesn't * recognize the first argument it gets and then bails out. * * A nice alternative would be to have a flag for getopt that says * "just keep parsing arguments even when you encounter an unknown * argument", but there isn't one. So there's no real clean way to * easily parse two sets of arguments without having one invocation * of getopt know about the other. * * Without this hack, the first invocation of getopt would work as * long as the generic arguments are first, but the second invocation * (in the subfunction) would fail in one of two ways. In the case * where you don't set optreset, it would fail because optind may be * pointing to the argument after the one it should be pointing at. * In the case where you do set optreset, and reset optind, it would * fail because getopt would run into the first set of options, which * it doesn't understand. * * All of this would "sort of" work if you could somehow figure out * whether optind had been incremented one option too far. The * mechanics of that, however, are more daunting than just giving * both invocations all of the expect options for either invocation. * * Needless to say, I wouldn't mind if someone invented a better * (non-GPL!) command line parsing interface than getopt. I * wouldn't mind if someone added more knobs to getopt to make it * work better. Who knows, I may talk myself into doing it someday, * if the standards weenies let me. As it is, it just leads to * hackery like this and causes people to avoid it in some cases. * * KDM, September 8th, 1998 */ if (subopt != NULL) sprintf(combinedopt, "%s%s", mainopt, subopt); else sprintf(combinedopt, "%s", mainopt); /* * For these options we do not parse optional device arguments and * we do not open a passthrough device. */ if ((cmdlist == CAM_CMD_RESCAN) || (cmdlist == CAM_CMD_RESET) || (cmdlist == CAM_CMD_DEVTREE) || (cmdlist == CAM_CMD_USAGE) || (cmdlist == CAM_CMD_DEBUG)) devopen = 0; #ifndef MINIMALISTIC if ((devopen == 1) && (argc > 2 && argv[2][0] != '-')) { char name[30]; int rv; if (isdigit(argv[2][0])) { /* device specified as bus:target[:lun] */ rv = parse_btl(argv[2], &bus, &target, &lun, &arglist); if (rv < 2) errx(1, "numeric device specification must " "be either bus:target, or " "bus:target:lun"); /* default to 0 if lun was not specified */ if ((arglist & CAM_ARG_LUN) == 0) { lun = 0; arglist |= CAM_ARG_LUN; } optstart++; } else { if (cam_get_device(argv[2], name, sizeof name, &unit) == -1) errx(1, "%s", cam_errbuf); device = strdup(name); arglist |= CAM_ARG_DEVICE | CAM_ARG_UNIT; optstart++; } } #endif /* MINIMALISTIC */ /* * Start getopt processing at argv[2/3], since we've already * accepted argv[1..2] as the command name, and as a possible * device name. */ optind = optstart; /* * Now we run through the argument list looking for generic * options, and ignoring options that possibly belong to * subfunctions. */ while ((c = getopt(argc, argv, combinedopt))!= -1){ switch(c) { case 'C': retry_count = strtol(optarg, NULL, 0); if (retry_count < 0) errx(1, "retry count %d is < 0", retry_count); arglist |= CAM_ARG_RETRIES; break; case 'E': arglist |= CAM_ARG_ERR_RECOVER; break; case 'n': arglist |= CAM_ARG_DEVICE; tstr = optarg; while (isspace(*tstr) && (*tstr != '\0')) tstr++; device = (char *)strdup(tstr); break; case 't': timeout = strtol(optarg, NULL, 0); if (timeout < 0) errx(1, "invalid timeout %d", timeout); /* Convert the timeout from seconds to ms */ timeout *= 1000; arglist |= CAM_ARG_TIMEOUT; break; case 'u': arglist |= CAM_ARG_UNIT; unit = strtol(optarg, NULL, 0); break; case 'v': arglist |= CAM_ARG_VERBOSE; break; default: break; } } #ifndef MINIMALISTIC /* * For most commands we'll want to open the passthrough device * associated with the specified device. In the case of the rescan * commands, we don't use a passthrough device at all, just the * transport layer device. */ if (devopen == 1) { if (((arglist & (CAM_ARG_BUS|CAM_ARG_TARGET)) == 0) && (((arglist & CAM_ARG_DEVICE) == 0) || ((arglist & CAM_ARG_UNIT) == 0))) { errx(1, "subcommand \"%s\" requires a valid device " "identifier", argv[1]); } if ((cam_dev = ((arglist & (CAM_ARG_BUS | CAM_ARG_TARGET))? cam_open_btl(bus, target, lun, O_RDWR, NULL) : cam_open_spec_device(device,unit,O_RDWR,NULL))) == NULL) errx(1,"%s", cam_errbuf); } #endif /* MINIMALISTIC */ /* * Reset optind to 2, and reset getopt, so these routines can parse * the arguments again. */ optind = optstart; optreset = 1; switch(cmdlist) { #ifndef MINIMALISTIC case CAM_CMD_DEVLIST: error = getdevlist(cam_dev); break; #endif /* MINIMALISTIC */ case CAM_CMD_DEVTREE: error = getdevtree(); break; #ifndef MINIMALISTIC case CAM_CMD_TUR: error = testunitready(cam_dev, retry_count, timeout, 0); break; case CAM_CMD_INQUIRY: error = scsidoinquiry(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_IDENTIFY: error = ataidentify(cam_dev, retry_count, timeout); break; case CAM_CMD_STARTSTOP: error = scsistart(cam_dev, arglist & CAM_ARG_START_UNIT, arglist & CAM_ARG_EJECT, retry_count, timeout); break; #endif /* MINIMALISTIC */ case CAM_CMD_RESCAN: error = dorescan_or_reset(argc, argv, 1); break; case CAM_CMD_RESET: error = dorescan_or_reset(argc, argv, 0); break; #ifndef MINIMALISTIC case CAM_CMD_READ_DEFECTS: error = readdefects(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_MODE_PAGE: modepage(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SCSI_CMD: error = scsicmd(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SMP_CMD: error = smpcmd(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SMP_RG: error = smpreportgeneral(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SMP_PC: error = smpphycontrol(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SMP_PHYLIST: error = smpphylist(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_SMP_MANINFO: error = smpmaninfo(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_DEBUG: error = camdebug(argc, argv, combinedopt); break; case CAM_CMD_TAG: error = tagcontrol(cam_dev, argc, argv, combinedopt); break; case CAM_CMD_RATE: error = ratecontrol(cam_dev, retry_count, timeout, argc, argv, combinedopt); break; case CAM_CMD_FORMAT: error = scsiformat(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_REPORTLUNS: error = scsireportluns(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_READCAP: error = scsireadcapacity(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_IDLE: case CAM_CMD_STANDBY: case CAM_CMD_SLEEP: error = atapm(cam_dev, argc, argv, combinedopt, retry_count, timeout); break; case CAM_CMD_DOWNLOAD_FW: error = fwdownload(cam_dev, argc, argv, combinedopt, arglist & CAM_ARG_VERBOSE, retry_count, timeout); break; #endif /* MINIMALISTIC */ case CAM_CMD_USAGE: usage(1); break; default: usage(0); error = 1; break; } if (cam_dev != NULL) cam_close_device(cam_dev); exit(error); } Index: head/sbin/devfs/rule.c =================================================================== --- head/sbin/devfs/rule.c (revision 229777) +++ head/sbin/devfs/rule.c (revision 229778) @@ -1,462 +1,462 @@ /*- * Copyright (c) 2002 Dima Dorfman. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Rule subsystem manipulation. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" -static void rulespec_infp(FILE *fp, unsigned long reqest, devfs_rsnum rsnum); +static void rulespec_infp(FILE *fp, unsigned long request, devfs_rsnum rsnum); static void rulespec_instr(struct devfs_rule *dr, const char *str, devfs_rsnum rsnum); static void rulespec_intok(struct devfs_rule *dr, int ac, char **av, devfs_rsnum rsnum); static void rulespec_outfp(FILE *fp, struct devfs_rule *dr); static command_t rule_add, rule_apply, rule_applyset; static command_t rule_del, rule_delset, rule_show, rule_showsets; static ctbl_t ctbl_rule = { { "add", rule_add }, { "apply", rule_apply }, { "applyset", rule_applyset }, { "del", rule_del }, { "delset", rule_delset }, { "show", rule_show }, { "showsets", rule_showsets }, { NULL, NULL } }; static struct intstr ist_type[] = { { "disk", D_DISK }, { "mem", D_MEM }, { "tape", D_TAPE }, { "tty", D_TTY }, { NULL, -1 } }; static devfs_rsnum in_rsnum; int rule_main(int ac, char **av) { struct cmd *c; int ch; setprogname("devfs rule"); optreset = optind = 1; while ((ch = getopt(ac, av, "s:")) != -1) switch (ch) { case 's': in_rsnum = eatonum(optarg); break; default: usage(); } ac -= optind; av += optind; if (ac < 1) usage(); for (c = ctbl_rule; c->name != NULL; ++c) if (strcmp(c->name, av[0]) == 0) exit((*c->handler)(ac, av)); errx(1, "unknown command: %s", av[0]); } static int rule_add(int ac, char **av) { struct devfs_rule dr; int rv; if (ac < 2) usage(); if (strcmp(av[1], "-") == 0) rulespec_infp(stdin, DEVFSIO_RADD, in_rsnum); else { rulespec_intok(&dr, ac - 1, av + 1, in_rsnum); rv = ioctl(mpfd, DEVFSIO_RADD, &dr); if (rv == -1) err(1, "ioctl DEVFSIO_RADD"); } return (0); } static int rule_apply(int ac __unused, char **av __unused) { struct devfs_rule dr; devfs_rnum rnum; devfs_rid rid; int rv; if (ac < 2) usage(); if (!atonum(av[1], &rnum)) { if (strcmp(av[1], "-") == 0) rulespec_infp(stdin, DEVFSIO_RAPPLY, in_rsnum); else { rulespec_intok(&dr, ac - 1, av + 1, in_rsnum); rv = ioctl(mpfd, DEVFSIO_RAPPLY, &dr); if (rv == -1) err(1, "ioctl DEVFSIO_RAPPLY"); } } else { rid = mkrid(in_rsnum, rnum); rv = ioctl(mpfd, DEVFSIO_RAPPLYID, &rid); if (rv == -1) err(1, "ioctl DEVFSIO_RAPPLYID"); } return (0); } static int rule_applyset(int ac, char **av __unused) { int rv; if (ac != 1) usage(); rv = ioctl(mpfd, DEVFSIO_SAPPLY, &in_rsnum); if (rv == -1) err(1, "ioctl DEVFSIO_SAPPLY"); return (0); } static int rule_del(int ac __unused, char **av) { devfs_rid rid; int rv; if (av[1] == NULL) usage(); rid = mkrid(in_rsnum, eatoi(av[1])); rv = ioctl(mpfd, DEVFSIO_RDEL, &rid); if (rv == -1) err(1, "ioctl DEVFSIO_RDEL"); return (0); } static int rule_delset(int ac, char **av __unused) { struct devfs_rule dr; int rv; if (ac != 1) usage(); memset(&dr, '\0', sizeof(dr)); dr.dr_magic = DEVFS_MAGIC; dr.dr_id = mkrid(in_rsnum, 0); while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1) { rv = ioctl(mpfd, DEVFSIO_RDEL, &dr.dr_id); if (rv == -1) err(1, "ioctl DEVFSIO_RDEL"); } if (errno != ENOENT) err(1, "ioctl DEVFSIO_RGETNEXT"); return (0); } static int rule_show(int ac __unused, char **av) { struct devfs_rule dr; devfs_rnum rnum; int rv; memset(&dr, '\0', sizeof(dr)); dr.dr_magic = DEVFS_MAGIC; if (av[1] != NULL) { rnum = eatoi(av[1]); dr.dr_id = mkrid(in_rsnum, rnum - 1); rv = ioctl(mpfd, DEVFSIO_RGETNEXT, &dr); if (rv == -1) err(1, "ioctl DEVFSIO_RGETNEXT"); if (rid2rn(dr.dr_id) == rnum) rulespec_outfp(stdout, &dr); } else { dr.dr_id = mkrid(in_rsnum, 0); while (ioctl(mpfd, DEVFSIO_RGETNEXT, &dr) != -1) rulespec_outfp(stdout, &dr); if (errno != ENOENT) err(1, "ioctl DEVFSIO_RGETNEXT"); } return (0); } static int rule_showsets(int ac, char **av __unused) { devfs_rsnum rsnum; if (ac != 1) usage(); rsnum = 0; while (ioctl(mpfd, DEVFSIO_SGETNEXT, &rsnum) != -1) printf("%d\n", rsnum); if (errno != ENOENT) err(1, "ioctl DEVFSIO_SGETNEXT"); return (0); } int ruleset_main(int ac, char **av) { devfs_rsnum rsnum; int rv; setprogname("devfs ruleset"); if (ac < 2) usage(); rsnum = eatonum(av[1]); rv = ioctl(mpfd, DEVFSIO_SUSE, &rsnum); if (rv == -1) err(1, "ioctl DEVFSIO_SUSE"); return (0); } /* * Input rules from a file (probably the standard input). This * differs from the other rulespec_in*() routines in that it also * calls ioctl() for the rules, since it is impractical (and not very * useful) to return a list (or array) of rules, just so the caller * can call call ioctl() for each of them. */ static void rulespec_infp(FILE *fp, unsigned long request, devfs_rsnum rsnum) { struct devfs_rule dr; char *line; int rv; assert(fp == stdin); /* XXX: De-hardcode "stdin" from error msg. */ while (efgetln(fp, &line)) { rulespec_instr(&dr, line, rsnum); rv = ioctl(mpfd, request, &dr); if (rv == -1) err(1, "ioctl"); free(line); /* efgetln() always malloc()s. */ } if (ferror(stdin)) err(1, "stdin"); } /* * Construct a /struct devfs_rule/ from a string. */ static void rulespec_instr(struct devfs_rule *dr, const char *str, devfs_rsnum rsnum) { char **av; int ac; tokenize(str, &ac, &av); if (ac == 0) errx(1, "unexpected end of rulespec"); rulespec_intok(dr, ac, av, rsnum); free(av[0]); free(av); } /* * Construct a /struct devfs_rule/ from ac and av. */ static void rulespec_intok(struct devfs_rule *dr, int ac __unused, char **av, devfs_rsnum rsnum) { struct intstr *is; struct passwd *pw; struct group *gr; devfs_rnum rnum; void *set; memset(dr, '\0', sizeof(*dr)); /* * We don't maintain ac hereinafter. */ if (av[0] == NULL) errx(1, "unexpected end of rulespec"); /* If the first argument is an integer, treat it as a rule number. */ if (!atonum(av[0], &rnum)) rnum = 0; /* auto-number */ else ++av; /* * These aren't table-driven since that would result in more * tiny functions than I care to deal with. */ for (;;) { if (av[0] == NULL) break; else if (strcmp(av[0], "type") == 0) { if (av[1] == NULL) errx(1, "expecting argument for type"); for (is = ist_type; is->s != NULL; ++is) if (strcmp(av[1], is->s) == 0) { dr->dr_dswflags |= is->i; break; } if (is->s == NULL) errx(1, "unknown type: %s", av[1]); dr->dr_icond |= DRC_DSWFLAGS; av += 2; } else if (strcmp(av[0], "path") == 0) { if (av[1] == NULL) errx(1, "expecting argument for path"); if (strlcpy(dr->dr_pathptrn, av[1], DEVFS_MAXPTRNLEN) >= DEVFS_MAXPTRNLEN) warnx("pattern specified too long; truncated"); dr->dr_icond |= DRC_PATHPTRN; av += 2; } else break; } while (av[0] != NULL) { if (strcmp(av[0], "hide") == 0) { dr->dr_iacts |= DRA_BACTS; dr->dr_bacts |= DRB_HIDE; ++av; } else if (strcmp(av[0], "unhide") == 0) { dr->dr_iacts |= DRA_BACTS; dr->dr_bacts |= DRB_UNHIDE; ++av; } else if (strcmp(av[0], "user") == 0) { if (av[1] == NULL) errx(1, "expecting argument for user"); dr->dr_iacts |= DRA_UID; pw = getpwnam(av[1]); if (pw != NULL) dr->dr_uid = pw->pw_uid; else dr->dr_uid = eatoi(av[1]); /* XXX overflow */ av += 2; } else if (strcmp(av[0], "group") == 0) { if (av[1] == NULL) errx(1, "expecting argument for group"); dr->dr_iacts |= DRA_GID; gr = getgrnam(av[1]); if (gr != NULL) dr->dr_gid = gr->gr_gid; else dr->dr_gid = eatoi(av[1]); /* XXX overflow */ av += 2; } else if (strcmp(av[0], "mode") == 0) { if (av[1] == NULL) errx(1, "expecting argument for mode"); dr->dr_iacts |= DRA_MODE; set = setmode(av[1]); if (set == NULL) errx(1, "invalid mode: %s", av[1]); dr->dr_mode = getmode(set, 0); av += 2; } else if (strcmp(av[0], "include") == 0) { if (av[1] == NULL) errx(1, "expecting argument for include"); dr->dr_iacts |= DRA_INCSET; dr->dr_incset = eatonum(av[1]); av += 2; } else errx(1, "unknown argument: %s", av[0]); } dr->dr_id = mkrid(rsnum, rnum); dr->dr_magic = DEVFS_MAGIC; } /* * Write a human-readable (and machine-parsable, by rulespec_in*()) * representation of dr to bufp. *bufp should be free(3)'d when the * caller is finished with it. */ static void rulespec_outfp(FILE *fp, struct devfs_rule *dr) { struct intstr *is; struct passwd *pw; struct group *gr; fprintf(fp, "%d", rid2rn(dr->dr_id)); if (dr->dr_icond & DRC_DSWFLAGS) for (is = ist_type; is->s != NULL; ++is) if (dr->dr_dswflags & is->i) fprintf(fp, " type %s", is->s); if (dr->dr_icond & DRC_PATHPTRN) fprintf(fp, " path %s", dr->dr_pathptrn); if (dr->dr_iacts & DRA_BACTS) { if (dr->dr_bacts & DRB_HIDE) fprintf(fp, " hide"); if (dr->dr_bacts & DRB_UNHIDE) fprintf(fp, " unhide"); } if (dr->dr_iacts & DRA_UID) { pw = getpwuid(dr->dr_uid); if (pw == NULL) fprintf(fp, " user %d", dr->dr_uid); else fprintf(fp, " user %s", pw->pw_name); } if (dr->dr_iacts & DRA_GID) { gr = getgrgid(dr->dr_gid); if (gr == NULL) fprintf(fp, " group %d", dr->dr_gid); else fprintf(fp, " group %s", gr->gr_name); } if (dr->dr_iacts & DRA_MODE) fprintf(fp, " mode %o", dr->dr_mode); if (dr->dr_iacts & DRA_INCSET) fprintf(fp, " include %d", dr->dr_incset); fprintf(fp, "\n"); } Index: head/sbin/dhclient/dhclient-script.8 =================================================================== --- head/sbin/dhclient/dhclient-script.8 (revision 229777) +++ head/sbin/dhclient/dhclient-script.8 (revision 229778) @@ -1,297 +1,297 @@ .\" $OpenBSD: dhclient-script.8,v 1.2 2004/04/09 18:30:15 jmc Exp $ .\" .\" Copyright (c) 1997 The Internet Software Consortium. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of The Internet Software Consortium nor the names .\" of its contributors may be used to endorse or promote products derived .\" from this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND .\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, .\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE .\" DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR .\" CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF .\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND .\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, .\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" This software has been written for the Internet Software Consortium .\" by Ted Lemon in cooperation with Vixie .\" Enterprises. To learn more about the Internet Software Consortium, .\" see ``http://www.isc.org/isc''. To learn more about Vixie .\" Enterprises, see ``http://www.vix.com''. .\" .\" $FreeBSD$ .\" .Dd September 6, 2010 .Dt DHCLIENT-SCRIPT 8 .Os .Sh NAME .Nm dhclient-script .Nd DHCP client network configuration script .Sh DESCRIPTION The DHCP client network configuration script is invoked from time to time by .Xr dhclient 8 . This script is used by the DHCP client to set each interface's initial configuration prior to requesting an address, to test the address once it has been offered, and to set the interface's final configuration once a lease has been acquired. If no lease is acquired, the script is used to test predefined leases, if any, and also called once if no valid lease can be identified. .Pp .\" No standard client script exists for some operating systems, even though .\" the actual client may work, so a pioneering user may well need to create .\" a new script or modify an existing one. In general, customizations specific to a particular computer should be done in the .Pa /etc/dhclient.conf file. .Sh OPERATION When .Xr dhclient 8 needs to invoke the client configuration script, it sets up a number of environment variables and runs .Nm . In all cases, .Va $reason is set to the name of the reason why the script has been invoked. The following reasons are currently defined: .Li MEDIUM , PREINIT , ARPCHECK , ARPSEND , BOUND , RENEW , REBIND , REBOOT , .Li EXPIRE , FAIL and .Li TIMEOUT . .Bl -tag -width ".Li ARPCHECK" .It Li MEDIUM The DHCP client is requesting that an interface's media type be set. The interface name is passed in .Va $interface , and the media type is passed in .Va $medium . .It Li PREINIT The DHCP client is requesting that an interface be configured as required in order to send packets prior to receiving an actual address. .\" For clients which use the BSD socket library, This means configuring the interface with an IP address of 0.0.0.0 and a broadcast address of 255.255.255.255. .\" For other clients, it may be possible to simply configure the interface up .\" without actually giving it an IP address at all. The interface name is passed in .Va $interface , and the media type in .Va $medium . .Pp If an IP alias has been declared in .Xr dhclient.conf 5 , its address will be passed in .Va $alias_ip_address , and that IP alias should be deleted from the interface, along with any routes to it. .It Li ARPSEND The DHCP client is requesting that an address that has been offered to it be checked to see if somebody else is using it, by sending an ARP request for that address. It is not clear how to implement this, so no examples exist yet. The IP address to check is passed in .Va $new_ip_address , and the interface name is passed in .Va $interface . .It Li ARPCHECK The DHCP client wants to know if a response to the ARP request sent using .Li ARPSEND has been received. If one has, the script should exit with a nonzero status, indicating that the offered address has already been requested and should be declined. The .Va $new_ip_address and .Va $interface variables are set as with .Li ARPSEND . .It Li BOUND The DHCP client has done an initial binding to a new address. The new IP address is passed in .Va $new_ip_address , and the interface name is passed in .Va $interface . The media type is passed in .Va $medium . Any options acquired from the server are passed using the option name described in .Xr dhcp-options 5 , except that dashes .Pq Ql - are replaced by underscores .Pq Ql _ in order to make valid shell variables, and the variable names start with .Dq Li new_ . So for example, the new subnet mask would be passed in .Va $new_subnet_mask . .Pp When a binding has been completed, a lot of network parameters are likely to need to be set up. A new .Pa /etc/resolv.conf needs to be created, using the values of .Va $new_domain_name and .Va $new_domain_name_servers (which may list more than one server, separated by spaces). A default route should be set using .Va $new_routers , and static routes may need to be set up using .Va $new_static_routes . .Pp If an IP alias has been declared, it must be set up here. The alias IP address will be written as .Va $alias_ip_address , and other DHCP options that are set for the alias (e.g., subnet mask) will be passed in variables named as described previously except starting with .Dq Li $alias_ instead of .Dq Li $new_ . Care should be taken that the alias IP address not be used if it is identical to the bound IP address .Pq Va $new_ip_address , since the other alias parameters may be incorrect in this case. .It Li RENEW When a binding has been renewed, the script is called as in .Li BOUND , except that in addition to all the variables starting with .Dq Li $new_ , there is another set of variables starting with .Dq Li $old_ . Persistent settings that may have changed need to be deleted - for example, if a local route to the bound address is being configured, the old local route should be deleted. If the default route has changed, the old default route should be deleted. If the static routes have changed, the old ones should be deleted. Otherwise, processing can be done as with .Li BOUND . .It Li REBIND The DHCP client has rebound to a new DHCP server. This can be handled as with .Li RENEW , except that if the IP address has changed, the ARP table should be cleared. .It Li REBOOT The DHCP client has successfully reacquired its old address after a reboot. This can be processed as with .Li BOUND . .It Li EXPIRE The DHCP client has failed to renew its lease or acquire a new one, and the lease has expired. The IP address must be relinquished, and all related parameters should be deleted, as in .Li RENEW and .Li REBIND . .It Li FAIL The DHCP client has been unable to contact any DHCP servers, and any leases that have been tested have not proved to be valid. The parameters from the last lease tested should be deconfigured. This can be handled in the same way as .Li EXPIRE . .It Li TIMEOUT The DHCP client has been unable to contact any DHCP servers. However, an old lease has been identified, and its parameters have been passed in as with .Li BOUND . The client configuration script should test these parameters and, if it has reason to believe they are valid, should exit with a value of zero. If not, it should exit with a nonzero value. .El .Pp Before taking action according to .Va $reason , .Nm will check for the existence of .Pa /etc/dhclient-enter-hooks . If found, it will be sourced .Pq see Xr sh 1 . After taking action according to .Va $reason , .Nm will check for the existence of .Pa /etc/dhclient-exit-hooks . If found, it will be sourced .Pq see Xr sh 1 . -These hooks scripts can be used to dynamically modify the enviornment at +These hooks scripts can be used to dynamically modify the environment at appropriate times during the DHCP negotiations. For example, if the administrator wishes to disable alias IP numbers on the DHCP interface, they might want to put the following in .Pa /etc/dhclient-enter-hooks : .Bd -literal -offset indent [ ."$reason" = .PREINIT ] && ifconfig $interface 0.0.0.0 .Ed .Pp The usual way to test a lease is to set up the network as with .Li REBIND (since this may be called to test more than one lease) and then ping the first router defined in .Va $routers . If a response is received, the lease must be valid for the network to which the interface is currently connected. It would be more complete to try to ping all of the routers listed in .Va $new_routers , as well as those listed in .Va $new_static_routes , but current scripts do not do this. .\" .Sh FILES .\" Each operating system should generally have its own script file, .\" although the script files for similar operating systems may be similar .\" or even identical. .\" The script files included in the Internet Software Consortium DHCP .\" distribution appear in the distribution tree under client/scripts, .\" and bear the names of the operating systems on which they are intended .\" to work. .Sh SEE ALSO .Xr sh 1 , .Xr dhclient.conf 5 , .Xr dhclient.leases 5 , .Xr dhclient 8 , .Xr dhcpd 8 , .Xr dhcrelay 8 .Sh AUTHORS .An -nosplit The original version of .Nm was written for the Internet Software Consortium by .An Ted Lemon Aq mellon@fugue.com in cooperation with Vixie Enterprises. .Pp The .Ox implementation of .Nm was written by .An Kenneth R. Westerback Aq krw@openbsd.org . .Sh BUGS If more than one interface is being used, there is no obvious way to avoid clashes between server-supplied configuration parameters - for example, the stock .Nm rewrites .Pa /etc/resolv.conf . If more than one interface is being configured, .Pa /etc/resolv.conf will be repeatedly initialized to the values provided by one server, and then the other. Assuming the information provided by both servers is valid, this should not cause any real problems, but it could be confusing. Index: head/sbin/dhclient/options.c =================================================================== --- head/sbin/dhclient/options.c (revision 229777) +++ head/sbin/dhclient/options.c (revision 229778) @@ -1,894 +1,894 @@ /* $OpenBSD: options.c,v 1.15 2004/12/26 03:17:07 deraadt Exp $ */ /* DHCP options parsing and reassembly. */ /* * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie * Enterprises. To learn more about the Internet Software Consortium, * see ``http://www.vix.com/isc''. To learn more about Vixie * Enterprises, see ``http://www.vix.com''. */ #include __FBSDID("$FreeBSD$"); #include #define DHCP_OPTION_DATA #include "dhcpd.h" int bad_options = 0; int bad_options_max = 5; void parse_options(struct packet *); void parse_option_buffer(struct packet *, unsigned char *, int); int store_options(unsigned char *, int, struct tree_cache **, unsigned char *, int, int, int, int); void expand_domain_search(struct packet *packet); int find_search_domain_name_len(struct option_data *option, int *offset); void expand_search_domain_name(struct option_data *option, int *offset, unsigned char **domain_search); /* * Parse all available options out of the specified packet. */ void parse_options(struct packet *packet) { /* Initially, zero all option pointers. */ memset(packet->options, 0, sizeof(packet->options)); /* If we don't see the magic cookie, there's nothing to parse. */ if (memcmp(packet->raw->options, DHCP_OPTIONS_COOKIE, 4)) { packet->options_valid = 0; return; } /* * Go through the options field, up to the end of the packet or * the End field. */ parse_option_buffer(packet, &packet->raw->options[4], packet->packet_length - DHCP_FIXED_NON_UDP - 4); /* * If we parsed a DHCP Option Overload option, parse more * options out of the buffer(s) containing them. */ if (packet->options_valid && packet->options[DHO_DHCP_OPTION_OVERLOAD].data) { if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) parse_option_buffer(packet, (unsigned char *)packet->raw->file, sizeof(packet->raw->file)); if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) parse_option_buffer(packet, (unsigned char *)packet->raw->sname, sizeof(packet->raw->sname)); } /* Expand DHCP Domain Search option. */ if (packet->options_valid) { expand_domain_search(packet); } } /* * Parse options out of the specified buffer, storing addresses of * option values in packet->options and setting packet->options_valid if * no errors are encountered. */ void parse_option_buffer(struct packet *packet, unsigned char *buffer, int length) { unsigned char *s, *t, *end = buffer + length; int len, code; for (s = buffer; *s != DHO_END && s < end; ) { code = s[0]; /* Pad options don't have a length - just skip them. */ if (code == DHO_PAD) { s++; continue; } if (s + 2 > end) { len = 65536; goto bogus; } /* * All other fields (except end, see above) have a * one-byte length. */ len = s[1]; /* * If the length is outrageous, silently skip the rest, * and mark the packet bad. Unfortunately some crappy * dhcp servers always seem to give us garbage on the * end of a packet. so rather than keep refusing, give * up and try to take one after seeing a few without * anything good. */ if (s + len + 2 > end) { bogus: bad_options++; warning("option %s (%d) %s.", dhcp_options[code].name, len, "larger than buffer"); if (bad_options == bad_options_max) { packet->options_valid = 1; bad_options = 0; warning("Many bogus options seen in offers. " "Taking this offer in spite of bogus " "options - hope for the best!"); } else { warning("rejecting bogus offer."); packet->options_valid = 0; } return; } /* * If we haven't seen this option before, just make * space for it and copy it there. */ if (!packet->options[code].data) { if (!(t = calloc(1, len + 1))) error("Can't allocate storage for option %s.", dhcp_options[code].name); /* * Copy and NUL-terminate the option (in case * it's an ASCII string. */ memcpy(t, &s[2], len); t[len] = 0; packet->options[code].len = len; packet->options[code].data = t; } else { /* * If it's a repeat, concatenate it to whatever * we last saw. This is really only required * for clients, but what the heck... */ t = calloc(1, len + packet->options[code].len + 1); if (!t) error("Can't expand storage for option %s.", dhcp_options[code].name); memcpy(t, packet->options[code].data, packet->options[code].len); memcpy(t + packet->options[code].len, &s[2], len); packet->options[code].len += len; t[packet->options[code].len] = 0; free(packet->options[code].data); packet->options[code].data = t; } s += len + 2; } packet->options_valid = 1; } /* * Expand DHCP Domain Search option. The value of this option is * encoded like DNS' list of labels. See: * RFC 3397 * RFC 1035 */ void expand_domain_search(struct packet *packet) { int offset, expanded_len, next_domain_len; struct option_data *option; unsigned char *domain_search, *cursor; if (packet->options[DHO_DOMAIN_SEARCH].data == NULL) return; option = &packet->options[DHO_DOMAIN_SEARCH]; /* Compute final expanded length. */ expanded_len = 0; offset = 0; while (offset < option->len) { next_domain_len = find_search_domain_name_len(option, &offset); if (next_domain_len < 0) /* The Domain Search option value is invalid. */ return; /* We add 1 for the space between domain names. */ expanded_len += next_domain_len + 1; } if (expanded_len > 0) /* Remove 1 for the superfluous trailing space. */ --expanded_len; domain_search = malloc(expanded_len + 1); if (domain_search == NULL) error("Can't allocate storage for expanded domain-search\n"); offset = 0; cursor = domain_search; while (offset < option->len) { expand_search_domain_name(option, &offset, &cursor); cursor[0] = ' '; cursor++; } domain_search[expanded_len] = '\0'; free(option->data); option->len = expanded_len; option->data = domain_search; } int find_search_domain_name_len(struct option_data *option, int *offset) { int domain_name_len, i, label_len, pointer, pointed_len; domain_name_len = 0; i = *offset; while (i < option->len) { label_len = option->data[i]; if (label_len == 0) { /* * A zero-length label marks the end of this * domain name. */ *offset = i + 1; return (domain_name_len); } else if (label_len & 0xC0) { /* This is a pointer to another list of labels. */ if (i + 1 >= option->len) { /* The pointer is truncated. */ warning("Truncated pointer in DHCP Domain " "Search option."); return (-1); } pointer = ((label_len & ~(0xC0)) << 8) + option->data[i + 1]; if (pointer >= *offset) { /* - * The pointer must indicates a prior - * occurance. + * The pointer must indicate a prior + * occurrence. */ warning("Invalid forward pointer in DHCP " "Domain Search option compression."); return (-1); } pointed_len = find_search_domain_name_len(option, &pointer); domain_name_len += pointed_len; *offset = i + 2; return (domain_name_len); } if (i + label_len >= option->len) { warning("Truncated label in DHCP Domain Search " "option."); return (-1); } /* * Update the domain name length with the length of the * current label, plus a trailing dot ('.'). */ domain_name_len += label_len + 1; /* Move cursor. */ i += label_len + 1; } warning("Truncated DHCP Domain Search option."); return (-1); } void expand_search_domain_name(struct option_data *option, int *offset, unsigned char **domain_search) { int i, label_len, pointer; unsigned char *cursor; /* * This is the same loop than the function above * (find_search_domain_name_len). Therefore, we remove checks, * they're already done. Here, we just make the copy. */ i = *offset; cursor = *domain_search; while (i < option->len) { label_len = option->data[i]; if (label_len == 0) { /* * A zero-length label marks the end of this * domain name. */ *offset = i + 1; *domain_search = cursor; return; } else if (label_len & 0xC0) { /* This is a pointer to another list of labels. */ pointer = ((label_len & ~(0xC0)) << 8) + option->data[i + 1]; expand_search_domain_name(option, &pointer, &cursor); *offset = i + 2; *domain_search = cursor; return; } /* Copy the label found. */ memcpy(cursor, option->data + i + 1, label_len); cursor[label_len] = '.'; /* Move cursor. */ i += label_len + 1; cursor += label_len + 1; } } /* * cons options into a big buffer, and then split them out into the * three separate buffers if needed. This allows us to cons up a set of * vendor options using the same routine. */ int cons_options(struct packet *inpacket, struct dhcp_packet *outpacket, int mms, struct tree_cache **options, int overload, /* Overload flags that may be set. */ int terminate, int bootpp, u_int8_t *prl, int prl_len) { unsigned char priority_list[300], buffer[4096]; int priority_len, main_buffer_size, mainbufix, bufix; int option_size, length; /* * If the client has provided a maximum DHCP message size, use * that; otherwise, if it's BOOTP, only 64 bytes; otherwise use * up to the minimum IP MTU size (576 bytes). * * XXX if a BOOTP client specifies a max message size, we will * honor it. */ if (!mms && inpacket && inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data && (inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].len >= sizeof(u_int16_t))) mms = getUShort( inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data); if (mms) main_buffer_size = mms - DHCP_FIXED_LEN; else if (bootpp) main_buffer_size = 64; else main_buffer_size = 576 - DHCP_FIXED_LEN; if (main_buffer_size > sizeof(buffer)) main_buffer_size = sizeof(buffer); /* Preload the option priority list with mandatory options. */ priority_len = 0; priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE; priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER; priority_list[priority_len++] = DHO_DHCP_LEASE_TIME; priority_list[priority_len++] = DHO_DHCP_MESSAGE; /* * If the client has provided a list of options that it wishes * returned, use it to prioritize. Otherwise, prioritize based * on the default priority list. */ if (inpacket && inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data) { int prlen = inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].len; if (prlen + priority_len > sizeof(priority_list)) prlen = sizeof(priority_list) - priority_len; memcpy(&priority_list[priority_len], inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data, prlen); priority_len += prlen; prl = priority_list; } else if (prl) { if (prl_len + priority_len > sizeof(priority_list)) prl_len = sizeof(priority_list) - priority_len; memcpy(&priority_list[priority_len], prl, prl_len); priority_len += prl_len; prl = priority_list; } else { memcpy(&priority_list[priority_len], dhcp_option_default_priority_list, sizeof_dhcp_option_default_priority_list); priority_len += sizeof_dhcp_option_default_priority_list; } /* Copy the options into the big buffer... */ option_size = store_options( buffer, (main_buffer_size - 7 + ((overload & 1) ? DHCP_FILE_LEN : 0) + ((overload & 2) ? DHCP_SNAME_LEN : 0)), options, priority_list, priority_len, main_buffer_size, (main_buffer_size + ((overload & 1) ? DHCP_FILE_LEN : 0)), terminate); /* Put the cookie up front... */ memcpy(outpacket->options, DHCP_OPTIONS_COOKIE, 4); mainbufix = 4; /* * If we're going to have to overload, store the overload option * at the beginning. If we can, though, just store the whole * thing in the packet's option buffer and leave it at that. */ if (option_size <= main_buffer_size - mainbufix) { memcpy(&outpacket->options[mainbufix], buffer, option_size); mainbufix += option_size; if (mainbufix < main_buffer_size) outpacket->options[mainbufix++] = DHO_END; length = DHCP_FIXED_NON_UDP + mainbufix; } else { outpacket->options[mainbufix++] = DHO_DHCP_OPTION_OVERLOAD; outpacket->options[mainbufix++] = 1; if (option_size > main_buffer_size - mainbufix + DHCP_FILE_LEN) outpacket->options[mainbufix++] = 3; else outpacket->options[mainbufix++] = 1; memcpy(&outpacket->options[mainbufix], buffer, main_buffer_size - mainbufix); bufix = main_buffer_size - mainbufix; length = DHCP_FIXED_NON_UDP + mainbufix; if (overload & 1) { if (option_size - bufix <= DHCP_FILE_LEN) { memcpy(outpacket->file, &buffer[bufix], option_size - bufix); mainbufix = option_size - bufix; if (mainbufix < DHCP_FILE_LEN) outpacket->file[mainbufix++] = (char)DHO_END; while (mainbufix < DHCP_FILE_LEN) outpacket->file[mainbufix++] = (char)DHO_PAD; } else { memcpy(outpacket->file, &buffer[bufix], DHCP_FILE_LEN); bufix += DHCP_FILE_LEN; } } if ((overload & 2) && option_size < bufix) { memcpy(outpacket->sname, &buffer[bufix], option_size - bufix); mainbufix = option_size - bufix; if (mainbufix < DHCP_SNAME_LEN) outpacket->file[mainbufix++] = (char)DHO_END; while (mainbufix < DHCP_SNAME_LEN) outpacket->file[mainbufix++] = (char)DHO_PAD; } } return (length); } /* * Store all the requested options into the requested buffer. */ int store_options(unsigned char *buffer, int buflen, struct tree_cache **options, unsigned char *priority_list, int priority_len, int first_cutoff, int second_cutoff, int terminate) { int bufix = 0, option_stored[256], i, ix, tto; /* Zero out the stored-lengths array. */ memset(option_stored, 0, sizeof(option_stored)); /* * Copy out the options in the order that they appear in the * priority list... */ for (i = 0; i < priority_len; i++) { /* Code for next option to try to store. */ int code = priority_list[i]; int optstart; /* * Number of bytes left to store (some may already have * been stored by a previous pass). */ int length; /* If no data is available for this option, skip it. */ if (!options[code]) { continue; } /* * The client could ask for things that are mandatory, * in which case we should avoid storing them twice... */ if (option_stored[code]) continue; option_stored[code] = 1; /* We should now have a constant length for the option. */ length = options[code]->len; /* Do we add a NUL? */ if (terminate && dhcp_options[code].format[0] == 't') { length++; tto = 1; } else tto = 0; /* Try to store the option. */ /* * If the option's length is more than 255, we must * store it in multiple hunks. Store 255-byte hunks * first. However, in any case, if the option data will * cross a buffer boundary, split it across that * boundary. */ ix = 0; optstart = bufix; while (length) { unsigned char incr = length > 255 ? 255 : length; /* * If this hunk of the buffer will cross a * boundary, only go up to the boundary in this * pass. */ if (bufix < first_cutoff && bufix + incr > first_cutoff) incr = first_cutoff - bufix; else if (bufix < second_cutoff && bufix + incr > second_cutoff) incr = second_cutoff - bufix; /* * If this option is going to overflow the * buffer, skip it. */ if (bufix + 2 + incr > buflen) { bufix = optstart; break; } /* Everything looks good - copy it in! */ buffer[bufix] = code; buffer[bufix + 1] = incr; if (tto && incr == length) { memcpy(buffer + bufix + 2, options[code]->value + ix, incr - 1); buffer[bufix + 2 + incr - 1] = 0; } else memcpy(buffer + bufix + 2, options[code]->value + ix, incr); length -= incr; ix += incr; bufix += 2 + incr; } } return (bufix); } /* * Format the specified option so that a human can easily read it. */ char * pretty_print_option(unsigned int code, unsigned char *data, int len, int emit_commas, int emit_quotes) { static char optbuf[32768]; /* XXX */ int hunksize = 0, numhunk = -1, numelem = 0; char fmtbuf[32], *op = optbuf; int i, j, k, opleft = sizeof(optbuf); unsigned char *dp = data; struct in_addr foo; char comma; /* Code should be between 0 and 255. */ if (code > 255) error("pretty_print_option: bad code %d", code); if (emit_commas) comma = ','; else comma = ' '; /* Figure out the size of the data. */ for (i = 0; dhcp_options[code].format[i]; i++) { if (!numhunk) { warning("%s: Excess information in format string: %s", dhcp_options[code].name, &(dhcp_options[code].format[i])); break; } numelem++; fmtbuf[i] = dhcp_options[code].format[i]; switch (dhcp_options[code].format[i]) { case 'A': --numelem; fmtbuf[i] = 0; numhunk = 0; break; case 'X': for (k = 0; k < len; k++) if (!isascii(data[k]) || !isprint(data[k])) break; if (k == len) { fmtbuf[i] = 't'; numhunk = -2; } else { fmtbuf[i] = 'x'; hunksize++; comma = ':'; numhunk = 0; } fmtbuf[i + 1] = 0; break; case 't': fmtbuf[i] = 't'; fmtbuf[i + 1] = 0; numhunk = -2; break; case 'I': case 'l': case 'L': hunksize += 4; break; case 's': case 'S': hunksize += 2; break; case 'b': case 'B': case 'f': hunksize++; break; case 'e': break; default: warning("%s: garbage in format string: %s", dhcp_options[code].name, &(dhcp_options[code].format[i])); break; } } /* Check for too few bytes... */ if (hunksize > len) { warning("%s: expecting at least %d bytes; got %d", dhcp_options[code].name, hunksize, len); return (""); } /* Check for too many bytes... */ if (numhunk == -1 && hunksize < len) warning("%s: %d extra bytes", dhcp_options[code].name, len - hunksize); /* If this is an array, compute its size. */ if (!numhunk) numhunk = len / hunksize; /* See if we got an exact number of hunks. */ if (numhunk > 0 && numhunk * hunksize < len) warning("%s: %d extra bytes at end of array", dhcp_options[code].name, len - numhunk * hunksize); /* A one-hunk array prints the same as a single hunk. */ if (numhunk < 0) numhunk = 1; /* Cycle through the array (or hunk) printing the data. */ for (i = 0; i < numhunk; i++) { for (j = 0; j < numelem; j++) { int opcount; switch (fmtbuf[j]) { case 't': if (emit_quotes) { *op++ = '"'; opleft--; } for (; dp < data + len; dp++) { if (!isascii(*dp) || !isprint(*dp)) { if (dp + 1 != data + len || *dp != 0) { snprintf(op, opleft, "\\%03o", *dp); op += 4; opleft -= 4; } } else if (*dp == '"' || *dp == '\'' || *dp == '$' || *dp == '`' || *dp == '\\') { *op++ = '\\'; *op++ = *dp; opleft -= 2; } else { *op++ = *dp; opleft--; } } if (emit_quotes) { *op++ = '"'; opleft--; } *op = 0; break; case 'I': foo.s_addr = htonl(getULong(dp)); opcount = strlcpy(op, inet_ntoa(foo), opleft); if (opcount >= opleft) goto toobig; opleft -= opcount; dp += 4; break; case 'l': opcount = snprintf(op, opleft, "%ld", (long)getLong(dp)); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; dp += 4; break; case 'L': opcount = snprintf(op, opleft, "%ld", (unsigned long)getULong(dp)); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; dp += 4; break; case 's': opcount = snprintf(op, opleft, "%d", getShort(dp)); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; dp += 2; break; case 'S': opcount = snprintf(op, opleft, "%d", getUShort(dp)); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; dp += 2; break; case 'b': opcount = snprintf(op, opleft, "%d", *(char *)dp++); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; break; case 'B': opcount = snprintf(op, opleft, "%d", *dp++); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; break; case 'x': opcount = snprintf(op, opleft, "%x", *dp++); if (opcount >= opleft || opcount == -1) goto toobig; opleft -= opcount; break; case 'f': opcount = strlcpy(op, *dp++ ? "true" : "false", opleft); if (opcount >= opleft) goto toobig; opleft -= opcount; break; default: warning("Unexpected format code %c", fmtbuf[j]); } op += strlen(op); opleft -= strlen(op); if (opleft < 1) goto toobig; if (j + 1 < numelem && comma != ':') { *op++ = ' '; opleft--; } } if (i + 1 < numhunk) { *op++ = comma; opleft--; } if (opleft < 1) goto toobig; } return (optbuf); toobig: warning("dhcp option too large"); return (""); } void do_packet(struct interface_info *interface, struct dhcp_packet *packet, int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom) { struct packet tp; int i; if (packet->hlen > sizeof(packet->chaddr)) { note("Discarding packet with invalid hlen."); return; } memset(&tp, 0, sizeof(tp)); tp.raw = packet; tp.packet_length = len; tp.client_port = from_port; tp.client_addr = from; tp.interface = interface; tp.haddr = hfrom; parse_options(&tp); if (tp.options_valid && tp.options[DHO_DHCP_MESSAGE_TYPE].data) tp.packet_type = tp.options[DHO_DHCP_MESSAGE_TYPE].data[0]; if (tp.packet_type) dhcp(&tp); else bootp(&tp); /* Free the data associated with the options. */ for (i = 0; i < 256; i++) if (tp.options[i].len && tp.options[i].data) free(tp.options[i].data); } Index: head/sbin/fsck_ffs/fsck.h =================================================================== --- head/sbin/fsck_ffs/fsck.h (revision 229777) +++ head/sbin/fsck_ffs/fsck.h (revision 229778) @@ -1,400 +1,400 @@ /* * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * * This software was developed for the FreeBSD Project by Marshall * Kirk McKusick and Network Associates Laboratories, the Security * Research Division of Network Associates, Inc. under DARPA/SPAWAR * contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA CHATS * research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)fsck.h 8.4 (Berkeley) 5/9/95 * $FreeBSD$ */ #ifndef _FSCK_H_ #define _FSCK_H_ #include #include #include #define MAXDUP 10 /* limit on dup blks (per inode) */ #define MAXBAD 10 /* limit on bad blks (per inode) */ #define MAXBUFSPACE 40*1024 /* maximum space to allocate to buffers */ #define INOBUFSIZE 56*1024 /* size of buffer to read inodes in pass1 */ union dinode { struct ufs1_dinode dp1; struct ufs2_dinode dp2; }; #define DIP(dp, field) \ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \ (dp)->dp1.field : (dp)->dp2.field) #define DIP_SET(dp, field, val) do { \ if (sblock.fs_magic == FS_UFS1_MAGIC) \ (dp)->dp1.field = (val); \ else \ (dp)->dp2.field = (val); \ } while (0) /* * Each inode on the file system is described by the following structure. * The linkcnt is initially set to the value in the inode. Each time it * is found during the descent in passes 2, 3, and 4 the count is * decremented. Any inodes whose count is non-zero after pass 4 needs to * have its link count adjusted by the value remaining in ino_linkcnt. */ struct inostat { char ino_state; /* state of inode, see below */ char ino_type; /* type of inode */ short ino_linkcnt; /* number of links not found */ }; /* * Inode states. */ #define USTATE 0x1 /* inode not allocated */ #define FSTATE 0x2 /* inode is file */ #define FZLINK 0x3 /* inode is file with a link count of zero */ #define DSTATE 0x4 /* inode is directory */ #define DZLINK 0x5 /* inode is directory with a zero link count */ #define DFOUND 0x6 /* directory found during descent */ /* 0x7 UNUSED - see S_IS_DVALID() definition */ #define DCLEAR 0x8 /* directory is to be cleared */ #define FCLEAR 0x9 /* file is to be cleared */ /* DUNFOUND === (state == DSTATE || state == DZLINK) */ #define S_IS_DUNFOUND(state) (((state) & ~0x1) == DSTATE) /* DVALID === (state == DSTATE || state == DZLINK || state == DFOUND) */ #define S_IS_DVALID(state) (((state) & ~0x3) == DSTATE) #define INO_IS_DUNFOUND(ino) S_IS_DUNFOUND(inoinfo(ino)->ino_state) #define INO_IS_DVALID(ino) S_IS_DVALID(inoinfo(ino)->ino_state) /* * Inode state information is contained on per cylinder group lists * which are described by the following structure. */ struct inostatlist { long il_numalloced; /* number of inodes allocated in this cg */ struct inostat *il_stat;/* inostat info for this cylinder group */ } *inostathead; /* * buffer cache structure. */ struct bufarea { struct bufarea *b_next; /* free list queue */ struct bufarea *b_prev; /* free list queue */ ufs2_daddr_t b_bno; int b_size; int b_errs; int b_flags; union { char *b_buf; /* buffer space */ ufs1_daddr_t *b_indir1; /* UFS1 indirect block */ ufs2_daddr_t *b_indir2; /* UFS2 indirect block */ struct fs *b_fs; /* super block */ struct cg *b_cg; /* cylinder group */ struct ufs1_dinode *b_dinode1; /* UFS1 inode block */ struct ufs2_dinode *b_dinode2; /* UFS2 inode block */ } b_un; char b_dirty; }; #define IBLK(bp, i) \ ((sblock.fs_magic == FS_UFS1_MAGIC) ? \ (bp)->b_un.b_indir1[i] : (bp)->b_un.b_indir2[i]) #define IBLK_SET(bp, i, val) do { \ if (sblock.fs_magic == FS_UFS1_MAGIC) \ (bp)->b_un.b_indir1[i] = (val); \ else \ (bp)->b_un.b_indir2[i] = (val); \ } while (0) #define B_INUSE 1 #define MINBUFS 5 /* minimum number of buffers required */ struct bufarea bufhead; /* head of list of other blks in filesys */ struct bufarea sblk; /* file system superblock */ struct bufarea cgblk; /* cylinder group blocks */ struct bufarea *pdirbp; /* current directory contents */ struct bufarea *pbp; /* current inode block */ #define dirty(bp) do { \ if (fswritefd < 0) \ pfatal("SETTING DIRTY FLAG IN READ_ONLY MODE\n"); \ else \ (bp)->b_dirty = 1; \ } while (0) #define initbarea(bp) do { \ (bp)->b_dirty = 0; \ (bp)->b_bno = (ufs2_daddr_t)-1; \ (bp)->b_flags = 0; \ } while (0) #define sbdirty() dirty(&sblk) #define cgdirty() dirty(&cgblk) #define sblock (*sblk.b_un.b_fs) #define cgrp (*cgblk.b_un.b_cg) enum fixstate {DONTKNOW, NOFIX, FIX, IGNORE}; ino_t cursnapshot; struct inodesc { enum fixstate id_fix; /* policy on fixing errors */ int (*id_func)(struct inodesc *); /* function to be applied to blocks of inode */ ino_t id_number; /* inode number described */ ino_t id_parent; /* for DATA nodes, their parent */ ufs_lbn_t id_lbn; /* logical block number of current block */ ufs2_daddr_t id_blkno; /* current block number being examined */ int id_numfrags; /* number of frags contained in block */ off_t id_filesize; /* for DATA nodes, the size of the directory */ ufs2_daddr_t id_entryno;/* for DATA nodes, current entry number */ int id_loc; /* for DATA nodes, current location in dir */ struct direct *id_dirp; /* for DATA nodes, ptr to current entry */ char *id_name; /* for DATA nodes, name to find or enter */ char id_type; /* type of descriptor, DATA or ADDR */ }; /* file types */ #define DATA 1 /* a directory */ #define SNAP 2 /* a snapshot */ #define ADDR 3 /* anything but a directory or a snapshot */ /* * Linked list of duplicate blocks. * * The list is composed of two parts. The first part of the * list (from duplist through the node pointed to by muldup) * contains a single copy of each duplicate block that has been * found. The second part of the list (from muldup to the end) * contains duplicate blocks that have been found more than once. * To check if a block has been found as a duplicate it is only * necessary to search from duplist through muldup. To find the * total number of times that a block has been found as a duplicate - * the entire list must be searched for occurences of the block + * the entire list must be searched for occurrences of the block * in question. The following diagram shows a sample list where * w (found twice), x (found once), y (found three times), and z * (found once) are duplicate block numbers: * * w -> y -> x -> z -> y -> w -> y * ^ ^ * | | * duplist muldup */ struct dups { struct dups *next; ufs2_daddr_t dup; }; struct dups *duplist; /* head of dup list */ struct dups *muldup; /* end of unique duplicate dup block numbers */ /* * Inode cache data structures. */ struct inoinfo { struct inoinfo *i_nexthash; /* next entry in hash chain */ ino_t i_number; /* inode number of this entry */ ino_t i_parent; /* inode number of parent */ ino_t i_dotdot; /* inode number of `..' */ size_t i_isize; /* size of inode */ u_int i_numblks; /* size of block array in bytes */ ufs2_daddr_t i_blks[1]; /* actually longer */ } **inphead, **inpsort; long numdirs, dirhash, listmax, inplast; long countdirs; /* number of directories we actually found */ #define MIBSIZE 3 /* size of fsck sysctl MIBs */ int adjrefcnt[MIBSIZE]; /* MIB command to adjust inode reference cnt */ int adjblkcnt[MIBSIZE]; /* MIB command to adjust inode block count */ int adjndir[MIBSIZE]; /* MIB command to adjust number of directories */ int adjnbfree[MIBSIZE]; /* MIB command to adjust number of free blocks */ int adjnifree[MIBSIZE]; /* MIB command to adjust number of free inodes */ int adjnffree[MIBSIZE]; /* MIB command to adjust number of free frags */ int adjnumclusters[MIBSIZE]; /* MIB command to adjust number of free clusters */ int freefiles[MIBSIZE]; /* MIB command to free a set of files */ int freedirs[MIBSIZE]; /* MIB command to free a set of directories */ int freeblks[MIBSIZE]; /* MIB command to free a set of data blocks */ struct fsck_cmd cmd; /* sysctl file system update commands */ char snapname[BUFSIZ]; /* when doing snapshots, the name of the file */ char *cdevname; /* name of device being checked */ long dev_bsize; /* computed value of DEV_BSIZE */ long secsize; /* actual disk sector size */ u_int real_dev_bsize; /* actual disk sector size, not overriden */ char nflag; /* assume a no response */ char yflag; /* assume a yes response */ int bkgrdflag; /* use a snapshot to run on an active system */ int bflag; /* location of alternate super block */ int debug; /* output debugging info */ int Eflag; /* zero out empty data blocks */ int inoopt; /* trim out unused inodes */ char ckclean; /* only do work if not cleanly unmounted */ int cvtlevel; /* convert to newer file system format */ int bkgrdcheck; /* determine if background check is possible */ int bkgrdsumadj; /* whether the kernel have ability to adjust superblock summary */ char usedsoftdep; /* just fix soft dependency inconsistencies */ char preen; /* just fix normal inconsistencies */ char rerun; /* rerun fsck. Only used in non-preen mode */ int returntosingle; /* 1 => return to single user mode on exit */ char resolved; /* cleared if unresolved changes => not clean */ char havesb; /* superblock has been read */ char skipclean; /* skip clean file systems if preening */ int fsmodified; /* 1 => write done to file system */ int fsreadfd; /* file descriptor for reading file system */ int fswritefd; /* file descriptor for writing file system */ ufs2_daddr_t maxfsblock; /* number of blocks in the file system */ char *blockmap; /* ptr to primary blk allocation map */ ino_t maxino; /* number of inodes in file system */ ino_t lfdir; /* lost & found directory inode number */ const char *lfname; /* lost & found directory name */ int lfmode; /* lost & found directory creation mode */ ufs2_daddr_t n_blks; /* number of blocks in use */ ino_t n_files; /* number of files in use */ volatile sig_atomic_t got_siginfo; /* received a SIGINFO */ volatile sig_atomic_t got_sigalarm; /* received a SIGALRM */ #define clearinode(dp) \ if (sblock.fs_magic == FS_UFS1_MAGIC) { \ (dp)->dp1 = ufs1_zino; \ } else { \ (dp)->dp2 = ufs2_zino; \ } struct ufs1_dinode ufs1_zino; struct ufs2_dinode ufs2_zino; #define setbmap(blkno) setbit(blockmap, blkno) #define testbmap(blkno) isset(blockmap, blkno) #define clrbmap(blkno) clrbit(blockmap, blkno) #define STOP 0x01 #define SKIP 0x02 #define KEEPON 0x04 #define ALTERED 0x08 #define FOUND 0x10 #define EEXIT 8 /* Standard error exit. */ struct fstab; void adjust(struct inodesc *, int lcnt); ufs2_daddr_t allocblk(long frags); ino_t allocdir(ino_t parent, ino_t request, int mode); ino_t allocino(ino_t request, int type); void blkerror(ino_t ino, const char *type, ufs2_daddr_t blk); char *blockcheck(char *name); int blread(int fd, char *buf, ufs2_daddr_t blk, long size); void bufinit(void); void blwrite(int fd, char *buf, ufs2_daddr_t blk, long size); void blerase(int fd, ufs2_daddr_t blk, long size); void cacheino(union dinode *dp, ino_t inumber); void catch(int); void catchquit(int); int changeino(ino_t dir, const char *name, ino_t newnum); int check_cgmagic(int cg, struct cg *cgp); int chkrange(ufs2_daddr_t blk, int cnt); void ckfini(int markclean); int ckinode(union dinode *dp, struct inodesc *); void clri(struct inodesc *, const char *type, int flag); int clearentry(struct inodesc *); void direrror(ino_t ino, const char *errmesg); int dirscan(struct inodesc *); int dofix(struct inodesc *, const char *msg); int eascan(struct inodesc *, struct ufs2_dinode *dp); void fileerror(ino_t cwd, ino_t ino, const char *errmesg); int findino(struct inodesc *); int findname(struct inodesc *); void flush(int fd, struct bufarea *bp); void freeblk(ufs2_daddr_t blkno, long frags); void freeino(ino_t ino); void freeinodebuf(void); int ftypeok(union dinode *dp); void getblk(struct bufarea *bp, ufs2_daddr_t blk, long size); struct bufarea *getdatablk(ufs2_daddr_t blkno, long size); struct inoinfo *getinoinfo(ino_t inumber); union dinode *getnextinode(ino_t inumber, int rebuildcg); void getpathname(char *namebuf, ino_t curdir, ino_t ino); union dinode *ginode(ino_t inumber); void infohandler(int sig); void alarmhandler(int sig); void inocleanup(void); void inodirty(void); struct inostat *inoinfo(ino_t inum); int linkup(ino_t orphan, ino_t parentdir, char *name); int makeentry(ino_t parent, ino_t ino, const char *name); void panic(const char *fmt, ...) __printflike(1, 2); void pass1(void); void pass1b(void); int pass1check(struct inodesc *); void pass2(void); void pass3(void); void pass4(void); int pass4check(struct inodesc *); void pass5(void); void pfatal(const char *fmt, ...) __printflike(1, 2); void pinode(ino_t ino); void propagate(void); void pwarn(const char *fmt, ...) __printflike(1, 2); int readsb(int listerr); int reply(const char *question); void rwerror(const char *mesg, ufs2_daddr_t blk); void sblock_init(void); void setinodebuf(ino_t); int setup(char *dev); void gjournal_check(const char *filesys); int suj_check(const char *filesys); void update_maps(struct cg *, struct cg*, int); #endif /* !_FSCK_H_ */ Index: head/sbin/fsck_ffs/fsutil.c =================================================================== --- head/sbin/fsck_ffs/fsutil.c (revision 229777) +++ head/sbin/fsck_ffs/fsutil.c (revision 229778) @@ -1,798 +1,798 @@ /* * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static const char sccsid[] = "@(#)utilities.c 8.6 (Berkeley) 5/19/95"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" static void slowio_start(void); static void slowio_end(void); long diskreads, totalreads; /* Disk cache statistics */ struct timeval slowio_starttime; int slowio_delay_usec = 10000; /* Initial IO delay for background fsck */ int slowio_pollcnt; int ftypeok(union dinode *dp) { switch (DIP(dp, di_mode) & IFMT) { case IFDIR: case IFREG: case IFBLK: case IFCHR: case IFLNK: case IFSOCK: case IFIFO: return (1); default: if (debug) printf("bad file type 0%o\n", DIP(dp, di_mode)); return (0); } } int reply(const char *question) { int persevere; char c; if (preen) pfatal("INTERNAL ERROR: GOT TO reply()"); persevere = !strcmp(question, "CONTINUE"); printf("\n"); if (!persevere && (nflag || (fswritefd < 0 && bkgrdflag == 0))) { printf("%s? no\n\n", question); resolved = 0; return (0); } if (yflag || (persevere && nflag)) { printf("%s? yes\n\n", question); return (1); } do { printf("%s? [yn] ", question); (void) fflush(stdout); c = getc(stdin); while (c != '\n' && getc(stdin) != '\n') { if (feof(stdin)) { resolved = 0; return (0); } } } while (c != 'y' && c != 'Y' && c != 'n' && c != 'N'); printf("\n"); if (c == 'y' || c == 'Y') return (1); resolved = 0; return (0); } /* * Look up state information for an inode. */ struct inostat * inoinfo(ino_t inum) { static struct inostat unallocated = { USTATE, 0, 0 }; struct inostatlist *ilp; int iloff; if (inum > maxino) errx(EEXIT, "inoinfo: inumber %d out of range", inum); ilp = &inostathead[inum / sblock.fs_ipg]; iloff = inum % sblock.fs_ipg; if (iloff >= ilp->il_numalloced) return (&unallocated); return (&ilp->il_stat[iloff]); } /* * Malloc buffers and set up cache. */ void bufinit(void) { struct bufarea *bp; long bufcnt, i; char *bufp; pbp = pdirbp = (struct bufarea *)0; bufp = malloc((unsigned int)sblock.fs_bsize); if (bufp == 0) errx(EEXIT, "cannot allocate buffer pool"); cgblk.b_un.b_buf = bufp; initbarea(&cgblk); bufhead.b_next = bufhead.b_prev = &bufhead; bufcnt = MAXBUFSPACE / sblock.fs_bsize; if (bufcnt < MINBUFS) bufcnt = MINBUFS; for (i = 0; i < bufcnt; i++) { bp = (struct bufarea *)malloc(sizeof(struct bufarea)); bufp = malloc((unsigned int)sblock.fs_bsize); if (bp == NULL || bufp == NULL) { if (i >= MINBUFS) break; errx(EEXIT, "cannot allocate buffer pool"); } bp->b_un.b_buf = bufp; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; initbarea(bp); } bufhead.b_size = i; /* save number of buffers */ } /* * Manage a cache of directory blocks. */ struct bufarea * getdatablk(ufs2_daddr_t blkno, long size) { struct bufarea *bp; for (bp = bufhead.b_next; bp != &bufhead; bp = bp->b_next) if (bp->b_bno == fsbtodb(&sblock, blkno)) goto foundit; for (bp = bufhead.b_prev; bp != &bufhead; bp = bp->b_prev) if ((bp->b_flags & B_INUSE) == 0) break; if (bp == &bufhead) errx(EEXIT, "deadlocked buffer pool"); getblk(bp, blkno, size); /* fall through */ foundit: bp->b_prev->b_next = bp->b_next; bp->b_next->b_prev = bp->b_prev; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; bp->b_flags |= B_INUSE; return (bp); } void getblk(struct bufarea *bp, ufs2_daddr_t blk, long size) { ufs2_daddr_t dblk; totalreads++; dblk = fsbtodb(&sblock, blk); if (bp->b_bno != dblk) { flush(fswritefd, bp); diskreads++; bp->b_errs = blread(fsreadfd, bp->b_un.b_buf, dblk, size); bp->b_bno = dblk; bp->b_size = size; } } void flush(int fd, struct bufarea *bp) { int i, j; if (!bp->b_dirty) return; bp->b_dirty = 0; if (fswritefd < 0) { pfatal("WRITING IN READ_ONLY MODE.\n"); return; } if (bp->b_errs != 0) pfatal("WRITING %sZERO'ED BLOCK %lld TO DISK\n", (bp->b_errs == bp->b_size / dev_bsize) ? "" : "PARTIALLY ", (long long)bp->b_bno); bp->b_errs = 0; blwrite(fd, bp->b_un.b_buf, bp->b_bno, (long)bp->b_size); if (bp != &sblk) return; for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) { blwrite(fswritefd, (char *)sblock.fs_csp + i, fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag), sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize); } } void rwerror(const char *mesg, ufs2_daddr_t blk) { if (bkgrdcheck) exit(EEXIT); if (preen == 0) printf("\n"); pfatal("CANNOT %s: %ld", mesg, (long)blk); if (reply("CONTINUE") == 0) exit(EEXIT); } void ckfini(int markclean) { struct bufarea *bp, *nbp; int ofsmodified, cnt = 0; if (bkgrdflag) { unlink(snapname); if ((!(sblock.fs_flags & FS_UNCLEAN)) != markclean) { cmd.value = FS_UNCLEAN; cmd.size = markclean ? -1 : 1; if (sysctlbyname("vfs.ffs.setflags", 0, 0, &cmd, sizeof cmd) == -1) rwerror("SET FILE SYSTEM FLAGS", FS_UNCLEAN); if (!preen) { printf("\n***** FILE SYSTEM MARKED %s *****\n", markclean ? "CLEAN" : "DIRTY"); if (!markclean) rerun = 1; } } else if (!preen && !markclean) { printf("\n***** FILE SYSTEM STILL DIRTY *****\n"); rerun = 1; } } if (fswritefd < 0) { (void)close(fsreadfd); return; } flush(fswritefd, &sblk); if (havesb && cursnapshot == 0 && sblock.fs_magic == FS_UFS2_MAGIC && sblk.b_bno != sblock.fs_sblockloc / dev_bsize && !preen && reply("UPDATE STANDARD SUPERBLOCK")) { sblk.b_bno = sblock.fs_sblockloc / dev_bsize; sbdirty(); flush(fswritefd, &sblk); } flush(fswritefd, &cgblk); free(cgblk.b_un.b_buf); for (bp = bufhead.b_prev; bp && bp != &bufhead; bp = nbp) { cnt++; flush(fswritefd, bp); nbp = bp->b_prev; free(bp->b_un.b_buf); free((char *)bp); } if (bufhead.b_size != cnt) errx(EEXIT, "panic: lost %d buffers", bufhead.b_size - cnt); pbp = pdirbp = (struct bufarea *)0; if (cursnapshot == 0 && sblock.fs_clean != markclean) { if ((sblock.fs_clean = markclean) != 0) { sblock.fs_flags &= ~(FS_UNCLEAN | FS_NEEDSFSCK); sblock.fs_pendingblocks = 0; sblock.fs_pendinginodes = 0; } sbdirty(); ofsmodified = fsmodified; flush(fswritefd, &sblk); fsmodified = ofsmodified; if (!preen) { printf("\n***** FILE SYSTEM MARKED %s *****\n", markclean ? "CLEAN" : "DIRTY"); if (!markclean) rerun = 1; } } else if (!preen) { if (markclean) { printf("\n***** FILE SYSTEM IS CLEAN *****\n"); } else { printf("\n***** FILE SYSTEM STILL DIRTY *****\n"); rerun = 1; } } if (debug && totalreads > 0) printf("cache missed %ld of %ld (%d%%)\n", diskreads, totalreads, (int)(diskreads * 100 / totalreads)); (void)close(fsreadfd); (void)close(fswritefd); } int blread(int fd, char *buf, ufs2_daddr_t blk, long size) { char *cp; int i, errs; off_t offset; offset = blk; offset *= dev_bsize; if (bkgrdflag) slowio_start(); if (lseek(fd, offset, 0) < 0) rwerror("SEEK BLK", blk); else if (read(fd, buf, (int)size) == size) { if (bkgrdflag) slowio_end(); return (0); } rwerror("READ BLK", blk); if (lseek(fd, offset, 0) < 0) rwerror("SEEK BLK", blk); errs = 0; memset(buf, 0, (size_t)size); printf("THE FOLLOWING DISK SECTORS COULD NOT BE READ:"); for (cp = buf, i = 0; i < size; i += secsize, cp += secsize) { if (read(fd, cp, (int)secsize) != secsize) { (void)lseek(fd, offset + i + secsize, 0); if (secsize != dev_bsize && dev_bsize != 1) printf(" %jd (%jd),", (intmax_t)(blk * dev_bsize + i) / secsize, (intmax_t)blk + i / dev_bsize); else printf(" %jd,", (intmax_t)blk + i / dev_bsize); errs++; } } printf("\n"); if (errs) resolved = 0; return (errs); } void blwrite(int fd, char *buf, ufs2_daddr_t blk, long size) { int i; char *cp; off_t offset; if (fd < 0) return; offset = blk; offset *= dev_bsize; if (lseek(fd, offset, 0) < 0) rwerror("SEEK BLK", blk); else if (write(fd, buf, (int)size) == size) { fsmodified = 1; return; } resolved = 0; rwerror("WRITE BLK", blk); if (lseek(fd, offset, 0) < 0) rwerror("SEEK BLK", blk); printf("THE FOLLOWING SECTORS COULD NOT BE WRITTEN:"); for (cp = buf, i = 0; i < size; i += dev_bsize, cp += dev_bsize) if (write(fd, cp, (int)dev_bsize) != dev_bsize) { (void)lseek(fd, offset + i + dev_bsize, 0); printf(" %jd,", (intmax_t)blk + i / dev_bsize); } printf("\n"); return; } void blerase(int fd, ufs2_daddr_t blk, long size) { off_t ioarg[2]; if (fd < 0) return; ioarg[0] = blk * dev_bsize; ioarg[1] = size; ioctl(fd, DIOCGDELETE, ioarg); /* we don't really care if we succeed or not */ return; } /* * Verify cylinder group's magic number and other parameters. If the * test fails, offer an option to rebuild the whole cylinder group. */ int check_cgmagic(int cg, struct cg *cgp) { /* * Extended cylinder group checks. */ if (cg_chkmagic(cgp) && ((sblock.fs_magic == FS_UFS1_MAGIC && cgp->cg_old_niblk == sblock.fs_ipg && cgp->cg_ndblk <= sblock.fs_fpg && cgp->cg_old_ncyl <= sblock.fs_old_cpg) || (sblock.fs_magic == FS_UFS2_MAGIC && cgp->cg_niblk == sblock.fs_ipg && cgp->cg_ndblk <= sblock.fs_fpg && cgp->cg_initediblk <= sblock.fs_ipg))) { return (1); } pfatal("CYLINDER GROUP %d: BAD MAGIC NUMBER", cg); if (!reply("REBUILD CYLINDER GROUP")) { printf("YOU WILL NEED TO RERUN FSCK.\n"); rerun = 1; return (1); } /* * Zero out the cylinder group and then initialize critical fields. * Bit maps and summaries will be recalculated by later passes. */ memset(cgp, 0, (size_t)sblock.fs_cgsize); cgp->cg_magic = CG_MAGIC; cgp->cg_cgx = cg; cgp->cg_niblk = sblock.fs_ipg; cgp->cg_initediblk = sblock.fs_ipg < 2 * INOPB(&sblock) ? sblock.fs_ipg : 2 * INOPB(&sblock); if (cgbase(&sblock, cg) + sblock.fs_fpg < sblock.fs_size) cgp->cg_ndblk = sblock.fs_fpg; else cgp->cg_ndblk = sblock.fs_size - cgbase(&sblock, cg); cgp->cg_iusedoff = &cgp->cg_space[0] - (u_char *)(&cgp->cg_firstfield); if (sblock.fs_magic == FS_UFS1_MAGIC) { cgp->cg_niblk = 0; cgp->cg_initediblk = 0; cgp->cg_old_ncyl = sblock.fs_old_cpg; cgp->cg_old_niblk = sblock.fs_ipg; cgp->cg_old_btotoff = cgp->cg_iusedoff; cgp->cg_old_boff = cgp->cg_old_btotoff + sblock.fs_old_cpg * sizeof(int32_t); cgp->cg_iusedoff = cgp->cg_old_boff + sblock.fs_old_cpg * sizeof(u_int16_t); } cgp->cg_freeoff = cgp->cg_iusedoff + howmany(sblock.fs_ipg, CHAR_BIT); cgp->cg_nextfreeoff = cgp->cg_freeoff + howmany(sblock.fs_fpg,CHAR_BIT); if (sblock.fs_contigsumsize > 0) { cgp->cg_nclusterblks = cgp->cg_ndblk / sblock.fs_frag; cgp->cg_clustersumoff = roundup(cgp->cg_nextfreeoff, sizeof(u_int32_t)); cgp->cg_clustersumoff -= sizeof(u_int32_t); cgp->cg_clusteroff = cgp->cg_clustersumoff + (sblock.fs_contigsumsize + 1) * sizeof(u_int32_t); cgp->cg_nextfreeoff = cgp->cg_clusteroff + howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT); } cgdirty(); return (0); } /* * allocate a data block with the specified number of fragments */ ufs2_daddr_t allocblk(long frags) { int i, j, k, cg, baseblk; struct cg *cgp = &cgrp; if (frags <= 0 || frags > sblock.fs_frag) return (0); for (i = 0; i < maxfsblock - sblock.fs_frag; i += sblock.fs_frag) { for (j = 0; j <= sblock.fs_frag - frags; j++) { if (testbmap(i + j)) continue; for (k = 1; k < frags; k++) if (testbmap(i + j + k)) break; if (k < frags) { j += k; continue; } cg = dtog(&sblock, i + j); getblk(&cgblk, cgtod(&sblock, cg), sblock.fs_cgsize); if (!check_cgmagic(cg, cgp)) return (0); baseblk = dtogd(&sblock, i + j); for (k = 0; k < frags; k++) { setbmap(i + j + k); clrbit(cg_blksfree(cgp), baseblk + k); } n_blks += frags; if (frags == sblock.fs_frag) cgp->cg_cs.cs_nbfree--; else cgp->cg_cs.cs_nffree -= frags; cgdirty(); return (i + j); } } return (0); } /* * Free a previously allocated block */ void freeblk(ufs2_daddr_t blkno, long frags) { struct inodesc idesc; idesc.id_blkno = blkno; idesc.id_numfrags = frags; (void)pass4check(&idesc); } /* Slow down IO so as to leave some disk bandwidth for other processes */ void slowio_start() { /* Delay one in every 8 operations */ slowio_pollcnt = (slowio_pollcnt + 1) & 7; if (slowio_pollcnt == 0) { gettimeofday(&slowio_starttime, NULL); } } void slowio_end() { struct timeval tv; int delay_usec; if (slowio_pollcnt != 0) return; /* Update the slowdown interval. */ gettimeofday(&tv, NULL); delay_usec = (tv.tv_sec - slowio_starttime.tv_sec) * 1000000 + (tv.tv_usec - slowio_starttime.tv_usec); if (delay_usec < 64) delay_usec = 64; if (delay_usec > 2500000) delay_usec = 2500000; slowio_delay_usec = (slowio_delay_usec * 63 + delay_usec) >> 6; /* delay by 8 times the average IO delay */ if (slowio_delay_usec > 64) usleep(slowio_delay_usec * 8); } /* * Find a pathname */ void getpathname(char *namebuf, ino_t curdir, ino_t ino) { int len; char *cp; struct inodesc idesc; static int busy = 0; if (curdir == ino && ino == ROOTINO) { (void)strcpy(namebuf, "/"); return; } if (busy || !INO_IS_DVALID(curdir)) { (void)strcpy(namebuf, "?"); return; } busy = 1; memset(&idesc, 0, sizeof(struct inodesc)); idesc.id_type = DATA; idesc.id_fix = IGNORE; cp = &namebuf[MAXPATHLEN - 1]; *cp = '\0'; if (curdir != ino) { idesc.id_parent = curdir; goto namelookup; } while (ino != ROOTINO) { idesc.id_number = ino; idesc.id_func = findino; idesc.id_name = strdup(".."); if ((ckinode(ginode(ino), &idesc) & FOUND) == 0) break; namelookup: idesc.id_number = idesc.id_parent; idesc.id_parent = ino; idesc.id_func = findname; idesc.id_name = namebuf; if ((ckinode(ginode(idesc.id_number), &idesc)&FOUND) == 0) break; len = strlen(namebuf); cp -= len; memmove(cp, namebuf, (size_t)len); *--cp = '/'; if (cp < &namebuf[MAXNAMLEN]) break; ino = idesc.id_number; } busy = 0; if (ino != ROOTINO) *--cp = '?'; memmove(namebuf, cp, (size_t)(&namebuf[MAXPATHLEN] - cp)); } void catch(int sig __unused) { ckfini(0); exit(12); } /* * When preening, allow a single quit to signal * a special exit after file system checks complete * so that reboot sequence may be interrupted. */ void catchquit(int sig __unused) { printf("returning to single-user after file system check\n"); returntosingle = 1; (void)signal(SIGQUIT, SIG_DFL); } /* * determine whether an inode should be fixed. */ int dofix(struct inodesc *idesc, const char *msg) { switch (idesc->id_fix) { case DONTKNOW: if (idesc->id_type == DATA) direrror(idesc->id_number, msg); else pwarn("%s", msg); if (preen) { printf(" (SALVAGED)\n"); idesc->id_fix = FIX; return (ALTERED); } if (reply("SALVAGE") == 0) { idesc->id_fix = NOFIX; return (0); } idesc->id_fix = FIX; return (ALTERED); case FIX: return (ALTERED); case NOFIX: case IGNORE: return (0); default: errx(EEXIT, "UNKNOWN INODESC FIX MODE %d", idesc->id_fix); } /* NOTREACHED */ return (0); } #include /* - * An unexpected inconsistency occured. + * An unexpected inconsistency occurred. * Die if preening or file system is running with soft dependency protocol, * otherwise just print message and continue. */ void pfatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (!preen) { (void)vfprintf(stdout, fmt, ap); va_end(ap); if (usedsoftdep) (void)fprintf(stdout, "\nUNEXPECTED SOFT UPDATE INCONSISTENCY\n"); /* * Force foreground fsck to clean up inconsistency. */ if (bkgrdflag) { cmd.value = FS_NEEDSFSCK; cmd.size = 1; if (sysctlbyname("vfs.ffs.setflags", 0, 0, &cmd, sizeof cmd) == -1) pwarn("CANNOT SET FS_NEEDSFSCK FLAG\n"); fprintf(stdout, "CANNOT RUN IN BACKGROUND\n"); ckfini(0); exit(EEXIT); } return; } if (cdevname == NULL) cdevname = strdup("fsck"); (void)fprintf(stdout, "%s: ", cdevname); (void)vfprintf(stdout, fmt, ap); (void)fprintf(stdout, "\n%s: UNEXPECTED%sINCONSISTENCY; RUN fsck MANUALLY.\n", cdevname, usedsoftdep ? " SOFT UPDATE " : " "); /* * Force foreground fsck to clean up inconsistency. */ if (bkgrdflag) { cmd.value = FS_NEEDSFSCK; cmd.size = 1; if (sysctlbyname("vfs.ffs.setflags", 0, 0, &cmd, sizeof cmd) == -1) pwarn("CANNOT SET FS_NEEDSFSCK FLAG\n"); } ckfini(0); exit(EEXIT); } /* * Pwarn just prints a message when not preening or running soft dependency * protocol, or a warning (preceded by filename) when preening. */ void pwarn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (preen) (void)fprintf(stdout, "%s: ", cdevname); (void)vfprintf(stdout, fmt, ap); va_end(ap); } /* * Stub for routines from kernel. */ void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); pfatal("INTERNAL INCONSISTENCY:"); (void)vfprintf(stdout, fmt, ap); va_end(ap); exit(EEXIT); } Index: head/sbin/hastctl/hastctl.8 =================================================================== --- head/sbin/hastctl/hastctl.8 (revision 229777) +++ head/sbin/hastctl/hastctl.8 (revision 229778) @@ -1,220 +1,220 @@ .\" Copyright (c) 2010 The FreeBSD Foundation .\" All rights reserved. .\" .\" This software was developed by Pawel Jakub Dawidek under sponsorship from .\" the FreeBSD Foundation. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd April 10, 2011 .Dt HASTCTL 8 .Os .Sh NAME .Nm hastctl .Nd "Highly Available Storage control utility" .Sh SYNOPSIS .Nm .Cm create .Op Fl d .Op Fl c Ar config .Op Fl e Ar extentsize .Op Fl k Ar keepdirty .Op Fl m Ar mediasize .Ar name ... .Nm .Cm role .Op Fl d .Op Fl c Ar config .Aq init | primary | secondary .Ar all | name ... .Nm .Cm status .Op Fl d .Op Fl c Ar config .Op Ar all | name ... .Nm .Cm dump .Op Fl d .Op Fl c Ar config .Op Ar all | name ... .Sh DESCRIPTION The .Nm utility is used to control the behaviour of the .Xr hastd 8 daemon. .Pp This utility should be used by HA software like .Nm heartbeat or .Nm ucarp to setup HAST resources role when changing from primary mode to secondary or vice versa. Be aware that if a file system like UFS exists on HAST provider and primary node dies, file system has to be checked for inconsistencies with the .Xr fsck 8 utility after switching secondary node to primary role. .Pp The first argument to .Nm indicates an action to be performed: .Bl -tag -width ".Cm create" .It Cm create Initialize local provider configured for the given resource. Additional options include: .Bl -tag -width ".Fl e Ar extentsize" .It Fl e Ar extentsize Size of an extent. Extent is a block which is used for synchronization. .Xr hastd 8 maintains a map of dirty extents and extent is the smallest region that can be marked as dirty. If any part of an extent is modified, entire extent will be synchronized when nodes connect. If extent size is too small, there will be too much disk activity related to dirty map updates, which will degrade performance of the given resource. If extent size is too large, synchronization, even in case of short -outage, can take a long time increasing the risk of loosing up-to-date +outage, can take a long time increasing the risk of losing up-to-date node before synchronization process is completed. The default extent size is .Va 2MB . .It Fl k Ar keepdirty Maximum number of dirty extents to keep dirty all the time. Most recently used extents are kept dirty to reduce number of metadata updates. The default number of most recently used extents which will be kept dirty is .Va 64 . .It Fl m Ar mediasize Size of the smaller provider used as backend storage on both nodes. This option can be omitted if node providers have the same size on both sides. .El .Pp If size is suffixed with a k, M, G or T, it is taken as a kilobyte, megabyte, gigabyte or terabyte measurement respectively. .It Cm role Change role of the given resource. The role can be one of: .Bl -tag -width ".Cm secondary" .It Cm init Resource is turned off. .It Cm primary Local .Xr hastd 8 daemon will act as primary node for the given resource. System on which resource role is set to primary can use .Pa /dev/hast/ GEOM provider. .It Cm secondary Local .Xr hastd 8 daemon will act as secondary node for the given resource - it will wait for connection from the primary node and will handle I/O requests received from it. GEOM provider .Pa /dev/hast/ will not be created on secondary node. .El .It Cm status Present status of the configured resources. .It Cm dump Dump metadata stored on local component for the configured resources. .El .Pp In addition, every subcommand can be followed by the following options: .Bl -tag -width ".Fl c Ar config" .It Fl c Ar config Specify alternative location of the configuration file. The default location is .Pa /etc/hast.conf . .It Fl d Print debugging information. This option can be specified multiple times to raise the verbosity level. .El .Sh FILES .Bl -tag -width ".Pa /var/run/hastctl" -compact .It Pa /etc/hast.conf Configuration file for .Nm and .Xr hastd 8 . .It Pa /var/run/hastctl Control socket used by .Nm to communicate with the .Xr hastd 8 daemon. .El .Sh EXIT STATUS Exit status is 0 on success, or one of the values described in .Xr sysexits 3 on failure. .Sh EXAMPLES Initialize HAST provider, create file system on it and mount it. .Bd -literal -offset indent nodeB# hastctl create shared nodeB# hastd nodeB# hastctl role secondary shared nodeA# hastctl create shared nodeA# hastd nodeA# hastctl role primary shared nodeA# newfs -U /dev/hast/shared nodeA# mount -o noatime /dev/hast/shared /shared nodeA# application_start .Ed .Pp Switch roles for the .Nm shared HAST resource. .Bd -literal -offset indent nodeA# application_stop nodeA# umount -f /shared nodeA# hastctl role secondary shared nodeB# hastctl role primary shared nodeB# fsck -t ufs /dev/hast/shared nodeB# mount -o noatime /dev/hast/shared /shared nodeB# application_start .Ed .Sh SEE ALSO .Xr sysexits 3 , .Xr geom 4 , .Xr hast.conf 5 , .Xr fsck 8 , .Xr ggatec 8 , .Xr ggatel 8 , .Xr hastd 8 , .Xr mount 8 , .Xr newfs 8 . .Sh AUTHORS The .Nm was developed by .An Pawel Jakub Dawidek Aq pjd@FreeBSD.org under sponsorship of the FreeBSD Foundation. Index: head/sbin/hastd/activemap.c =================================================================== --- head/sbin/hastd/activemap.c (revision 229777) +++ head/sbin/hastd/activemap.c (revision 229778) @@ -1,701 +1,701 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include /* powerof2() */ #include #include #include #include #include #include #include #include #include "activemap.h" #ifndef PJDLOG_ASSERT #include #define PJDLOG_ASSERT(...) assert(__VA_ARGS__) #endif #define ACTIVEMAP_MAGIC 0xac71e4 struct activemap { int am_magic; /* Magic value. */ off_t am_mediasize; /* Media size in bytes. */ uint32_t am_extentsize; /* Extent size in bytes, must be power of 2. */ uint8_t am_extentshift;/* 2 ^ extentbits == extentsize */ int am_nextents; /* Number of extents. */ size_t am_mapsize; /* Bitmap size in bytes. */ uint16_t *am_memtab; /* An array that holds number of pending writes per extent. */ bitstr_t *am_diskmap; /* On-disk bitmap of dirty extents. */ bitstr_t *am_memmap; /* In-memory bitmap of dirty extents. */ size_t am_diskmapsize; /* Map size rounded up to sector size. */ uint64_t am_ndirty; /* Number of dirty regions. */ bitstr_t *am_syncmap; /* Bitmap of extents to sync. */ off_t am_syncoff; /* Next synchronization offset. */ TAILQ_HEAD(skeepdirty, keepdirty) am_keepdirty; /* List of extents that we keep dirty to reduce bitmap updates. */ int am_nkeepdirty; /* Number of am_keepdirty elements. */ int am_nkeepdirty_limit; /* Maximum number of am_keepdirty elements. */ }; struct keepdirty { int kd_extent; TAILQ_ENTRY(keepdirty) kd_next; }; /* * Helper function taken from sys/systm.h to calculate extentshift. */ static uint32_t bitcount32(uint32_t x) { x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1); x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2); x = (x + (x >> 4)) & 0x0f0f0f0f; x = (x + (x >> 8)); x = (x + (x >> 16)) & 0x000000ff; return (x); } static __inline int off2ext(const struct activemap *amp, off_t offset) { int extent; PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); extent = (offset >> amp->am_extentshift); PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); return (extent); } static __inline off_t ext2off(const struct activemap *amp, int extent) { off_t offset; PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); offset = ((off_t)extent << amp->am_extentshift); PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); return (offset); } /* * Function calculates number of requests needed to synchronize the given * extent. */ static __inline int ext2reqs(const struct activemap *amp, int ext) { off_t left; if (ext < amp->am_nextents - 1) return (((amp->am_extentsize - 1) / MAXPHYS) + 1); PJDLOG_ASSERT(ext == amp->am_nextents - 1); left = amp->am_mediasize % amp->am_extentsize; if (left == 0) left = amp->am_extentsize; return (((left - 1) / MAXPHYS) + 1); } /* * Initialize activemap structure and allocate memory for internal needs. * Function returns 0 on success and -1 if any of the allocations failed. */ int activemap_init(struct activemap **ampp, uint64_t mediasize, uint32_t extentsize, uint32_t sectorsize, uint32_t keepdirty) { struct activemap *amp; PJDLOG_ASSERT(ampp != NULL); PJDLOG_ASSERT(mediasize > 0); PJDLOG_ASSERT(extentsize > 0); PJDLOG_ASSERT(powerof2(extentsize)); PJDLOG_ASSERT(sectorsize > 0); PJDLOG_ASSERT(powerof2(sectorsize)); PJDLOG_ASSERT(keepdirty > 0); amp = malloc(sizeof(*amp)); if (amp == NULL) return (-1); amp->am_mediasize = mediasize; amp->am_nkeepdirty_limit = keepdirty; amp->am_extentsize = extentsize; amp->am_extentshift = bitcount32(extentsize - 1); amp->am_nextents = ((mediasize - 1) / extentsize) + 1; amp->am_mapsize = sizeof(bitstr_t) * bitstr_size(amp->am_nextents); amp->am_diskmapsize = roundup2(amp->am_mapsize, sectorsize); amp->am_ndirty = 0; amp->am_syncoff = -2; TAILQ_INIT(&->am_keepdirty); amp->am_nkeepdirty = 0; amp->am_memtab = calloc(amp->am_nextents, sizeof(amp->am_memtab[0])); amp->am_diskmap = calloc(1, amp->am_diskmapsize); amp->am_memmap = bit_alloc(amp->am_nextents); amp->am_syncmap = bit_alloc(amp->am_nextents); /* * Check to see if any of the allocations above failed. */ if (amp->am_memtab == NULL || amp->am_diskmap == NULL || amp->am_memmap == NULL || amp->am_syncmap == NULL) { if (amp->am_memtab != NULL) free(amp->am_memtab); if (amp->am_diskmap != NULL) free(amp->am_diskmap); if (amp->am_memmap != NULL) free(amp->am_memmap); if (amp->am_syncmap != NULL) free(amp->am_syncmap); amp->am_magic = 0; free(amp); errno = ENOMEM; return (-1); } amp->am_magic = ACTIVEMAP_MAGIC; *ampp = amp; return (0); } static struct keepdirty * keepdirty_find(struct activemap *amp, int extent) { struct keepdirty *kd; TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) { if (kd->kd_extent == extent) break; } return (kd); } static bool keepdirty_add(struct activemap *amp, int extent) { struct keepdirty *kd; kd = keepdirty_find(amp, extent); if (kd != NULL) { /* - * Only move element at the begining. + * Only move element at the beginning. */ TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); return (false); } /* * Add new element, but first remove the most unused one if * we have too many. */ if (amp->am_nkeepdirty >= amp->am_nkeepdirty_limit) { kd = TAILQ_LAST(&->am_keepdirty, skeepdirty); PJDLOG_ASSERT(kd != NULL); TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); amp->am_nkeepdirty--; PJDLOG_ASSERT(amp->am_nkeepdirty > 0); } if (kd == NULL) kd = malloc(sizeof(*kd)); /* We can ignore allocation failure. */ if (kd != NULL) { kd->kd_extent = extent; amp->am_nkeepdirty++; TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); } return (true); } static void keepdirty_fill(struct activemap *amp) { struct keepdirty *kd; TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) bit_set(amp->am_diskmap, kd->kd_extent); } static void keepdirty_free(struct activemap *amp) { struct keepdirty *kd; while ((kd = TAILQ_FIRST(&->am_keepdirty)) != NULL) { TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); amp->am_nkeepdirty--; free(kd); } PJDLOG_ASSERT(amp->am_nkeepdirty == 0); } /* * Function frees resources allocated by activemap_init() function. */ void activemap_free(struct activemap *amp) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); amp->am_magic = 0; keepdirty_free(amp); free(amp->am_memtab); free(amp->am_diskmap); free(amp->am_memmap); free(amp->am_syncmap); } /* * Function should be called before we handle write requests. It updates * internal structures and returns true if on-disk metadata should be updated. */ bool activemap_write_start(struct activemap *amp, off_t offset, off_t length) { bool modified; off_t end; int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(length > 0); modified = false; end = offset + length - 1; for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { /* * If the number of pending writes is increased from 0, * we have to mark the extent as dirty also in on-disk bitmap. * By returning true we inform the caller that on-disk bitmap * was modified and has to be flushed to disk. */ if (amp->am_memtab[ext]++ == 0) { PJDLOG_ASSERT(!bit_test(amp->am_memmap, ext)); bit_set(amp->am_memmap, ext); amp->am_ndirty++; } if (keepdirty_add(amp, ext)) modified = true; } return (modified); } /* * Function should be called after receiving write confirmation. It updates * internal structures and returns true if on-disk metadata should be updated. */ bool activemap_write_complete(struct activemap *amp, off_t offset, off_t length) { bool modified; off_t end; int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(length > 0); modified = false; end = offset + length - 1; for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { /* * If the number of pending writes goes down to 0, we have to * mark the extent as clean also in on-disk bitmap. * By returning true we inform the caller that on-disk bitmap * was modified and has to be flushed to disk. */ PJDLOG_ASSERT(amp->am_memtab[ext] > 0); PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); if (--amp->am_memtab[ext] == 0) { bit_clear(amp->am_memmap, ext); amp->am_ndirty--; if (keepdirty_find(amp, ext) == NULL) modified = true; } } return (modified); } /* * Function should be called after finishing synchronization of one extent. * It returns true if on-disk metadata should be updated. */ bool activemap_extent_complete(struct activemap *amp, int extent) { bool modified; int reqs; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); modified = false; reqs = ext2reqs(amp, extent); PJDLOG_ASSERT(amp->am_memtab[extent] >= reqs); amp->am_memtab[extent] -= reqs; PJDLOG_ASSERT(bit_test(amp->am_memmap, extent)); if (amp->am_memtab[extent] == 0) { bit_clear(amp->am_memmap, extent); amp->am_ndirty--; modified = true; } return (modified); } /* * Function returns number of dirty regions. */ uint64_t activemap_ndirty(const struct activemap *amp) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); return (amp->am_ndirty); } /* * Function compare on-disk bitmap and in-memory bitmap and returns true if * they differ and should be flushed to the disk. */ bool activemap_differ(const struct activemap *amp) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); return (memcmp(amp->am_diskmap, amp->am_memmap, amp->am_mapsize) != 0); } /* * Function returns number of bytes used by bitmap. */ size_t activemap_size(const struct activemap *amp) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); return (amp->am_mapsize); } /* * Function returns number of bytes needed for storing on-disk bitmap. * This is the same as activemap_size(), but rounded up to sector size. */ size_t activemap_ondisk_size(const struct activemap *amp) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); return (amp->am_diskmapsize); } /* * Function copies the given buffer read from disk to the internal bitmap. */ void activemap_copyin(struct activemap *amp, const unsigned char *buf, size_t size) { int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(size >= amp->am_mapsize); memcpy(amp->am_diskmap, buf, amp->am_mapsize); memcpy(amp->am_memmap, buf, amp->am_mapsize); memcpy(amp->am_syncmap, buf, amp->am_mapsize); bit_ffs(amp->am_memmap, amp->am_nextents, &ext); if (ext == -1) { /* There are no dirty extents, so we can leave now. */ return; } /* * Set synchronization offset to the first dirty extent. */ activemap_sync_rewind(amp); /* * We have dirty extents and we want them to stay that way until * we synchronize, so we set number of pending writes to number * of requests needed to synchronize one extent. */ amp->am_ndirty = 0; for (; ext < amp->am_nextents; ext++) { if (bit_test(amp->am_memmap, ext)) { amp->am_memtab[ext] = ext2reqs(amp, ext); amp->am_ndirty++; } } } /* * Function merges the given bitmap with existing one. */ void activemap_merge(struct activemap *amp, const unsigned char *buf, size_t size) { bitstr_t *remmap = __DECONST(bitstr_t *, buf); int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(size >= amp->am_mapsize); bit_ffs(remmap, amp->am_nextents, &ext); if (ext == -1) { /* There are no dirty extents, so we can leave now. */ return; } /* * We have dirty extents and we want them to stay that way until * we synchronize, so we set number of pending writes to number * of requests needed to synchronize one extent. */ for (; ext < amp->am_nextents; ext++) { /* Local extent already dirty. */ if (bit_test(amp->am_syncmap, ext)) continue; /* Remote extent isn't dirty. */ if (!bit_test(remmap, ext)) continue; bit_set(amp->am_syncmap, ext); bit_set(amp->am_memmap, ext); bit_set(amp->am_diskmap, ext); if (amp->am_memtab[ext] == 0) amp->am_ndirty++; amp->am_memtab[ext] = ext2reqs(amp, ext); } /* * Set synchronization offset to the first dirty extent. */ activemap_sync_rewind(amp); } /* * Function returns pointer to internal bitmap that should be written to disk. */ const unsigned char * activemap_bitmap(struct activemap *amp, size_t *sizep) { PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); if (sizep != NULL) *sizep = amp->am_diskmapsize; memcpy(amp->am_diskmap, amp->am_memmap, amp->am_mapsize); keepdirty_fill(amp); return ((const unsigned char *)amp->am_diskmap); } /* * Function calculates size needed to store bitmap on disk. */ size_t activemap_calc_ondisk_size(uint64_t mediasize, uint32_t extentsize, uint32_t sectorsize) { uint64_t nextents, mapsize; PJDLOG_ASSERT(mediasize > 0); PJDLOG_ASSERT(extentsize > 0); PJDLOG_ASSERT(powerof2(extentsize)); PJDLOG_ASSERT(sectorsize > 0); PJDLOG_ASSERT(powerof2(sectorsize)); nextents = ((mediasize - 1) / extentsize) + 1; mapsize = sizeof(bitstr_t) * bitstr_size(nextents); return (roundup2(mapsize, sectorsize)); } /* * Set synchronization offset to the first dirty extent. */ void activemap_sync_rewind(struct activemap *amp) { int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); if (ext == -1) { /* There are no extents to synchronize. */ amp->am_syncoff = -2; return; } /* - * Mark that we want to start synchronization from the begining. + * Mark that we want to start synchronization from the beginning. */ amp->am_syncoff = -1; } /* * Return next offset of where we should synchronize. */ off_t activemap_sync_offset(struct activemap *amp, off_t *lengthp, int *syncextp) { off_t syncoff, left; int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); PJDLOG_ASSERT(lengthp != NULL); PJDLOG_ASSERT(syncextp != NULL); *syncextp = -1; if (amp->am_syncoff == -2) return (-1); if (amp->am_syncoff >= 0 && (amp->am_syncoff + MAXPHYS >= amp->am_mediasize || off2ext(amp, amp->am_syncoff) != off2ext(amp, amp->am_syncoff + MAXPHYS))) { /* * We are about to change extent, so mark previous one as clean. */ ext = off2ext(amp, amp->am_syncoff); bit_clear(amp->am_syncmap, ext); *syncextp = ext; amp->am_syncoff = -1; } if (amp->am_syncoff == -1) { /* * Let's find first extent to synchronize. */ bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); if (ext == -1) { amp->am_syncoff = -2; return (-1); } amp->am_syncoff = ext2off(amp, ext); } else { /* * We don't change extent, so just increase offset. */ amp->am_syncoff += MAXPHYS; if (amp->am_syncoff >= amp->am_mediasize) { amp->am_syncoff = -2; return (-1); } } syncoff = amp->am_syncoff; left = ext2off(amp, off2ext(amp, syncoff)) + amp->am_extentsize - syncoff; if (syncoff + left > amp->am_mediasize) left = amp->am_mediasize - syncoff; if (left > MAXPHYS) left = MAXPHYS; PJDLOG_ASSERT(left >= 0 && left <= MAXPHYS); PJDLOG_ASSERT(syncoff >= 0 && syncoff < amp->am_mediasize); PJDLOG_ASSERT(syncoff + left >= 0 && syncoff + left <= amp->am_mediasize); *lengthp = left; return (syncoff); } /* * Mark extent(s) containing the given region for synchronization. * Most likely one of the components is unavailable. */ bool activemap_need_sync(struct activemap *amp, off_t offset, off_t length) { bool modified; off_t end; int ext; PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); modified = false; end = offset + length - 1; for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { if (bit_test(amp->am_syncmap, ext)) { /* Already marked for synchronization. */ PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); continue; } bit_set(amp->am_syncmap, ext); if (!bit_test(amp->am_memmap, ext)) { bit_set(amp->am_memmap, ext); amp->am_ndirty++; } amp->am_memtab[ext] += ext2reqs(amp, ext); modified = true; } return (modified); } void activemap_dump(const struct activemap *amp) { int bit; printf("M: "); for (bit = 0; bit < amp->am_nextents; bit++) printf("%d", bit_test(amp->am_memmap, bit) ? 1 : 0); printf("\n"); printf("D: "); for (bit = 0; bit < amp->am_nextents; bit++) printf("%d", bit_test(amp->am_diskmap, bit) ? 1 : 0); printf("\n"); printf("S: "); for (bit = 0; bit < amp->am_nextents; bit++) printf("%d", bit_test(amp->am_syncmap, bit) ? 1 : 0); printf("\n"); } Index: head/sbin/hastd/hast_compression.c =================================================================== --- head/sbin/hastd/hast_compression.c (revision 229777) +++ head/sbin/hastd/hast_compression.c (revision 229778) @@ -1,283 +1,283 @@ /*- * Copyright (c) 2011 Pawel Jakub Dawidek * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "hast_compression.h" static bool allzeros(const void *data, size_t size) { const uint64_t *p = data; unsigned int i; uint64_t v; PJDLOG_ASSERT((size % sizeof(*p)) == 0); /* * This is the fastest method I found for checking if the given * buffer contain all zeros. * Because inside the loop we don't check at every step, we would * get an answer only after walking through entire buffer. * To return early if the buffer doesn't contain all zeros, we probe - * 8 bytes at the begining, in the middle and at the end of the buffer + * 8 bytes at the beginning, in the middle and at the end of the buffer * first. */ size >>= 3; /* divide by 8 */ if ((p[0] | p[size >> 1] | p[size - 1]) != 0) return (false); v = 0; for (i = 0; i < size; i++) v |= *p++; return (v == 0); } static void * hast_hole_compress(const unsigned char *data, size_t *sizep) { uint32_t size; void *newbuf; if (!allzeros(data, *sizep)) return (NULL); newbuf = malloc(sizeof(size)); if (newbuf == NULL) { pjdlog_warning("Unable to compress (no memory: %zu).", (size_t)*sizep); return (NULL); } size = htole32((uint32_t)*sizep); bcopy(&size, newbuf, sizeof(size)); *sizep = sizeof(size); return (newbuf); } static void * hast_hole_decompress(const unsigned char *data, size_t *sizep) { uint32_t size; void *newbuf; if (*sizep != sizeof(size)) { pjdlog_error("Unable to decompress (invalid size: %zu).", *sizep); return (NULL); } bcopy(data, &size, sizeof(size)); size = le32toh(size); newbuf = malloc(size); if (newbuf == NULL) { pjdlog_error("Unable to decompress (no memory: %zu).", (size_t)size); return (NULL); } bzero(newbuf, size); *sizep = size; return (newbuf); } /* Minimum block size to try to compress. */ #define HAST_LZF_COMPRESS_MIN 1024 static void * hast_lzf_compress(const unsigned char *data, size_t *sizep) { unsigned char *newbuf; uint32_t origsize; size_t newsize; origsize = *sizep; if (origsize <= HAST_LZF_COMPRESS_MIN) return (NULL); newsize = sizeof(origsize) + origsize - HAST_LZF_COMPRESS_MIN; newbuf = malloc(newsize); if (newbuf == NULL) { pjdlog_warning("Unable to compress (no memory: %zu).", newsize); return (NULL); } newsize = lzf_compress(data, *sizep, newbuf + sizeof(origsize), newsize - sizeof(origsize)); if (newsize == 0) { free(newbuf); return (NULL); } origsize = htole32(origsize); bcopy(&origsize, newbuf, sizeof(origsize)); *sizep = sizeof(origsize) + newsize; return (newbuf); } static void * hast_lzf_decompress(const unsigned char *data, size_t *sizep) { unsigned char *newbuf; uint32_t origsize; size_t newsize; PJDLOG_ASSERT(*sizep > sizeof(origsize)); bcopy(data, &origsize, sizeof(origsize)); origsize = le32toh(origsize); PJDLOG_ASSERT(origsize > HAST_LZF_COMPRESS_MIN); newbuf = malloc(origsize); if (newbuf == NULL) { pjdlog_error("Unable to decompress (no memory: %zu).", (size_t)origsize); return (NULL); } newsize = lzf_decompress(data + sizeof(origsize), *sizep - sizeof(origsize), newbuf, origsize); if (newsize == 0) { free(newbuf); pjdlog_error("Unable to decompress."); return (NULL); } PJDLOG_ASSERT(newsize == origsize); *sizep = newsize; return (newbuf); } const char * compression_name(int num) { switch (num) { case HAST_COMPRESSION_NONE: return ("none"); case HAST_COMPRESSION_HOLE: return ("hole"); case HAST_COMPRESSION_LZF: return ("lzf"); } return ("unknown"); } int compression_send(const struct hast_resource *res, struct nv *nv, void **datap, size_t *sizep, bool *freedatap) { unsigned char *newbuf; int compression; size_t size; size = *sizep; compression = res->hr_compression; switch (compression) { case HAST_COMPRESSION_NONE: return (0); case HAST_COMPRESSION_HOLE: newbuf = hast_hole_compress(*datap, &size); break; case HAST_COMPRESSION_LZF: /* Try 'hole' compression first. */ newbuf = hast_hole_compress(*datap, &size); if (newbuf != NULL) compression = HAST_COMPRESSION_HOLE; else newbuf = hast_lzf_compress(*datap, &size); break; default: PJDLOG_ABORT("Invalid compression: %d.", res->hr_compression); } if (newbuf == NULL) { /* Unable to compress the data. */ return (0); } nv_add_string(nv, compression_name(compression), "compression"); if (nv_error(nv) != 0) { free(newbuf); errno = nv_error(nv); return (-1); } if (*freedatap) free(*datap); *freedatap = true; *datap = newbuf; *sizep = size; return (0); } int compression_recv(const struct hast_resource *res __unused, struct nv *nv, void **datap, size_t *sizep, bool *freedatap) { unsigned char *newbuf; const char *algo; size_t size; algo = nv_get_string(nv, "compression"); if (algo == NULL) return (0); /* No compression. */ newbuf = NULL; size = *sizep; if (strcmp(algo, "hole") == 0) newbuf = hast_hole_decompress(*datap, &size); else if (strcmp(algo, "lzf") == 0) newbuf = hast_lzf_decompress(*datap, &size); else { pjdlog_error("Unknown compression algorithm '%s'.", algo); return (-1); /* Unknown compression algorithm. */ } if (newbuf == NULL) return (-1); if (*freedatap) free(*datap); *freedatap = true; *datap = newbuf; *sizep = size; return (0); } Index: head/sbin/hastd/hastd.c =================================================================== --- head/sbin/hastd/hastd.c (revision 229777) +++ head/sbin/hastd/hastd.c (revision 229778) @@ -1,1302 +1,1302 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation * Copyright (c) 2010-2011 Pawel Jakub Dawidek * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "control.h" #include "event.h" #include "hast.h" #include "hast_proto.h" #include "hastd.h" #include "hooks.h" #include "subr.h" /* Path to configuration file. */ const char *cfgpath = HAST_CONFIG; /* Hastd configuration. */ static struct hastd_config *cfg; /* Was SIGINT or SIGTERM signal received? */ bool sigexit_received = false; /* PID file handle. */ struct pidfh *pfh; /* How often check for hooks running for too long. */ #define REPORT_INTERVAL 5 static void usage(void) { errx(EX_USAGE, "[-dFh] [-c config] [-P pidfile]"); } static void g_gate_load(void) { if (modfind("g_gate") == -1) { /* Not present in kernel, try loading it. */ if (kldload("geom_gate") == -1 || modfind("g_gate") == -1) { if (errno != EEXIST) { pjdlog_exit(EX_OSERR, "Unable to load geom_gate module"); } } } } void descriptors_cleanup(struct hast_resource *res) { struct hast_resource *tres; struct hastd_listen *lst; TAILQ_FOREACH(tres, &cfg->hc_resources, hr_next) { if (tres == res) { PJDLOG_VERIFY(res->hr_role == HAST_ROLE_SECONDARY || (res->hr_remotein == NULL && res->hr_remoteout == NULL)); continue; } if (tres->hr_remotein != NULL) proto_close(tres->hr_remotein); if (tres->hr_remoteout != NULL) proto_close(tres->hr_remoteout); if (tres->hr_ctrl != NULL) proto_close(tres->hr_ctrl); if (tres->hr_event != NULL) proto_close(tres->hr_event); if (tres->hr_conn != NULL) proto_close(tres->hr_conn); } if (cfg->hc_controlin != NULL) proto_close(cfg->hc_controlin); proto_close(cfg->hc_controlconn); TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) { if (lst->hl_conn != NULL) proto_close(lst->hl_conn); } (void)pidfile_close(pfh); hook_fini(); pjdlog_fini(); } static const char * dtype2str(mode_t mode) { if (S_ISBLK(mode)) return ("block device"); else if (S_ISCHR(mode)) return ("character device"); else if (S_ISDIR(mode)) return ("directory"); else if (S_ISFIFO(mode)) return ("pipe or FIFO"); else if (S_ISLNK(mode)) return ("symbolic link"); else if (S_ISREG(mode)) return ("regular file"); else if (S_ISSOCK(mode)) return ("socket"); else if (S_ISWHT(mode)) return ("whiteout"); else return ("unknown"); } void descriptors_assert(const struct hast_resource *res, int pjdlogmode) { char msg[256]; struct stat sb; long maxfd; bool isopen; mode_t mode; int fd; /* * At this point descriptor to syslog socket is closed, so if we want * to log assertion message, we have to first store it in 'msg' local * buffer and then open syslog socket and log it. */ msg[0] = '\0'; maxfd = sysconf(_SC_OPEN_MAX); if (maxfd < 0) { pjdlog_init(pjdlogmode); pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); pjdlog_errno(LOG_WARNING, "sysconf(_SC_OPEN_MAX) failed"); pjdlog_fini(); maxfd = 16384; } for (fd = 0; fd <= maxfd; fd++) { if (fstat(fd, &sb) == 0) { isopen = true; mode = sb.st_mode; } else if (errno == EBADF) { isopen = false; mode = 0; } else { (void)snprintf(msg, sizeof(msg), "Unable to fstat descriptor %d: %s", fd, strerror(errno)); break; } if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (%s) is closed, but should be open.", fd, (fd == STDIN_FILENO ? "stdin" : (fd == STDOUT_FILENO ? "stdout" : "stderr"))); break; } } else if (fd == proto_descriptor(res->hr_event)) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (event) is closed, but should be open.", fd); break; } if (!S_ISSOCK(mode)) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (event) is %s, but should be %s.", fd, dtype2str(mode), dtype2str(S_IFSOCK)); break; } } else if (fd == proto_descriptor(res->hr_ctrl)) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (ctrl) is closed, but should be open.", fd); break; } if (!S_ISSOCK(mode)) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (ctrl) is %s, but should be %s.", fd, dtype2str(mode), dtype2str(S_IFSOCK)); break; } } else if (res->hr_role == HAST_ROLE_PRIMARY && fd == proto_descriptor(res->hr_conn)) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (conn) is closed, but should be open.", fd); break; } if (!S_ISSOCK(mode)) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (conn) is %s, but should be %s.", fd, dtype2str(mode), dtype2str(S_IFSOCK)); break; } } else if (res->hr_role == HAST_ROLE_SECONDARY && res->hr_conn != NULL && fd == proto_descriptor(res->hr_conn)) { if (isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (conn) is open, but should be closed.", fd); break; } } else if (res->hr_role == HAST_ROLE_SECONDARY && fd == proto_descriptor(res->hr_remotein)) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (remote in) is closed, but should be open.", fd); break; } if (!S_ISSOCK(mode)) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (remote in) is %s, but should be %s.", fd, dtype2str(mode), dtype2str(S_IFSOCK)); break; } } else if (res->hr_role == HAST_ROLE_SECONDARY && fd == proto_descriptor(res->hr_remoteout)) { if (!isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (remote out) is closed, but should be open.", fd); break; } if (!S_ISSOCK(mode)) { (void)snprintf(msg, sizeof(msg), "Descriptor %d (remote out) is %s, but should be %s.", fd, dtype2str(mode), dtype2str(S_IFSOCK)); break; } } else { if (isopen) { (void)snprintf(msg, sizeof(msg), "Descriptor %d is open (%s), but should be closed.", fd, dtype2str(mode)); break; } } } if (msg[0] != '\0') { pjdlog_init(pjdlogmode); pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); PJDLOG_ABORT("%s", msg); } } static void child_exit_log(unsigned int pid, int status) { if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { pjdlog_debug(1, "Worker process exited gracefully (pid=%u).", pid); } else if (WIFSIGNALED(status)) { pjdlog_error("Worker process killed (pid=%u, signal=%d).", pid, WTERMSIG(status)); } else { pjdlog_error("Worker process exited ungracefully (pid=%u, exitcode=%d).", pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1); } } static void child_exit(void) { struct hast_resource *res; int status; pid_t pid; while ((pid = wait3(&status, WNOHANG, NULL)) > 0) { /* Find resource related to the process that just exited. */ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (pid == res->hr_workerpid) break; } if (res == NULL) { /* * This can happen when new connection arrives and we * cancel child responsible for the old one or if this * was hook which we executed. */ hook_check_one(pid, status); continue; } pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); child_exit_log(pid, status); child_cleanup(res); if (res->hr_role == HAST_ROLE_PRIMARY) { /* * Restart child process if it was killed by signal * or exited because of temporary problem. */ if (WIFSIGNALED(status) || (WIFEXITED(status) && WEXITSTATUS(status) == EX_TEMPFAIL)) { sleep(1); pjdlog_info("Restarting worker process."); hastd_primary(res); } else { res->hr_role = HAST_ROLE_INIT; pjdlog_info("Changing resource role back to %s.", role2str(res->hr_role)); } } pjdlog_prefix_set("%s", ""); } } static bool resource_needs_restart(const struct hast_resource *res0, const struct hast_resource *res1) { PJDLOG_ASSERT(strcmp(res0->hr_name, res1->hr_name) == 0); if (strcmp(res0->hr_provname, res1->hr_provname) != 0) return (true); if (strcmp(res0->hr_localpath, res1->hr_localpath) != 0) return (true); if (res0->hr_role == HAST_ROLE_INIT || res0->hr_role == HAST_ROLE_SECONDARY) { if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0) return (true); if (strcmp(res0->hr_sourceaddr, res1->hr_sourceaddr) != 0) return (true); if (res0->hr_replication != res1->hr_replication) return (true); if (res0->hr_checksum != res1->hr_checksum) return (true); if (res0->hr_compression != res1->hr_compression) return (true); if (res0->hr_timeout != res1->hr_timeout) return (true); if (strcmp(res0->hr_exec, res1->hr_exec) != 0) return (true); /* * When metaflush has changed we don't really need restart, * but it is just easier this way. */ if (res0->hr_metaflush != res1->hr_metaflush) return (true); } return (false); } static bool resource_needs_reload(const struct hast_resource *res0, const struct hast_resource *res1) { PJDLOG_ASSERT(strcmp(res0->hr_name, res1->hr_name) == 0); PJDLOG_ASSERT(strcmp(res0->hr_provname, res1->hr_provname) == 0); PJDLOG_ASSERT(strcmp(res0->hr_localpath, res1->hr_localpath) == 0); if (res0->hr_role != HAST_ROLE_PRIMARY) return (false); if (strcmp(res0->hr_remoteaddr, res1->hr_remoteaddr) != 0) return (true); if (strcmp(res0->hr_sourceaddr, res1->hr_sourceaddr) != 0) return (true); if (res0->hr_replication != res1->hr_replication) return (true); if (res0->hr_checksum != res1->hr_checksum) return (true); if (res0->hr_compression != res1->hr_compression) return (true); if (res0->hr_timeout != res1->hr_timeout) return (true); if (strcmp(res0->hr_exec, res1->hr_exec) != 0) return (true); if (res0->hr_metaflush != res1->hr_metaflush) return (true); return (false); } static void resource_reload(const struct hast_resource *res) { struct nv *nvin, *nvout; int error; PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY); nvout = nv_alloc(); nv_add_uint8(nvout, CONTROL_RELOAD, "cmd"); nv_add_string(nvout, res->hr_remoteaddr, "remoteaddr"); nv_add_string(nvout, res->hr_sourceaddr, "sourceaddr"); nv_add_int32(nvout, (int32_t)res->hr_replication, "replication"); nv_add_int32(nvout, (int32_t)res->hr_checksum, "checksum"); nv_add_int32(nvout, (int32_t)res->hr_compression, "compression"); nv_add_int32(nvout, (int32_t)res->hr_timeout, "timeout"); nv_add_string(nvout, res->hr_exec, "exec"); nv_add_int32(nvout, (int32_t)res->hr_metaflush, "metaflush"); if (nv_error(nvout) != 0) { nv_free(nvout); pjdlog_error("Unable to allocate header for reload message."); return; } if (hast_proto_send(res, res->hr_ctrl, nvout, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "Unable to send reload message"); nv_free(nvout); return; } nv_free(nvout); /* Receive response. */ if (hast_proto_recv_hdr(res->hr_ctrl, &nvin) < 0) { pjdlog_errno(LOG_ERR, "Unable to receive reload reply"); return; } error = nv_get_int16(nvin, "error"); nv_free(nvin); if (error != 0) { pjdlog_common(LOG_ERR, 0, error, "Reload failed"); return; } } static void hastd_reload(void) { struct hastd_config *newcfg; struct hast_resource *nres, *cres, *tres; struct hastd_listen *nlst, *clst; struct pidfh *newpfh; unsigned int nlisten; uint8_t role; pid_t otherpid; pjdlog_info("Reloading configuration..."); newpfh = NULL; newcfg = yy_config_parse(cfgpath, false); if (newcfg == NULL) goto failed; /* * Check if control address has changed. */ if (strcmp(cfg->hc_controladdr, newcfg->hc_controladdr) != 0) { if (proto_server(newcfg->hc_controladdr, &newcfg->hc_controlconn) < 0) { pjdlog_errno(LOG_ERR, "Unable to listen on control address %s", newcfg->hc_controladdr); goto failed; } } /* * Check if any listen address has changed. */ nlisten = 0; TAILQ_FOREACH(nlst, &newcfg->hc_listen, hl_next) { TAILQ_FOREACH(clst, &cfg->hc_listen, hl_next) { if (strcmp(nlst->hl_addr, clst->hl_addr) == 0) break; } if (clst != NULL && clst->hl_conn != NULL) { pjdlog_info("Keep listening on address %s.", nlst->hl_addr); nlst->hl_conn = clst->hl_conn; nlisten++; } else if (proto_server(nlst->hl_addr, &nlst->hl_conn) == 0) { pjdlog_info("Listening on new address %s.", nlst->hl_addr); nlisten++; } else { pjdlog_errno(LOG_WARNING, "Unable to listen on address %s", nlst->hl_addr); } } if (nlisten == 0) { pjdlog_error("No addresses to listen on."); goto failed; } /* * Check if pidfile's path has changed. */ if (strcmp(cfg->hc_pidfile, newcfg->hc_pidfile) != 0) { newpfh = pidfile_open(newcfg->hc_pidfile, 0600, &otherpid); if (newpfh == NULL) { if (errno == EEXIST) { pjdlog_errno(LOG_WARNING, "Another hastd is already running, pidfile: %s, pid: %jd.", newcfg->hc_pidfile, (intmax_t)otherpid); } else { pjdlog_errno(LOG_WARNING, "Unable to open or create pidfile %s", newcfg->hc_pidfile); } } else if (pidfile_write(newpfh) < 0) { /* Write PID to a file. */ pjdlog_errno(LOG_WARNING, "Unable to write PID to file %s", newcfg->hc_pidfile); } else { pjdlog_debug(1, "PID stored in %s.", newcfg->hc_pidfile); } } /* No failures from now on. */ /* * Switch to new control socket. */ if (newcfg->hc_controlconn != NULL) { pjdlog_info("Control socket changed from %s to %s.", cfg->hc_controladdr, newcfg->hc_controladdr); proto_close(cfg->hc_controlconn); cfg->hc_controlconn = newcfg->hc_controlconn; newcfg->hc_controlconn = NULL; strlcpy(cfg->hc_controladdr, newcfg->hc_controladdr, sizeof(cfg->hc_controladdr)); } /* * Switch to new pidfile. */ (void)pidfile_remove(pfh); pfh = newpfh; (void)strlcpy(cfg->hc_pidfile, newcfg->hc_pidfile, sizeof(cfg->hc_pidfile)); /* * Switch to new listen addresses. Close all that were removed. */ while ((clst = TAILQ_FIRST(&cfg->hc_listen)) != NULL) { TAILQ_FOREACH(nlst, &newcfg->hc_listen, hl_next) { if (strcmp(nlst->hl_addr, clst->hl_addr) == 0) break; } if (nlst == NULL && clst->hl_conn != NULL) { proto_close(clst->hl_conn); pjdlog_info("No longer listening on address %s.", clst->hl_addr); } TAILQ_REMOVE(&cfg->hc_listen, clst, hl_next); free(clst); } TAILQ_CONCAT(&cfg->hc_listen, &newcfg->hc_listen, hl_next); /* * Stop and remove resources that were removed from the configuration. */ TAILQ_FOREACH_SAFE(cres, &cfg->hc_resources, hr_next, tres) { TAILQ_FOREACH(nres, &newcfg->hc_resources, hr_next) { if (strcmp(cres->hr_name, nres->hr_name) == 0) break; } if (nres == NULL) { control_set_role(cres, HAST_ROLE_INIT); TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next); pjdlog_info("Resource %s removed.", cres->hr_name); free(cres); } } /* * Move new resources to the current configuration. */ TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) { TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) { if (strcmp(cres->hr_name, nres->hr_name) == 0) break; } if (cres == NULL) { TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next); TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next); pjdlog_info("Resource %s added.", nres->hr_name); } } /* * Deal with modified resources. * Depending on what has changed exactly we might want to perform * different actions. * * We do full resource restart in the following situations: * Resource role is INIT or SECONDARY. * Resource role is PRIMARY and path to local component or provider * name has changed. * In case of PRIMARY, the worker process will be killed and restarted, * which also means removing /dev/hast/ provider and * recreating it. * * We do just reload (send SIGHUP to worker process) if we act as * PRIMARY, but only if remote address, source address, replication * mode, timeout, execution path or metaflush has changed. * For those, there is no need to restart worker process. * If PRIMARY receives SIGHUP, it will reconnect if remote address or * source address has changed or it will set new timeout if only timeout * has changed or it will update metaflush if only metaflush has * changed. */ TAILQ_FOREACH_SAFE(nres, &newcfg->hc_resources, hr_next, tres) { TAILQ_FOREACH(cres, &cfg->hc_resources, hr_next) { if (strcmp(cres->hr_name, nres->hr_name) == 0) break; } PJDLOG_ASSERT(cres != NULL); if (resource_needs_restart(cres, nres)) { pjdlog_info("Resource %s configuration was modified, restarting it.", cres->hr_name); role = cres->hr_role; control_set_role(cres, HAST_ROLE_INIT); TAILQ_REMOVE(&cfg->hc_resources, cres, hr_next); free(cres); TAILQ_REMOVE(&newcfg->hc_resources, nres, hr_next); TAILQ_INSERT_TAIL(&cfg->hc_resources, nres, hr_next); control_set_role(nres, role); } else if (resource_needs_reload(cres, nres)) { pjdlog_info("Resource %s configuration was modified, reloading it.", cres->hr_name); strlcpy(cres->hr_remoteaddr, nres->hr_remoteaddr, sizeof(cres->hr_remoteaddr)); strlcpy(cres->hr_sourceaddr, nres->hr_sourceaddr, sizeof(cres->hr_sourceaddr)); cres->hr_replication = nres->hr_replication; cres->hr_checksum = nres->hr_checksum; cres->hr_compression = nres->hr_compression; cres->hr_timeout = nres->hr_timeout; strlcpy(cres->hr_exec, nres->hr_exec, sizeof(cres->hr_exec)); cres->hr_metaflush = nres->hr_metaflush; if (cres->hr_workerpid != 0) resource_reload(cres); } } yy_config_free(newcfg); pjdlog_info("Configuration reloaded successfully."); return; failed: if (newcfg != NULL) { if (newcfg->hc_controlconn != NULL) proto_close(newcfg->hc_controlconn); while ((nlst = TAILQ_FIRST(&newcfg->hc_listen)) != NULL) { if (nlst->hl_conn != NULL) { TAILQ_FOREACH(clst, &cfg->hc_listen, hl_next) { if (strcmp(nlst->hl_addr, clst->hl_addr) == 0) { break; } } if (clst == NULL || clst->hl_conn == NULL) proto_close(nlst->hl_conn); } TAILQ_REMOVE(&newcfg->hc_listen, nlst, hl_next); free(nlst); } yy_config_free(newcfg); } if (newpfh != NULL) (void)pidfile_remove(newpfh); pjdlog_warning("Configuration not reloaded."); } static void terminate_workers(void) { struct hast_resource *res; pjdlog_info("Termination signal received, exiting."); TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (res->hr_workerpid == 0) continue; pjdlog_info("Terminating worker process (resource=%s, role=%s, pid=%u).", res->hr_name, role2str(res->hr_role), res->hr_workerpid); if (kill(res->hr_workerpid, SIGTERM) == 0) continue; pjdlog_errno(LOG_WARNING, "Unable to send signal to worker process (resource=%s, role=%s, pid=%u).", res->hr_name, role2str(res->hr_role), res->hr_workerpid); } } static void listen_accept(struct hastd_listen *lst) { struct hast_resource *res; struct proto_conn *conn; struct nv *nvin, *nvout, *nverr; const char *resname; const unsigned char *token; char laddr[256], raddr[256]; size_t size; pid_t pid; int status; proto_local_address(lst->hl_conn, laddr, sizeof(laddr)); pjdlog_debug(1, "Accepting connection to %s.", laddr); if (proto_accept(lst->hl_conn, &conn) < 0) { pjdlog_errno(LOG_ERR, "Unable to accept connection %s", laddr); return; } proto_local_address(conn, laddr, sizeof(laddr)); proto_remote_address(conn, raddr, sizeof(raddr)); pjdlog_info("Connection from %s to %s.", raddr, laddr); /* Error in setting timeout is not critical, but why should it fail? */ if (proto_timeout(conn, HAST_TIMEOUT) < 0) pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); nvin = nvout = nverr = NULL; /* * Before receiving any data see if remote host have access to any * resource. */ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (proto_address_match(conn, res->hr_remoteaddr)) break; } if (res == NULL) { pjdlog_error("Client %s isn't known.", raddr); goto close; } /* Ok, remote host can access at least one resource. */ if (hast_proto_recv_hdr(conn, &nvin) < 0) { pjdlog_errno(LOG_ERR, "Unable to receive header from %s", raddr); goto close; } resname = nv_get_string(nvin, "resource"); if (resname == NULL) { pjdlog_error("No 'resource' field in the header received from %s.", raddr); goto close; } pjdlog_debug(2, "%s: resource=%s", raddr, resname); token = nv_get_uint8_array(nvin, &size, "token"); /* - * NULL token means that this is first conection. + * NULL token means that this is first connection. */ if (token != NULL && size != sizeof(res->hr_token)) { pjdlog_error("Received token of invalid size from %s (expected %zu, got %zu).", raddr, sizeof(res->hr_token), size); goto close; } /* * From now on we want to send errors to the remote node. */ nverr = nv_alloc(); /* Find resource related to this connection. */ TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (strcmp(resname, res->hr_name) == 0) break; } /* Have we found the resource? */ if (res == NULL) { pjdlog_error("No resource '%s' as requested by %s.", resname, raddr); nv_add_stringf(nverr, "errmsg", "Resource not configured."); goto fail; } /* Now that we know resource name setup log prefix. */ pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); /* Does the remote host have access to this resource? */ if (!proto_address_match(conn, res->hr_remoteaddr)) { pjdlog_error("Client %s has no access to the resource.", raddr); nv_add_stringf(nverr, "errmsg", "No access to the resource."); goto fail; } /* Is the resource marked as secondary? */ if (res->hr_role != HAST_ROLE_SECONDARY) { pjdlog_warning("We act as %s for the resource and not as %s as requested by %s.", role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY), raddr); nv_add_stringf(nverr, "errmsg", "Remote node acts as %s for the resource and not as %s.", role2str(res->hr_role), role2str(HAST_ROLE_SECONDARY)); if (res->hr_role == HAST_ROLE_PRIMARY) { /* * If we act as primary request the other side to wait * for us a bit, as we might be finishing cleanups. */ nv_add_uint8(nverr, 1, "wait"); } goto fail; } /* Does token (if exists) match? */ if (token != NULL && memcmp(token, res->hr_token, sizeof(res->hr_token)) != 0) { pjdlog_error("Token received from %s doesn't match.", raddr); nv_add_stringf(nverr, "errmsg", "Token doesn't match."); goto fail; } /* * If there is no token, but we have half-open connection * (only remotein) or full connection (worker process is running) * we have to cancel those and accept the new connection. */ if (token == NULL) { PJDLOG_ASSERT(res->hr_remoteout == NULL); pjdlog_debug(1, "Initial connection from %s.", raddr); if (res->hr_workerpid != 0) { PJDLOG_ASSERT(res->hr_remotein == NULL); pjdlog_debug(1, "Worker process exists (pid=%u), stopping it.", (unsigned int)res->hr_workerpid); /* Stop child process. */ if (kill(res->hr_workerpid, SIGINT) < 0) { pjdlog_errno(LOG_ERR, "Unable to stop worker process (pid=%u)", (unsigned int)res->hr_workerpid); /* * Other than logging the problem we * ignore it - nothing smart to do. */ } /* Wait for it to exit. */ else if ((pid = waitpid(res->hr_workerpid, &status, 0)) != res->hr_workerpid) { /* We can only log the problem. */ pjdlog_errno(LOG_ERR, "Waiting for worker process (pid=%u) failed", (unsigned int)res->hr_workerpid); } else { child_exit_log(res->hr_workerpid, status); } child_cleanup(res); } else if (res->hr_remotein != NULL) { char oaddr[256]; proto_remote_address(res->hr_remotein, oaddr, sizeof(oaddr)); pjdlog_debug(1, "Canceling half-open connection from %s on connection from %s.", oaddr, raddr); proto_close(res->hr_remotein); res->hr_remotein = NULL; } } /* * Checks and cleanups are done. */ if (token == NULL) { arc4random_buf(res->hr_token, sizeof(res->hr_token)); nvout = nv_alloc(); nv_add_uint8_array(nvout, res->hr_token, sizeof(res->hr_token), "token"); if (nv_error(nvout) != 0) { pjdlog_common(LOG_ERR, 0, nv_error(nvout), "Unable to prepare return header for %s", raddr); nv_add_stringf(nverr, "errmsg", "Remote node was unable to prepare return header: %s.", strerror(nv_error(nvout))); goto fail; } if (hast_proto_send(NULL, conn, nvout, NULL, 0) < 0) { int error = errno; pjdlog_errno(LOG_ERR, "Unable to send response to %s", raddr); nv_add_stringf(nverr, "errmsg", "Remote node was unable to send response: %s.", strerror(error)); goto fail; } res->hr_remotein = conn; pjdlog_debug(1, "Incoming connection from %s configured.", raddr); } else { res->hr_remoteout = conn; pjdlog_debug(1, "Outgoing connection to %s configured.", raddr); hastd_secondary(res, nvin); } nv_free(nvin); nv_free(nvout); nv_free(nverr); pjdlog_prefix_set("%s", ""); return; fail: if (nv_error(nverr) != 0) { pjdlog_common(LOG_ERR, 0, nv_error(nverr), "Unable to prepare error header for %s", raddr); goto close; } if (hast_proto_send(NULL, conn, nverr, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "Unable to send error to %s", raddr); goto close; } close: if (nvin != NULL) nv_free(nvin); if (nvout != NULL) nv_free(nvout); if (nverr != NULL) nv_free(nverr); proto_close(conn); pjdlog_prefix_set("%s", ""); } static void connection_migrate(struct hast_resource *res) { struct proto_conn *conn; int16_t val = 0; pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY); if (proto_recv(res->hr_conn, &val, sizeof(val)) < 0) { pjdlog_errno(LOG_WARNING, "Unable to receive connection command"); return; } if (proto_client(res->hr_sourceaddr[0] != '\0' ? res->hr_sourceaddr : NULL, res->hr_remoteaddr, &conn) < 0) { val = errno; pjdlog_errno(LOG_WARNING, "Unable to create outgoing connection to %s", res->hr_remoteaddr); goto out; } if (proto_connect(conn, -1) < 0) { val = errno; pjdlog_errno(LOG_WARNING, "Unable to connect to %s", res->hr_remoteaddr); proto_close(conn); goto out; } val = 0; out: if (proto_send(res->hr_conn, &val, sizeof(val)) < 0) { pjdlog_errno(LOG_WARNING, "Unable to send reply to connection request"); } if (val == 0 && proto_connection_send(res->hr_conn, conn) < 0) pjdlog_errno(LOG_WARNING, "Unable to send connection"); pjdlog_prefix_set("%s", ""); } static void check_signals(void) { struct timespec sigtimeout; sigset_t mask; int signo; sigtimeout.tv_sec = 0; sigtimeout.tv_nsec = 0; PJDLOG_VERIFY(sigemptyset(&mask) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0); while ((signo = sigtimedwait(&mask, NULL, &sigtimeout)) != -1) { switch (signo) { case SIGINT: case SIGTERM: sigexit_received = true; terminate_workers(); proto_close(cfg->hc_controlconn); exit(EX_OK); break; case SIGCHLD: child_exit(); break; case SIGHUP: hastd_reload(); break; default: PJDLOG_ABORT("Unexpected signal (%d).", signo); } } } static void main_loop(void) { struct hast_resource *res; struct hastd_listen *lst; struct timeval seltimeout; int fd, maxfd, ret; time_t lastcheck, now; fd_set rfds; lastcheck = time(NULL); seltimeout.tv_sec = REPORT_INTERVAL; seltimeout.tv_usec = 0; for (;;) { check_signals(); /* Setup descriptors for select(2). */ FD_ZERO(&rfds); maxfd = fd = proto_descriptor(cfg->hc_controlconn); PJDLOG_ASSERT(fd >= 0); FD_SET(fd, &rfds); TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) { if (lst->hl_conn == NULL) continue; fd = proto_descriptor(lst->hl_conn); PJDLOG_ASSERT(fd >= 0); FD_SET(fd, &rfds); maxfd = fd > maxfd ? fd : maxfd; } TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (res->hr_event == NULL) continue; fd = proto_descriptor(res->hr_event); PJDLOG_ASSERT(fd >= 0); FD_SET(fd, &rfds); maxfd = fd > maxfd ? fd : maxfd; if (res->hr_role == HAST_ROLE_PRIMARY) { /* Only primary workers asks for connections. */ PJDLOG_ASSERT(res->hr_conn != NULL); fd = proto_descriptor(res->hr_conn); PJDLOG_ASSERT(fd >= 0); FD_SET(fd, &rfds); maxfd = fd > maxfd ? fd : maxfd; } else { PJDLOG_ASSERT(res->hr_conn == NULL); } } PJDLOG_ASSERT(maxfd + 1 <= (int)FD_SETSIZE); ret = select(maxfd + 1, &rfds, NULL, NULL, &seltimeout); now = time(NULL); if (lastcheck + REPORT_INTERVAL <= now) { hook_check(); lastcheck = now; } if (ret == 0) { /* * select(2) timed out, so there should be no * descriptors to check. */ continue; } else if (ret == -1) { if (errno == EINTR) continue; KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "select() failed"); } /* * Check for signals before we do anything to update our * info about terminated workers in the meantime. */ check_signals(); if (FD_ISSET(proto_descriptor(cfg->hc_controlconn), &rfds)) control_handle(cfg); TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) { if (lst->hl_conn == NULL) continue; if (FD_ISSET(proto_descriptor(lst->hl_conn), &rfds)) listen_accept(lst); } TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (res->hr_event == NULL) continue; if (FD_ISSET(proto_descriptor(res->hr_event), &rfds)) { if (event_recv(res) == 0) continue; /* The worker process exited? */ proto_close(res->hr_event); res->hr_event = NULL; if (res->hr_conn != NULL) { proto_close(res->hr_conn); res->hr_conn = NULL; } continue; } if (res->hr_role == HAST_ROLE_PRIMARY) { PJDLOG_ASSERT(res->hr_conn != NULL); if (FD_ISSET(proto_descriptor(res->hr_conn), &rfds)) { connection_migrate(res); } } else { PJDLOG_ASSERT(res->hr_conn == NULL); } } } } static void dummy_sighandler(int sig __unused) { /* Nothing to do. */ } int main(int argc, char *argv[]) { struct hastd_listen *lst; const char *pidfile; pid_t otherpid; bool foreground; int debuglevel; sigset_t mask; foreground = false; debuglevel = 0; pidfile = NULL; for (;;) { int ch; ch = getopt(argc, argv, "c:dFhP:"); if (ch == -1) break; switch (ch) { case 'c': cfgpath = optarg; break; case 'd': debuglevel++; break; case 'F': foreground = true; break; case 'P': pidfile = optarg; break; case 'h': default: usage(); } } argc -= optind; argv += optind; pjdlog_init(PJDLOG_MODE_STD); pjdlog_debug_set(debuglevel); g_gate_load(); /* * When path to the configuration file is relative, obtain full path, * so we can always find the file, even after daemonizing and changing * working directory to /. */ if (cfgpath[0] != '/') { const char *newcfgpath; newcfgpath = realpath(cfgpath, NULL); if (newcfgpath == NULL) { pjdlog_exit(EX_CONFIG, "Unable to obtain full path of %s", cfgpath); } cfgpath = newcfgpath; } cfg = yy_config_parse(cfgpath, true); PJDLOG_ASSERT(cfg != NULL); if (pidfile != NULL) { if (strlcpy(cfg->hc_pidfile, pidfile, sizeof(cfg->hc_pidfile)) >= sizeof(cfg->hc_pidfile)) { pjdlog_exitx(EX_CONFIG, "Pidfile path is too long."); } } pfh = pidfile_open(cfg->hc_pidfile, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) { pjdlog_exitx(EX_TEMPFAIL, "Another hastd is already running, pidfile: %s, pid: %jd.", cfg->hc_pidfile, (intmax_t)otherpid); } /* If we cannot create pidfile for other reasons, only warn. */ pjdlog_errno(LOG_WARNING, "Unable to open or create pidfile %s", cfg->hc_pidfile); } /* * Restore default actions for interesting signals in case parent * process (like init(8)) decided to ignore some of them (like SIGHUP). */ PJDLOG_VERIFY(signal(SIGHUP, SIG_DFL) != SIG_ERR); PJDLOG_VERIFY(signal(SIGINT, SIG_DFL) != SIG_ERR); PJDLOG_VERIFY(signal(SIGTERM, SIG_DFL) != SIG_ERR); /* * Because SIGCHLD is ignored by default, setup dummy handler for it, * so we can mask it. */ PJDLOG_VERIFY(signal(SIGCHLD, dummy_sighandler) != SIG_ERR); PJDLOG_VERIFY(sigemptyset(&mask) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0); PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); /* Listen on control address. */ if (proto_server(cfg->hc_controladdr, &cfg->hc_controlconn) < 0) { KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to listen on control address %s", cfg->hc_controladdr); } /* Listen for remote connections. */ TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) { if (proto_server(lst->hl_addr, &lst->hl_conn) < 0) { KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to listen on address %s", lst->hl_addr); } } if (!foreground) { if (daemon(0, 0) < 0) { KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to daemonize"); } /* Start logging to syslog. */ pjdlog_mode_set(PJDLOG_MODE_SYSLOG); /* Write PID to a file. */ if (pidfile_write(pfh) < 0) { pjdlog_errno(LOG_WARNING, "Unable to write PID to a file %s", cfg->hc_pidfile); } else { pjdlog_debug(1, "PID stored in %s.", cfg->hc_pidfile); } } pjdlog_info("Started successfully, running protocol version %d.", HAST_PROTO_VERSION); pjdlog_debug(1, "Listening on control address %s.", cfg->hc_controladdr); TAILQ_FOREACH(lst, &cfg->hc_listen, hl_next) pjdlog_info("Listening on address %s.", lst->hl_addr); hook_init(); main_loop(); exit(0); } Index: head/sbin/hastd/lzf.h =================================================================== --- head/sbin/hastd/lzf.h (revision 229777) +++ head/sbin/hastd/lzf.h (revision 229778) @@ -1,211 +1,211 @@ /* * Copyright (c) 2000-2008 Marc Alexander Lehmann * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License ("GPL") version 2 or any later version, * in which case the provisions of the GPL are applicable instead of * the above. If you wish to allow the use of your version of this file * only under the terms of the GPL and not to allow others to use your * version of this file under the BSD license, indicate your decision * by deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete the * provisions above, a recipient may use your version of this file under * either the BSD or the GPL. */ #ifndef LZF_H #define LZF_H /*********************************************************************** ** ** lzf -- an extremely fast/free compression/decompression-method ** http://liblzf.plan9.de/ ** ** This algorithm is believed to be patent-free. ** ***********************************************************************/ #define LZF_VERSION 0x0105 /* 1.5, API version */ /* * Compress in_len bytes stored at the memory block starting at * in_data and write the result to out_data, up to a maximum length * of out_len bytes. * * If the output buffer is not large enough or any error occurs return 0, * otherwise return the number of bytes used, which might be considerably * more than in_len (but less than 104% of the original size), so it * makes sense to always use out_len == in_len - 1), to ensure _some_ * compression, and store the data uncompressed otherwise (with a flag, of * course. * * lzf_compress might use different algorithms on different systems and * even different runs, thus might result in different compressed strings * depending on the phase of the moon or similar factors. However, all * these strings are architecture-independent and will result in the * original data when decompressed using lzf_decompress. * * The buffers must not be overlapping. * * If the option LZF_STATE_ARG is enabled, an extra argument must be * supplied which is not reflected in this header file. Refer to lzfP.h * and lzf_c.c. * */ unsigned int lzf_compress (const void *const in_data, unsigned int in_len, void *out_data, unsigned int out_len); /* * Decompress data compressed with some version of the lzf_compress * function and stored at location in_data and length in_len. The result * will be stored at out_data up to a maximum of out_len characters. * * If the output buffer is not large enough to hold the decompressed * data, a 0 is returned and errno is set to E2BIG. Otherwise the number * of decompressed bytes (i.e. the original length of the data) is * returned. * * If an error in the compressed data is detected, a zero is returned and * errno is set to EINVAL. * * This function is very fast, about as fast as a copying loop. */ unsigned int lzf_decompress (const void *const in_data, unsigned int in_len, void *out_data, unsigned int out_len); /* * Size of hashtable is (1 << HLOG) * sizeof (char *) * decompression is independent of the hash table size * the difference between 15 and 14 is very small * for small blocks (and 14 is usually a bit faster). * For a low-memory/faster configuration, use HLOG == 13; * For best compression, use 15 or 16 (or more, up to 23). */ #ifndef HLOG # define HLOG 16 #endif /* * Sacrifice very little compression quality in favour of compression speed. * This gives almost the same compression as the default code, and is * (very roughly) 15% faster. This is the preferred mode of operation. */ #ifndef VERY_FAST # define VERY_FAST 1 #endif /* * Sacrifice some more compression quality in favour of compression speed. * (roughly 1-2% worse compression for large blocks and * 9-10% for small, redundant, blocks and >>20% better speed in both cases) * In short: when in need for speed, enable this for binary data, * possibly disable this for text data. */ #ifndef ULTRA_FAST # define ULTRA_FAST 0 #endif /* * Unconditionally aligning does not cost very much, so do it if unsure */ #ifndef STRICT_ALIGN # define STRICT_ALIGN !(defined(__i386) || defined (__amd64)) #endif /* * You may choose to pre-set the hash table (might be faster on some * modern cpus and large (>>64k) blocks, and also makes compression * deterministic/repeatable when the configuration otherwise is the same). */ #ifndef INIT_HTAB # define INIT_HTAB 1 #endif /* * Avoid assigning values to errno variable? for some embedding purposes - * (linux kernel for example), this is neccessary. NOTE: this breaks + * (linux kernel for example), this is necessary. NOTE: this breaks * the documentation in lzf.h. */ #ifndef AVOID_ERRNO # define AVOID_ERRNO 0 #endif /* * Wether to pass the LZF_STATE variable as argument, or allocate it * on the stack. For small-stack environments, define this to 1. * NOTE: this breaks the prototype in lzf.h. */ #ifndef LZF_STATE_ARG # define LZF_STATE_ARG 0 #endif /* * Wether to add extra checks for input validity in lzf_decompress * and return EINVAL if the input stream has been corrupted. This * only shields against overflowing the input buffer and will not * detect most corrupted streams. - * This check is not normally noticable on modern hardware + * This check is not normally noticeable on modern hardware * (<1% slowdown), but might slow down older cpus considerably. */ #ifndef CHECK_INPUT # define CHECK_INPUT 1 #endif /*****************************************************************************/ /* nothing should be changed below */ typedef unsigned char u8; typedef const u8 *LZF_STATE[1 << (HLOG)]; #if !STRICT_ALIGN /* for unaligned accesses we need a 16 bit datatype. */ # include # if USHRT_MAX == 65535 typedef unsigned short u16; # elif UINT_MAX == 65535 typedef unsigned int u16; # else # undef STRICT_ALIGN # define STRICT_ALIGN 1 # endif #endif #if ULTRA_FAST # if defined(VERY_FAST) # undef VERY_FAST # endif #endif #if INIT_HTAB # ifdef __cplusplus # include # else # include # endif #endif #endif Index: head/sbin/hastd/primary.c =================================================================== --- head/sbin/hastd/primary.c (revision 229777) +++ head/sbin/hastd/primary.c (revision 229778) @@ -1,2272 +1,2272 @@ /*- * Copyright (c) 2009 The FreeBSD Foundation * Copyright (c) 2010-2011 Pawel Jakub Dawidek * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "control.h" #include "event.h" #include "hast.h" #include "hast_proto.h" #include "hastd.h" #include "hooks.h" #include "metadata.h" #include "proto.h" #include "pjdlog.h" #include "subr.h" #include "synch.h" /* The is only one remote component for now. */ #define ISREMOTE(no) ((no) == 1) struct hio { /* * Number of components we are still waiting for. * When this field goes to 0, we can send the request back to the * kernel. Each component has to decrease this counter by one * even on failure. */ unsigned int hio_countdown; /* * Each component has a place to store its own error. * Once the request is handled by all components we can decide if the * request overall is successful or not. */ int *hio_errors; /* * Structure used to communicate with GEOM Gate class. */ struct g_gate_ctl_io hio_ggio; /* * Request was already confirmed to GEOM Gate. */ bool hio_done; /* * Remember replication from the time the request was initiated, * so we won't get confused when replication changes on reload. */ int hio_replication; TAILQ_ENTRY(hio) *hio_next; }; #define hio_free_next hio_next[0] #define hio_done_next hio_next[0] /* * Free list holds unused structures. When free list is empty, we have to wait * until some in-progress requests are freed. */ static TAILQ_HEAD(, hio) hio_free_list; static pthread_mutex_t hio_free_list_lock; static pthread_cond_t hio_free_list_cond; /* * There is one send list for every component. One requests is placed on all * send lists - each component gets the same request, but each component is * responsible for managing his own send list. */ static TAILQ_HEAD(, hio) *hio_send_list; static pthread_mutex_t *hio_send_list_lock; static pthread_cond_t *hio_send_list_cond; /* * There is one recv list for every component, although local components don't * use recv lists as local requests are done synchronously. */ static TAILQ_HEAD(, hio) *hio_recv_list; static pthread_mutex_t *hio_recv_list_lock; static pthread_cond_t *hio_recv_list_cond; /* * Request is placed on done list by the slowest component (the one that * decreased hio_countdown from 1 to 0). */ static TAILQ_HEAD(, hio) hio_done_list; static pthread_mutex_t hio_done_list_lock; static pthread_cond_t hio_done_list_cond; /* * Structure below are for interaction with sync thread. */ static bool sync_inprogress; static pthread_mutex_t sync_lock; static pthread_cond_t sync_cond; /* * The lock below allows to synchornize access to remote connections. */ static pthread_rwlock_t *hio_remote_lock; /* * Lock to synchronize metadata updates. Also synchronize access to * hr_primary_localcnt and hr_primary_remotecnt fields. */ static pthread_mutex_t metadata_lock; /* * Maximum number of outstanding I/O requests. */ #define HAST_HIO_MAX 256 /* * Number of components. At this point there are only two components: local * and remote, but in the future it might be possible to use multiple local * and remote components. */ #define HAST_NCOMPONENTS 2 #define ISCONNECTED(res, no) \ ((res)->hr_remotein != NULL && (res)->hr_remoteout != NULL) #define QUEUE_INSERT1(hio, name, ncomp) do { \ bool _wakeup; \ \ mtx_lock(&hio_##name##_list_lock[(ncomp)]); \ _wakeup = TAILQ_EMPTY(&hio_##name##_list[(ncomp)]); \ TAILQ_INSERT_TAIL(&hio_##name##_list[(ncomp)], (hio), \ hio_next[(ncomp)]); \ mtx_unlock(&hio_##name##_list_lock[ncomp]); \ if (_wakeup) \ cv_signal(&hio_##name##_list_cond[(ncomp)]); \ } while (0) #define QUEUE_INSERT2(hio, name) do { \ bool _wakeup; \ \ mtx_lock(&hio_##name##_list_lock); \ _wakeup = TAILQ_EMPTY(&hio_##name##_list); \ TAILQ_INSERT_TAIL(&hio_##name##_list, (hio), hio_##name##_next);\ mtx_unlock(&hio_##name##_list_lock); \ if (_wakeup) \ cv_signal(&hio_##name##_list_cond); \ } while (0) #define QUEUE_TAKE1(hio, name, ncomp, timeout) do { \ bool _last; \ \ mtx_lock(&hio_##name##_list_lock[(ncomp)]); \ _last = false; \ while (((hio) = TAILQ_FIRST(&hio_##name##_list[(ncomp)])) == NULL && !_last) { \ cv_timedwait(&hio_##name##_list_cond[(ncomp)], \ &hio_##name##_list_lock[(ncomp)], (timeout)); \ if ((timeout) != 0) \ _last = true; \ } \ if (hio != NULL) { \ TAILQ_REMOVE(&hio_##name##_list[(ncomp)], (hio), \ hio_next[(ncomp)]); \ } \ mtx_unlock(&hio_##name##_list_lock[(ncomp)]); \ } while (0) #define QUEUE_TAKE2(hio, name) do { \ mtx_lock(&hio_##name##_list_lock); \ while (((hio) = TAILQ_FIRST(&hio_##name##_list)) == NULL) { \ cv_wait(&hio_##name##_list_cond, \ &hio_##name##_list_lock); \ } \ TAILQ_REMOVE(&hio_##name##_list, (hio), hio_##name##_next); \ mtx_unlock(&hio_##name##_list_lock); \ } while (0) #define SYNCREQ(hio) do { \ (hio)->hio_ggio.gctl_unit = -1; \ (hio)->hio_ggio.gctl_seq = 1; \ } while (0) #define ISSYNCREQ(hio) ((hio)->hio_ggio.gctl_unit == -1) #define SYNCREQDONE(hio) do { (hio)->hio_ggio.gctl_unit = -2; } while (0) #define ISSYNCREQDONE(hio) ((hio)->hio_ggio.gctl_unit == -2) static struct hast_resource *gres; static pthread_mutex_t range_lock; static struct rangelocks *range_regular; static bool range_regular_wait; static pthread_cond_t range_regular_cond; static struct rangelocks *range_sync; static bool range_sync_wait; static pthread_cond_t range_sync_cond; static bool fullystarted; static void *ggate_recv_thread(void *arg); static void *local_send_thread(void *arg); static void *remote_send_thread(void *arg); static void *remote_recv_thread(void *arg); static void *ggate_send_thread(void *arg); static void *sync_thread(void *arg); static void *guard_thread(void *arg); static void cleanup(struct hast_resource *res) { int rerrno; /* Remember errno. */ rerrno = errno; /* Destroy ggate provider if we created one. */ if (res->hr_ggateunit >= 0) { struct g_gate_ctl_destroy ggiod; bzero(&ggiod, sizeof(ggiod)); ggiod.gctl_version = G_GATE_VERSION; ggiod.gctl_unit = res->hr_ggateunit; ggiod.gctl_force = 1; if (ioctl(res->hr_ggatefd, G_GATE_CMD_DESTROY, &ggiod) < 0) { pjdlog_errno(LOG_WARNING, "Unable to destroy hast/%s device", res->hr_provname); } res->hr_ggateunit = -1; } /* Restore errno. */ errno = rerrno; } static __dead2 void primary_exit(int exitcode, const char *fmt, ...) { va_list ap; PJDLOG_ASSERT(exitcode != EX_OK); va_start(ap, fmt); pjdlogv_errno(LOG_ERR, fmt, ap); va_end(ap); cleanup(gres); exit(exitcode); } static __dead2 void primary_exitx(int exitcode, const char *fmt, ...) { va_list ap; va_start(ap, fmt); pjdlogv(exitcode == EX_OK ? LOG_INFO : LOG_ERR, fmt, ap); va_end(ap); cleanup(gres); exit(exitcode); } static int hast_activemap_flush(struct hast_resource *res) { const unsigned char *buf; size_t size; buf = activemap_bitmap(res->hr_amp, &size); PJDLOG_ASSERT(buf != NULL); PJDLOG_ASSERT((size % res->hr_local_sectorsize) == 0); if (pwrite(res->hr_localfd, buf, size, METADATA_SIZE) != (ssize_t)size) { pjdlog_errno(LOG_ERR, "Unable to flush activemap to disk"); return (-1); } if (res->hr_metaflush == 1 && g_flush(res->hr_localfd) == -1) { if (errno == EOPNOTSUPP) { pjdlog_warning("The %s provider doesn't support flushing write cache. Disabling it.", res->hr_localpath); res->hr_metaflush = 0; } else { pjdlog_errno(LOG_ERR, "Unable to flush disk cache on activemap update"); return (-1); } } return (0); } static bool real_remote(const struct hast_resource *res) { return (strcmp(res->hr_remoteaddr, "none") != 0); } static void init_environment(struct hast_resource *res __unused) { struct hio *hio; unsigned int ii, ncomps; /* * In the future it might be per-resource value. */ ncomps = HAST_NCOMPONENTS; /* * Allocate memory needed by lists. */ hio_send_list = malloc(sizeof(hio_send_list[0]) * ncomps); if (hio_send_list == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for send lists.", sizeof(hio_send_list[0]) * ncomps); } hio_send_list_lock = malloc(sizeof(hio_send_list_lock[0]) * ncomps); if (hio_send_list_lock == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for send list locks.", sizeof(hio_send_list_lock[0]) * ncomps); } hio_send_list_cond = malloc(sizeof(hio_send_list_cond[0]) * ncomps); if (hio_send_list_cond == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for send list condition variables.", sizeof(hio_send_list_cond[0]) * ncomps); } hio_recv_list = malloc(sizeof(hio_recv_list[0]) * ncomps); if (hio_recv_list == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for recv lists.", sizeof(hio_recv_list[0]) * ncomps); } hio_recv_list_lock = malloc(sizeof(hio_recv_list_lock[0]) * ncomps); if (hio_recv_list_lock == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for recv list locks.", sizeof(hio_recv_list_lock[0]) * ncomps); } hio_recv_list_cond = malloc(sizeof(hio_recv_list_cond[0]) * ncomps); if (hio_recv_list_cond == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for recv list condition variables.", sizeof(hio_recv_list_cond[0]) * ncomps); } hio_remote_lock = malloc(sizeof(hio_remote_lock[0]) * ncomps); if (hio_remote_lock == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for remote connections locks.", sizeof(hio_remote_lock[0]) * ncomps); } /* * Initialize lists, their locks and theirs condition variables. */ TAILQ_INIT(&hio_free_list); mtx_init(&hio_free_list_lock); cv_init(&hio_free_list_cond); for (ii = 0; ii < HAST_NCOMPONENTS; ii++) { TAILQ_INIT(&hio_send_list[ii]); mtx_init(&hio_send_list_lock[ii]); cv_init(&hio_send_list_cond[ii]); TAILQ_INIT(&hio_recv_list[ii]); mtx_init(&hio_recv_list_lock[ii]); cv_init(&hio_recv_list_cond[ii]); rw_init(&hio_remote_lock[ii]); } TAILQ_INIT(&hio_done_list); mtx_init(&hio_done_list_lock); cv_init(&hio_done_list_cond); mtx_init(&metadata_lock); /* * Allocate requests pool and initialize requests. */ for (ii = 0; ii < HAST_HIO_MAX; ii++) { hio = malloc(sizeof(*hio)); if (hio == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for hio request.", sizeof(*hio)); } hio->hio_countdown = 0; hio->hio_errors = malloc(sizeof(hio->hio_errors[0]) * ncomps); if (hio->hio_errors == NULL) { primary_exitx(EX_TEMPFAIL, "Unable allocate %zu bytes of memory for hio errors.", sizeof(hio->hio_errors[0]) * ncomps); } hio->hio_next = malloc(sizeof(hio->hio_next[0]) * ncomps); if (hio->hio_next == NULL) { primary_exitx(EX_TEMPFAIL, "Unable allocate %zu bytes of memory for hio_next field.", sizeof(hio->hio_next[0]) * ncomps); } hio->hio_ggio.gctl_version = G_GATE_VERSION; hio->hio_ggio.gctl_data = malloc(MAXPHYS); if (hio->hio_ggio.gctl_data == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for gctl_data.", MAXPHYS); } hio->hio_ggio.gctl_length = MAXPHYS; hio->hio_ggio.gctl_error = 0; TAILQ_INSERT_HEAD(&hio_free_list, hio, hio_free_next); } } static bool init_resuid(struct hast_resource *res) { mtx_lock(&metadata_lock); if (res->hr_resuid != 0) { mtx_unlock(&metadata_lock); return (false); } else { /* Initialize unique resource identifier. */ arc4random_buf(&res->hr_resuid, sizeof(res->hr_resuid)); mtx_unlock(&metadata_lock); if (metadata_write(res) < 0) exit(EX_NOINPUT); return (true); } } static void init_local(struct hast_resource *res) { unsigned char *buf; size_t mapsize; if (metadata_read(res, true) < 0) exit(EX_NOINPUT); mtx_init(&res->hr_amp_lock); if (activemap_init(&res->hr_amp, res->hr_datasize, res->hr_extentsize, res->hr_local_sectorsize, res->hr_keepdirty) < 0) { primary_exit(EX_TEMPFAIL, "Unable to create activemap"); } mtx_init(&range_lock); cv_init(&range_regular_cond); if (rangelock_init(&range_regular) < 0) primary_exit(EX_TEMPFAIL, "Unable to create regular range lock"); cv_init(&range_sync_cond); if (rangelock_init(&range_sync) < 0) primary_exit(EX_TEMPFAIL, "Unable to create sync range lock"); mapsize = activemap_ondisk_size(res->hr_amp); buf = calloc(1, mapsize); if (buf == NULL) { primary_exitx(EX_TEMPFAIL, "Unable to allocate buffer for activemap."); } if (pread(res->hr_localfd, buf, mapsize, METADATA_SIZE) != (ssize_t)mapsize) { primary_exit(EX_NOINPUT, "Unable to read activemap"); } activemap_copyin(res->hr_amp, buf, mapsize); free(buf); if (res->hr_resuid != 0) return; /* * We're using provider for the first time. Initialize local and remote * counters. We don't initialize resuid here, as we want to do it just * in time. The reason for this is that we want to inform secondary * that there were no writes yet, so there is no need to synchronize * anything. */ res->hr_primary_localcnt = 0; res->hr_primary_remotecnt = 0; if (metadata_write(res) < 0) exit(EX_NOINPUT); } static int primary_connect(struct hast_resource *res, struct proto_conn **connp) { struct proto_conn *conn; int16_t val; val = 1; if (proto_send(res->hr_conn, &val, sizeof(val)) < 0) { primary_exit(EX_TEMPFAIL, "Unable to send connection request to parent"); } if (proto_recv(res->hr_conn, &val, sizeof(val)) < 0) { primary_exit(EX_TEMPFAIL, "Unable to receive reply to connection request from parent"); } if (val != 0) { errno = val; pjdlog_errno(LOG_WARNING, "Unable to connect to %s", res->hr_remoteaddr); return (-1); } if (proto_connection_recv(res->hr_conn, true, &conn) < 0) { primary_exit(EX_TEMPFAIL, "Unable to receive connection from parent"); } if (proto_connect_wait(conn, res->hr_timeout) < 0) { pjdlog_errno(LOG_WARNING, "Unable to connect to %s", res->hr_remoteaddr); proto_close(conn); return (-1); } /* Error in setting timeout is not critical, but why should it fail? */ if (proto_timeout(conn, res->hr_timeout) < 0) pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); *connp = conn; return (0); } static int init_remote(struct hast_resource *res, struct proto_conn **inp, struct proto_conn **outp) { struct proto_conn *in, *out; struct nv *nvout, *nvin; const unsigned char *token; unsigned char *map; const char *errmsg; int32_t extentsize; int64_t datasize; uint32_t mapsize; size_t size; int error; PJDLOG_ASSERT((inp == NULL && outp == NULL) || (inp != NULL && outp != NULL)); PJDLOG_ASSERT(real_remote(res)); in = out = NULL; errmsg = NULL; if (primary_connect(res, &out) == -1) return (ECONNREFUSED); error = ECONNABORTED; /* * First handshake step. * Setup outgoing connection with remote node. */ nvout = nv_alloc(); nv_add_string(nvout, res->hr_name, "resource"); if (nv_error(nvout) != 0) { pjdlog_common(LOG_WARNING, 0, nv_error(nvout), "Unable to allocate header for connection with %s", res->hr_remoteaddr); nv_free(nvout); goto close; } if (hast_proto_send(res, out, nvout, NULL, 0) < 0) { pjdlog_errno(LOG_WARNING, "Unable to send handshake header to %s", res->hr_remoteaddr); nv_free(nvout); goto close; } nv_free(nvout); if (hast_proto_recv_hdr(out, &nvin) < 0) { pjdlog_errno(LOG_WARNING, "Unable to receive handshake header from %s", res->hr_remoteaddr); goto close; } errmsg = nv_get_string(nvin, "errmsg"); if (errmsg != NULL) { pjdlog_warning("%s", errmsg); if (nv_exists(nvin, "wait")) error = EBUSY; nv_free(nvin); goto close; } token = nv_get_uint8_array(nvin, &size, "token"); if (token == NULL) { pjdlog_warning("Handshake header from %s has no 'token' field.", res->hr_remoteaddr); nv_free(nvin); goto close; } if (size != sizeof(res->hr_token)) { pjdlog_warning("Handshake header from %s contains 'token' of wrong size (got %zu, expected %zu).", res->hr_remoteaddr, size, sizeof(res->hr_token)); nv_free(nvin); goto close; } bcopy(token, res->hr_token, sizeof(res->hr_token)); nv_free(nvin); /* * Second handshake step. * Setup incoming connection with remote node. */ if (primary_connect(res, &in) == -1) goto close; nvout = nv_alloc(); nv_add_string(nvout, res->hr_name, "resource"); nv_add_uint8_array(nvout, res->hr_token, sizeof(res->hr_token), "token"); if (res->hr_resuid == 0) { /* * The resuid field was not yet initialized. * Because we do synchronization inside init_resuid(), it is * possible that someone already initialized it, the function * will return false then, but if we successfully initialized * it, we will get true. True means that there were no writes * to this resource yet and we want to inform secondary that * synchronization is not needed by sending "virgin" argument. */ if (init_resuid(res)) nv_add_int8(nvout, 1, "virgin"); } nv_add_uint64(nvout, res->hr_resuid, "resuid"); nv_add_uint64(nvout, res->hr_primary_localcnt, "localcnt"); nv_add_uint64(nvout, res->hr_primary_remotecnt, "remotecnt"); if (nv_error(nvout) != 0) { pjdlog_common(LOG_WARNING, 0, nv_error(nvout), "Unable to allocate header for connection with %s", res->hr_remoteaddr); nv_free(nvout); goto close; } if (hast_proto_send(res, in, nvout, NULL, 0) < 0) { pjdlog_errno(LOG_WARNING, "Unable to send handshake header to %s", res->hr_remoteaddr); nv_free(nvout); goto close; } nv_free(nvout); if (hast_proto_recv_hdr(out, &nvin) < 0) { pjdlog_errno(LOG_WARNING, "Unable to receive handshake header from %s", res->hr_remoteaddr); goto close; } errmsg = nv_get_string(nvin, "errmsg"); if (errmsg != NULL) { pjdlog_warning("%s", errmsg); nv_free(nvin); goto close; } datasize = nv_get_int64(nvin, "datasize"); if (datasize != res->hr_datasize) { pjdlog_warning("Data size differs between nodes (local=%jd, remote=%jd).", (intmax_t)res->hr_datasize, (intmax_t)datasize); nv_free(nvin); goto close; } extentsize = nv_get_int32(nvin, "extentsize"); if (extentsize != res->hr_extentsize) { pjdlog_warning("Extent size differs between nodes (local=%zd, remote=%zd).", (ssize_t)res->hr_extentsize, (ssize_t)extentsize); nv_free(nvin); goto close; } res->hr_secondary_localcnt = nv_get_uint64(nvin, "localcnt"); res->hr_secondary_remotecnt = nv_get_uint64(nvin, "remotecnt"); res->hr_syncsrc = nv_get_uint8(nvin, "syncsrc"); if (nv_exists(nvin, "virgin")) { /* * Secondary was reinitialized, bump localcnt if it is 0 as * only we have the data. */ PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_PRIMARY); PJDLOG_ASSERT(res->hr_secondary_localcnt == 0); if (res->hr_primary_localcnt == 0) { PJDLOG_ASSERT(res->hr_secondary_remotecnt == 0); mtx_lock(&metadata_lock); res->hr_primary_localcnt++; pjdlog_debug(1, "Increasing localcnt to %ju.", (uintmax_t)res->hr_primary_localcnt); (void)metadata_write(res); mtx_unlock(&metadata_lock); } } map = NULL; mapsize = nv_get_uint32(nvin, "mapsize"); if (mapsize > 0) { map = malloc(mapsize); if (map == NULL) { pjdlog_error("Unable to allocate memory for remote activemap (mapsize=%ju).", (uintmax_t)mapsize); nv_free(nvin); goto close; } /* * Remote node have some dirty extents on its own, lets * download its activemap. */ if (hast_proto_recv_data(res, out, nvin, map, mapsize) < 0) { pjdlog_errno(LOG_ERR, "Unable to receive remote activemap"); nv_free(nvin); free(map); goto close; } /* * Merge local and remote bitmaps. */ activemap_merge(res->hr_amp, map, mapsize); free(map); /* * Now that we merged bitmaps from both nodes, flush it to the * disk before we start to synchronize. */ (void)hast_activemap_flush(res); } nv_free(nvin); #ifdef notyet /* Setup directions. */ if (proto_send(out, NULL, 0) == -1) pjdlog_errno(LOG_WARNING, "Unable to set connection direction"); if (proto_recv(in, NULL, 0) == -1) pjdlog_errno(LOG_WARNING, "Unable to set connection direction"); #endif pjdlog_info("Connected to %s.", res->hr_remoteaddr); if (inp != NULL && outp != NULL) { *inp = in; *outp = out; } else { res->hr_remotein = in; res->hr_remoteout = out; } event_send(res, EVENT_CONNECT); return (0); close: if (errmsg != NULL && strcmp(errmsg, "Split-brain condition!") == 0) event_send(res, EVENT_SPLITBRAIN); proto_close(out); if (in != NULL) proto_close(in); return (error); } static void sync_start(void) { mtx_lock(&sync_lock); sync_inprogress = true; mtx_unlock(&sync_lock); cv_signal(&sync_cond); } static void sync_stop(void) { mtx_lock(&sync_lock); if (sync_inprogress) sync_inprogress = false; mtx_unlock(&sync_lock); } static void init_ggate(struct hast_resource *res) { struct g_gate_ctl_create ggiocreate; struct g_gate_ctl_cancel ggiocancel; /* * We communicate with ggate via /dev/ggctl. Open it. */ res->hr_ggatefd = open("/dev/" G_GATE_CTL_NAME, O_RDWR); if (res->hr_ggatefd < 0) primary_exit(EX_OSFILE, "Unable to open /dev/" G_GATE_CTL_NAME); /* * Create provider before trying to connect, as connection failure * is not critical, but may take some time. */ bzero(&ggiocreate, sizeof(ggiocreate)); ggiocreate.gctl_version = G_GATE_VERSION; ggiocreate.gctl_mediasize = res->hr_datasize; ggiocreate.gctl_sectorsize = res->hr_local_sectorsize; ggiocreate.gctl_flags = 0; ggiocreate.gctl_maxcount = 0; ggiocreate.gctl_timeout = 0; ggiocreate.gctl_unit = G_GATE_NAME_GIVEN; snprintf(ggiocreate.gctl_name, sizeof(ggiocreate.gctl_name), "hast/%s", res->hr_provname); if (ioctl(res->hr_ggatefd, G_GATE_CMD_CREATE, &ggiocreate) == 0) { pjdlog_info("Device hast/%s created.", res->hr_provname); res->hr_ggateunit = ggiocreate.gctl_unit; return; } if (errno != EEXIST) { primary_exit(EX_OSERR, "Unable to create hast/%s device", res->hr_provname); } pjdlog_debug(1, "Device hast/%s already exists, we will try to take it over.", res->hr_provname); /* * If we received EEXIST, we assume that the process who created the * provider died and didn't clean up. In that case we will start from * where he left of. */ bzero(&ggiocancel, sizeof(ggiocancel)); ggiocancel.gctl_version = G_GATE_VERSION; ggiocancel.gctl_unit = G_GATE_NAME_GIVEN; snprintf(ggiocancel.gctl_name, sizeof(ggiocancel.gctl_name), "hast/%s", res->hr_provname); if (ioctl(res->hr_ggatefd, G_GATE_CMD_CANCEL, &ggiocancel) == 0) { pjdlog_info("Device hast/%s recovered.", res->hr_provname); res->hr_ggateunit = ggiocancel.gctl_unit; return; } primary_exit(EX_OSERR, "Unable to take over hast/%s device", res->hr_provname); } void hastd_primary(struct hast_resource *res) { pthread_t td; pid_t pid; int error, mode, debuglevel; /* * Create communication channel for sending control commands from * parent to child. */ if (proto_client(NULL, "socketpair://", &res->hr_ctrl) < 0) { /* TODO: There's no need for this to be fatal error. */ KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to create control sockets between parent and child"); } /* * Create communication channel for sending events from child to parent. */ if (proto_client(NULL, "socketpair://", &res->hr_event) < 0) { /* TODO: There's no need for this to be fatal error. */ KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to create event sockets between child and parent"); } /* * Create communication channel for sending connection requests from * child to parent. */ if (proto_client(NULL, "socketpair://", &res->hr_conn) < 0) { /* TODO: There's no need for this to be fatal error. */ KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_OSERR, "Unable to create connection sockets between child and parent"); } pid = fork(); if (pid == -1) { /* TODO: There's no need for this to be fatal error. */ KEEP_ERRNO((void)pidfile_remove(pfh)); pjdlog_exit(EX_TEMPFAIL, "Unable to fork"); } if (pid > 0) { /* This is parent. */ /* Declare that we are receiver. */ proto_recv(res->hr_event, NULL, 0); proto_recv(res->hr_conn, NULL, 0); /* Declare that we are sender. */ proto_send(res->hr_ctrl, NULL, 0); res->hr_workerpid = pid; return; } gres = res; mode = pjdlog_mode_get(); debuglevel = pjdlog_debug_get(); /* Declare that we are sender. */ proto_send(res->hr_event, NULL, 0); proto_send(res->hr_conn, NULL, 0); /* Declare that we are receiver. */ proto_recv(res->hr_ctrl, NULL, 0); descriptors_cleanup(res); descriptors_assert(res, mode); pjdlog_init(mode); pjdlog_debug_set(debuglevel); pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); setproctitle("%s (%s)", res->hr_name, role2str(res->hr_role)); init_local(res); init_ggate(res); init_environment(res); if (drop_privs(res) != 0) { cleanup(res); exit(EX_CONFIG); } pjdlog_info("Privileges successfully dropped."); /* * Create the guard thread first, so we can handle signals from the - * very begining. + * very beginning. */ error = pthread_create(&td, NULL, guard_thread, res); PJDLOG_ASSERT(error == 0); /* * Create the control thread before sending any event to the parent, * as we can deadlock when parent sends control request to worker, * but worker has no control thread started yet, so parent waits. * In the meantime worker sends an event to the parent, but parent * is unable to handle the event, because it waits for control * request response. */ error = pthread_create(&td, NULL, ctrl_thread, res); PJDLOG_ASSERT(error == 0); if (real_remote(res)) { error = init_remote(res, NULL, NULL); if (error == 0) { sync_start(); } else if (error == EBUSY) { time_t start = time(NULL); pjdlog_warning("Waiting for remote node to become %s for %ds.", role2str(HAST_ROLE_SECONDARY), res->hr_timeout); for (;;) { sleep(1); error = init_remote(res, NULL, NULL); if (error != EBUSY) break; if (time(NULL) > start + res->hr_timeout) break; } if (error == EBUSY) { pjdlog_warning("Remote node is still %s, starting anyway.", role2str(HAST_ROLE_PRIMARY)); } } } error = pthread_create(&td, NULL, ggate_recv_thread, res); PJDLOG_ASSERT(error == 0); error = pthread_create(&td, NULL, local_send_thread, res); PJDLOG_ASSERT(error == 0); error = pthread_create(&td, NULL, remote_send_thread, res); PJDLOG_ASSERT(error == 0); error = pthread_create(&td, NULL, remote_recv_thread, res); PJDLOG_ASSERT(error == 0); error = pthread_create(&td, NULL, ggate_send_thread, res); PJDLOG_ASSERT(error == 0); fullystarted = true; (void)sync_thread(res); } static void reqlog(int loglevel, int debuglevel, struct g_gate_ctl_io *ggio, const char *fmt, ...) { char msg[1024]; va_list ap; int len; va_start(ap, fmt); len = vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); if ((size_t)len < sizeof(msg)) { switch (ggio->gctl_cmd) { case BIO_READ: (void)snprintf(msg + len, sizeof(msg) - len, "READ(%ju, %ju).", (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length); break; case BIO_DELETE: (void)snprintf(msg + len, sizeof(msg) - len, "DELETE(%ju, %ju).", (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length); break; case BIO_FLUSH: (void)snprintf(msg + len, sizeof(msg) - len, "FLUSH."); break; case BIO_WRITE: (void)snprintf(msg + len, sizeof(msg) - len, "WRITE(%ju, %ju).", (uintmax_t)ggio->gctl_offset, (uintmax_t)ggio->gctl_length); break; default: (void)snprintf(msg + len, sizeof(msg) - len, "UNKNOWN(%u).", (unsigned int)ggio->gctl_cmd); break; } } pjdlog_common(loglevel, debuglevel, -1, "%s", msg); } static void remote_close(struct hast_resource *res, int ncomp) { rw_wlock(&hio_remote_lock[ncomp]); /* * Check for a race between dropping rlock and acquiring wlock - * another thread can close connection in-between. */ if (!ISCONNECTED(res, ncomp)) { PJDLOG_ASSERT(res->hr_remotein == NULL); PJDLOG_ASSERT(res->hr_remoteout == NULL); rw_unlock(&hio_remote_lock[ncomp]); return; } PJDLOG_ASSERT(res->hr_remotein != NULL); PJDLOG_ASSERT(res->hr_remoteout != NULL); pjdlog_debug(2, "Closing incoming connection to %s.", res->hr_remoteaddr); proto_close(res->hr_remotein); res->hr_remotein = NULL; pjdlog_debug(2, "Closing outgoing connection to %s.", res->hr_remoteaddr); proto_close(res->hr_remoteout); res->hr_remoteout = NULL; rw_unlock(&hio_remote_lock[ncomp]); pjdlog_warning("Disconnected from %s.", res->hr_remoteaddr); /* * Stop synchronization if in-progress. */ sync_stop(); event_send(res, EVENT_DISCONNECT); } /* * Acknowledge write completion to the kernel, but don't update activemap yet. */ static void write_complete(struct hast_resource *res, struct hio *hio) { struct g_gate_ctl_io *ggio; unsigned int ncomp; PJDLOG_ASSERT(!hio->hio_done); ggio = &hio->hio_ggio; PJDLOG_ASSERT(ggio->gctl_cmd == BIO_WRITE); /* * Bump local count if this is first write after * connection failure with remote node. */ ncomp = 1; rw_rlock(&hio_remote_lock[ncomp]); if (!ISCONNECTED(res, ncomp)) { mtx_lock(&metadata_lock); if (res->hr_primary_localcnt == res->hr_secondary_remotecnt) { res->hr_primary_localcnt++; pjdlog_debug(1, "Increasing localcnt to %ju.", (uintmax_t)res->hr_primary_localcnt); (void)metadata_write(res); } mtx_unlock(&metadata_lock); } rw_unlock(&hio_remote_lock[ncomp]); if (ioctl(res->hr_ggatefd, G_GATE_CMD_DONE, ggio) < 0) primary_exit(EX_OSERR, "G_GATE_CMD_DONE failed"); hio->hio_done = true; } /* * Thread receives ggate I/O requests from the kernel and passes them to * appropriate threads: * WRITE - always goes to both local_send and remote_send threads * READ (when the block is up-to-date on local component) - * only local_send thread * READ (when the block isn't up-to-date on local component) - * only remote_send thread * DELETE - always goes to both local_send and remote_send threads * FLUSH - always goes to both local_send and remote_send threads */ static void * ggate_recv_thread(void *arg) { struct hast_resource *res = arg; struct g_gate_ctl_io *ggio; struct hio *hio; unsigned int ii, ncomp, ncomps; int error; for (;;) { pjdlog_debug(2, "ggate_recv: Taking free request."); QUEUE_TAKE2(hio, free); pjdlog_debug(2, "ggate_recv: (%p) Got free request.", hio); ggio = &hio->hio_ggio; ggio->gctl_unit = res->hr_ggateunit; ggio->gctl_length = MAXPHYS; ggio->gctl_error = 0; hio->hio_done = false; hio->hio_replication = res->hr_replication; pjdlog_debug(2, "ggate_recv: (%p) Waiting for request from the kernel.", hio); if (ioctl(res->hr_ggatefd, G_GATE_CMD_START, ggio) < 0) { if (sigexit_received) pthread_exit(NULL); primary_exit(EX_OSERR, "G_GATE_CMD_START failed"); } error = ggio->gctl_error; switch (error) { case 0: break; case ECANCELED: /* Exit gracefully. */ if (!sigexit_received) { pjdlog_debug(2, "ggate_recv: (%p) Received cancel from the kernel.", hio); pjdlog_info("Received cancel from the kernel, exiting."); } pthread_exit(NULL); case ENOMEM: /* * Buffer too small? Impossible, we allocate MAXPHYS * bytes - request can't be bigger than that. */ /* FALLTHROUGH */ case ENXIO: default: primary_exitx(EX_OSERR, "G_GATE_CMD_START failed: %s.", strerror(error)); } ncomp = 0; ncomps = HAST_NCOMPONENTS; for (ii = 0; ii < ncomps; ii++) hio->hio_errors[ii] = EINVAL; reqlog(LOG_DEBUG, 2, ggio, "ggate_recv: (%p) Request received from the kernel: ", hio); /* * Inform all components about new write request. * For read request prefer local component unless the given * range is out-of-date, then use remote component. */ switch (ggio->gctl_cmd) { case BIO_READ: res->hr_stat_read++; ncomps = 1; mtx_lock(&metadata_lock); if (res->hr_syncsrc == HAST_SYNCSRC_UNDEF || res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) { /* * This range is up-to-date on local component, * so handle request locally. */ /* Local component is 0 for now. */ ncomp = 0; } else /* if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) */ { PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_SECONDARY); /* * This range is out-of-date on local component, * so send request to the remote node. */ /* Remote component is 1 for now. */ ncomp = 1; } mtx_unlock(&metadata_lock); break; case BIO_WRITE: res->hr_stat_write++; if (res->hr_resuid == 0 && res->hr_primary_localcnt == 0) { /* This is first write. */ res->hr_primary_localcnt = 1; } for (;;) { mtx_lock(&range_lock); if (rangelock_islocked(range_sync, ggio->gctl_offset, ggio->gctl_length)) { pjdlog_debug(2, "regular: Range offset=%jd length=%zu locked.", (intmax_t)ggio->gctl_offset, (size_t)ggio->gctl_length); range_regular_wait = true; cv_wait(&range_regular_cond, &range_lock); range_regular_wait = false; mtx_unlock(&range_lock); continue; } if (rangelock_add(range_regular, ggio->gctl_offset, ggio->gctl_length) < 0) { mtx_unlock(&range_lock); pjdlog_debug(2, "regular: Range offset=%jd length=%zu is already locked, waiting.", (intmax_t)ggio->gctl_offset, (size_t)ggio->gctl_length); sleep(1); continue; } mtx_unlock(&range_lock); break; } mtx_lock(&res->hr_amp_lock); if (activemap_write_start(res->hr_amp, ggio->gctl_offset, ggio->gctl_length)) { res->hr_stat_activemap_update++; (void)hast_activemap_flush(res); } mtx_unlock(&res->hr_amp_lock); break; case BIO_DELETE: res->hr_stat_delete++; break; case BIO_FLUSH: res->hr_stat_flush++; break; } pjdlog_debug(2, "ggate_recv: (%p) Moving request to the send queues.", hio); refcount_init(&hio->hio_countdown, ncomps); for (ii = ncomp; ii < ncomps; ii++) QUEUE_INSERT1(hio, send, ii); } /* NOTREACHED */ return (NULL); } /* * Thread reads from or writes to local component. * If local read fails, it redirects it to remote_send thread. */ static void * local_send_thread(void *arg) { struct hast_resource *res = arg; struct g_gate_ctl_io *ggio; struct hio *hio; unsigned int ncomp, rncomp; ssize_t ret; /* Local component is 0 for now. */ ncomp = 0; /* Remote component is 1 for now. */ rncomp = 1; for (;;) { pjdlog_debug(2, "local_send: Taking request."); QUEUE_TAKE1(hio, send, ncomp, 0); pjdlog_debug(2, "local_send: (%p) Got request.", hio); ggio = &hio->hio_ggio; switch (ggio->gctl_cmd) { case BIO_READ: ret = pread(res->hr_localfd, ggio->gctl_data, ggio->gctl_length, ggio->gctl_offset + res->hr_localoff); if (ret == ggio->gctl_length) hio->hio_errors[ncomp] = 0; else if (!ISSYNCREQ(hio)) { /* * If READ failed, try to read from remote node. */ if (ret < 0) { reqlog(LOG_WARNING, 0, ggio, "Local request failed (%s), trying remote node. ", strerror(errno)); } else if (ret != ggio->gctl_length) { reqlog(LOG_WARNING, 0, ggio, "Local request failed (%zd != %jd), trying remote node. ", ret, (intmax_t)ggio->gctl_length); } QUEUE_INSERT1(hio, send, rncomp); continue; } break; case BIO_WRITE: ret = pwrite(res->hr_localfd, ggio->gctl_data, ggio->gctl_length, ggio->gctl_offset + res->hr_localoff); if (ret < 0) { hio->hio_errors[ncomp] = errno; reqlog(LOG_WARNING, 0, ggio, "Local request failed (%s): ", strerror(errno)); } else if (ret != ggio->gctl_length) { hio->hio_errors[ncomp] = EIO; reqlog(LOG_WARNING, 0, ggio, "Local request failed (%zd != %jd): ", ret, (intmax_t)ggio->gctl_length); } else { hio->hio_errors[ncomp] = 0; if (hio->hio_replication == HAST_REPLICATION_ASYNC) { ggio->gctl_error = 0; write_complete(res, hio); } } break; case BIO_DELETE: ret = g_delete(res->hr_localfd, ggio->gctl_offset + res->hr_localoff, ggio->gctl_length); if (ret < 0) { hio->hio_errors[ncomp] = errno; reqlog(LOG_WARNING, 0, ggio, "Local request failed (%s): ", strerror(errno)); } else { hio->hio_errors[ncomp] = 0; } break; case BIO_FLUSH: if (!res->hr_localflush) { ret = -1; errno = EOPNOTSUPP; break; } ret = g_flush(res->hr_localfd); if (ret < 0) { if (errno == EOPNOTSUPP) res->hr_localflush = false; hio->hio_errors[ncomp] = errno; reqlog(LOG_WARNING, 0, ggio, "Local request failed (%s): ", strerror(errno)); } else { hio->hio_errors[ncomp] = 0; } break; } if (!refcount_release(&hio->hio_countdown)) continue; if (ISSYNCREQ(hio)) { mtx_lock(&sync_lock); SYNCREQDONE(hio); mtx_unlock(&sync_lock); cv_signal(&sync_cond); } else { pjdlog_debug(2, "local_send: (%p) Moving request to the done queue.", hio); QUEUE_INSERT2(hio, done); } } /* NOTREACHED */ return (NULL); } static void keepalive_send(struct hast_resource *res, unsigned int ncomp) { struct nv *nv; rw_rlock(&hio_remote_lock[ncomp]); if (!ISCONNECTED(res, ncomp)) { rw_unlock(&hio_remote_lock[ncomp]); return; } PJDLOG_ASSERT(res->hr_remotein != NULL); PJDLOG_ASSERT(res->hr_remoteout != NULL); nv = nv_alloc(); nv_add_uint8(nv, HIO_KEEPALIVE, "cmd"); if (nv_error(nv) != 0) { rw_unlock(&hio_remote_lock[ncomp]); nv_free(nv); pjdlog_debug(1, "keepalive_send: Unable to prepare header to send."); return; } if (hast_proto_send(res, res->hr_remoteout, nv, NULL, 0) < 0) { rw_unlock(&hio_remote_lock[ncomp]); pjdlog_common(LOG_DEBUG, 1, errno, "keepalive_send: Unable to send request"); nv_free(nv); remote_close(res, ncomp); return; } rw_unlock(&hio_remote_lock[ncomp]); nv_free(nv); pjdlog_debug(2, "keepalive_send: Request sent."); } /* * Thread sends request to secondary node. */ static void * remote_send_thread(void *arg) { struct hast_resource *res = arg; struct g_gate_ctl_io *ggio; time_t lastcheck, now; struct hio *hio; struct nv *nv; unsigned int ncomp; bool wakeup; uint64_t offset, length; uint8_t cmd; void *data; /* Remote component is 1 for now. */ ncomp = 1; lastcheck = time(NULL); for (;;) { pjdlog_debug(2, "remote_send: Taking request."); QUEUE_TAKE1(hio, send, ncomp, HAST_KEEPALIVE); if (hio == NULL) { now = time(NULL); if (lastcheck + HAST_KEEPALIVE <= now) { keepalive_send(res, ncomp); lastcheck = now; } continue; } pjdlog_debug(2, "remote_send: (%p) Got request.", hio); ggio = &hio->hio_ggio; switch (ggio->gctl_cmd) { case BIO_READ: cmd = HIO_READ; data = NULL; offset = ggio->gctl_offset; length = ggio->gctl_length; break; case BIO_WRITE: cmd = HIO_WRITE; data = ggio->gctl_data; offset = ggio->gctl_offset; length = ggio->gctl_length; break; case BIO_DELETE: cmd = HIO_DELETE; data = NULL; offset = ggio->gctl_offset; length = ggio->gctl_length; break; case BIO_FLUSH: cmd = HIO_FLUSH; data = NULL; offset = 0; length = 0; break; default: PJDLOG_ABORT("invalid condition"); } nv = nv_alloc(); nv_add_uint8(nv, cmd, "cmd"); nv_add_uint64(nv, (uint64_t)ggio->gctl_seq, "seq"); nv_add_uint64(nv, offset, "offset"); nv_add_uint64(nv, length, "length"); if (nv_error(nv) != 0) { hio->hio_errors[ncomp] = nv_error(nv); pjdlog_debug(2, "remote_send: (%p) Unable to prepare header to send.", hio); reqlog(LOG_ERR, 0, ggio, "Unable to prepare header to send (%s): ", strerror(nv_error(nv))); /* Move failed request immediately to the done queue. */ goto done_queue; } /* * Protect connection from disappearing. */ rw_rlock(&hio_remote_lock[ncomp]); if (!ISCONNECTED(res, ncomp)) { rw_unlock(&hio_remote_lock[ncomp]); hio->hio_errors[ncomp] = ENOTCONN; goto done_queue; } /* * Move the request to recv queue before sending it, because * in different order we can get reply before we move request * to recv queue. */ pjdlog_debug(2, "remote_send: (%p) Moving request to the recv queue.", hio); mtx_lock(&hio_recv_list_lock[ncomp]); wakeup = TAILQ_EMPTY(&hio_recv_list[ncomp]); TAILQ_INSERT_TAIL(&hio_recv_list[ncomp], hio, hio_next[ncomp]); mtx_unlock(&hio_recv_list_lock[ncomp]); if (hast_proto_send(res, res->hr_remoteout, nv, data, data != NULL ? length : 0) < 0) { hio->hio_errors[ncomp] = errno; rw_unlock(&hio_remote_lock[ncomp]); pjdlog_debug(2, "remote_send: (%p) Unable to send request.", hio); reqlog(LOG_ERR, 0, ggio, "Unable to send request (%s): ", strerror(hio->hio_errors[ncomp])); remote_close(res, ncomp); /* * Take request back from the receive queue and move * it immediately to the done queue. */ mtx_lock(&hio_recv_list_lock[ncomp]); TAILQ_REMOVE(&hio_recv_list[ncomp], hio, hio_next[ncomp]); mtx_unlock(&hio_recv_list_lock[ncomp]); goto done_queue; } rw_unlock(&hio_remote_lock[ncomp]); nv_free(nv); if (wakeup) cv_signal(&hio_recv_list_cond[ncomp]); continue; done_queue: nv_free(nv); if (ISSYNCREQ(hio)) { if (!refcount_release(&hio->hio_countdown)) continue; mtx_lock(&sync_lock); SYNCREQDONE(hio); mtx_unlock(&sync_lock); cv_signal(&sync_cond); continue; } if (ggio->gctl_cmd == BIO_WRITE) { mtx_lock(&res->hr_amp_lock); if (activemap_need_sync(res->hr_amp, ggio->gctl_offset, ggio->gctl_length)) { (void)hast_activemap_flush(res); } mtx_unlock(&res->hr_amp_lock); } if (!refcount_release(&hio->hio_countdown)) continue; pjdlog_debug(2, "remote_send: (%p) Moving request to the done queue.", hio); QUEUE_INSERT2(hio, done); } /* NOTREACHED */ return (NULL); } /* * Thread receives answer from secondary node and passes it to ggate_send * thread. */ static void * remote_recv_thread(void *arg) { struct hast_resource *res = arg; struct g_gate_ctl_io *ggio; struct hio *hio; struct nv *nv; unsigned int ncomp; uint64_t seq; int error; /* Remote component is 1 for now. */ ncomp = 1; for (;;) { /* Wait until there is anything to receive. */ mtx_lock(&hio_recv_list_lock[ncomp]); while (TAILQ_EMPTY(&hio_recv_list[ncomp])) { pjdlog_debug(2, "remote_recv: No requests, waiting."); cv_wait(&hio_recv_list_cond[ncomp], &hio_recv_list_lock[ncomp]); } mtx_unlock(&hio_recv_list_lock[ncomp]); rw_rlock(&hio_remote_lock[ncomp]); if (!ISCONNECTED(res, ncomp)) { rw_unlock(&hio_remote_lock[ncomp]); /* * Connection is dead, so move all pending requests to * the done queue (one-by-one). */ mtx_lock(&hio_recv_list_lock[ncomp]); hio = TAILQ_FIRST(&hio_recv_list[ncomp]); PJDLOG_ASSERT(hio != NULL); TAILQ_REMOVE(&hio_recv_list[ncomp], hio, hio_next[ncomp]); mtx_unlock(&hio_recv_list_lock[ncomp]); goto done_queue; } if (hast_proto_recv_hdr(res->hr_remotein, &nv) < 0) { pjdlog_errno(LOG_ERR, "Unable to receive reply header"); rw_unlock(&hio_remote_lock[ncomp]); remote_close(res, ncomp); continue; } rw_unlock(&hio_remote_lock[ncomp]); seq = nv_get_uint64(nv, "seq"); if (seq == 0) { pjdlog_error("Header contains no 'seq' field."); nv_free(nv); continue; } mtx_lock(&hio_recv_list_lock[ncomp]); TAILQ_FOREACH(hio, &hio_recv_list[ncomp], hio_next[ncomp]) { if (hio->hio_ggio.gctl_seq == seq) { TAILQ_REMOVE(&hio_recv_list[ncomp], hio, hio_next[ncomp]); break; } } mtx_unlock(&hio_recv_list_lock[ncomp]); if (hio == NULL) { pjdlog_error("Found no request matching received 'seq' field (%ju).", (uintmax_t)seq); nv_free(nv); continue; } ggio = &hio->hio_ggio; error = nv_get_int16(nv, "error"); if (error != 0) { /* Request failed on remote side. */ hio->hio_errors[ncomp] = error; reqlog(LOG_WARNING, 0, ggio, "Remote request failed (%s): ", strerror(error)); nv_free(nv); goto done_queue; } switch (ggio->gctl_cmd) { case BIO_READ: rw_rlock(&hio_remote_lock[ncomp]); if (!ISCONNECTED(res, ncomp)) { rw_unlock(&hio_remote_lock[ncomp]); nv_free(nv); goto done_queue; } if (hast_proto_recv_data(res, res->hr_remotein, nv, ggio->gctl_data, ggio->gctl_length) < 0) { hio->hio_errors[ncomp] = errno; pjdlog_errno(LOG_ERR, "Unable to receive reply data"); rw_unlock(&hio_remote_lock[ncomp]); nv_free(nv); remote_close(res, ncomp); goto done_queue; } rw_unlock(&hio_remote_lock[ncomp]); break; case BIO_WRITE: case BIO_DELETE: case BIO_FLUSH: break; default: PJDLOG_ABORT("invalid condition"); } hio->hio_errors[ncomp] = 0; nv_free(nv); done_queue: if (!refcount_release(&hio->hio_countdown)) continue; if (ISSYNCREQ(hio)) { mtx_lock(&sync_lock); SYNCREQDONE(hio); mtx_unlock(&sync_lock); cv_signal(&sync_cond); } else { pjdlog_debug(2, "remote_recv: (%p) Moving request to the done queue.", hio); QUEUE_INSERT2(hio, done); } } /* NOTREACHED */ return (NULL); } /* * Thread sends answer to the kernel. */ static void * ggate_send_thread(void *arg) { struct hast_resource *res = arg; struct g_gate_ctl_io *ggio; struct hio *hio; unsigned int ii, ncomps; ncomps = HAST_NCOMPONENTS; for (;;) { pjdlog_debug(2, "ggate_send: Taking request."); QUEUE_TAKE2(hio, done); pjdlog_debug(2, "ggate_send: (%p) Got request.", hio); ggio = &hio->hio_ggio; for (ii = 0; ii < ncomps; ii++) { if (hio->hio_errors[ii] == 0) { /* * One successful request is enough to declare * success. */ ggio->gctl_error = 0; break; } } if (ii == ncomps) { /* * None of the requests were successful. * Use the error from local component except the * case when we did only remote request. */ if (ggio->gctl_cmd == BIO_READ && res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) ggio->gctl_error = hio->hio_errors[1]; else ggio->gctl_error = hio->hio_errors[0]; } if (ggio->gctl_error == 0 && ggio->gctl_cmd == BIO_WRITE) { mtx_lock(&res->hr_amp_lock); if (activemap_write_complete(res->hr_amp, ggio->gctl_offset, ggio->gctl_length)) { res->hr_stat_activemap_update++; (void)hast_activemap_flush(res); } mtx_unlock(&res->hr_amp_lock); } if (ggio->gctl_cmd == BIO_WRITE) { /* * Unlock range we locked. */ mtx_lock(&range_lock); rangelock_del(range_regular, ggio->gctl_offset, ggio->gctl_length); if (range_sync_wait) cv_signal(&range_sync_cond); mtx_unlock(&range_lock); if (!hio->hio_done) write_complete(res, hio); } else { if (ioctl(res->hr_ggatefd, G_GATE_CMD_DONE, ggio) < 0) { primary_exit(EX_OSERR, "G_GATE_CMD_DONE failed"); } } pjdlog_debug(2, "ggate_send: (%p) Moving request to the free queue.", hio); QUEUE_INSERT2(hio, free); } /* NOTREACHED */ return (NULL); } /* * Thread synchronize local and remote components. */ static void * sync_thread(void *arg __unused) { struct hast_resource *res = arg; struct hio *hio; struct g_gate_ctl_io *ggio; struct timeval tstart, tend, tdiff; unsigned int ii, ncomp, ncomps; off_t offset, length, synced; bool dorewind; int syncext; ncomps = HAST_NCOMPONENTS; dorewind = true; synced = 0; offset = -1; for (;;) { mtx_lock(&sync_lock); if (offset >= 0 && !sync_inprogress) { gettimeofday(&tend, NULL); timersub(&tend, &tstart, &tdiff); pjdlog_info("Synchronization interrupted after %#.0T. " "%NB synchronized so far.", &tdiff, (intmax_t)synced); event_send(res, EVENT_SYNCINTR); } while (!sync_inprogress) { dorewind = true; synced = 0; cv_wait(&sync_cond, &sync_lock); } mtx_unlock(&sync_lock); /* * Obtain offset at which we should synchronize. * Rewind synchronization if needed. */ mtx_lock(&res->hr_amp_lock); if (dorewind) activemap_sync_rewind(res->hr_amp); offset = activemap_sync_offset(res->hr_amp, &length, &syncext); if (syncext != -1) { /* * We synchronized entire syncext extent, we can mark * it as clean now. */ if (activemap_extent_complete(res->hr_amp, syncext)) (void)hast_activemap_flush(res); } mtx_unlock(&res->hr_amp_lock); if (dorewind) { dorewind = false; if (offset < 0) pjdlog_info("Nodes are in sync."); else { pjdlog_info("Synchronization started. %NB to go.", (intmax_t)(res->hr_extentsize * activemap_ndirty(res->hr_amp))); event_send(res, EVENT_SYNCSTART); gettimeofday(&tstart, NULL); } } if (offset < 0) { sync_stop(); pjdlog_debug(1, "Nothing to synchronize."); /* * Synchronization complete, make both localcnt and * remotecnt equal. */ ncomp = 1; rw_rlock(&hio_remote_lock[ncomp]); if (ISCONNECTED(res, ncomp)) { if (synced > 0) { int64_t bps; gettimeofday(&tend, NULL); timersub(&tend, &tstart, &tdiff); bps = (int64_t)((double)synced / ((double)tdiff.tv_sec + (double)tdiff.tv_usec / 1000000)); pjdlog_info("Synchronization complete. " "%NB synchronized in %#.0lT (%NB/sec).", (intmax_t)synced, &tdiff, (intmax_t)bps); event_send(res, EVENT_SYNCDONE); } mtx_lock(&metadata_lock); res->hr_syncsrc = HAST_SYNCSRC_UNDEF; res->hr_primary_localcnt = res->hr_secondary_remotecnt; res->hr_primary_remotecnt = res->hr_secondary_localcnt; pjdlog_debug(1, "Setting localcnt to %ju and remotecnt to %ju.", (uintmax_t)res->hr_primary_localcnt, (uintmax_t)res->hr_primary_remotecnt); (void)metadata_write(res); mtx_unlock(&metadata_lock); } rw_unlock(&hio_remote_lock[ncomp]); continue; } pjdlog_debug(2, "sync: Taking free request."); QUEUE_TAKE2(hio, free); pjdlog_debug(2, "sync: (%p) Got free request.", hio); /* * Lock the range we are going to synchronize. We don't want * race where someone writes between our read and write. */ for (;;) { mtx_lock(&range_lock); if (rangelock_islocked(range_regular, offset, length)) { pjdlog_debug(2, "sync: Range offset=%jd length=%jd locked.", (intmax_t)offset, (intmax_t)length); range_sync_wait = true; cv_wait(&range_sync_cond, &range_lock); range_sync_wait = false; mtx_unlock(&range_lock); continue; } if (rangelock_add(range_sync, offset, length) < 0) { mtx_unlock(&range_lock); pjdlog_debug(2, "sync: Range offset=%jd length=%jd is already locked, waiting.", (intmax_t)offset, (intmax_t)length); sleep(1); continue; } mtx_unlock(&range_lock); break; } /* * First read the data from synchronization source. */ SYNCREQ(hio); ggio = &hio->hio_ggio; ggio->gctl_cmd = BIO_READ; ggio->gctl_offset = offset; ggio->gctl_length = length; ggio->gctl_error = 0; hio->hio_done = false; hio->hio_replication = res->hr_replication; for (ii = 0; ii < ncomps; ii++) hio->hio_errors[ii] = EINVAL; reqlog(LOG_DEBUG, 2, ggio, "sync: (%p) Sending sync request: ", hio); pjdlog_debug(2, "sync: (%p) Moving request to the send queue.", hio); mtx_lock(&metadata_lock); if (res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) { /* * This range is up-to-date on local component, * so handle request locally. */ /* Local component is 0 for now. */ ncomp = 0; } else /* if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) */ { PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_SECONDARY); /* * This range is out-of-date on local component, * so send request to the remote node. */ /* Remote component is 1 for now. */ ncomp = 1; } mtx_unlock(&metadata_lock); refcount_init(&hio->hio_countdown, 1); QUEUE_INSERT1(hio, send, ncomp); /* * Let's wait for READ to finish. */ mtx_lock(&sync_lock); while (!ISSYNCREQDONE(hio)) cv_wait(&sync_cond, &sync_lock); mtx_unlock(&sync_lock); if (hio->hio_errors[ncomp] != 0) { pjdlog_error("Unable to read synchronization data: %s.", strerror(hio->hio_errors[ncomp])); goto free_queue; } /* * We read the data from synchronization source, now write it * to synchronization target. */ SYNCREQ(hio); ggio->gctl_cmd = BIO_WRITE; for (ii = 0; ii < ncomps; ii++) hio->hio_errors[ii] = EINVAL; reqlog(LOG_DEBUG, 2, ggio, "sync: (%p) Sending sync request: ", hio); pjdlog_debug(2, "sync: (%p) Moving request to the send queue.", hio); mtx_lock(&metadata_lock); if (res->hr_syncsrc == HAST_SYNCSRC_PRIMARY) { /* * This range is up-to-date on local component, * so we update remote component. */ /* Remote component is 1 for now. */ ncomp = 1; } else /* if (res->hr_syncsrc == HAST_SYNCSRC_SECONDARY) */ { PJDLOG_ASSERT(res->hr_syncsrc == HAST_SYNCSRC_SECONDARY); /* * This range is out-of-date on local component, * so we update it. */ /* Local component is 0 for now. */ ncomp = 0; } mtx_unlock(&metadata_lock); pjdlog_debug(2, "sync: (%p) Moving request to the send queue.", hio); refcount_init(&hio->hio_countdown, 1); QUEUE_INSERT1(hio, send, ncomp); /* * Let's wait for WRITE to finish. */ mtx_lock(&sync_lock); while (!ISSYNCREQDONE(hio)) cv_wait(&sync_cond, &sync_lock); mtx_unlock(&sync_lock); if (hio->hio_errors[ncomp] != 0) { pjdlog_error("Unable to write synchronization data: %s.", strerror(hio->hio_errors[ncomp])); goto free_queue; } synced += length; free_queue: mtx_lock(&range_lock); rangelock_del(range_sync, offset, length); if (range_regular_wait) cv_signal(&range_regular_cond); mtx_unlock(&range_lock); pjdlog_debug(2, "sync: (%p) Moving request to the free queue.", hio); QUEUE_INSERT2(hio, free); } /* NOTREACHED */ return (NULL); } void primary_config_reload(struct hast_resource *res, struct nv *nv) { unsigned int ii, ncomps; int modified, vint; const char *vstr; pjdlog_info("Reloading configuration..."); PJDLOG_ASSERT(res->hr_role == HAST_ROLE_PRIMARY); PJDLOG_ASSERT(gres == res); nv_assert(nv, "remoteaddr"); nv_assert(nv, "sourceaddr"); nv_assert(nv, "replication"); nv_assert(nv, "checksum"); nv_assert(nv, "compression"); nv_assert(nv, "timeout"); nv_assert(nv, "exec"); nv_assert(nv, "metaflush"); ncomps = HAST_NCOMPONENTS; #define MODIFIED_REMOTEADDR 0x01 #define MODIFIED_SOURCEADDR 0x02 #define MODIFIED_REPLICATION 0x04 #define MODIFIED_CHECKSUM 0x08 #define MODIFIED_COMPRESSION 0x10 #define MODIFIED_TIMEOUT 0x20 #define MODIFIED_EXEC 0x40 #define MODIFIED_METAFLUSH 0x80 modified = 0; vstr = nv_get_string(nv, "remoteaddr"); if (strcmp(gres->hr_remoteaddr, vstr) != 0) { /* * Don't copy res->hr_remoteaddr to gres just yet. * We want remote_close() to log disconnect from the old * addresses, not from the new ones. */ modified |= MODIFIED_REMOTEADDR; } vstr = nv_get_string(nv, "sourceaddr"); if (strcmp(gres->hr_sourceaddr, vstr) != 0) { strlcpy(gres->hr_sourceaddr, vstr, sizeof(gres->hr_sourceaddr)); modified |= MODIFIED_SOURCEADDR; } vint = nv_get_int32(nv, "replication"); if (gres->hr_replication != vint) { gres->hr_replication = vint; modified |= MODIFIED_REPLICATION; } vint = nv_get_int32(nv, "checksum"); if (gres->hr_checksum != vint) { gres->hr_checksum = vint; modified |= MODIFIED_CHECKSUM; } vint = nv_get_int32(nv, "compression"); if (gres->hr_compression != vint) { gres->hr_compression = vint; modified |= MODIFIED_COMPRESSION; } vint = nv_get_int32(nv, "timeout"); if (gres->hr_timeout != vint) { gres->hr_timeout = vint; modified |= MODIFIED_TIMEOUT; } vstr = nv_get_string(nv, "exec"); if (strcmp(gres->hr_exec, vstr) != 0) { strlcpy(gres->hr_exec, vstr, sizeof(gres->hr_exec)); modified |= MODIFIED_EXEC; } vint = nv_get_int32(nv, "metaflush"); if (gres->hr_metaflush != vint) { gres->hr_metaflush = vint; modified |= MODIFIED_METAFLUSH; } /* * Change timeout for connected sockets. * Don't bother if we need to reconnect. */ if ((modified & MODIFIED_TIMEOUT) != 0 && (modified & (MODIFIED_REMOTEADDR | MODIFIED_SOURCEADDR)) == 0) { for (ii = 0; ii < ncomps; ii++) { if (!ISREMOTE(ii)) continue; rw_rlock(&hio_remote_lock[ii]); if (!ISCONNECTED(gres, ii)) { rw_unlock(&hio_remote_lock[ii]); continue; } rw_unlock(&hio_remote_lock[ii]); if (proto_timeout(gres->hr_remotein, gres->hr_timeout) < 0) { pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); } if (proto_timeout(gres->hr_remoteout, gres->hr_timeout) < 0) { pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); } } } if ((modified & (MODIFIED_REMOTEADDR | MODIFIED_SOURCEADDR)) != 0) { for (ii = 0; ii < ncomps; ii++) { if (!ISREMOTE(ii)) continue; remote_close(gres, ii); } if (modified & MODIFIED_REMOTEADDR) { vstr = nv_get_string(nv, "remoteaddr"); strlcpy(gres->hr_remoteaddr, vstr, sizeof(gres->hr_remoteaddr)); } } #undef MODIFIED_REMOTEADDR #undef MODIFIED_SOURCEADDR #undef MODIFIED_REPLICATION #undef MODIFIED_CHECKSUM #undef MODIFIED_COMPRESSION #undef MODIFIED_TIMEOUT #undef MODIFIED_EXEC #undef MODIFIED_METAFLUSH pjdlog_info("Configuration reloaded successfully."); } static void guard_one(struct hast_resource *res, unsigned int ncomp) { struct proto_conn *in, *out; if (!ISREMOTE(ncomp)) return; rw_rlock(&hio_remote_lock[ncomp]); if (!real_remote(res)) { rw_unlock(&hio_remote_lock[ncomp]); return; } if (ISCONNECTED(res, ncomp)) { PJDLOG_ASSERT(res->hr_remotein != NULL); PJDLOG_ASSERT(res->hr_remoteout != NULL); rw_unlock(&hio_remote_lock[ncomp]); pjdlog_debug(2, "remote_guard: Connection to %s is ok.", res->hr_remoteaddr); return; } PJDLOG_ASSERT(res->hr_remotein == NULL); PJDLOG_ASSERT(res->hr_remoteout == NULL); /* * Upgrade the lock. It doesn't have to be atomic as no other thread * can change connection status from disconnected to connected. */ rw_unlock(&hio_remote_lock[ncomp]); pjdlog_debug(2, "remote_guard: Reconnecting to %s.", res->hr_remoteaddr); in = out = NULL; if (init_remote(res, &in, &out) == 0) { rw_wlock(&hio_remote_lock[ncomp]); PJDLOG_ASSERT(res->hr_remotein == NULL); PJDLOG_ASSERT(res->hr_remoteout == NULL); PJDLOG_ASSERT(in != NULL && out != NULL); res->hr_remotein = in; res->hr_remoteout = out; rw_unlock(&hio_remote_lock[ncomp]); pjdlog_info("Successfully reconnected to %s.", res->hr_remoteaddr); sync_start(); } else { /* Both connections should be NULL. */ PJDLOG_ASSERT(res->hr_remotein == NULL); PJDLOG_ASSERT(res->hr_remoteout == NULL); PJDLOG_ASSERT(in == NULL && out == NULL); pjdlog_debug(2, "remote_guard: Reconnect to %s failed.", res->hr_remoteaddr); } } /* * Thread guards remote connections and reconnects when needed, handles * signals, etc. */ static void * guard_thread(void *arg) { struct hast_resource *res = arg; unsigned int ii, ncomps; struct timespec timeout; time_t lastcheck, now; sigset_t mask; int signo; ncomps = HAST_NCOMPONENTS; lastcheck = time(NULL); PJDLOG_VERIFY(sigemptyset(&mask) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0); PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0); timeout.tv_sec = HAST_KEEPALIVE; timeout.tv_nsec = 0; signo = -1; for (;;) { switch (signo) { case SIGINT: case SIGTERM: sigexit_received = true; primary_exitx(EX_OK, "Termination signal received, exiting."); break; default: break; } /* * Don't check connections until we fully started, * as we may still be looping, waiting for remote node * to switch from primary to secondary. */ if (fullystarted) { pjdlog_debug(2, "remote_guard: Checking connections."); now = time(NULL); if (lastcheck + HAST_KEEPALIVE <= now) { for (ii = 0; ii < ncomps; ii++) guard_one(res, ii); lastcheck = now; } } signo = sigtimedwait(&mask, NULL, &timeout); } /* NOTREACHED */ return (NULL); } Index: head/sbin/hastd/proto.c =================================================================== --- head/sbin/hastd/proto.c (revision 229777) +++ head/sbin/hastd/proto.c (revision 229778) @@ -1,446 +1,446 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "pjdlog.h" #include "proto.h" #include "proto_impl.h" #define PROTO_CONN_MAGIC 0x907041c struct proto_conn { int pc_magic; struct proto *pc_proto; void *pc_ctx; int pc_side; #define PROTO_SIDE_CLIENT 0 #define PROTO_SIDE_SERVER_LISTEN 1 #define PROTO_SIDE_SERVER_WORK 2 }; static TAILQ_HEAD(, proto) protos = TAILQ_HEAD_INITIALIZER(protos); void proto_register(struct proto *proto, bool isdefault) { static bool seen_default = false; if (!isdefault) TAILQ_INSERT_HEAD(&protos, proto, prt_next); else { PJDLOG_ASSERT(!seen_default); seen_default = true; TAILQ_INSERT_TAIL(&protos, proto, prt_next); } } static struct proto_conn * proto_alloc(struct proto *proto, int side) { struct proto_conn *conn; PJDLOG_ASSERT(proto != NULL); PJDLOG_ASSERT(side == PROTO_SIDE_CLIENT || side == PROTO_SIDE_SERVER_LISTEN || side == PROTO_SIDE_SERVER_WORK); conn = malloc(sizeof(*conn)); if (conn != NULL) { conn->pc_proto = proto; conn->pc_side = side; conn->pc_magic = PROTO_CONN_MAGIC; } return (conn); } static void proto_free(struct proto_conn *conn) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT || conn->pc_side == PROTO_SIDE_SERVER_LISTEN || conn->pc_side == PROTO_SIDE_SERVER_WORK); PJDLOG_ASSERT(conn->pc_proto != NULL); bzero(conn, sizeof(*conn)); free(conn); } static int proto_common_setup(const char *srcaddr, const char *dstaddr, struct proto_conn **connp, int side) { struct proto *proto; struct proto_conn *conn; void *ctx; int ret; PJDLOG_ASSERT(side == PROTO_SIDE_CLIENT || side == PROTO_SIDE_SERVER_LISTEN); TAILQ_FOREACH(proto, &protos, prt_next) { if (side == PROTO_SIDE_CLIENT) { if (proto->prt_client == NULL) ret = -1; else ret = proto->prt_client(srcaddr, dstaddr, &ctx); } else /* if (side == PROTO_SIDE_SERVER_LISTEN) */ { if (proto->prt_server == NULL) ret = -1; else ret = proto->prt_server(dstaddr, &ctx); } /* * ret == 0 - success * ret == -1 - dstaddr is not for this protocol - * ret > 0 - right protocol, but an error occured + * ret > 0 - right protocol, but an error occurred */ if (ret >= 0) break; } if (proto == NULL) { /* Unrecognized address. */ errno = EINVAL; return (-1); } if (ret > 0) { - /* An error occured. */ + /* An error occurred. */ errno = ret; return (-1); } conn = proto_alloc(proto, side); if (conn == NULL) { if (proto->prt_close != NULL) proto->prt_close(ctx); errno = ENOMEM; return (-1); } conn->pc_ctx = ctx; *connp = conn; return (0); } int proto_client(const char *srcaddr, const char *dstaddr, struct proto_conn **connp) { return (proto_common_setup(srcaddr, dstaddr, connp, PROTO_SIDE_CLIENT)); } int proto_connect(struct proto_conn *conn, int timeout) { int ret; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_connect != NULL); PJDLOG_ASSERT(timeout >= -1); ret = conn->pc_proto->prt_connect(conn->pc_ctx, timeout); if (ret != 0) { errno = ret; return (-1); } return (0); } int proto_connect_wait(struct proto_conn *conn, int timeout) { int ret; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_CLIENT); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_connect_wait != NULL); PJDLOG_ASSERT(timeout >= 0); ret = conn->pc_proto->prt_connect_wait(conn->pc_ctx, timeout); if (ret != 0) { errno = ret; return (-1); } return (0); } int proto_server(const char *addr, struct proto_conn **connp) { return (proto_common_setup(NULL, addr, connp, PROTO_SIDE_SERVER_LISTEN)); } int proto_accept(struct proto_conn *conn, struct proto_conn **newconnp) { struct proto_conn *newconn; int ret; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_side == PROTO_SIDE_SERVER_LISTEN); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_accept != NULL); newconn = proto_alloc(conn->pc_proto, PROTO_SIDE_SERVER_WORK); if (newconn == NULL) return (-1); ret = conn->pc_proto->prt_accept(conn->pc_ctx, &newconn->pc_ctx); if (ret != 0) { proto_free(newconn); errno = ret; return (-1); } *newconnp = newconn; return (0); } int proto_send(const struct proto_conn *conn, const void *data, size_t size) { int ret; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_send != NULL); ret = conn->pc_proto->prt_send(conn->pc_ctx, data, size, -1); if (ret != 0) { errno = ret; return (-1); } return (0); } int proto_recv(const struct proto_conn *conn, void *data, size_t size) { int ret; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_recv != NULL); ret = conn->pc_proto->prt_recv(conn->pc_ctx, data, size, NULL); if (ret != 0) { errno = ret; return (-1); } return (0); } int proto_connection_send(const struct proto_conn *conn, struct proto_conn *mconn) { const char *protoname; int ret, fd; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_send != NULL); PJDLOG_ASSERT(mconn != NULL); PJDLOG_ASSERT(mconn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(mconn->pc_proto != NULL); fd = proto_descriptor(mconn); PJDLOG_ASSERT(fd >= 0); protoname = mconn->pc_proto->prt_name; PJDLOG_ASSERT(protoname != NULL); ret = conn->pc_proto->prt_send(conn->pc_ctx, protoname, strlen(protoname) + 1, fd); proto_close(mconn); if (ret != 0) { errno = ret; return (-1); } return (0); } int proto_connection_recv(const struct proto_conn *conn, bool client, struct proto_conn **newconnp) { char protoname[128]; struct proto *proto; struct proto_conn *newconn; int ret, fd; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_recv != NULL); PJDLOG_ASSERT(newconnp != NULL); bzero(protoname, sizeof(protoname)); ret = conn->pc_proto->prt_recv(conn->pc_ctx, protoname, sizeof(protoname) - 1, &fd); if (ret != 0) { errno = ret; return (-1); } PJDLOG_ASSERT(fd >= 0); TAILQ_FOREACH(proto, &protos, prt_next) { if (strcmp(proto->prt_name, protoname) == 0) break; } if (proto == NULL) { errno = EINVAL; return (-1); } newconn = proto_alloc(proto, client ? PROTO_SIDE_CLIENT : PROTO_SIDE_SERVER_WORK); if (newconn == NULL) return (-1); PJDLOG_ASSERT(newconn->pc_proto->prt_wrap != NULL); ret = newconn->pc_proto->prt_wrap(fd, client, &newconn->pc_ctx); if (ret != 0) { proto_free(newconn); errno = ret; return (-1); } *newconnp = newconn; return (0); } int proto_descriptor(const struct proto_conn *conn) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_descriptor != NULL); return (conn->pc_proto->prt_descriptor(conn->pc_ctx)); } bool proto_address_match(const struct proto_conn *conn, const char *addr) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_address_match != NULL); return (conn->pc_proto->prt_address_match(conn->pc_ctx, addr)); } void proto_local_address(const struct proto_conn *conn, char *addr, size_t size) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_local_address != NULL); conn->pc_proto->prt_local_address(conn->pc_ctx, addr, size); } void proto_remote_address(const struct proto_conn *conn, char *addr, size_t size) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_remote_address != NULL); conn->pc_proto->prt_remote_address(conn->pc_ctx, addr, size); } int proto_timeout(const struct proto_conn *conn, int timeout) { struct timeval tv; int fd; PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); fd = proto_descriptor(conn); if (fd < 0) return (-1); tv.tv_sec = timeout; tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) return (-1); if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) return (-1); return (0); } void proto_close(struct proto_conn *conn) { PJDLOG_ASSERT(conn != NULL); PJDLOG_ASSERT(conn->pc_magic == PROTO_CONN_MAGIC); PJDLOG_ASSERT(conn->pc_proto != NULL); PJDLOG_ASSERT(conn->pc_proto->prt_close != NULL); conn->pc_proto->prt_close(conn->pc_ctx); proto_free(conn); } Index: head/sbin/ifconfig/ifieee80211.c =================================================================== --- head/sbin/ifconfig/ifieee80211.c (revision 229777) +++ head/sbin/ifconfig/ifieee80211.c (revision 229778) @@ -1,5316 +1,5316 @@ /* * Copyright 2001 The Aerospace Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of The Aerospace Corporation may not be used to endorse or * promote products derived from this software. * * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AEROSPACE CORPORATION BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /*- * Copyright (c) 1997, 1998, 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, * NASA Ames Research Center. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* NB: for offsetof */ #include "ifconfig.h" #include "regdomain.h" #ifndef IEEE80211_FIXED_RATE_NONE #define IEEE80211_FIXED_RATE_NONE 0xff #endif /* XXX need these publicly defined or similar */ #ifndef IEEE80211_NODE_AUTH #define IEEE80211_NODE_AUTH 0x000001 /* authorized for data */ #define IEEE80211_NODE_QOS 0x000002 /* QoS enabled */ #define IEEE80211_NODE_ERP 0x000004 /* ERP enabled */ #define IEEE80211_NODE_PWR_MGT 0x000010 /* power save mode enabled */ #define IEEE80211_NODE_AREF 0x000020 /* authentication ref held */ #define IEEE80211_NODE_HT 0x000040 /* HT enabled */ #define IEEE80211_NODE_HTCOMPAT 0x000080 /* HT setup w/ vendor OUI's */ #define IEEE80211_NODE_WPS 0x000100 /* WPS association */ #define IEEE80211_NODE_TSN 0x000200 /* TSN association */ #define IEEE80211_NODE_AMPDU_RX 0x000400 /* AMPDU rx enabled */ #define IEEE80211_NODE_AMPDU_TX 0x000800 /* AMPDU tx enabled */ #define IEEE80211_NODE_MIMO_PS 0x001000 /* MIMO power save enabled */ #define IEEE80211_NODE_MIMO_RTS 0x002000 /* send RTS in MIMO PS */ #define IEEE80211_NODE_RIFS 0x004000 /* RIFS enabled */ #define IEEE80211_NODE_SGI20 0x008000 /* Short GI in HT20 enabled */ #define IEEE80211_NODE_SGI40 0x010000 /* Short GI in HT40 enabled */ #define IEEE80211_NODE_ASSOCID 0x020000 /* xmit requires associd */ #define IEEE80211_NODE_AMSDU_RX 0x040000 /* AMSDU rx enabled */ #define IEEE80211_NODE_AMSDU_TX 0x080000 /* AMSDU tx enabled */ #endif #define MAXCHAN 1536 /* max 1.5K channels */ #define MAXCOL 78 static int col; static char spacer; static void LINE_INIT(char c); static void LINE_BREAK(void); static void LINE_CHECK(const char *fmt, ...); static const char *modename[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = "auto", [IEEE80211_MODE_11A] = "11a", [IEEE80211_MODE_11B] = "11b", [IEEE80211_MODE_11G] = "11g", [IEEE80211_MODE_FH] = "fh", [IEEE80211_MODE_TURBO_A] = "turboA", [IEEE80211_MODE_TURBO_G] = "turboG", [IEEE80211_MODE_STURBO_A] = "sturbo", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", [IEEE80211_MODE_HALF] = "half", [IEEE80211_MODE_QUARTER] = "quarter" }; static void set80211(int s, int type, int val, int len, void *data); static int get80211(int s, int type, void *data, int len); static int get80211len(int s, int type, void *data, int len, int *plen); static int get80211val(int s, int type, int *val); static const char *get_string(const char *val, const char *sep, u_int8_t *buf, int *lenp); static void print_string(const u_int8_t *buf, int len); static void print_regdomain(const struct ieee80211_regdomain *, int); static void print_channels(int, const struct ieee80211req_chaninfo *, int allchans, int verbose); static void regdomain_makechannels(struct ieee80211_regdomain_req *, const struct ieee80211_devcaps_req *); static const char *mesh_linkstate_string(uint8_t state); static struct ieee80211req_chaninfo *chaninfo; static struct ieee80211_regdomain regdomain; static int gotregdomain = 0; static struct ieee80211_roamparams_req roamparams; static int gotroam = 0; static struct ieee80211_txparams_req txparams; static int gottxparams = 0; static struct ieee80211_channel curchan; static int gotcurchan = 0; static struct ifmediareq *ifmr; static int htconf = 0; static int gothtconf = 0; static void gethtconf(int s) { if (gothtconf) return; if (get80211val(s, IEEE80211_IOC_HTCONF, &htconf) < 0) warn("unable to get HT configuration information"); gothtconf = 1; } /* * Collect channel info from the kernel. We use this (mostly) * to handle mapping between frequency and IEEE channel number. */ static void getchaninfo(int s) { if (chaninfo != NULL) return; chaninfo = malloc(IEEE80211_CHANINFO_SIZE(MAXCHAN)); if (chaninfo == NULL) errx(1, "no space for channel list"); if (get80211(s, IEEE80211_IOC_CHANINFO, chaninfo, IEEE80211_CHANINFO_SIZE(MAXCHAN)) < 0) err(1, "unable to get channel information"); ifmr = ifmedia_getstate(s); gethtconf(s); } static struct regdata * getregdata(void) { static struct regdata *rdp = NULL; if (rdp == NULL) { rdp = lib80211_alloc_regdata(); if (rdp == NULL) errx(-1, "missing or corrupted regdomain database"); } return rdp; } /* * Given the channel at index i with attributes from, * check if there is a channel with attributes to in * the channel table. With suitable attributes this * allows the caller to look for promotion; e.g. from * 11b > 11g. */ static int canpromote(int i, int from, int to) { const struct ieee80211_channel *fc = &chaninfo->ic_chans[i]; int j; if ((fc->ic_flags & from) != from) return i; /* NB: quick check exploiting ordering of chans w/ same frequency */ if (i+1 < chaninfo->ic_nchans && chaninfo->ic_chans[i+1].ic_freq == fc->ic_freq && (chaninfo->ic_chans[i+1].ic_flags & to) == to) return i+1; /* brute force search in case channel list is not ordered */ for (j = 0; j < chaninfo->ic_nchans; j++) { const struct ieee80211_channel *tc = &chaninfo->ic_chans[j]; if (j != i && tc->ic_freq == fc->ic_freq && (tc->ic_flags & to) == to) return j; } return i; } /* * Handle channel promotion. When a channel is specified with * only a frequency we want to promote it to the ``best'' channel * available. The channel list has separate entries for 11b, 11g, * 11a, and 11n[ga] channels so specifying a frequency w/o any * attributes requires we upgrade, e.g. from 11b -> 11g. This * gets complicated when the channel is specified on the same * command line with a media request that constrains the available * channe list (e.g. mode 11a); we want to honor that to avoid * confusing behaviour. */ static int promote(int i) { /* * Query the current mode of the interface in case it's * constrained (e.g. to 11a). We must do this carefully * as there may be a pending ifmedia request in which case * asking the kernel will give us the wrong answer. This * is an unfortunate side-effect of the way ifconfig is * structure for modularity (yech). * * NB: ifmr is actually setup in getchaninfo (above); we * assume it's called coincident with to this call so * we have a ``current setting''; otherwise we must pass * the socket descriptor down to here so we can make * the ifmedia_getstate call ourselves. */ int chanmode = ifmr != NULL ? IFM_MODE(ifmr->ifm_current) : IFM_AUTO; /* when ambiguous promote to ``best'' */ /* NB: we abitrarily pick HT40+ over HT40- */ if (chanmode != IFM_IEEE80211_11B) i = canpromote(i, IEEE80211_CHAN_B, IEEE80211_CHAN_G); if (chanmode != IFM_IEEE80211_11G && (htconf & 1)) { i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT20); if (htconf & 2) { i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D); i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U); } } if (chanmode != IFM_IEEE80211_11A && (htconf & 1)) { i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20); if (htconf & 2) { i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D); i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U); } } return i; } static void mapfreq(struct ieee80211_channel *chan, int freq, int flags) { int i; for (i = 0; i < chaninfo->ic_nchans; i++) { const struct ieee80211_channel *c = &chaninfo->ic_chans[i]; if (c->ic_freq == freq && (c->ic_flags & flags) == flags) { if (flags == 0) { /* when ambiguous promote to ``best'' */ c = &chaninfo->ic_chans[promote(i)]; } *chan = *c; return; } } errx(1, "unknown/undefined frequency %u/0x%x", freq, flags); } static void mapchan(struct ieee80211_channel *chan, int ieee, int flags) { int i; for (i = 0; i < chaninfo->ic_nchans; i++) { const struct ieee80211_channel *c = &chaninfo->ic_chans[i]; if (c->ic_ieee == ieee && (c->ic_flags & flags) == flags) { if (flags == 0) { /* when ambiguous promote to ``best'' */ c = &chaninfo->ic_chans[promote(i)]; } *chan = *c; return; } } errx(1, "unknown/undefined channel number %d flags 0x%x", ieee, flags); } static const struct ieee80211_channel * getcurchan(int s) { if (gotcurchan) return &curchan; if (get80211(s, IEEE80211_IOC_CURCHAN, &curchan, sizeof(curchan)) < 0) { int val; /* fall back to legacy ioctl */ if (get80211val(s, IEEE80211_IOC_CHANNEL, &val) < 0) err(-1, "cannot figure out current channel"); getchaninfo(s); mapchan(&curchan, val, 0); } gotcurchan = 1; return &curchan; } static enum ieee80211_phymode chan2mode(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_HTA(c)) return IEEE80211_MODE_11NA; if (IEEE80211_IS_CHAN_HTG(c)) return IEEE80211_MODE_11NG; if (IEEE80211_IS_CHAN_108A(c)) return IEEE80211_MODE_TURBO_A; if (IEEE80211_IS_CHAN_108G(c)) return IEEE80211_MODE_TURBO_G; if (IEEE80211_IS_CHAN_ST(c)) return IEEE80211_MODE_STURBO_A; if (IEEE80211_IS_CHAN_FHSS(c)) return IEEE80211_MODE_FH; if (IEEE80211_IS_CHAN_HALF(c)) return IEEE80211_MODE_HALF; if (IEEE80211_IS_CHAN_QUARTER(c)) return IEEE80211_MODE_QUARTER; if (IEEE80211_IS_CHAN_A(c)) return IEEE80211_MODE_11A; if (IEEE80211_IS_CHAN_ANYG(c)) return IEEE80211_MODE_11G; if (IEEE80211_IS_CHAN_B(c)) return IEEE80211_MODE_11B; return IEEE80211_MODE_AUTO; } static void getroam(int s) { if (gotroam) return; if (get80211(s, IEEE80211_IOC_ROAM, &roamparams, sizeof(roamparams)) < 0) err(1, "unable to get roaming parameters"); gotroam = 1; } static void setroam_cb(int s, void *arg) { struct ieee80211_roamparams_req *roam = arg; set80211(s, IEEE80211_IOC_ROAM, 0, sizeof(*roam), roam); } static void gettxparams(int s) { if (gottxparams) return; if (get80211(s, IEEE80211_IOC_TXPARAMS, &txparams, sizeof(txparams)) < 0) err(1, "unable to get transmit parameters"); gottxparams = 1; } static void settxparams_cb(int s, void *arg) { struct ieee80211_txparams_req *txp = arg; set80211(s, IEEE80211_IOC_TXPARAMS, 0, sizeof(*txp), txp); } static void getregdomain(int s) { if (gotregdomain) return; if (get80211(s, IEEE80211_IOC_REGDOMAIN, ®domain, sizeof(regdomain)) < 0) err(1, "unable to get regulatory domain info"); gotregdomain = 1; } static void getdevcaps(int s, struct ieee80211_devcaps_req *dc) { if (get80211(s, IEEE80211_IOC_DEVCAPS, dc, IEEE80211_DEVCAPS_SPACE(dc)) < 0) err(1, "unable to get device capabilities"); } static void setregdomain_cb(int s, void *arg) { struct ieee80211_regdomain_req *req; struct ieee80211_regdomain *rd = arg; struct ieee80211_devcaps_req *dc; struct regdata *rdp = getregdata(); if (rd->country != NO_COUNTRY) { const struct country *cc; /* * Check current country seting to make sure it's * compatible with the new regdomain. If not, then * override it with any default country for this * SKU. If we cannot arrange a match, then abort. */ cc = lib80211_country_findbycc(rdp, rd->country); if (cc == NULL) errx(1, "unknown ISO country code %d", rd->country); if (cc->rd->sku != rd->regdomain) { const struct regdomain *rp; /* * Check if country is incompatible with regdomain. * To enable multiple regdomains for a country code * we permit a mismatch between the regdomain and * the country's associated regdomain when the * regdomain is setup w/o a default country. For * example, US is bound to the FCC regdomain but * we allow US to be combined with FCC3 because FCC3 * has not default country. This allows bogus * combinations like FCC3+DK which are resolved when * constructing the channel list by deferring to the * regdomain to construct the channel list. */ rp = lib80211_regdomain_findbysku(rdp, rd->regdomain); if (rp == NULL) errx(1, "country %s (%s) is not usable with " "regdomain %d", cc->isoname, cc->name, rd->regdomain); else if (rp->cc != NULL && rp->cc != cc) errx(1, "country %s (%s) is not usable with " "regdomain %s", cc->isoname, cc->name, rp->name); } } /* * Fetch the device capabilities and calculate the * full set of netbands for which we request a new * channel list be constructed. Once that's done we * push the regdomain info + channel list to the kernel. */ dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN)); if (dc == NULL) errx(1, "no space for device capabilities"); dc->dc_chaninfo.ic_nchans = MAXCHAN; getdevcaps(s, dc); #if 0 if (verbose) { printf("drivercaps: 0x%x\n", dc->dc_drivercaps); printf("cryptocaps: 0x%x\n", dc->dc_cryptocaps); printf("htcaps : 0x%x\n", dc->dc_htcaps); memcpy(chaninfo, &dc->dc_chaninfo, IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo)); print_channels(s, &dc->dc_chaninfo, 1/*allchans*/, 1/*verbose*/); } #endif req = malloc(IEEE80211_REGDOMAIN_SIZE(dc->dc_chaninfo.ic_nchans)); if (req == NULL) errx(1, "no space for regdomain request"); req->rd = *rd; regdomain_makechannels(req, dc); if (verbose) { LINE_INIT(':'); print_regdomain(rd, 1/*verbose*/); LINE_BREAK(); /* blech, reallocate channel list for new data */ if (chaninfo != NULL) free(chaninfo); chaninfo = malloc(IEEE80211_CHANINFO_SPACE(&req->chaninfo)); if (chaninfo == NULL) errx(1, "no space for channel list"); memcpy(chaninfo, &req->chaninfo, IEEE80211_CHANINFO_SPACE(&req->chaninfo)); print_channels(s, &req->chaninfo, 1/*allchans*/, 1/*verbose*/); } if (req->chaninfo.ic_nchans == 0) errx(1, "no channels calculated"); set80211(s, IEEE80211_IOC_REGDOMAIN, 0, IEEE80211_REGDOMAIN_SPACE(req), req); free(req); free(dc); } static int ieee80211_mhz2ieee(int freq, int flags) { struct ieee80211_channel chan; mapfreq(&chan, freq, flags); return chan.ic_ieee; } static int isanyarg(const char *arg) { return (strncmp(arg, "-", 1) == 0 || strncasecmp(arg, "any", 3) == 0 || strncasecmp(arg, "off", 3) == 0); } static void set80211ssid(const char *val, int d, int s, const struct afswtch *rafp) { int ssid; int len; u_int8_t data[IEEE80211_NWID_LEN]; ssid = 0; len = strlen(val); if (len > 2 && isdigit((int)val[0]) && val[1] == ':') { ssid = atoi(val)-1; val += 2; } bzero(data, sizeof(data)); len = sizeof(data); if (get_string(val, NULL, data, &len) == NULL) exit(1); set80211(s, IEEE80211_IOC_SSID, ssid, len, data); } static void set80211meshid(const char *val, int d, int s, const struct afswtch *rafp) { int len; u_int8_t data[IEEE80211_NWID_LEN]; memset(data, 0, sizeof(data)); len = sizeof(data); if (get_string(val, NULL, data, &len) == NULL) exit(1); set80211(s, IEEE80211_IOC_MESH_ID, 0, len, data); } static void set80211stationname(const char *val, int d, int s, const struct afswtch *rafp) { int len; u_int8_t data[33]; bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); set80211(s, IEEE80211_IOC_STATIONNAME, 0, len, data); } /* * Parse a channel specification for attributes/flags. * The syntax is: * freq/xx channel width (5,10,20,40,40+,40-) * freq:mode channel mode (a,b,g,h,n,t,s,d) * * These can be combined in either order; e.g. 2437:ng/40. * Modes are case insensitive. * * The result is not validated here; it's assumed to be * checked against the channel table fetched from the kernel. */ static int getchannelflags(const char *val, int freq) { #define _CHAN_HT 0x80000000 const char *cp; int flags; flags = 0; cp = strchr(val, ':'); if (cp != NULL) { for (cp++; isalpha((int) *cp); cp++) { /* accept mixed case */ int c = *cp; if (isupper(c)) c = tolower(c); switch (c) { case 'a': /* 802.11a */ flags |= IEEE80211_CHAN_A; break; case 'b': /* 802.11b */ flags |= IEEE80211_CHAN_B; break; case 'g': /* 802.11g */ flags |= IEEE80211_CHAN_G; break; case 'h': /* ht = 802.11n */ case 'n': /* 802.11n */ flags |= _CHAN_HT; /* NB: private */ break; case 'd': /* dt = Atheros Dynamic Turbo */ flags |= IEEE80211_CHAN_TURBO; break; case 't': /* ht, dt, st, t */ /* dt and unadorned t specify Dynamic Turbo */ if ((flags & (IEEE80211_CHAN_STURBO|_CHAN_HT)) == 0) flags |= IEEE80211_CHAN_TURBO; break; case 's': /* st = Atheros Static Turbo */ flags |= IEEE80211_CHAN_STURBO; break; default: errx(-1, "%s: Invalid channel attribute %c\n", val, *cp); } } } cp = strchr(val, '/'); if (cp != NULL) { char *ep; u_long cw = strtoul(cp+1, &ep, 10); switch (cw) { case 5: flags |= IEEE80211_CHAN_QUARTER; break; case 10: flags |= IEEE80211_CHAN_HALF; break; case 20: /* NB: this may be removed below */ flags |= IEEE80211_CHAN_HT20; break; case 40: if (ep != NULL && *ep == '+') flags |= IEEE80211_CHAN_HT40U; else if (ep != NULL && *ep == '-') flags |= IEEE80211_CHAN_HT40D; break; default: errx(-1, "%s: Invalid channel width\n", val); } } /* * Cleanup specifications. */ if ((flags & _CHAN_HT) == 0) { /* * If user specified freq/20 or freq/40 quietly remove * HT cw attributes depending on channel use. To give * an explicit 20/40 width for an HT channel you must * indicate it is an HT channel since all HT channels * are also usable for legacy operation; e.g. freq:n/40. */ flags &= ~IEEE80211_CHAN_HT; } else { /* * Remove private indicator that this is an HT channel * and if no explicit channel width has been given * provide the default settings. */ flags &= ~_CHAN_HT; if ((flags & IEEE80211_CHAN_HT) == 0) { struct ieee80211_channel chan; /* * Consult the channel list to see if we can use * HT40+ or HT40- (if both the map routines choose). */ if (freq > 255) mapfreq(&chan, freq, 0); else mapchan(&chan, freq, 0); flags |= (chan.ic_flags & IEEE80211_CHAN_HT); } } return flags; #undef _CHAN_HT } static void getchannel(int s, struct ieee80211_channel *chan, const char *val) { int v, flags; char *eptr; memset(chan, 0, sizeof(*chan)); if (isanyarg(val)) { chan->ic_freq = IEEE80211_CHAN_ANY; return; } getchaninfo(s); errno = 0; v = strtol(val, &eptr, 10); if (val[0] == '\0' || val == eptr || errno == ERANGE || /* channel may be suffixed with nothing, :flag, or /width */ (eptr[0] != '\0' && eptr[0] != ':' && eptr[0] != '/')) errx(1, "invalid channel specification%s", errno == ERANGE ? " (out of range)" : ""); flags = getchannelflags(val, v); if (v > 255) { /* treat as frequency */ mapfreq(chan, v, flags); } else { mapchan(chan, v, flags); } } static void set80211channel(const char *val, int d, int s, const struct afswtch *rafp) { struct ieee80211_channel chan; getchannel(s, &chan, val); set80211(s, IEEE80211_IOC_CURCHAN, 0, sizeof(chan), &chan); } static void set80211chanswitch(const char *val, int d, int s, const struct afswtch *rafp) { struct ieee80211_chanswitch_req csr; getchannel(s, &csr.csa_chan, val); csr.csa_mode = 1; csr.csa_count = 5; set80211(s, IEEE80211_IOC_CHANSWITCH, 0, sizeof(csr), &csr); } static void set80211authmode(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "none") == 0) { mode = IEEE80211_AUTH_NONE; } else if (strcasecmp(val, "open") == 0) { mode = IEEE80211_AUTH_OPEN; } else if (strcasecmp(val, "shared") == 0) { mode = IEEE80211_AUTH_SHARED; } else if (strcasecmp(val, "8021x") == 0) { mode = IEEE80211_AUTH_8021X; } else if (strcasecmp(val, "wpa") == 0) { mode = IEEE80211_AUTH_WPA; } else { errx(1, "unknown authmode"); } set80211(s, IEEE80211_IOC_AUTHMODE, mode, 0, NULL); } static void set80211powersavemode(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_POWERSAVE_OFF; } else if (strcasecmp(val, "on") == 0) { mode = IEEE80211_POWERSAVE_ON; } else if (strcasecmp(val, "cam") == 0) { mode = IEEE80211_POWERSAVE_CAM; } else if (strcasecmp(val, "psp") == 0) { mode = IEEE80211_POWERSAVE_PSP; } else if (strcasecmp(val, "psp-cam") == 0) { mode = IEEE80211_POWERSAVE_PSP_CAM; } else { errx(1, "unknown powersavemode"); } set80211(s, IEEE80211_IOC_POWERSAVE, mode, 0, NULL); } static void set80211powersave(const char *val, int d, int s, const struct afswtch *rafp) { if (d == 0) set80211(s, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_OFF, 0, NULL); else set80211(s, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_ON, 0, NULL); } static void set80211powersavesleep(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_POWERSAVESLEEP, atoi(val), 0, NULL); } static void set80211wepmode(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_WEP_OFF; } else if (strcasecmp(val, "on") == 0) { mode = IEEE80211_WEP_ON; } else if (strcasecmp(val, "mixed") == 0) { mode = IEEE80211_WEP_MIXED; } else { errx(1, "unknown wep mode"); } set80211(s, IEEE80211_IOC_WEP, mode, 0, NULL); } static void set80211wep(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_WEP, d, 0, NULL); } static int isundefarg(const char *arg) { return (strcmp(arg, "-") == 0 || strncasecmp(arg, "undef", 5) == 0); } static void set80211weptxkey(const char *val, int d, int s, const struct afswtch *rafp) { if (isundefarg(val)) set80211(s, IEEE80211_IOC_WEPTXKEY, IEEE80211_KEYIX_NONE, 0, NULL); else set80211(s, IEEE80211_IOC_WEPTXKEY, atoi(val)-1, 0, NULL); } static void set80211wepkey(const char *val, int d, int s, const struct afswtch *rafp) { int key = 0; int len; u_int8_t data[IEEE80211_KEYBUF_SIZE]; if (isdigit((int)val[0]) && val[1] == ':') { key = atoi(val)-1; val += 2; } bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); set80211(s, IEEE80211_IOC_WEPKEY, key, len, data); } /* - * This function is purely a NetBSD compatability interface. The NetBSD + * This function is purely a NetBSD compatibility interface. The NetBSD * interface is too inflexible, but it's there so we'll support it since * it's not all that hard. */ static void set80211nwkey(const char *val, int d, int s, const struct afswtch *rafp) { int txkey; int i, len; u_int8_t data[IEEE80211_KEYBUF_SIZE]; set80211(s, IEEE80211_IOC_WEP, IEEE80211_WEP_ON, 0, NULL); if (isdigit((int)val[0]) && val[1] == ':') { txkey = val[0]-'0'-1; val += 2; for (i = 0; i < 4; i++) { bzero(data, sizeof(data)); len = sizeof(data); val = get_string(val, ",", data, &len); if (val == NULL) exit(1); set80211(s, IEEE80211_IOC_WEPKEY, i, len, data); } } else { bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); txkey = 0; set80211(s, IEEE80211_IOC_WEPKEY, 0, len, data); bzero(data, sizeof(data)); for (i = 1; i < 4; i++) set80211(s, IEEE80211_IOC_WEPKEY, i, 0, data); } set80211(s, IEEE80211_IOC_WEPTXKEY, txkey, 0, NULL); } static void set80211rtsthreshold(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_RTSTHRESHOLD, isundefarg(val) ? IEEE80211_RTS_MAX : atoi(val), 0, NULL); } static void set80211protmode(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_PROTMODE_OFF; } else if (strcasecmp(val, "cts") == 0) { mode = IEEE80211_PROTMODE_CTS; } else if (strncasecmp(val, "rtscts", 3) == 0) { mode = IEEE80211_PROTMODE_RTSCTS; } else { errx(1, "unknown protection mode"); } set80211(s, IEEE80211_IOC_PROTMODE, mode, 0, NULL); } static void set80211htprotmode(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_PROTMODE_OFF; } else if (strncasecmp(val, "rts", 3) == 0) { mode = IEEE80211_PROTMODE_RTSCTS; } else { errx(1, "unknown protection mode"); } set80211(s, IEEE80211_IOC_HTPROTMODE, mode, 0, NULL); } static void set80211txpower(const char *val, int d, int s, const struct afswtch *rafp) { double v = atof(val); int txpow; txpow = (int) (2*v); if (txpow != 2*v) errx(-1, "invalid tx power (must be .5 dBm units)"); set80211(s, IEEE80211_IOC_TXPOWER, txpow, 0, NULL); } #define IEEE80211_ROAMING_DEVICE 0 #define IEEE80211_ROAMING_AUTO 1 #define IEEE80211_ROAMING_MANUAL 2 static void set80211roaming(const char *val, int d, int s, const struct afswtch *rafp) { int mode; if (strcasecmp(val, "device") == 0) { mode = IEEE80211_ROAMING_DEVICE; } else if (strcasecmp(val, "auto") == 0) { mode = IEEE80211_ROAMING_AUTO; } else if (strcasecmp(val, "manual") == 0) { mode = IEEE80211_ROAMING_MANUAL; } else { errx(1, "unknown roaming mode"); } set80211(s, IEEE80211_IOC_ROAMING, mode, 0, NULL); } static void set80211wme(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_WME, d, 0, NULL); } static void set80211hidessid(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_HIDESSID, d, 0, NULL); } static void set80211apbridge(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_APBRIDGE, d, 0, NULL); } static void set80211fastframes(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_FF, d, 0, NULL); } static void set80211dturbo(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_TURBOP, d, 0, NULL); } static void set80211chanlist(const char *val, int d, int s, const struct afswtch *rafp) { struct ieee80211req_chanlist chanlist; char *temp, *cp, *tp; temp = malloc(strlen(val) + 1); if (temp == NULL) errx(1, "malloc failed"); strcpy(temp, val); memset(&chanlist, 0, sizeof(chanlist)); cp = temp; for (;;) { int first, last, f, c; tp = strchr(cp, ','); if (tp != NULL) *tp++ = '\0'; switch (sscanf(cp, "%u-%u", &first, &last)) { case 1: if (first > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", first, IEEE80211_CHAN_MAX); setbit(chanlist.ic_channels, first); break; case 2: if (first > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", first, IEEE80211_CHAN_MAX); if (last > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", last, IEEE80211_CHAN_MAX); if (first > last) errx(-1, "void channel range, %u > %u", first, last); for (f = first; f <= last; f++) setbit(chanlist.ic_channels, f); break; } if (tp == NULL) break; c = *tp; while (isspace(c)) tp++; if (!isdigit(c)) break; cp = tp; } set80211(s, IEEE80211_IOC_CHANLIST, 0, sizeof(chanlist), &chanlist); } static void set80211bssid(const char *val, int d, int s, const struct afswtch *rafp) { if (!isanyarg(val)) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(s, IEEE80211_IOC_BSSID, 0, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } else { uint8_t zerobssid[IEEE80211_ADDR_LEN]; memset(zerobssid, 0, sizeof(zerobssid)); set80211(s, IEEE80211_IOC_BSSID, 0, IEEE80211_ADDR_LEN, zerobssid); } } static int getac(const char *ac) { if (strcasecmp(ac, "ac_be") == 0 || strcasecmp(ac, "be") == 0) return WME_AC_BE; if (strcasecmp(ac, "ac_bk") == 0 || strcasecmp(ac, "bk") == 0) return WME_AC_BK; if (strcasecmp(ac, "ac_vi") == 0 || strcasecmp(ac, "vi") == 0) return WME_AC_VI; if (strcasecmp(ac, "ac_vo") == 0 || strcasecmp(ac, "vo") == 0) return WME_AC_VO; errx(1, "unknown wme access class %s", ac); } static DECL_CMD_FUNC2(set80211cwmin, ac, val) { set80211(s, IEEE80211_IOC_WME_CWMIN, atoi(val), getac(ac), NULL); } static DECL_CMD_FUNC2(set80211cwmax, ac, val) { set80211(s, IEEE80211_IOC_WME_CWMAX, atoi(val), getac(ac), NULL); } static DECL_CMD_FUNC2(set80211aifs, ac, val) { set80211(s, IEEE80211_IOC_WME_AIFS, atoi(val), getac(ac), NULL); } static DECL_CMD_FUNC2(set80211txoplimit, ac, val) { set80211(s, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val), getac(ac), NULL); } static DECL_CMD_FUNC(set80211acm, ac, d) { set80211(s, IEEE80211_IOC_WME_ACM, 1, getac(ac), NULL); } static DECL_CMD_FUNC(set80211noacm, ac, d) { set80211(s, IEEE80211_IOC_WME_ACM, 0, getac(ac), NULL); } static DECL_CMD_FUNC(set80211ackpolicy, ac, d) { set80211(s, IEEE80211_IOC_WME_ACKPOLICY, 1, getac(ac), NULL); } static DECL_CMD_FUNC(set80211noackpolicy, ac, d) { set80211(s, IEEE80211_IOC_WME_ACKPOLICY, 0, getac(ac), NULL); } static DECL_CMD_FUNC2(set80211bsscwmin, ac, val) { set80211(s, IEEE80211_IOC_WME_CWMIN, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static DECL_CMD_FUNC2(set80211bsscwmax, ac, val) { set80211(s, IEEE80211_IOC_WME_CWMAX, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static DECL_CMD_FUNC2(set80211bssaifs, ac, val) { set80211(s, IEEE80211_IOC_WME_AIFS, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static DECL_CMD_FUNC2(set80211bsstxoplimit, ac, val) { set80211(s, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static DECL_CMD_FUNC(set80211dtimperiod, val, d) { set80211(s, IEEE80211_IOC_DTIM_PERIOD, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211bintval, val, d) { set80211(s, IEEE80211_IOC_BEACON_INTERVAL, atoi(val), 0, NULL); } static void set80211macmac(int s, int op, const char *val) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(s, op, 0, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } static DECL_CMD_FUNC(set80211addmac, val, d) { set80211macmac(s, IEEE80211_IOC_ADDMAC, val); } static DECL_CMD_FUNC(set80211delmac, val, d) { set80211macmac(s, IEEE80211_IOC_DELMAC, val); } static DECL_CMD_FUNC(set80211kickmac, val, d) { char *temp; struct sockaddr_dl sdl; struct ieee80211req_mlme mlme; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); memset(&mlme, 0, sizeof(mlme)); mlme.im_op = IEEE80211_MLME_DEAUTH; mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE; memcpy(mlme.im_macaddr, LLADDR(&sdl), IEEE80211_ADDR_LEN); set80211(s, IEEE80211_IOC_MLME, 0, sizeof(mlme), &mlme); } static DECL_CMD_FUNC(set80211maccmd, val, d) { set80211(s, IEEE80211_IOC_MACCMD, d, 0, NULL); } static void set80211meshrtmac(int s, int req, const char *val) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(s, IEEE80211_IOC_MESH_RTCMD, req, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } static DECL_CMD_FUNC(set80211addmeshrt, val, d) { set80211meshrtmac(s, IEEE80211_MESH_RTCMD_ADD, val); } static DECL_CMD_FUNC(set80211delmeshrt, val, d) { set80211meshrtmac(s, IEEE80211_MESH_RTCMD_DELETE, val); } static DECL_CMD_FUNC(set80211meshrtcmd, val, d) { set80211(s, IEEE80211_IOC_MESH_RTCMD, d, 0, NULL); } static DECL_CMD_FUNC(set80211hwmprootmode, val, d) { int mode; if (strcasecmp(val, "normal") == 0) mode = IEEE80211_HWMP_ROOTMODE_NORMAL; else if (strcasecmp(val, "proactive") == 0) mode = IEEE80211_HWMP_ROOTMODE_PROACTIVE; else if (strcasecmp(val, "rann") == 0) mode = IEEE80211_HWMP_ROOTMODE_RANN; else mode = IEEE80211_HWMP_ROOTMODE_DISABLED; set80211(s, IEEE80211_IOC_HWMP_ROOTMODE, mode, 0, NULL); } static DECL_CMD_FUNC(set80211hwmpmaxhops, val, d) { set80211(s, IEEE80211_IOC_HWMP_MAXHOPS, atoi(val), 0, NULL); } static void set80211pureg(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_PUREG, d, 0, NULL); } static void set80211quiet(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_QUIET, d, 0, NULL); } static DECL_CMD_FUNC(set80211quietperiod, val, d) { set80211(s, IEEE80211_IOC_QUIET_PERIOD, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211quietcount, val, d) { set80211(s, IEEE80211_IOC_QUIET_COUNT, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211quietduration, val, d) { set80211(s, IEEE80211_IOC_QUIET_DUR, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211quietoffset, val, d) { set80211(s, IEEE80211_IOC_QUIET_OFFSET, atoi(val), 0, NULL); } static void set80211bgscan(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_BGSCAN, d, 0, NULL); } static DECL_CMD_FUNC(set80211bgscanidle, val, d) { set80211(s, IEEE80211_IOC_BGSCAN_IDLE, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211bgscanintvl, val, d) { set80211(s, IEEE80211_IOC_BGSCAN_INTERVAL, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211scanvalid, val, d) { set80211(s, IEEE80211_IOC_SCANVALID, atoi(val), 0, NULL); } /* * Parse an optional trailing specification of which netbands * to apply a parameter to. This is basically the same syntax * as used for channels but you can concatenate to specify * multiple. For example: * 14:abg apply to 11a, 11b, and 11g * 6:ht apply to 11na and 11ng * We don't make a big effort to catch silly things; this is * really a convenience mechanism. */ static int getmodeflags(const char *val) { const char *cp; int flags; flags = 0; cp = strchr(val, ':'); if (cp != NULL) { for (cp++; isalpha((int) *cp); cp++) { /* accept mixed case */ int c = *cp; if (isupper(c)) c = tolower(c); switch (c) { case 'a': /* 802.11a */ flags |= IEEE80211_CHAN_A; break; case 'b': /* 802.11b */ flags |= IEEE80211_CHAN_B; break; case 'g': /* 802.11g */ flags |= IEEE80211_CHAN_G; break; case 'n': /* 802.11n */ flags |= IEEE80211_CHAN_HT; break; case 'd': /* dt = Atheros Dynamic Turbo */ flags |= IEEE80211_CHAN_TURBO; break; case 't': /* ht, dt, st, t */ /* dt and unadorned t specify Dynamic Turbo */ if ((flags & (IEEE80211_CHAN_STURBO|IEEE80211_CHAN_HT)) == 0) flags |= IEEE80211_CHAN_TURBO; break; case 's': /* st = Atheros Static Turbo */ flags |= IEEE80211_CHAN_STURBO; break; case 'h': /* 1/2-width channels */ flags |= IEEE80211_CHAN_HALF; break; case 'q': /* 1/4-width channels */ flags |= IEEE80211_CHAN_QUARTER; break; default: errx(-1, "%s: Invalid mode attribute %c\n", val, *cp); } } } return flags; } #define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT|IEEE80211_CHAN_5GHZ) #define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT|IEEE80211_CHAN_2GHZ) #define _APPLY(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\ _base.params[IEEE80211_MODE_11NA]._param = _v; \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } else if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_11NA]._param = _v; \ else \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } \ if (_flags & IEEE80211_CHAN_TURBO) { \ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ } else if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ else \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ } \ if (_flags & IEEE80211_CHAN_STURBO) \ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \ if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \ _base.params[IEEE80211_MODE_11A]._param = _v; \ if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \ _base.params[IEEE80211_MODE_11G]._param = _v; \ if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \ _base.params[IEEE80211_MODE_11B]._param = _v; \ if (_flags & IEEE80211_CHAN_HALF) \ _base.params[IEEE80211_MODE_HALF]._param = _v; \ if (_flags & IEEE80211_CHAN_QUARTER) \ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \ } while (0) #define _APPLY1(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_11NA]._param = _v; \ else \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } else if ((_flags & IEEE80211_CHAN_108A) == IEEE80211_CHAN_108A) \ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ else if ((_flags & IEEE80211_CHAN_108G) == IEEE80211_CHAN_108G) \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ else if ((_flags & IEEE80211_CHAN_ST) == IEEE80211_CHAN_ST) \ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \ else if (_flags & IEEE80211_CHAN_HALF) \ _base.params[IEEE80211_MODE_HALF]._param = _v; \ else if (_flags & IEEE80211_CHAN_QUARTER) \ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \ else if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \ _base.params[IEEE80211_MODE_11A]._param = _v; \ else if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \ _base.params[IEEE80211_MODE_11G]._param = _v; \ else if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \ _base.params[IEEE80211_MODE_11B]._param = _v; \ } while (0) #define _APPLY_RATE(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \ } \ _APPLY(_flags, _base, _param, _v); \ } while (0) #define _APPLY_RATE1(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \ } \ _APPLY1(_flags, _base, _param, _v); \ } while (0) static DECL_CMD_FUNC(set80211roamrssi, val, d) { double v = atof(val); int rssi, flags; rssi = (int) (2*v); if (rssi != 2*v) errx(-1, "invalid rssi (must be .5 dBm units)"); flags = getmodeflags(val); getroam(s); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY1(flags, roamparams, rssi, rssi); } else _APPLY(flags, roamparams, rssi, rssi); callback_register(setroam_cb, &roamparams); } static int getrate(const char *val, const char *tag) { double v = atof(val); int rate; rate = (int) (2*v); if (rate != 2*v) errx(-1, "invalid %s rate (must be .5 Mb/s units)", tag); return rate; /* NB: returns 2x the specified value */ } static DECL_CMD_FUNC(set80211roamrate, val, d) { int rate, flags; rate = getrate(val, "roam"); flags = getmodeflags(val); getroam(s); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY_RATE1(flags, roamparams, rate, rate); } else _APPLY_RATE(flags, roamparams, rate, rate); callback_register(setroam_cb, &roamparams); } static DECL_CMD_FUNC(set80211mcastrate, val, d) { int rate, flags; rate = getrate(val, "mcast"); flags = getmodeflags(val); gettxparams(s); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY_RATE1(flags, txparams, mcastrate, rate); } else _APPLY_RATE(flags, txparams, mcastrate, rate); callback_register(settxparams_cb, &txparams); } static DECL_CMD_FUNC(set80211mgtrate, val, d) { int rate, flags; rate = getrate(val, "mgmt"); flags = getmodeflags(val); gettxparams(s); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY_RATE1(flags, txparams, mgmtrate, rate); } else _APPLY_RATE(flags, txparams, mgmtrate, rate); callback_register(settxparams_cb, &txparams); } static DECL_CMD_FUNC(set80211ucastrate, val, d) { int flags; gettxparams(s); flags = getmodeflags(val); if (isanyarg(val)) { if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY1(flags, txparams, ucastrate, IEEE80211_FIXED_RATE_NONE); } else _APPLY(flags, txparams, ucastrate, IEEE80211_FIXED_RATE_NONE); } else { int rate = getrate(val, "ucast"); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY_RATE1(flags, txparams, ucastrate, rate); } else _APPLY_RATE(flags, txparams, ucastrate, rate); } callback_register(settxparams_cb, &txparams); } static DECL_CMD_FUNC(set80211maxretry, val, d) { int v = atoi(val), flags; flags = getmodeflags(val); gettxparams(s); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(s)->ic_flags; _APPLY1(flags, txparams, maxretry, v); } else _APPLY(flags, txparams, maxretry, v); callback_register(settxparams_cb, &txparams); } #undef _APPLY_RATE #undef _APPLY #undef IEEE80211_CHAN_HTA #undef IEEE80211_CHAN_HTG static DECL_CMD_FUNC(set80211fragthreshold, val, d) { set80211(s, IEEE80211_IOC_FRAGTHRESHOLD, isundefarg(val) ? IEEE80211_FRAG_MAX : atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211bmissthreshold, val, d) { set80211(s, IEEE80211_IOC_BMISSTHRESHOLD, isundefarg(val) ? IEEE80211_HWBMISS_MAX : atoi(val), 0, NULL); } static void set80211burst(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_BURST, d, 0, NULL); } static void set80211doth(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_DOTH, d, 0, NULL); } static void set80211dfs(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_DFS, d, 0, NULL); } static void set80211shortgi(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_SHORTGI, d ? (IEEE80211_HTCAP_SHORTGI20 | IEEE80211_HTCAP_SHORTGI40) : 0, 0, NULL); } static void set80211ampdu(const char *val, int d, int s, const struct afswtch *rafp) { int ampdu; if (get80211val(s, IEEE80211_IOC_AMPDU, &du) < 0) errx(-1, "cannot get AMPDU setting"); if (d < 0) { d = -d; ampdu &= ~d; } else ampdu |= d; set80211(s, IEEE80211_IOC_AMPDU, ampdu, 0, NULL); } static DECL_CMD_FUNC(set80211ampdulimit, val, d) { int v; switch (atoi(val)) { case 8: case 8*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_8K; break; case 16: case 16*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_16K; break; case 32: case 32*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_32K; break; case 64: case 64*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_64K; break; default: errx(-1, "invalid A-MPDU limit %s", val); } set80211(s, IEEE80211_IOC_AMPDU_LIMIT, v, 0, NULL); } static DECL_CMD_FUNC(set80211ampdudensity, val, d) { int v; if (isanyarg(val) || strcasecmp(val, "na") == 0) v = IEEE80211_HTCAP_MPDUDENSITY_NA; else switch ((int)(atof(val)*4)) { case 0: v = IEEE80211_HTCAP_MPDUDENSITY_NA; break; case 1: v = IEEE80211_HTCAP_MPDUDENSITY_025; break; case 2: v = IEEE80211_HTCAP_MPDUDENSITY_05; break; case 4: v = IEEE80211_HTCAP_MPDUDENSITY_1; break; case 8: v = IEEE80211_HTCAP_MPDUDENSITY_2; break; case 16: v = IEEE80211_HTCAP_MPDUDENSITY_4; break; case 32: v = IEEE80211_HTCAP_MPDUDENSITY_8; break; case 64: v = IEEE80211_HTCAP_MPDUDENSITY_16; break; default: errx(-1, "invalid A-MPDU density %s", val); } set80211(s, IEEE80211_IOC_AMPDU_DENSITY, v, 0, NULL); } static void set80211amsdu(const char *val, int d, int s, const struct afswtch *rafp) { int amsdu; if (get80211val(s, IEEE80211_IOC_AMSDU, &amsdu) < 0) err(-1, "cannot get AMSDU setting"); if (d < 0) { d = -d; amsdu &= ~d; } else amsdu |= d; set80211(s, IEEE80211_IOC_AMSDU, amsdu, 0, NULL); } static DECL_CMD_FUNC(set80211amsdulimit, val, d) { set80211(s, IEEE80211_IOC_AMSDU_LIMIT, atoi(val), 0, NULL); } static void set80211puren(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_PUREN, d, 0, NULL); } static void set80211htcompat(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_HTCOMPAT, d, 0, NULL); } static void set80211htconf(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_HTCONF, d, 0, NULL); htconf = d; } static void set80211dwds(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_DWDS, d, 0, NULL); } static void set80211inact(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_INACTIVITY, d, 0, NULL); } static void set80211tsn(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_TSN, d, 0, NULL); } static void set80211dotd(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_DOTD, d, 0, NULL); } static void set80211smps(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_SMPS, d, 0, NULL); } static void set80211rifs(const char *val, int d, int s, const struct afswtch *rafp) { set80211(s, IEEE80211_IOC_RIFS, d, 0, NULL); } static DECL_CMD_FUNC(set80211tdmaslot, val, d) { set80211(s, IEEE80211_IOC_TDMA_SLOT, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211tdmaslotcnt, val, d) { set80211(s, IEEE80211_IOC_TDMA_SLOTCNT, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211tdmaslotlen, val, d) { set80211(s, IEEE80211_IOC_TDMA_SLOTLEN, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211tdmabintval, val, d) { set80211(s, IEEE80211_IOC_TDMA_BINTERVAL, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211meshttl, val, d) { set80211(s, IEEE80211_IOC_MESH_TTL, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211meshforward, val, d) { set80211(s, IEEE80211_IOC_MESH_FWRD, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211meshpeering, val, d) { set80211(s, IEEE80211_IOC_MESH_AP, atoi(val), 0, NULL); } static DECL_CMD_FUNC(set80211meshmetric, val, d) { char v[12]; memcpy(v, val, sizeof(v)); set80211(s, IEEE80211_IOC_MESH_PR_METRIC, 0, 0, v); } static DECL_CMD_FUNC(set80211meshpath, val, d) { char v[12]; memcpy(v, val, sizeof(v)); set80211(s, IEEE80211_IOC_MESH_PR_PATH, 0, 0, v); } static int regdomain_sort(const void *a, const void *b) { #define CHAN_ALL \ (IEEE80211_CHAN_ALLTURBO|IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER) const struct ieee80211_channel *ca = a; const struct ieee80211_channel *cb = b; return ca->ic_freq == cb->ic_freq ? (ca->ic_flags & CHAN_ALL) - (cb->ic_flags & CHAN_ALL) : ca->ic_freq - cb->ic_freq; #undef CHAN_ALL } static const struct ieee80211_channel * chanlookup(const struct ieee80211_channel chans[], int nchans, int freq, int flags) { int i; flags &= IEEE80211_CHAN_ALLTURBO; for (i = 0; i < nchans; i++) { const struct ieee80211_channel *c = &chans[i]; if (c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } static int chanfind(const struct ieee80211_channel chans[], int nchans, int flags) { int i; for (i = 0; i < nchans; i++) { const struct ieee80211_channel *c = &chans[i]; if ((c->ic_flags & flags) == flags) return 1; } return 0; } /* * Check channel compatibility. */ static int checkchan(const struct ieee80211req_chaninfo *avail, int freq, int flags) { flags &= ~REQ_FLAGS; /* * Check if exact channel is in the calibration table; * everything below is to deal with channels that we * want to include but that are not explicitly listed. */ if (flags & IEEE80211_CHAN_HT40) { /* NB: we use an HT40 channel center that matches HT20 */ flags = (flags &~ IEEE80211_CHAN_HT40) | IEEE80211_CHAN_HT20; } if (chanlookup(avail->ic_chans, avail->ic_nchans, freq, flags) != NULL) return 1; if (flags & IEEE80211_CHAN_GSM) { /* * XXX GSM frequency mapping is handled in the kernel * so we cannot find them in the calibration table; * just accept the channel and the kernel will reject * the channel list if it's wrong. */ return 1; } /* * If this is a 1/2 or 1/4 width channel allow it if a full * width channel is present for this frequency, and the device * supports fractional channels on this band. This is a hack * that avoids bloating the calibration table; it may be better * by per-band attributes though (we are effectively calculating * this attribute by scanning the channel list ourself). */ if ((flags & (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == 0) return 0; if (chanlookup(avail->ic_chans, avail->ic_nchans, freq, flags &~ (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == NULL) return 0; if (flags & IEEE80211_CHAN_HALF) { return chanfind(avail->ic_chans, avail->ic_nchans, IEEE80211_CHAN_HALF | (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ))); } else { return chanfind(avail->ic_chans, avail->ic_nchans, IEEE80211_CHAN_QUARTER | (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ))); } } static void regdomain_addchans(struct ieee80211req_chaninfo *ci, const netband_head *bands, const struct ieee80211_regdomain *reg, uint32_t chanFlags, const struct ieee80211req_chaninfo *avail) { const struct netband *nb; const struct freqband *b; struct ieee80211_channel *c, *prev; int freq, hi_adj, lo_adj, channelSep; uint32_t flags; hi_adj = (chanFlags & IEEE80211_CHAN_HT40U) ? -20 : 0; lo_adj = (chanFlags & IEEE80211_CHAN_HT40D) ? 20 : 0; channelSep = (chanFlags & IEEE80211_CHAN_2GHZ) ? 0 : 40; LIST_FOREACH(nb, bands, next) { b = nb->band; if (verbose) { printf("%s:", __func__); printb(" chanFlags", chanFlags, IEEE80211_CHAN_BITS); printb(" bandFlags", nb->flags | b->flags, IEEE80211_CHAN_BITS); putchar('\n'); } prev = NULL; for (freq = b->freqStart + lo_adj; freq <= b->freqEnd + hi_adj; freq += b->chanSep) { /* * Construct flags for the new channel. We take * the attributes from the band descriptions except * for HT40 which is enabled generically (i.e. +/- * extension channel) in the band description and * then constrained according by channel separation. */ flags = nb->flags | b->flags; if (flags & IEEE80211_CHAN_HT) { /* * HT channels are generated specially; we're * called to add HT20, HT40+, and HT40- chan's * so we need to expand only band specs for * the HT channel type being added. */ if ((chanFlags & IEEE80211_CHAN_HT20) && (flags & IEEE80211_CHAN_HT20) == 0) { if (verbose) printf("%u: skip, not an " "HT20 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_HT40) && (flags & IEEE80211_CHAN_HT40) == 0) { if (verbose) printf("%u: skip, not an " "HT40 channel\n", freq); continue; } /* NB: HT attribute comes from caller */ flags &= ~IEEE80211_CHAN_HT; flags |= chanFlags & IEEE80211_CHAN_HT; } /* * Check if device can operate on this frequency. */ if (!checkchan(avail, freq, flags)) { if (verbose) { printf("%u: skip, ", freq); printb("flags", flags, IEEE80211_CHAN_BITS); printf(" not available\n"); } continue; } if ((flags & REQ_ECM) && !reg->ecm) { if (verbose) printf("%u: skip, ECM channel\n", freq); continue; } if ((flags & REQ_INDOOR) && reg->location == 'O') { if (verbose) printf("%u: skip, indoor channel\n", freq); continue; } if ((flags & REQ_OUTDOOR) && reg->location == 'I') { if (verbose) printf("%u: skip, outdoor channel\n", freq); continue; } if ((flags & IEEE80211_CHAN_HT40) && prev != NULL && (freq - prev->ic_freq) < channelSep) { if (verbose) printf("%u: skip, only %u channel " "separation, need %d\n", freq, freq - prev->ic_freq, channelSep); continue; } if (ci->ic_nchans == IEEE80211_CHAN_MAX) { if (verbose) printf("%u: skip, channel table full\n", freq); break; } c = &ci->ic_chans[ci->ic_nchans++]; memset(c, 0, sizeof(*c)); c->ic_freq = freq; c->ic_flags = flags; if (c->ic_flags & IEEE80211_CHAN_DFS) c->ic_maxregpower = nb->maxPowerDFS; else c->ic_maxregpower = nb->maxPower; if (verbose) { printf("[%3d] add freq %u ", ci->ic_nchans-1, c->ic_freq); printb("flags", c->ic_flags, IEEE80211_CHAN_BITS); printf(" power %u\n", c->ic_maxregpower); } /* NB: kernel fills in other fields */ prev = c; } } } static void regdomain_makechannels( struct ieee80211_regdomain_req *req, const struct ieee80211_devcaps_req *dc) { struct regdata *rdp = getregdata(); const struct country *cc; const struct ieee80211_regdomain *reg = &req->rd; struct ieee80211req_chaninfo *ci = &req->chaninfo; const struct regdomain *rd; /* * Locate construction table for new channel list. We treat * the regdomain/SKU as definitive so a country can be in * multiple with different properties (e.g. US in FCC+FCC3). * If no regdomain is specified then we fallback on the country * code to find the associated regdomain since countries always * belong to at least one regdomain. */ if (reg->regdomain == 0) { cc = lib80211_country_findbycc(rdp, reg->country); if (cc == NULL) errx(1, "internal error, country %d not found", reg->country); rd = cc->rd; } else rd = lib80211_regdomain_findbysku(rdp, reg->regdomain); if (rd == NULL) errx(1, "internal error, regdomain %d not found", reg->regdomain); if (rd->sku != SKU_DEBUG) { /* * regdomain_addchans incrememnts the channel count for * each channel it adds so initialize ic_nchans to zero. * Note that we know we have enough space to hold all possible * channels because the devcaps list size was used to * allocate our request. */ ci->ic_nchans = 0; if (!LIST_EMPTY(&rd->bands_11b)) regdomain_addchans(ci, &rd->bands_11b, reg, IEEE80211_CHAN_B, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11g)) regdomain_addchans(ci, &rd->bands_11g, reg, IEEE80211_CHAN_G, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11a)) regdomain_addchans(ci, &rd->bands_11a, reg, IEEE80211_CHAN_A, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11na) && dc->dc_htcaps != 0) { regdomain_addchans(ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20, &dc->dc_chaninfo); if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) { regdomain_addchans(ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U, &dc->dc_chaninfo); regdomain_addchans(ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D, &dc->dc_chaninfo); } } if (!LIST_EMPTY(&rd->bands_11ng) && dc->dc_htcaps != 0) { regdomain_addchans(ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT20, &dc->dc_chaninfo); if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) { regdomain_addchans(ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U, &dc->dc_chaninfo); regdomain_addchans(ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D, &dc->dc_chaninfo); } } qsort(ci->ic_chans, ci->ic_nchans, sizeof(ci->ic_chans[0]), regdomain_sort); } else memcpy(ci, &dc->dc_chaninfo, IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo)); } static void list_countries(void) { struct regdata *rdp = getregdata(); const struct country *cp; const struct regdomain *dp; int i; i = 0; printf("\nCountry codes:\n"); LIST_FOREACH(cp, &rdp->countries, next) { printf("%2s %-15.15s%s", cp->isoname, cp->name, ((i+1)%4) == 0 ? "\n" : " "); i++; } i = 0; printf("\nRegulatory domains:\n"); LIST_FOREACH(dp, &rdp->domains, next) { printf("%-15.15s%s", dp->name, ((i+1)%4) == 0 ? "\n" : " "); i++; } printf("\n"); } static void defaultcountry(const struct regdomain *rd) { struct regdata *rdp = getregdata(); const struct country *cc; cc = lib80211_country_findbycc(rdp, rd->cc->code); if (cc == NULL) errx(1, "internal error, ISO country code %d not " "defined for regdomain %s", rd->cc->code, rd->name); regdomain.country = cc->code; regdomain.isocc[0] = cc->isoname[0]; regdomain.isocc[1] = cc->isoname[1]; } static DECL_CMD_FUNC(set80211regdomain, val, d) { struct regdata *rdp = getregdata(); const struct regdomain *rd; rd = lib80211_regdomain_findbyname(rdp, val); if (rd == NULL) { char *eptr; long sku = strtol(val, &eptr, 0); if (eptr != val) rd = lib80211_regdomain_findbysku(rdp, sku); if (eptr == val || rd == NULL) errx(1, "unknown regdomain %s", val); } getregdomain(s); regdomain.regdomain = rd->sku; if (regdomain.country == 0 && rd->cc != NULL) { /* * No country code setup and there's a default * one for this regdomain fill it in. */ defaultcountry(rd); } callback_register(setregdomain_cb, ®domain); } static DECL_CMD_FUNC(set80211country, val, d) { struct regdata *rdp = getregdata(); const struct country *cc; cc = lib80211_country_findbyname(rdp, val); if (cc == NULL) { char *eptr; long code = strtol(val, &eptr, 0); if (eptr != val) cc = lib80211_country_findbycc(rdp, code); if (eptr == val || cc == NULL) errx(1, "unknown ISO country code %s", val); } getregdomain(s); regdomain.regdomain = cc->rd->sku; regdomain.country = cc->code; regdomain.isocc[0] = cc->isoname[0]; regdomain.isocc[1] = cc->isoname[1]; callback_register(setregdomain_cb, ®domain); } static void set80211location(const char *val, int d, int s, const struct afswtch *rafp) { getregdomain(s); regdomain.location = d; callback_register(setregdomain_cb, ®domain); } static void set80211ecm(const char *val, int d, int s, const struct afswtch *rafp) { getregdomain(s); regdomain.ecm = d; callback_register(setregdomain_cb, ®domain); } static void LINE_INIT(char c) { spacer = c; if (c == '\t') col = 8; else col = 1; } static void LINE_BREAK(void) { if (spacer != '\t') { printf("\n"); spacer = '\t'; } col = 8; /* 8-col tab */ } static void LINE_CHECK(const char *fmt, ...) { char buf[80]; va_list ap; int n; va_start(ap, fmt); n = vsnprintf(buf+1, sizeof(buf)-1, fmt, ap); va_end(ap); col += 1+n; if (col > MAXCOL) { LINE_BREAK(); col += n; } buf[0] = spacer; printf("%s", buf); spacer = ' '; } static int getmaxrate(const uint8_t rates[15], uint8_t nrates) { int i, maxrate = -1; for (i = 0; i < nrates; i++) { int rate = rates[i] & IEEE80211_RATE_VAL; if (rate > maxrate) maxrate = rate; } return maxrate / 2; } static const char * getcaps(int capinfo) { static char capstring[32]; char *cp = capstring; if (capinfo & IEEE80211_CAPINFO_ESS) *cp++ = 'E'; if (capinfo & IEEE80211_CAPINFO_IBSS) *cp++ = 'I'; if (capinfo & IEEE80211_CAPINFO_CF_POLLABLE) *cp++ = 'c'; if (capinfo & IEEE80211_CAPINFO_CF_POLLREQ) *cp++ = 'C'; if (capinfo & IEEE80211_CAPINFO_PRIVACY) *cp++ = 'P'; if (capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) *cp++ = 'S'; if (capinfo & IEEE80211_CAPINFO_PBCC) *cp++ = 'B'; if (capinfo & IEEE80211_CAPINFO_CHNL_AGILITY) *cp++ = 'A'; if (capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) *cp++ = 's'; if (capinfo & IEEE80211_CAPINFO_RSN) *cp++ = 'R'; if (capinfo & IEEE80211_CAPINFO_DSSSOFDM) *cp++ = 'D'; *cp = '\0'; return capstring; } static const char * getflags(int flags) { static char flagstring[32]; char *cp = flagstring; if (flags & IEEE80211_NODE_AUTH) *cp++ = 'A'; if (flags & IEEE80211_NODE_QOS) *cp++ = 'Q'; if (flags & IEEE80211_NODE_ERP) *cp++ = 'E'; if (flags & IEEE80211_NODE_PWR_MGT) *cp++ = 'P'; if (flags & IEEE80211_NODE_HT) { *cp++ = 'H'; if (flags & IEEE80211_NODE_HTCOMPAT) *cp++ = '+'; } if (flags & IEEE80211_NODE_WPS) *cp++ = 'W'; if (flags & IEEE80211_NODE_TSN) *cp++ = 'N'; if (flags & IEEE80211_NODE_AMPDU_TX) *cp++ = 'T'; if (flags & IEEE80211_NODE_AMPDU_RX) *cp++ = 'R'; if (flags & IEEE80211_NODE_MIMO_PS) { *cp++ = 'M'; if (flags & IEEE80211_NODE_MIMO_RTS) *cp++ = '+'; } if (flags & IEEE80211_NODE_RIFS) *cp++ = 'I'; if (flags & IEEE80211_NODE_SGI40) { *cp++ = 'S'; if (flags & IEEE80211_NODE_SGI20) *cp++ = '+'; } else if (flags & IEEE80211_NODE_SGI20) *cp++ = 's'; if (flags & IEEE80211_NODE_AMSDU_TX) *cp++ = 't'; if (flags & IEEE80211_NODE_AMSDU_RX) *cp++ = 'r'; *cp = '\0'; return flagstring; } static void printie(const char* tag, const uint8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { maxlen -= strlen(tag)+2; if (2*ielen > maxlen) maxlen--; printf("<"); for (; ielen > 0; ie++, ielen--) { if (maxlen-- <= 0) break; printf("%02x", *ie); } if (ielen != 0) printf("-"); printf(">"); } } #define LE_READ_2(p) \ ((u_int16_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8))) #define LE_READ_4(p) \ ((u_int32_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8) | \ (((const u_int8_t *)(p))[2] << 16) | \ (((const u_int8_t *)(p))[3] << 24))) /* * NB: The decoding routines assume a properly formatted ie * which should be safe as the kernel only retains them * if they parse ok. */ static void printwmeparam(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) static const char *acnames[] = { "BE", "BK", "VO", "VI" }; const struct ieee80211_wme_param *wme = (const struct ieee80211_wme_param *) ie; int i; printf("%s", tag); if (!verbose) return; printf("param_qosInfo); ie += offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { const struct ieee80211_wme_acparams *ac = &wme->params_acParams[i]; printf(" %s[%saifsn %u cwmin %u cwmax %u txop %u]" , acnames[i] , MS(ac->acp_aci_aifsn, WME_PARAM_ACM) ? "acm " : "" , MS(ac->acp_aci_aifsn, WME_PARAM_AIFSN) , MS(ac->acp_logcwminmax, WME_PARAM_LOGCWMIN) , MS(ac->acp_logcwminmax, WME_PARAM_LOGCWMAX) , LE_READ_2(&ac->acp_txop) ); } printf(">"); #undef MS } static void printwmeinfo(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { const struct ieee80211_wme_info *wme = (const struct ieee80211_wme_info *) ie; printf("", wme->wme_version, wme->wme_info); } } static void printhtcap(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { const struct ieee80211_ie_htcap *htcap = (const struct ieee80211_ie_htcap *) ie; const char *sep; int i, j; printf("hc_cap), htcap->hc_param); printf(" mcsset["); sep = ""; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) if (isset(htcap->hc_mcsset, i)) { for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++) if (isclr(htcap->hc_mcsset, j)) break; j--; if (i == j) printf("%s%u", sep, i); else printf("%s%u-%u", sep, i, j); i += j-i; sep = ","; } printf("] extcap 0x%x txbf 0x%x antenna 0x%x>", LE_READ_2(&htcap->hc_extcap), LE_READ_4(&htcap->hc_txbf), htcap->hc_antenna); } } static void printhtinfo(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { const struct ieee80211_ie_htinfo *htinfo = (const struct ieee80211_ie_htinfo *) ie; const char *sep; int i, j; printf("hi_ctrlchannel, htinfo->hi_byte1, htinfo->hi_byte2, htinfo->hi_byte3, LE_READ_2(&htinfo->hi_byte45)); printf(" basicmcs["); sep = ""; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) if (isset(htinfo->hi_basicmcsset, i)) { for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++) if (isclr(htinfo->hi_basicmcsset, j)) break; j--; if (i == j) printf("%s%u", sep, i); else printf("%s%u-%u", sep, i, j); i += j-i; sep = ","; } printf("]>"); } } static void printathie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { const struct ieee80211_ath_ie *ath = (const struct ieee80211_ath_ie *)ie; printf("<"); if (ath->ath_capability & ATHEROS_CAP_TURBO_PRIME) printf("DTURBO,"); if (ath->ath_capability & ATHEROS_CAP_COMPRESSION) printf("COMP,"); if (ath->ath_capability & ATHEROS_CAP_FAST_FRAME) printf("FF,"); if (ath->ath_capability & ATHEROS_CAP_XR) printf("XR,"); if (ath->ath_capability & ATHEROS_CAP_AR) printf("AR,"); if (ath->ath_capability & ATHEROS_CAP_BURST) printf("BURST,"); if (ath->ath_capability & ATHEROS_CAP_WME) printf("WME,"); if (ath->ath_capability & ATHEROS_CAP_BOOST) printf("BOOST,"); printf("0x%x>", LE_READ_2(ath->ath_defkeyix)); } } static void printmeshconf(const char *tag, const uint8_t *ie, size_t ielen, int maxlen) { #define MATCHOUI(field, oui, string) \ do { \ if (memcmp(field, oui, 4) == 0) \ printf("%s", string); \ } while (0) printf("%s", tag); if (verbose) { const struct ieee80211_meshconf_ie *mconf = (const struct ieee80211_meshconf_ie *)ie; printf("conf_pselid == IEEE80211_MESHCONF_PATH_HWMP) printf("HWMP"); else printf("UNKNOWN"); printf(" LINK:"); if (mconf->conf_pmetid == IEEE80211_MESHCONF_METRIC_AIRTIME) printf("AIRTIME"); else printf("UNKNOWN"); printf(" CONGESTION:"); if (mconf->conf_ccid == IEEE80211_MESHCONF_CC_DISABLED) printf("DISABLED"); else printf("UNKNOWN"); printf(" SYNC:"); if (mconf->conf_syncid == IEEE80211_MESHCONF_SYNC_NEIGHOFF) printf("NEIGHOFF"); else printf("UNKNOWN"); printf(" AUTH:"); if (mconf->conf_authid == IEEE80211_MESHCONF_AUTH_DISABLED) printf("DISABLED"); else printf("UNKNOWN"); printf(" FORM:0x%x CAPS:0x%x>", mconf->conf_form, mconf->conf_cap); } #undef MATCHOUI } static const char * wpa_cipher(const u_int8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): return "NONE"; case WPA_SEL(WPA_CSE_WEP40): return "WEP40"; case WPA_SEL(WPA_CSE_WEP104): return "WEP104"; case WPA_SEL(WPA_CSE_TKIP): return "TKIP"; case WPA_SEL(WPA_CSE_CCMP): return "AES-CCMP"; } return "?"; /* NB: so 1<< is discarded */ #undef WPA_SEL } static const char * wpa_keymgmt(const u_int8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_ASE_8021X_UNSPEC): return "8021X-UNSPEC"; case WPA_SEL(WPA_ASE_8021X_PSK): return "8021X-PSK"; case WPA_SEL(WPA_ASE_NONE): return "NONE"; } return "?"; #undef WPA_SEL } static void printwpaie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { u_int8_t len = ie[1]; printf("%s", tag); if (verbose) { const char *sep; int n; ie += 6, len -= 4; /* NB: len is payload only */ printf(" 0; n--) { printf("%s%s", sep, wpa_cipher(ie)); ie += 4, len -= 4; sep = "+"; } /* key management algorithms */ n = LE_READ_2(ie); ie += 2, len -= 2; sep = " km:"; for (; n > 0; n--) { printf("%s%s", sep, wpa_keymgmt(ie)); ie += 4, len -= 4; sep = "+"; } if (len > 2) /* optional capabilities */ printf(", caps 0x%x", LE_READ_2(ie)); printf(">"); } } static const char * rsn_cipher(const u_int8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): return "NONE"; case RSN_SEL(RSN_CSE_WEP40): return "WEP40"; case RSN_SEL(RSN_CSE_WEP104): return "WEP104"; case RSN_SEL(RSN_CSE_TKIP): return "TKIP"; case RSN_SEL(RSN_CSE_CCMP): return "AES-CCMP"; case RSN_SEL(RSN_CSE_WRAP): return "AES-OCB"; } return "?"; #undef WPA_SEL } static const char * rsn_keymgmt(const u_int8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_ASE_8021X_UNSPEC): return "8021X-UNSPEC"; case RSN_SEL(RSN_ASE_8021X_PSK): return "8021X-PSK"; case RSN_SEL(RSN_ASE_NONE): return "NONE"; } return "?"; #undef RSN_SEL } static void printrsnie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose) { const char *sep; int n; ie += 2, ielen -= 2; printf(" 0; n--) { printf("%s%s", sep, rsn_cipher(ie)); ie += 4, ielen -= 4; sep = "+"; } /* key management algorithms */ n = LE_READ_2(ie); ie += 2, ielen -= 2; sep = " km:"; for (; n > 0; n--) { printf("%s%s", sep, rsn_keymgmt(ie)); ie += 4, ielen -= 4; sep = "+"; } if (ielen > 2) /* optional capabilities */ printf(", caps 0x%x", LE_READ_2(ie)); /* XXXPMKID */ printf(">"); } } /* XXX move to a public include file */ #define IEEE80211_WPS_DEV_PASS_ID 0x1012 #define IEEE80211_WPS_SELECTED_REG 0x1041 #define IEEE80211_WPS_SETUP_STATE 0x1044 #define IEEE80211_WPS_UUID_E 0x1047 #define IEEE80211_WPS_VERSION 0x104a #define BE_READ_2(p) \ ((u_int16_t) \ ((((const u_int8_t *)(p))[1] ) | \ (((const u_int8_t *)(p))[0] << 8))) static void printwpsie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { #define N(a) (sizeof(a) / sizeof(a[0])) u_int8_t len = ie[1]; printf("%s", tag); if (verbose) { static const char *dev_pass_id[] = { "D", /* Default (PIN) */ "U", /* User-specified */ "M", /* Machine-specified */ "K", /* Rekey */ "P", /* PushButton */ "R" /* Registrar-specified */ }; int n; ie +=6, len -= 4; /* NB: len is payload only */ /* WPS IE in Beacon and Probe Resp frames have different fields */ printf("<"); while (len) { uint16_t tlv_type = BE_READ_2(ie); uint16_t tlv_len = BE_READ_2(ie + 2); ie += 4, len -= 4; switch (tlv_type) { case IEEE80211_WPS_VERSION: printf("v:%d.%d", *ie >> 4, *ie & 0xf); break; case IEEE80211_WPS_SETUP_STATE: /* Only 1 and 2 are valid */ if (*ie == 0 || *ie >= 3) printf(" state:B"); else printf(" st:%s", *ie == 1 ? "N" : "C"); break; case IEEE80211_WPS_SELECTED_REG: printf(" sel:%s", *ie ? "T" : "F"); break; case IEEE80211_WPS_DEV_PASS_ID: n = LE_READ_2(ie); if (n < N(dev_pass_id)) printf(" dpi:%s", dev_pass_id[n]); break; case IEEE80211_WPS_UUID_E: printf(" uuid-e:"); for (n = 0; n < (tlv_len - 1); n++) printf("%02x-", ie[n]); printf("%02x", ie[n]); break; } ie += tlv_len, len -= tlv_len; } printf(">"); } #undef N } static void printtdmaie(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { printf("%s", tag); if (verbose && ielen >= sizeof(struct ieee80211_tdma_param)) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *) ie; /* XXX tstamp */ printf("", tdma->tdma_version, tdma->tdma_slot, tdma->tdma_slotcnt, LE_READ_2(&tdma->tdma_slotlen), tdma->tdma_bintval, tdma->tdma_inuse[0]); } } /* * Copy the ssid string contents into buf, truncating to fit. If the * ssid is entirely printable then just copy intact. Otherwise convert * to hexadecimal. If the result is truncated then replace the last * three characters with "...". */ static int copy_essid(char buf[], size_t bufsize, const u_int8_t *essid, size_t essid_len) { const u_int8_t *p; size_t maxlen; int i; if (essid_len > bufsize) maxlen = bufsize; else maxlen = essid_len; /* determine printable or not */ for (i = 0, p = essid; i < maxlen; i++, p++) { if (*p < ' ' || *p > 0x7e) break; } if (i != maxlen) { /* not printable, print as hex */ if (bufsize < 3) return 0; strlcpy(buf, "0x", bufsize); bufsize -= 2; p = essid; for (i = 0; i < maxlen && bufsize >= 2; i++) { sprintf(&buf[2+2*i], "%02x", p[i]); bufsize -= 2; } if (i != essid_len) memcpy(&buf[2+2*i-3], "...", 3); } else { /* printable, truncate as needed */ memcpy(buf, essid, maxlen); if (maxlen != essid_len) memcpy(&buf[maxlen-3], "...", 3); } return maxlen; } static void printssid(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { char ssid[2*IEEE80211_NWID_LEN+1]; printf("%s<%.*s>", tag, copy_essid(ssid, maxlen, ie+2, ie[1]), ssid); } static void printrates(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { const char *sep; int i; printf("%s", tag); sep = "<"; for (i = 2; i < ielen; i++) { printf("%s%s%d", sep, ie[i] & IEEE80211_RATE_BASIC ? "B" : "", ie[i] & IEEE80211_RATE_VAL); sep = ","; } printf(">"); } static void printcountry(const char *tag, const u_int8_t *ie, size_t ielen, int maxlen) { const struct ieee80211_country_ie *cie = (const struct ieee80211_country_ie *) ie; int i, nbands, schan, nchan; printf("%s<%c%c%c", tag, cie->cc[0], cie->cc[1], cie->cc[2]); nbands = (cie->len - 3) / sizeof(cie->band[0]); for (i = 0; i < nbands; i++) { schan = cie->band[i].schan; nchan = cie->band[i].nchan; if (nchan != 1) printf(" %u-%u,%u", schan, schan + nchan-1, cie->band[i].maxtxpwr); else printf(" %u,%u", schan, cie->band[i].maxtxpwr); } printf(">"); } /* unaligned little endian access */ #define LE_READ_4(p) \ ((u_int32_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8) | \ (((const u_int8_t *)(p))[2] << 16) | \ (((const u_int8_t *)(p))[3] << 24))) static __inline int iswpaoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); } static __inline int iswmeinfo(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_INFO_OUI_SUBTYPE; } static __inline int iswmeparam(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_PARAM_OUI_SUBTYPE; } static __inline int isatherosoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI); } static __inline int istdmaoui(const uint8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((TDMA_OUI_TYPE<<24)|TDMA_OUI); } static __inline int iswpsoui(const uint8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WPS_OUI_TYPE<<24)|WPA_OUI); } static const char * iename(int elemid) { switch (elemid) { case IEEE80211_ELEMID_FHPARMS: return " FHPARMS"; case IEEE80211_ELEMID_CFPARMS: return " CFPARMS"; case IEEE80211_ELEMID_TIM: return " TIM"; case IEEE80211_ELEMID_IBSSPARMS:return " IBSSPARMS"; case IEEE80211_ELEMID_CHALLENGE:return " CHALLENGE"; case IEEE80211_ELEMID_PWRCNSTR: return " PWRCNSTR"; case IEEE80211_ELEMID_PWRCAP: return " PWRCAP"; case IEEE80211_ELEMID_TPCREQ: return " TPCREQ"; case IEEE80211_ELEMID_TPCREP: return " TPCREP"; case IEEE80211_ELEMID_SUPPCHAN: return " SUPPCHAN"; case IEEE80211_ELEMID_CSA: return " CSA"; case IEEE80211_ELEMID_MEASREQ: return " MEASREQ"; case IEEE80211_ELEMID_MEASREP: return " MEASREP"; case IEEE80211_ELEMID_QUIET: return " QUIET"; case IEEE80211_ELEMID_IBSSDFS: return " IBSSDFS"; case IEEE80211_ELEMID_TPC: return " TPC"; case IEEE80211_ELEMID_CCKM: return " CCKM"; } return " ???"; } static void printies(const u_int8_t *vp, int ielen, int maxcols) { while (ielen > 0) { switch (vp[0]) { case IEEE80211_ELEMID_SSID: if (verbose) printssid(" SSID", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_RATES: case IEEE80211_ELEMID_XRATES: if (verbose) printrates(vp[0] == IEEE80211_ELEMID_RATES ? " RATES" : " XRATES", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_DSPARMS: if (verbose) printf(" DSPARMS<%u>", vp[2]); break; case IEEE80211_ELEMID_COUNTRY: if (verbose) printcountry(" COUNTRY", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_ERP: if (verbose) printf(" ERP<0x%x>", vp[2]); break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(vp)) printwpaie(" WPA", vp, 2+vp[1], maxcols); else if (iswmeinfo(vp)) printwmeinfo(" WME", vp, 2+vp[1], maxcols); else if (iswmeparam(vp)) printwmeparam(" WME", vp, 2+vp[1], maxcols); else if (isatherosoui(vp)) printathie(" ATH", vp, 2+vp[1], maxcols); else if (iswpsoui(vp)) printwpsie(" WPS", vp, 2+vp[1], maxcols); else if (istdmaoui(vp)) printtdmaie(" TDMA", vp, 2+vp[1], maxcols); else if (verbose) printie(" VEN", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_RSN: printrsnie(" RSN", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_HTCAP: printhtcap(" HTCAP", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_HTINFO: if (verbose) printhtinfo(" HTINFO", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_MESHID: if (verbose) printssid(" MESHID", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_MESHCONF: printmeshconf(" MESHCONF", vp, 2+vp[1], maxcols); break; default: if (verbose) printie(iename(vp[0]), vp, 2+vp[1], maxcols); break; } ielen -= 2+vp[1]; vp += 2+vp[1]; } } static void printmimo(const struct ieee80211_mimo_info *mi) { /* NB: don't muddy display unless there's something to show */ if (mi->rssi[0] != 0 || mi->rssi[1] != 0 || mi->rssi[2] != 0) { /* XXX ignore EVM for now */ printf(" (rssi %d:%d:%d nf %d:%d:%d)", mi->rssi[0], mi->rssi[1], mi->rssi[2], mi->noise[0], mi->noise[1], mi->noise[2]); } } static void list_scan(int s) { uint8_t buf[24*1024]; char ssid[IEEE80211_NWID_LEN+1]; const uint8_t *cp; int len, ssidmax, idlen; if (get80211len(s, IEEE80211_IOC_SCAN_RESULTS, buf, sizeof(buf), &len) < 0) errx(1, "unable to get scan results"); if (len < sizeof(struct ieee80211req_scan_result)) return; getchaninfo(s); ssidmax = verbose ? IEEE80211_NWID_LEN - 1 : 14; printf("%-*.*s %-17.17s %4s %4s %-7s %3s %4s\n" , ssidmax, ssidmax, "SSID/MESH ID" , "BSSID" , "CHAN" , "RATE" , " S:N" , "INT" , "CAPS" ); cp = buf; do { const struct ieee80211req_scan_result *sr; const uint8_t *vp, *idp; sr = (const struct ieee80211req_scan_result *) cp; vp = cp + sr->isr_ie_off; if (sr->isr_meshid_len) { idp = vp + sr->isr_ssid_len; idlen = sr->isr_meshid_len; } else { idp = vp; idlen = sr->isr_ssid_len; } printf("%-*.*s %s %3d %3dM %3d:%-3d %3d %-4.4s" , ssidmax , copy_essid(ssid, ssidmax, idp, idlen) , ssid , ether_ntoa((const struct ether_addr *) sr->isr_bssid) , ieee80211_mhz2ieee(sr->isr_freq, sr->isr_flags) , getmaxrate(sr->isr_rates, sr->isr_nrates) , (sr->isr_rssi/2)+sr->isr_noise, sr->isr_noise , sr->isr_intval , getcaps(sr->isr_capinfo) ); printies(vp + sr->isr_ssid_len + sr->isr_meshid_len, sr->isr_ie_len, 24); printf("\n"); cp += sr->isr_len, len -= sr->isr_len; } while (len >= sizeof(struct ieee80211req_scan_result)); } static void scan_and_wait(int s) { struct ieee80211_scan_req sr; struct ieee80211req ireq; int sroute; sroute = socket(PF_ROUTE, SOCK_RAW, 0); if (sroute < 0) { perror("socket(PF_ROUTE,SOCK_RAW)"); return; } (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_SCAN_REQ; memset(&sr, 0, sizeof(sr)); sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_BGSCAN | IEEE80211_IOC_SCAN_NOPICK | IEEE80211_IOC_SCAN_ONCE; sr.sr_duration = IEEE80211_IOC_SCAN_FOREVER; sr.sr_nssid = 0; ireq.i_data = &sr; ireq.i_len = sizeof(sr); /* * NB: only root can trigger a scan so ignore errors. Also ignore * possible errors from net80211, even if no new scan could be * started there might still be a valid scan cache. */ if (ioctl(s, SIOCS80211, &ireq) == 0) { char buf[2048]; struct if_announcemsghdr *ifan; struct rt_msghdr *rtm; do { if (read(sroute, buf, sizeof(buf)) < 0) { perror("read(PF_ROUTE)"); break; } rtm = (struct rt_msghdr *) buf; if (rtm->rtm_version != RTM_VERSION) break; ifan = (struct if_announcemsghdr *) rtm; } while (rtm->rtm_type != RTM_IEEE80211 || ifan->ifan_what != RTM_IEEE80211_SCAN); } close(sroute); } static DECL_CMD_FUNC(set80211scan, val, d) { scan_and_wait(s); list_scan(s); } static enum ieee80211_opmode get80211opmode(int s); static int gettxseq(const struct ieee80211req_sta_info *si) { int i, txseq; if ((si->isi_state & IEEE80211_NODE_QOS) == 0) return si->isi_txseqs[0]; /* XXX not right but usually what folks want */ txseq = 0; for (i = 0; i < IEEE80211_TID_SIZE; i++) if (si->isi_txseqs[i] > txseq) txseq = si->isi_txseqs[i]; return txseq; } static int getrxseq(const struct ieee80211req_sta_info *si) { int i, rxseq; if ((si->isi_state & IEEE80211_NODE_QOS) == 0) return si->isi_rxseqs[0]; /* XXX not right but usually what folks want */ rxseq = 0; for (i = 0; i < IEEE80211_TID_SIZE; i++) if (si->isi_rxseqs[i] > rxseq) rxseq = si->isi_rxseqs[i]; return rxseq; } static void list_stations(int s) { union { struct ieee80211req_sta_req req; uint8_t buf[24*1024]; } u; enum ieee80211_opmode opmode = get80211opmode(s); const uint8_t *cp; int len; /* broadcast address =>'s get all stations */ (void) memset(u.req.is_u.macaddr, 0xff, IEEE80211_ADDR_LEN); if (opmode == IEEE80211_M_STA) { /* * Get information about the associated AP. */ (void) get80211(s, IEEE80211_IOC_BSSID, u.req.is_u.macaddr, IEEE80211_ADDR_LEN); } if (get80211len(s, IEEE80211_IOC_STA_INFO, &u, sizeof(u), &len) < 0) errx(1, "unable to get station information"); if (len < sizeof(struct ieee80211req_sta_info)) return; getchaninfo(s); if (opmode == IEEE80211_M_MBSS) printf("%-17.17s %4s %5s %5s %7s %4s %4s %4s %6s %6s\n" , "ADDR" , "CHAN" , "LOCAL" , "PEER" , "STATE" , "RATE" , "RSSI" , "IDLE" , "TXSEQ" , "RXSEQ" ); else printf("%-17.17s %4s %4s %4s %4s %4s %6s %6s %4s %-7s\n" , "ADDR" , "AID" , "CHAN" , "RATE" , "RSSI" , "IDLE" , "TXSEQ" , "RXSEQ" , "CAPS" , "FLAG" ); cp = (const uint8_t *) u.req.info; do { const struct ieee80211req_sta_info *si; si = (const struct ieee80211req_sta_info *) cp; if (si->isi_len < sizeof(*si)) break; if (opmode == IEEE80211_M_MBSS) printf("%s %4d %5x %5x %7.7s %3dM %4.1f %4d %6d %6d" , ether_ntoa((const struct ether_addr*) si->isi_macaddr) , ieee80211_mhz2ieee(si->isi_freq, si->isi_flags) , si->isi_localid , si->isi_peerid , mesh_linkstate_string(si->isi_peerstate) , si->isi_txmbps/2 , si->isi_rssi/2. , si->isi_inact , gettxseq(si) , getrxseq(si) ); else printf("%s %4u %4d %3dM %4.1f %4d %6d %6d %-4.4s %-7.7s" , ether_ntoa((const struct ether_addr*) si->isi_macaddr) , IEEE80211_AID(si->isi_associd) , ieee80211_mhz2ieee(si->isi_freq, si->isi_flags) , si->isi_txmbps/2 , si->isi_rssi/2. , si->isi_inact , gettxseq(si) , getrxseq(si) , getcaps(si->isi_capinfo) , getflags(si->isi_state) ); printies(cp + si->isi_ie_off, si->isi_ie_len, 24); printmimo(&si->isi_mimo); printf("\n"); cp += si->isi_len, len -= si->isi_len; } while (len >= sizeof(struct ieee80211req_sta_info)); } static const char * mesh_linkstate_string(uint8_t state) { #define N(a) (sizeof(a) / sizeof(a[0])) static const char *state_names[] = { [0] = "IDLE", [1] = "OPEN-TX", [2] = "OPEN-RX", [3] = "CONF-RX", [4] = "ESTAB", [5] = "HOLDING", }; if (state >= N(state_names)) { static char buf[10]; snprintf(buf, sizeof(buf), "#%u", state); return buf; } else return state_names[state]; #undef N } static const char * get_chaninfo(const struct ieee80211_channel *c, int precise, char buf[], size_t bsize) { buf[0] = '\0'; if (IEEE80211_IS_CHAN_FHSS(c)) strlcat(buf, " FHSS", bsize); if (IEEE80211_IS_CHAN_A(c)) strlcat(buf, " 11a", bsize); else if (IEEE80211_IS_CHAN_ANYG(c)) strlcat(buf, " 11g", bsize); else if (IEEE80211_IS_CHAN_B(c)) strlcat(buf, " 11b", bsize); if (IEEE80211_IS_CHAN_HALF(c)) strlcat(buf, "/10MHz", bsize); if (IEEE80211_IS_CHAN_QUARTER(c)) strlcat(buf, "/5MHz", bsize); if (IEEE80211_IS_CHAN_TURBO(c)) strlcat(buf, " Turbo", bsize); if (precise) { if (IEEE80211_IS_CHAN_HT20(c)) strlcat(buf, " ht/20", bsize); else if (IEEE80211_IS_CHAN_HT40D(c)) strlcat(buf, " ht/40-", bsize); else if (IEEE80211_IS_CHAN_HT40U(c)) strlcat(buf, " ht/40+", bsize); } else { if (IEEE80211_IS_CHAN_HT(c)) strlcat(buf, " ht", bsize); } return buf; } static void print_chaninfo(const struct ieee80211_channel *c, int verb) { char buf[14]; if (verb) printf("Channel %3u : %u%c%c%c%c%c MHz%-14.14s", ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq, IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ', IEEE80211_IS_CHAN_DFS(c) ? 'D' : ' ', IEEE80211_IS_CHAN_RADAR(c) ? 'R' : ' ', IEEE80211_IS_CHAN_CWINT(c) ? 'I' : ' ', IEEE80211_IS_CHAN_CACDONE(c) ? 'C' : ' ', get_chaninfo(c, verb, buf, sizeof(buf))); else printf("Channel %3u : %u%c MHz%-14.14s", ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq, IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ', get_chaninfo(c, verb, buf, sizeof(buf))); } static int chanpref(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_HT40(c)) return 40; if (IEEE80211_IS_CHAN_HT20(c)) return 30; if (IEEE80211_IS_CHAN_HALF(c)) return 10; if (IEEE80211_IS_CHAN_QUARTER(c)) return 5; if (IEEE80211_IS_CHAN_TURBO(c)) return 25; if (IEEE80211_IS_CHAN_A(c)) return 20; if (IEEE80211_IS_CHAN_G(c)) return 20; if (IEEE80211_IS_CHAN_B(c)) return 15; if (IEEE80211_IS_CHAN_PUREG(c)) return 15; return 0; } static void print_channels(int s, const struct ieee80211req_chaninfo *chans, int allchans, int verb) { struct ieee80211req_chaninfo *achans; uint8_t reported[IEEE80211_CHAN_BYTES]; const struct ieee80211_channel *c; int i, half; achans = malloc(IEEE80211_CHANINFO_SPACE(chans)); if (achans == NULL) errx(1, "no space for active channel list"); achans->ic_nchans = 0; memset(reported, 0, sizeof(reported)); if (!allchans) { struct ieee80211req_chanlist active; if (get80211(s, IEEE80211_IOC_CHANLIST, &active, sizeof(active)) < 0) errx(1, "unable to get active channel list"); for (i = 0; i < chans->ic_nchans; i++) { c = &chans->ic_chans[i]; if (!isset(active.ic_channels, c->ic_ieee)) continue; /* * Suppress compatible duplicates unless * verbose. The kernel gives us it's * complete channel list which has separate * entries for 11g/11b and 11a/turbo. */ if (isset(reported, c->ic_ieee) && !verb) { /* XXX we assume duplicates are adjacent */ achans->ic_chans[achans->ic_nchans-1] = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } } else { for (i = 0; i < chans->ic_nchans; i++) { c = &chans->ic_chans[i]; /* suppress duplicates as above */ if (isset(reported, c->ic_ieee) && !verb) { /* XXX we assume duplicates are adjacent */ struct ieee80211_channel *a = &achans->ic_chans[achans->ic_nchans-1]; if (chanpref(c) > chanpref(a)) *a = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } } half = achans->ic_nchans / 2; if (achans->ic_nchans % 2) half++; for (i = 0; i < achans->ic_nchans / 2; i++) { print_chaninfo(&achans->ic_chans[i], verb); print_chaninfo(&achans->ic_chans[half+i], verb); printf("\n"); } if (achans->ic_nchans % 2) { print_chaninfo(&achans->ic_chans[i], verb); printf("\n"); } free(achans); } static void list_channels(int s, int allchans) { getchaninfo(s); print_channels(s, chaninfo, allchans, verbose); } static void print_txpow(const struct ieee80211_channel *c) { printf("Channel %3u : %u MHz %3.1f reg %2d ", c->ic_ieee, c->ic_freq, c->ic_maxpower/2., c->ic_maxregpower); } static void print_txpow_verbose(const struct ieee80211_channel *c) { print_chaninfo(c, 1); printf("min %4.1f dBm max %3.1f dBm reg %2d dBm", c->ic_minpower/2., c->ic_maxpower/2., c->ic_maxregpower); /* indicate where regulatory cap limits power use */ if (c->ic_maxpower > 2*c->ic_maxregpower) printf(" <"); } static void list_txpow(int s) { struct ieee80211req_chaninfo *achans; uint8_t reported[IEEE80211_CHAN_BYTES]; struct ieee80211_channel *c, *prev; int i, half; getchaninfo(s); achans = malloc(IEEE80211_CHANINFO_SPACE(chaninfo)); if (achans == NULL) errx(1, "no space for active channel list"); achans->ic_nchans = 0; memset(reported, 0, sizeof(reported)); for (i = 0; i < chaninfo->ic_nchans; i++) { c = &chaninfo->ic_chans[i]; /* suppress duplicates as above */ if (isset(reported, c->ic_ieee) && !verbose) { /* XXX we assume duplicates are adjacent */ prev = &achans->ic_chans[achans->ic_nchans-1]; /* display highest power on channel */ if (c->ic_maxpower > prev->ic_maxpower) *prev = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } if (!verbose) { half = achans->ic_nchans / 2; if (achans->ic_nchans % 2) half++; for (i = 0; i < achans->ic_nchans / 2; i++) { print_txpow(&achans->ic_chans[i]); print_txpow(&achans->ic_chans[half+i]); printf("\n"); } if (achans->ic_nchans % 2) { print_txpow(&achans->ic_chans[i]); printf("\n"); } } else { for (i = 0; i < achans->ic_nchans; i++) { print_txpow_verbose(&achans->ic_chans[i]); printf("\n"); } } free(achans); } static void list_keys(int s) { } #define IEEE80211_C_BITS \ "\20\1STA\002803ENCAP\7FF\10TURBOP\11IBSS\12PMGT" \ "\13HOSTAP\14AHDEMO\15SWRETRY\16TXPMGT\17SHSLOT\20SHPREAMBLE" \ "\21MONITOR\22DFS\23MBSS\30WPA1\31WPA2\32BURST\33WME\34WDS\36BGSCAN" \ "\37TXFRAG\40TDMA" static void list_capabilities(int s) { struct ieee80211_devcaps_req *dc; if (verbose) dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN)); else dc = malloc(IEEE80211_DEVCAPS_SIZE(1)); if (dc == NULL) errx(1, "no space for device capabilities"); dc->dc_chaninfo.ic_nchans = verbose ? MAXCHAN : 1; getdevcaps(s, dc); printb("drivercaps", dc->dc_drivercaps, IEEE80211_C_BITS); if (dc->dc_cryptocaps != 0 || verbose) { putchar('\n'); printb("cryptocaps", dc->dc_cryptocaps, IEEE80211_CRYPTO_BITS); } if (dc->dc_htcaps != 0 || verbose) { putchar('\n'); printb("htcaps", dc->dc_htcaps, IEEE80211_HTCAP_BITS); } putchar('\n'); if (verbose) { chaninfo = &dc->dc_chaninfo; /* XXX */ print_channels(s, &dc->dc_chaninfo, 1/*allchans*/, verbose); } free(dc); } static int get80211wme(int s, int param, int ac, int *val) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = param; ireq.i_len = ac; if (ioctl(s, SIOCG80211, &ireq) < 0) { warn("cannot get WME parameter %d, ac %d%s", param, ac & IEEE80211_WMEPARAM_VAL, ac & IEEE80211_WMEPARAM_BSS ? " (BSS)" : ""); return -1; } *val = ireq.i_val; return 0; } static void list_wme_aci(int s, const char *tag, int ac) { int val; printf("\t%s", tag); /* show WME BSS parameters */ if (get80211wme(s, IEEE80211_IOC_WME_CWMIN, ac, &val) != -1) printf(" cwmin %2u", val); if (get80211wme(s, IEEE80211_IOC_WME_CWMAX, ac, &val) != -1) printf(" cwmax %2u", val); if (get80211wme(s, IEEE80211_IOC_WME_AIFS, ac, &val) != -1) printf(" aifs %2u", val); if (get80211wme(s, IEEE80211_IOC_WME_TXOPLIMIT, ac, &val) != -1) printf(" txopLimit %3u", val); if (get80211wme(s, IEEE80211_IOC_WME_ACM, ac, &val) != -1) { if (val) printf(" acm"); else if (verbose) printf(" -acm"); } /* !BSS only */ if ((ac & IEEE80211_WMEPARAM_BSS) == 0) { if (get80211wme(s, IEEE80211_IOC_WME_ACKPOLICY, ac, &val) != -1) { if (!val) printf(" -ack"); else if (verbose) printf(" ack"); } } printf("\n"); } static void list_wme(int s) { static const char *acnames[] = { "AC_BE", "AC_BK", "AC_VI", "AC_VO" }; int ac; if (verbose) { /* display both BSS and local settings */ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) { again: if (ac & IEEE80211_WMEPARAM_BSS) list_wme_aci(s, " ", ac); else list_wme_aci(s, acnames[ac], ac); if ((ac & IEEE80211_WMEPARAM_BSS) == 0) { ac |= IEEE80211_WMEPARAM_BSS; goto again; } else ac &= ~IEEE80211_WMEPARAM_BSS; } } else { /* display only channel settings */ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) list_wme_aci(s, acnames[ac], ac); } } static void list_roam(int s) { const struct ieee80211_roamparam *rp; int mode; getroam(s); for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) { rp = &roamparams.params[mode]; if (rp->rssi == 0 && rp->rate == 0) continue; if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) { if (rp->rssi & 1) LINE_CHECK("roam:%-7.7s rssi %2u.5dBm MCS %2u ", modename[mode], rp->rssi/2, rp->rate &~ IEEE80211_RATE_MCS); else LINE_CHECK("roam:%-7.7s rssi %4udBm MCS %2u ", modename[mode], rp->rssi/2, rp->rate &~ IEEE80211_RATE_MCS); } else { if (rp->rssi & 1) LINE_CHECK("roam:%-7.7s rssi %2u.5dBm rate %2u Mb/s", modename[mode], rp->rssi/2, rp->rate/2); else LINE_CHECK("roam:%-7.7s rssi %4udBm rate %2u Mb/s", modename[mode], rp->rssi/2, rp->rate/2); } } } static void list_txparams(int s) { const struct ieee80211_txparam *tp; int mode; gettxparams(s); for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) { tp = &txparams.params[mode]; if (tp->mgmtrate == 0 && tp->mcastrate == 0) continue; if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) { if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) LINE_CHECK("%-7.7s ucast NONE mgmt %2u MCS " "mcast %2u MCS maxretry %u", modename[mode], tp->mgmtrate &~ IEEE80211_RATE_MCS, tp->mcastrate &~ IEEE80211_RATE_MCS, tp->maxretry); else LINE_CHECK("%-7.7s ucast %2u MCS mgmt %2u MCS " "mcast %2u MCS maxretry %u", modename[mode], tp->ucastrate &~ IEEE80211_RATE_MCS, tp->mgmtrate &~ IEEE80211_RATE_MCS, tp->mcastrate &~ IEEE80211_RATE_MCS, tp->maxretry); } else { if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) LINE_CHECK("%-7.7s ucast NONE mgmt %2u Mb/s " "mcast %2u Mb/s maxretry %u", modename[mode], tp->mgmtrate/2, tp->mcastrate/2, tp->maxretry); else LINE_CHECK("%-7.7s ucast %2u Mb/s mgmt %2u Mb/s " "mcast %2u Mb/s maxretry %u", modename[mode], tp->ucastrate/2, tp->mgmtrate/2, tp->mcastrate/2, tp->maxretry); } } } static void printpolicy(int policy) { switch (policy) { case IEEE80211_MACCMD_POLICY_OPEN: printf("policy: open\n"); break; case IEEE80211_MACCMD_POLICY_ALLOW: printf("policy: allow\n"); break; case IEEE80211_MACCMD_POLICY_DENY: printf("policy: deny\n"); break; case IEEE80211_MACCMD_POLICY_RADIUS: printf("policy: radius\n"); break; default: printf("policy: unknown (%u)\n", policy); break; } } static void list_mac(int s) { struct ieee80211req ireq; struct ieee80211req_maclist *acllist; int i, nacls, policy, len; uint8_t *data; char c; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); /* XXX ?? */ ireq.i_type = IEEE80211_IOC_MACCMD; ireq.i_val = IEEE80211_MACCMD_POLICY; if (ioctl(s, SIOCG80211, &ireq) < 0) { if (errno == EINVAL) { printf("No acl policy loaded\n"); return; } err(1, "unable to get mac policy"); } policy = ireq.i_val; if (policy == IEEE80211_MACCMD_POLICY_OPEN) { c = '*'; } else if (policy == IEEE80211_MACCMD_POLICY_ALLOW) { c = '+'; } else if (policy == IEEE80211_MACCMD_POLICY_DENY) { c = '-'; } else if (policy == IEEE80211_MACCMD_POLICY_RADIUS) { c = 'r'; /* NB: should never have entries */ } else { printf("policy: unknown (%u)\n", policy); c = '?'; } if (verbose || c == '?') printpolicy(policy); ireq.i_val = IEEE80211_MACCMD_LIST; ireq.i_len = 0; if (ioctl(s, SIOCG80211, &ireq) < 0) err(1, "unable to get mac acl list size"); if (ireq.i_len == 0) { /* NB: no acls */ if (!(verbose || c == '?')) printpolicy(policy); return; } len = ireq.i_len; data = malloc(len); if (data == NULL) err(1, "out of memory for acl list"); ireq.i_data = data; if (ioctl(s, SIOCG80211, &ireq) < 0) err(1, "unable to get mac acl list"); nacls = len / sizeof(*acllist); acllist = (struct ieee80211req_maclist *) data; for (i = 0; i < nacls; i++) printf("%c%s\n", c, ether_ntoa( (const struct ether_addr *) acllist[i].ml_macaddr)); free(data); } static void print_regdomain(const struct ieee80211_regdomain *reg, int verb) { if ((reg->regdomain != 0 && reg->regdomain != reg->country) || verb) { const struct regdomain *rd = lib80211_regdomain_findbysku(getregdata(), reg->regdomain); if (rd == NULL) LINE_CHECK("regdomain %d", reg->regdomain); else LINE_CHECK("regdomain %s", rd->name); } if (reg->country != 0 || verb) { const struct country *cc = lib80211_country_findbycc(getregdata(), reg->country); if (cc == NULL) LINE_CHECK("country %d", reg->country); else LINE_CHECK("country %s", cc->isoname); } if (reg->location == 'I') LINE_CHECK("indoor"); else if (reg->location == 'O') LINE_CHECK("outdoor"); else if (verb) LINE_CHECK("anywhere"); if (reg->ecm) LINE_CHECK("ecm"); else if (verb) LINE_CHECK("-ecm"); } static void list_regdomain(int s, int channelsalso) { getregdomain(s); if (channelsalso) { getchaninfo(s); spacer = ':'; print_regdomain(®domain, 1); LINE_BREAK(); print_channels(s, chaninfo, 1/*allchans*/, 1/*verbose*/); } else print_regdomain(®domain, verbose); } static void list_mesh(int s) { struct ieee80211req ireq; struct ieee80211req_mesh_route routes[128]; struct ieee80211req_mesh_route *rt; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_MESH_RTCMD; ireq.i_val = IEEE80211_MESH_RTCMD_LIST; ireq.i_data = &routes; ireq.i_len = sizeof(routes); if (ioctl(s, SIOCG80211, &ireq) < 0) err(1, "unable to get the Mesh routing table"); printf("%-17.17s %-17.17s %4s %4s %4s %6s %s\n" , "DEST" , "NEXT HOP" , "HOPS" , "METRIC" , "LIFETIME" , "MSEQ" , "FLAGS"); for (rt = &routes[0]; rt - &routes[0] < ireq.i_len / sizeof(*rt); rt++){ printf("%s ", ether_ntoa((const struct ether_addr *)rt->imr_dest)); printf("%s %4u %4u %6u %6u %c%c\n", ether_ntoa((const struct ether_addr *)rt->imr_nexthop), rt->imr_nhops, rt->imr_metric, rt->imr_lifetime, rt->imr_lastmseq, (rt->imr_flags & IEEE80211_MESHRT_FLAGS_VALID) ? 'V' : '!', (rt->imr_flags & IEEE80211_MESHRT_FLAGS_PROXY) ? 'P' : ' '); } } static DECL_CMD_FUNC(set80211list, arg, d) { #define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0) LINE_INIT('\t'); if (iseq(arg, "sta")) list_stations(s); else if (iseq(arg, "scan") || iseq(arg, "ap")) list_scan(s); else if (iseq(arg, "chan") || iseq(arg, "freq")) list_channels(s, 1); else if (iseq(arg, "active")) list_channels(s, 0); else if (iseq(arg, "keys")) list_keys(s); else if (iseq(arg, "caps")) list_capabilities(s); else if (iseq(arg, "wme") || iseq(arg, "wmm")) list_wme(s); else if (iseq(arg, "mac")) list_mac(s); else if (iseq(arg, "txpow")) list_txpow(s); else if (iseq(arg, "roam")) list_roam(s); else if (iseq(arg, "txparam") || iseq(arg, "txparm")) list_txparams(s); else if (iseq(arg, "regdomain")) list_regdomain(s, 1); else if (iseq(arg, "countries")) list_countries(); else if (iseq(arg, "mesh")) list_mesh(s); else errx(1, "Don't know how to list %s for %s", arg, name); LINE_BREAK(); #undef iseq } static enum ieee80211_opmode get80211opmode(int s) { struct ifmediareq ifmr; (void) memset(&ifmr, 0, sizeof(ifmr)); (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name)); if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0) { if (ifmr.ifm_current & IFM_IEEE80211_ADHOC) { if (ifmr.ifm_current & IFM_FLAG0) return IEEE80211_M_AHDEMO; else return IEEE80211_M_IBSS; } if (ifmr.ifm_current & IFM_IEEE80211_HOSTAP) return IEEE80211_M_HOSTAP; if (ifmr.ifm_current & IFM_IEEE80211_MONITOR) return IEEE80211_M_MONITOR; if (ifmr.ifm_current & IFM_IEEE80211_MBSS) return IEEE80211_M_MBSS; } return IEEE80211_M_STA; } #if 0 static void printcipher(int s, struct ieee80211req *ireq, int keylenop) { switch (ireq->i_val) { case IEEE80211_CIPHER_WEP: ireq->i_type = keylenop; if (ioctl(s, SIOCG80211, ireq) != -1) printf("WEP-%s", ireq->i_len <= 5 ? "40" : ireq->i_len <= 13 ? "104" : "128"); else printf("WEP"); break; case IEEE80211_CIPHER_TKIP: printf("TKIP"); break; case IEEE80211_CIPHER_AES_OCB: printf("AES-OCB"); break; case IEEE80211_CIPHER_AES_CCM: printf("AES-CCM"); break; case IEEE80211_CIPHER_CKIP: printf("CKIP"); break; case IEEE80211_CIPHER_NONE: printf("NONE"); break; default: printf("UNKNOWN (0x%x)", ireq->i_val); break; } } #endif static void printkey(const struct ieee80211req_key *ik) { static const uint8_t zerodata[IEEE80211_KEYBUF_SIZE]; int keylen = ik->ik_keylen; int printcontents; printcontents = printkeys && (memcmp(ik->ik_keydata, zerodata, keylen) != 0 || verbose); if (printcontents) LINE_BREAK(); switch (ik->ik_type) { case IEEE80211_CIPHER_WEP: /* compatibility */ LINE_CHECK("wepkey %u:%s", ik->ik_keyix+1, keylen <= 5 ? "40-bit" : keylen <= 13 ? "104-bit" : "128-bit"); break; case IEEE80211_CIPHER_TKIP: if (keylen > 128/8) keylen -= 128/8; /* ignore MIC for now */ LINE_CHECK("TKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_AES_OCB: LINE_CHECK("AES-OCB %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_AES_CCM: LINE_CHECK("AES-CCM %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_CKIP: LINE_CHECK("CKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_NONE: LINE_CHECK("NULL %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; default: LINE_CHECK("UNKNOWN (0x%x) %u:%u-bit", ik->ik_type, ik->ik_keyix+1, 8*keylen); break; } if (printcontents) { int i; printf(" <"); for (i = 0; i < keylen; i++) printf("%02x", ik->ik_keydata[i]); printf(">"); if (ik->ik_type != IEEE80211_CIPHER_WEP && (ik->ik_keyrsc != 0 || verbose)) printf(" rsc %ju", (uintmax_t)ik->ik_keyrsc); if (ik->ik_type != IEEE80211_CIPHER_WEP && (ik->ik_keytsc != 0 || verbose)) printf(" tsc %ju", (uintmax_t)ik->ik_keytsc); if (ik->ik_flags != 0 && verbose) { const char *sep = " "; if (ik->ik_flags & IEEE80211_KEY_XMIT) printf("%stx", sep), sep = "+"; if (ik->ik_flags & IEEE80211_KEY_RECV) printf("%srx", sep), sep = "+"; if (ik->ik_flags & IEEE80211_KEY_DEFAULT) printf("%sdef", sep), sep = "+"; } LINE_BREAK(); } } static void printrate(const char *tag, int v, int defrate, int defmcs) { if ((v & IEEE80211_RATE_MCS) == 0) { if (v != defrate) { if (v & 1) LINE_CHECK("%s %d.5", tag, v/2); else LINE_CHECK("%s %d", tag, v/2); } } else { if (v != defmcs) LINE_CHECK("%s %d", tag, v &~ 0x80); } } static int getid(int s, int ix, void *data, size_t len, int *plen, int mesh) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = (!mesh) ? IEEE80211_IOC_SSID : IEEE80211_IOC_MESH_ID; ireq.i_val = ix; ireq.i_data = data; ireq.i_len = len; if (ioctl(s, SIOCG80211, &ireq) < 0) return -1; *plen = ireq.i_len; return 0; } static void ieee80211_status(int s) { static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; enum ieee80211_opmode opmode = get80211opmode(s); int i, num, wpa, wme, bgscan, bgscaninterval, val, len, wepmode; uint8_t data[32]; const struct ieee80211_channel *c; const struct ieee80211_roamparam *rp; const struct ieee80211_txparam *tp; if (getid(s, -1, data, sizeof(data), &len, 0) < 0) { /* If we can't get the SSID, this isn't an 802.11 device. */ return; } /* * Invalidate cached state so printing status for multiple * if's doesn't reuse the first interfaces' cached state. */ gotcurchan = 0; gotroam = 0; gottxparams = 0; gothtconf = 0; gotregdomain = 0; printf("\t"); if (opmode == IEEE80211_M_MBSS) { printf("meshid "); getid(s, 0, data, sizeof(data), &len, 1); print_string(data, len); } else { if (get80211val(s, IEEE80211_IOC_NUMSSIDS, &num) < 0) num = 0; printf("ssid "); if (num > 1) { for (i = 0; i < num; i++) { if (getid(s, i, data, sizeof(data), &len, 0) >= 0 && len > 0) { printf(" %d:", i + 1); print_string(data, len); } } } else print_string(data, len); } c = getcurchan(s); if (c->ic_freq != IEEE80211_CHAN_ANY) { char buf[14]; printf(" channel %d (%u MHz%s)", c->ic_ieee, c->ic_freq, get_chaninfo(c, 1, buf, sizeof(buf))); } else if (verbose) printf(" channel UNDEF"); if (get80211(s, IEEE80211_IOC_BSSID, data, IEEE80211_ADDR_LEN) >= 0 && (memcmp(data, zerobssid, sizeof(zerobssid)) != 0 || verbose)) printf(" bssid %s", ether_ntoa((struct ether_addr *)data)); if (get80211len(s, IEEE80211_IOC_STATIONNAME, data, sizeof(data), &len) != -1) { printf("\n\tstationname "); print_string(data, len); } spacer = ' '; /* force first break */ LINE_BREAK(); list_regdomain(s, 0); wpa = 0; if (get80211val(s, IEEE80211_IOC_AUTHMODE, &val) != -1) { switch (val) { case IEEE80211_AUTH_NONE: LINE_CHECK("authmode NONE"); break; case IEEE80211_AUTH_OPEN: LINE_CHECK("authmode OPEN"); break; case IEEE80211_AUTH_SHARED: LINE_CHECK("authmode SHARED"); break; case IEEE80211_AUTH_8021X: LINE_CHECK("authmode 802.1x"); break; case IEEE80211_AUTH_WPA: if (get80211val(s, IEEE80211_IOC_WPA, &wpa) < 0) wpa = 1; /* default to WPA1 */ switch (wpa) { case 2: LINE_CHECK("authmode WPA2/802.11i"); break; case 3: LINE_CHECK("authmode WPA1+WPA2/802.11i"); break; default: LINE_CHECK("authmode WPA"); break; } break; case IEEE80211_AUTH_AUTO: LINE_CHECK("authmode AUTO"); break; default: LINE_CHECK("authmode UNKNOWN (0x%x)", val); break; } } if (wpa || verbose) { if (get80211val(s, IEEE80211_IOC_WPS, &val) != -1) { if (val) LINE_CHECK("wps"); else if (verbose) LINE_CHECK("-wps"); } if (get80211val(s, IEEE80211_IOC_TSN, &val) != -1) { if (val) LINE_CHECK("tsn"); else if (verbose) LINE_CHECK("-tsn"); } if (ioctl(s, IEEE80211_IOC_COUNTERMEASURES, &val) != -1) { if (val) LINE_CHECK("countermeasures"); else if (verbose) LINE_CHECK("-countermeasures"); } #if 0 /* XXX not interesting with WPA done in user space */ ireq.i_type = IEEE80211_IOC_KEYMGTALGS; if (ioctl(s, SIOCG80211, &ireq) != -1) { } ireq.i_type = IEEE80211_IOC_MCASTCIPHER; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("mcastcipher "); printcipher(s, &ireq, IEEE80211_IOC_MCASTKEYLEN); spacer = ' '; } ireq.i_type = IEEE80211_IOC_UCASTCIPHER; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("ucastcipher "); printcipher(s, &ireq, IEEE80211_IOC_UCASTKEYLEN); } if (wpa & 2) { ireq.i_type = IEEE80211_IOC_RSNCAPS; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("RSN caps 0x%x", ireq.i_val); spacer = ' '; } } ireq.i_type = IEEE80211_IOC_UCASTCIPHERS; if (ioctl(s, SIOCG80211, &ireq) != -1) { } #endif } if (get80211val(s, IEEE80211_IOC_WEP, &wepmode) != -1 && wepmode != IEEE80211_WEP_NOSUP) { int firstkey; switch (wepmode) { case IEEE80211_WEP_OFF: LINE_CHECK("privacy OFF"); break; case IEEE80211_WEP_ON: LINE_CHECK("privacy ON"); break; case IEEE80211_WEP_MIXED: LINE_CHECK("privacy MIXED"); break; default: LINE_CHECK("privacy UNKNOWN (0x%x)", wepmode); break; } /* * If we get here then we've got WEP support so we need * to print WEP status. */ if (get80211val(s, IEEE80211_IOC_WEPTXKEY, &val) < 0) { warn("WEP support, but no tx key!"); goto end; } if (val != -1) LINE_CHECK("deftxkey %d", val+1); else if (wepmode != IEEE80211_WEP_OFF || verbose) LINE_CHECK("deftxkey UNDEF"); if (get80211val(s, IEEE80211_IOC_NUMWEPKEYS, &num) < 0) { warn("WEP support, but no NUMWEPKEYS support!"); goto end; } firstkey = 1; for (i = 0; i < num; i++) { struct ieee80211req_key ik; memset(&ik, 0, sizeof(ik)); ik.ik_keyix = i; if (get80211(s, IEEE80211_IOC_WPAKEY, &ik, sizeof(ik)) < 0) { warn("WEP support, but can get keys!"); goto end; } if (ik.ik_keylen != 0) { if (verbose) LINE_BREAK(); printkey(&ik); firstkey = 0; } } end: ; } if (get80211val(s, IEEE80211_IOC_POWERSAVE, &val) != -1 && val != IEEE80211_POWERSAVE_NOSUP ) { if (val != IEEE80211_POWERSAVE_OFF || verbose) { switch (val) { case IEEE80211_POWERSAVE_OFF: LINE_CHECK("powersavemode OFF"); break; case IEEE80211_POWERSAVE_CAM: LINE_CHECK("powersavemode CAM"); break; case IEEE80211_POWERSAVE_PSP: LINE_CHECK("powersavemode PSP"); break; case IEEE80211_POWERSAVE_PSP_CAM: LINE_CHECK("powersavemode PSP-CAM"); break; } if (get80211val(s, IEEE80211_IOC_POWERSAVESLEEP, &val) != -1) LINE_CHECK("powersavesleep %d", val); } } if (get80211val(s, IEEE80211_IOC_TXPOWER, &val) != -1) { if (val & 1) LINE_CHECK("txpower %d.5", val/2); else LINE_CHECK("txpower %d", val/2); } if (verbose) { if (get80211val(s, IEEE80211_IOC_TXPOWMAX, &val) != -1) LINE_CHECK("txpowmax %.1f", val/2.); } if (get80211val(s, IEEE80211_IOC_DOTD, &val) != -1) { if (val) LINE_CHECK("dotd"); else if (verbose) LINE_CHECK("-dotd"); } if (get80211val(s, IEEE80211_IOC_RTSTHRESHOLD, &val) != -1) { if (val != IEEE80211_RTS_MAX || verbose) LINE_CHECK("rtsthreshold %d", val); } if (get80211val(s, IEEE80211_IOC_FRAGTHRESHOLD, &val) != -1) { if (val != IEEE80211_FRAG_MAX || verbose) LINE_CHECK("fragthreshold %d", val); } if (opmode == IEEE80211_M_STA || verbose) { if (get80211val(s, IEEE80211_IOC_BMISSTHRESHOLD, &val) != -1) { if (val != IEEE80211_HWBMISS_MAX || verbose) LINE_CHECK("bmiss %d", val); } } if (!verbose) { gettxparams(s); tp = &txparams.params[chan2mode(c)]; printrate("ucastrate", tp->ucastrate, IEEE80211_FIXED_RATE_NONE, IEEE80211_FIXED_RATE_NONE); printrate("mcastrate", tp->mcastrate, 2*1, IEEE80211_RATE_MCS|0); printrate("mgmtrate", tp->mgmtrate, 2*1, IEEE80211_RATE_MCS|0); if (tp->maxretry != 6) /* XXX */ LINE_CHECK("maxretry %d", tp->maxretry); } else { LINE_BREAK(); list_txparams(s); } bgscaninterval = -1; (void) get80211val(s, IEEE80211_IOC_BGSCAN_INTERVAL, &bgscaninterval); if (get80211val(s, IEEE80211_IOC_SCANVALID, &val) != -1) { if (val != bgscaninterval || verbose) LINE_CHECK("scanvalid %u", val); } bgscan = 0; if (get80211val(s, IEEE80211_IOC_BGSCAN, &bgscan) != -1) { if (bgscan) LINE_CHECK("bgscan"); else if (verbose) LINE_CHECK("-bgscan"); } if (bgscan || verbose) { if (bgscaninterval != -1) LINE_CHECK("bgscanintvl %u", bgscaninterval); if (get80211val(s, IEEE80211_IOC_BGSCAN_IDLE, &val) != -1) LINE_CHECK("bgscanidle %u", val); if (!verbose) { getroam(s); rp = &roamparams.params[chan2mode(c)]; if (rp->rssi & 1) LINE_CHECK("roam:rssi %u.5", rp->rssi/2); else LINE_CHECK("roam:rssi %u", rp->rssi/2); LINE_CHECK("roam:rate %u", rp->rate/2); } else { LINE_BREAK(); list_roam(s); LINE_BREAK(); } } if (IEEE80211_IS_CHAN_ANYG(c) || verbose) { if (get80211val(s, IEEE80211_IOC_PUREG, &val) != -1) { if (val) LINE_CHECK("pureg"); else if (verbose) LINE_CHECK("-pureg"); } if (get80211val(s, IEEE80211_IOC_PROTMODE, &val) != -1) { switch (val) { case IEEE80211_PROTMODE_OFF: LINE_CHECK("protmode OFF"); break; case IEEE80211_PROTMODE_CTS: LINE_CHECK("protmode CTS"); break; case IEEE80211_PROTMODE_RTSCTS: LINE_CHECK("protmode RTSCTS"); break; default: LINE_CHECK("protmode UNKNOWN (0x%x)", val); break; } } } if (IEEE80211_IS_CHAN_HT(c) || verbose) { gethtconf(s); switch (htconf & 3) { case 0: case 2: LINE_CHECK("-ht"); break; case 1: LINE_CHECK("ht20"); break; case 3: if (verbose) LINE_CHECK("ht"); break; } if (get80211val(s, IEEE80211_IOC_HTCOMPAT, &val) != -1) { if (!val) LINE_CHECK("-htcompat"); else if (verbose) LINE_CHECK("htcompat"); } if (get80211val(s, IEEE80211_IOC_AMPDU, &val) != -1) { switch (val) { case 0: LINE_CHECK("-ampdu"); break; case 1: LINE_CHECK("ampdutx -ampdurx"); break; case 2: LINE_CHECK("-ampdutx ampdurx"); break; case 3: if (verbose) LINE_CHECK("ampdu"); break; } } if (get80211val(s, IEEE80211_IOC_AMPDU_LIMIT, &val) != -1) { switch (val) { case IEEE80211_HTCAP_MAXRXAMPDU_8K: LINE_CHECK("ampdulimit 8k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_16K: LINE_CHECK("ampdulimit 16k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_32K: LINE_CHECK("ampdulimit 32k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_64K: LINE_CHECK("ampdulimit 64k"); break; } } if (get80211val(s, IEEE80211_IOC_AMPDU_DENSITY, &val) != -1) { switch (val) { case IEEE80211_HTCAP_MPDUDENSITY_NA: if (verbose) LINE_CHECK("ampdudensity NA"); break; case IEEE80211_HTCAP_MPDUDENSITY_025: LINE_CHECK("ampdudensity .25"); break; case IEEE80211_HTCAP_MPDUDENSITY_05: LINE_CHECK("ampdudensity .5"); break; case IEEE80211_HTCAP_MPDUDENSITY_1: LINE_CHECK("ampdudensity 1"); break; case IEEE80211_HTCAP_MPDUDENSITY_2: LINE_CHECK("ampdudensity 2"); break; case IEEE80211_HTCAP_MPDUDENSITY_4: LINE_CHECK("ampdudensity 4"); break; case IEEE80211_HTCAP_MPDUDENSITY_8: LINE_CHECK("ampdudensity 8"); break; case IEEE80211_HTCAP_MPDUDENSITY_16: LINE_CHECK("ampdudensity 16"); break; } } if (get80211val(s, IEEE80211_IOC_AMSDU, &val) != -1) { switch (val) { case 0: LINE_CHECK("-amsdu"); break; case 1: LINE_CHECK("amsdutx -amsdurx"); break; case 2: LINE_CHECK("-amsdutx amsdurx"); break; case 3: if (verbose) LINE_CHECK("amsdu"); break; } } /* XXX amsdu limit */ if (get80211val(s, IEEE80211_IOC_SHORTGI, &val) != -1) { if (val) LINE_CHECK("shortgi"); else if (verbose) LINE_CHECK("-shortgi"); } if (get80211val(s, IEEE80211_IOC_HTPROTMODE, &val) != -1) { if (val == IEEE80211_PROTMODE_OFF) LINE_CHECK("htprotmode OFF"); else if (val != IEEE80211_PROTMODE_RTSCTS) LINE_CHECK("htprotmode UNKNOWN (0x%x)", val); else if (verbose) LINE_CHECK("htprotmode RTSCTS"); } if (get80211val(s, IEEE80211_IOC_PUREN, &val) != -1) { if (val) LINE_CHECK("puren"); else if (verbose) LINE_CHECK("-puren"); } if (get80211val(s, IEEE80211_IOC_SMPS, &val) != -1) { if (val == IEEE80211_HTCAP_SMPS_DYNAMIC) LINE_CHECK("smpsdyn"); else if (val == IEEE80211_HTCAP_SMPS_ENA) LINE_CHECK("smps"); else if (verbose) LINE_CHECK("-smps"); } if (get80211val(s, IEEE80211_IOC_RIFS, &val) != -1) { if (val) LINE_CHECK("rifs"); else if (verbose) LINE_CHECK("-rifs"); } } if (get80211val(s, IEEE80211_IOC_WME, &wme) != -1) { if (wme) LINE_CHECK("wme"); else if (verbose) LINE_CHECK("-wme"); } else wme = 0; if (get80211val(s, IEEE80211_IOC_BURST, &val) != -1) { if (val) LINE_CHECK("burst"); else if (verbose) LINE_CHECK("-burst"); } if (get80211val(s, IEEE80211_IOC_FF, &val) != -1) { if (val) LINE_CHECK("ff"); else if (verbose) LINE_CHECK("-ff"); } if (get80211val(s, IEEE80211_IOC_TURBOP, &val) != -1) { if (val) LINE_CHECK("dturbo"); else if (verbose) LINE_CHECK("-dturbo"); } if (get80211val(s, IEEE80211_IOC_DWDS, &val) != -1) { if (val) LINE_CHECK("dwds"); else if (verbose) LINE_CHECK("-dwds"); } if (opmode == IEEE80211_M_HOSTAP) { if (get80211val(s, IEEE80211_IOC_HIDESSID, &val) != -1) { if (val) LINE_CHECK("hidessid"); else if (verbose) LINE_CHECK("-hidessid"); } if (get80211val(s, IEEE80211_IOC_APBRIDGE, &val) != -1) { if (!val) LINE_CHECK("-apbridge"); else if (verbose) LINE_CHECK("apbridge"); } if (get80211val(s, IEEE80211_IOC_DTIM_PERIOD, &val) != -1) LINE_CHECK("dtimperiod %u", val); if (get80211val(s, IEEE80211_IOC_DOTH, &val) != -1) { if (!val) LINE_CHECK("-doth"); else if (verbose) LINE_CHECK("doth"); } if (get80211val(s, IEEE80211_IOC_DFS, &val) != -1) { if (!val) LINE_CHECK("-dfs"); else if (verbose) LINE_CHECK("dfs"); } if (get80211val(s, IEEE80211_IOC_INACTIVITY, &val) != -1) { if (!val) LINE_CHECK("-inact"); else if (verbose) LINE_CHECK("inact"); } } else { if (get80211val(s, IEEE80211_IOC_ROAMING, &val) != -1) { if (val != IEEE80211_ROAMING_AUTO || verbose) { switch (val) { case IEEE80211_ROAMING_DEVICE: LINE_CHECK("roaming DEVICE"); break; case IEEE80211_ROAMING_AUTO: LINE_CHECK("roaming AUTO"); break; case IEEE80211_ROAMING_MANUAL: LINE_CHECK("roaming MANUAL"); break; default: LINE_CHECK("roaming UNKNOWN (0x%x)", val); break; } } } } if (opmode == IEEE80211_M_AHDEMO) { if (get80211val(s, IEEE80211_IOC_TDMA_SLOT, &val) != -1) LINE_CHECK("tdmaslot %u", val); if (get80211val(s, IEEE80211_IOC_TDMA_SLOTCNT, &val) != -1) LINE_CHECK("tdmaslotcnt %u", val); if (get80211val(s, IEEE80211_IOC_TDMA_SLOTLEN, &val) != -1) LINE_CHECK("tdmaslotlen %u", val); if (get80211val(s, IEEE80211_IOC_TDMA_BINTERVAL, &val) != -1) LINE_CHECK("tdmabintval %u", val); } else if (get80211val(s, IEEE80211_IOC_BEACON_INTERVAL, &val) != -1) { /* XXX default define not visible */ if (val != 100 || verbose) LINE_CHECK("bintval %u", val); } if (wme && verbose) { LINE_BREAK(); list_wme(s); } if (opmode == IEEE80211_M_MBSS) { if (get80211val(s, IEEE80211_IOC_MESH_TTL, &val) != -1) { LINE_CHECK("meshttl %u", val); } if (get80211val(s, IEEE80211_IOC_MESH_AP, &val) != -1) { if (val) LINE_CHECK("meshpeering"); else LINE_CHECK("-meshpeering"); } if (get80211val(s, IEEE80211_IOC_MESH_FWRD, &val) != -1) { if (val) LINE_CHECK("meshforward"); else LINE_CHECK("-meshforward"); } if (get80211len(s, IEEE80211_IOC_MESH_PR_METRIC, data, 12, &len) != -1) { data[len] = '\0'; LINE_CHECK("meshmetric %s", data); } if (get80211len(s, IEEE80211_IOC_MESH_PR_PATH, data, 12, &len) != -1) { data[len] = '\0'; LINE_CHECK("meshpath %s", data); } if (get80211val(s, IEEE80211_IOC_HWMP_ROOTMODE, &val) != -1) { switch (val) { case IEEE80211_HWMP_ROOTMODE_DISABLED: LINE_CHECK("hwmprootmode DISABLED"); break; case IEEE80211_HWMP_ROOTMODE_NORMAL: LINE_CHECK("hwmprootmode NORMAL"); break; case IEEE80211_HWMP_ROOTMODE_PROACTIVE: LINE_CHECK("hwmprootmode PROACTIVE"); break; case IEEE80211_HWMP_ROOTMODE_RANN: LINE_CHECK("hwmprootmode RANN"); break; default: LINE_CHECK("hwmprootmode UNKNOWN(%d)", val); break; } } if (get80211val(s, IEEE80211_IOC_HWMP_MAXHOPS, &val) != -1) { LINE_CHECK("hwmpmaxhops %u", val); } } LINE_BREAK(); } static int get80211(int s, int type, void *data, int len) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = type; ireq.i_data = data; ireq.i_len = len; return ioctl(s, SIOCG80211, &ireq); } static int get80211len(int s, int type, void *data, int len, int *plen) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = type; ireq.i_len = len; assert(ireq.i_len == len); /* NB: check for 16-bit truncation */ ireq.i_data = data; if (ioctl(s, SIOCG80211, &ireq) < 0) return -1; *plen = ireq.i_len; return 0; } static int get80211val(int s, int type, int *val) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = type; if (ioctl(s, SIOCG80211, &ireq) < 0) return -1; *val = ireq.i_val; return 0; } static void set80211(int s, int type, int val, int len, void *data) { struct ieee80211req ireq; (void) memset(&ireq, 0, sizeof(ireq)); (void) strncpy(ireq.i_name, name, sizeof(ireq.i_name)); ireq.i_type = type; ireq.i_val = val; ireq.i_len = len; assert(ireq.i_len == len); /* NB: check for 16-bit truncation */ ireq.i_data = data; if (ioctl(s, SIOCS80211, &ireq) < 0) err(1, "SIOCS80211"); } static const char * get_string(const char *val, const char *sep, u_int8_t *buf, int *lenp) { int len; int hexstr; u_int8_t *p; len = *lenp; p = buf; hexstr = (val[0] == '0' && tolower((u_char)val[1]) == 'x'); if (hexstr) val += 2; for (;;) { if (*val == '\0') break; if (sep != NULL && strchr(sep, *val) != NULL) { val++; break; } if (hexstr) { if (!isxdigit((u_char)val[0])) { warnx("bad hexadecimal digits"); return NULL; } if (!isxdigit((u_char)val[1])) { warnx("odd count hexadecimal digits"); return NULL; } } if (p >= buf + len) { if (hexstr) warnx("hexadecimal digits too long"); else warnx("string too long"); return NULL; } if (hexstr) { #define tohex(x) (isdigit(x) ? (x) - '0' : tolower(x) - 'a' + 10) *p++ = (tohex((u_char)val[0]) << 4) | tohex((u_char)val[1]); #undef tohex val += 2; } else *p++ = *val++; } len = p - buf; /* The string "-" is treated as the empty string. */ if (!hexstr && len == 1 && buf[0] == '-') { len = 0; memset(buf, 0, *lenp); } else if (len < *lenp) memset(p, 0, *lenp - len); *lenp = len; return val; } static void print_string(const u_int8_t *buf, int len) { int i; int hasspc; i = 0; hasspc = 0; for (; i < len; i++) { if (!isprint(buf[i]) && buf[i] != '\0') break; if (isspace(buf[i])) hasspc++; } if (i == len) { if (hasspc || len == 0 || buf[0] == '\0') printf("\"%.*s\"", len, buf); else printf("%.*s", len, buf); } else { printf("0x"); for (i = 0; i < len; i++) printf("%02x", buf[i]); } } /* * Virtual AP cloning support. */ static struct ieee80211_clone_params params = { .icp_opmode = IEEE80211_M_STA, /* default to station mode */ }; static void wlan_create(int s, struct ifreq *ifr) { static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; if (params.icp_parent[0] == '\0') errx(1, "must specify a parent device (wlandev) when creating " "a wlan device"); if (params.icp_opmode == IEEE80211_M_WDS && memcmp(params.icp_bssid, zerobssid, sizeof(zerobssid)) == 0) errx(1, "no bssid specified for WDS (use wlanbssid)"); ifr->ifr_data = (caddr_t) ¶ms; if (ioctl(s, SIOCIFCREATE2, ifr) < 0) err(1, "SIOCIFCREATE2"); } static DECL_CMD_FUNC(set80211clone_wlandev, arg, d) { strlcpy(params.icp_parent, arg, IFNAMSIZ); } static DECL_CMD_FUNC(set80211clone_wlanbssid, arg, d) { const struct ether_addr *ea; ea = ether_aton(arg); if (ea == NULL) errx(1, "%s: cannot parse bssid", arg); memcpy(params.icp_bssid, ea->octet, IEEE80211_ADDR_LEN); } static DECL_CMD_FUNC(set80211clone_wlanaddr, arg, d) { const struct ether_addr *ea; ea = ether_aton(arg); if (ea == NULL) errx(1, "%s: cannot parse address", arg); memcpy(params.icp_macaddr, ea->octet, IEEE80211_ADDR_LEN); params.icp_flags |= IEEE80211_CLONE_MACADDR; } static DECL_CMD_FUNC(set80211clone_wlanmode, arg, d) { #define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0) if (iseq(arg, "sta")) params.icp_opmode = IEEE80211_M_STA; else if (iseq(arg, "ahdemo") || iseq(arg, "adhoc-demo")) params.icp_opmode = IEEE80211_M_AHDEMO; else if (iseq(arg, "ibss") || iseq(arg, "adhoc")) params.icp_opmode = IEEE80211_M_IBSS; else if (iseq(arg, "ap") || iseq(arg, "host")) params.icp_opmode = IEEE80211_M_HOSTAP; else if (iseq(arg, "wds")) params.icp_opmode = IEEE80211_M_WDS; else if (iseq(arg, "monitor")) params.icp_opmode = IEEE80211_M_MONITOR; else if (iseq(arg, "tdma")) { params.icp_opmode = IEEE80211_M_AHDEMO; params.icp_flags |= IEEE80211_CLONE_TDMA; } else if (iseq(arg, "mesh") || iseq(arg, "mp")) /* mesh point */ params.icp_opmode = IEEE80211_M_MBSS; else errx(1, "Don't know to create %s for %s", arg, name); #undef iseq } static void set80211clone_beacons(const char *val, int d, int s, const struct afswtch *rafp) { /* NB: inverted sense */ if (d) params.icp_flags &= ~IEEE80211_CLONE_NOBEACONS; else params.icp_flags |= IEEE80211_CLONE_NOBEACONS; } static void set80211clone_bssid(const char *val, int d, int s, const struct afswtch *rafp) { if (d) params.icp_flags |= IEEE80211_CLONE_BSSID; else params.icp_flags &= ~IEEE80211_CLONE_BSSID; } static void set80211clone_wdslegacy(const char *val, int d, int s, const struct afswtch *rafp) { if (d) params.icp_flags |= IEEE80211_CLONE_WDSLEGACY; else params.icp_flags &= ~IEEE80211_CLONE_WDSLEGACY; } static struct cmd ieee80211_cmds[] = { DEF_CMD_ARG("ssid", set80211ssid), DEF_CMD_ARG("nwid", set80211ssid), DEF_CMD_ARG("meshid", set80211meshid), DEF_CMD_ARG("stationname", set80211stationname), DEF_CMD_ARG("station", set80211stationname), /* BSD/OS */ DEF_CMD_ARG("channel", set80211channel), DEF_CMD_ARG("authmode", set80211authmode), DEF_CMD_ARG("powersavemode", set80211powersavemode), DEF_CMD("powersave", 1, set80211powersave), DEF_CMD("-powersave", 0, set80211powersave), DEF_CMD_ARG("powersavesleep", set80211powersavesleep), DEF_CMD_ARG("wepmode", set80211wepmode), DEF_CMD("wep", 1, set80211wep), DEF_CMD("-wep", 0, set80211wep), DEF_CMD_ARG("deftxkey", set80211weptxkey), DEF_CMD_ARG("weptxkey", set80211weptxkey), DEF_CMD_ARG("wepkey", set80211wepkey), DEF_CMD_ARG("nwkey", set80211nwkey), /* NetBSD */ DEF_CMD("-nwkey", 0, set80211wep), /* NetBSD */ DEF_CMD_ARG("rtsthreshold", set80211rtsthreshold), DEF_CMD_ARG("protmode", set80211protmode), DEF_CMD_ARG("txpower", set80211txpower), DEF_CMD_ARG("roaming", set80211roaming), DEF_CMD("wme", 1, set80211wme), DEF_CMD("-wme", 0, set80211wme), DEF_CMD("wmm", 1, set80211wme), DEF_CMD("-wmm", 0, set80211wme), DEF_CMD("hidessid", 1, set80211hidessid), DEF_CMD("-hidessid", 0, set80211hidessid), DEF_CMD("apbridge", 1, set80211apbridge), DEF_CMD("-apbridge", 0, set80211apbridge), DEF_CMD_ARG("chanlist", set80211chanlist), DEF_CMD_ARG("bssid", set80211bssid), DEF_CMD_ARG("ap", set80211bssid), DEF_CMD("scan", 0, set80211scan), DEF_CMD_ARG("list", set80211list), DEF_CMD_ARG2("cwmin", set80211cwmin), DEF_CMD_ARG2("cwmax", set80211cwmax), DEF_CMD_ARG2("aifs", set80211aifs), DEF_CMD_ARG2("txoplimit", set80211txoplimit), DEF_CMD_ARG("acm", set80211acm), DEF_CMD_ARG("-acm", set80211noacm), DEF_CMD_ARG("ack", set80211ackpolicy), DEF_CMD_ARG("-ack", set80211noackpolicy), DEF_CMD_ARG2("bss:cwmin", set80211bsscwmin), DEF_CMD_ARG2("bss:cwmax", set80211bsscwmax), DEF_CMD_ARG2("bss:aifs", set80211bssaifs), DEF_CMD_ARG2("bss:txoplimit", set80211bsstxoplimit), DEF_CMD_ARG("dtimperiod", set80211dtimperiod), DEF_CMD_ARG("bintval", set80211bintval), DEF_CMD("mac:open", IEEE80211_MACCMD_POLICY_OPEN, set80211maccmd), DEF_CMD("mac:allow", IEEE80211_MACCMD_POLICY_ALLOW, set80211maccmd), DEF_CMD("mac:deny", IEEE80211_MACCMD_POLICY_DENY, set80211maccmd), DEF_CMD("mac:radius", IEEE80211_MACCMD_POLICY_RADIUS, set80211maccmd), DEF_CMD("mac:flush", IEEE80211_MACCMD_FLUSH, set80211maccmd), DEF_CMD("mac:detach", IEEE80211_MACCMD_DETACH, set80211maccmd), DEF_CMD_ARG("mac:add", set80211addmac), DEF_CMD_ARG("mac:del", set80211delmac), DEF_CMD_ARG("mac:kick", set80211kickmac), DEF_CMD("pureg", 1, set80211pureg), DEF_CMD("-pureg", 0, set80211pureg), DEF_CMD("ff", 1, set80211fastframes), DEF_CMD("-ff", 0, set80211fastframes), DEF_CMD("dturbo", 1, set80211dturbo), DEF_CMD("-dturbo", 0, set80211dturbo), DEF_CMD("bgscan", 1, set80211bgscan), DEF_CMD("-bgscan", 0, set80211bgscan), DEF_CMD_ARG("bgscanidle", set80211bgscanidle), DEF_CMD_ARG("bgscanintvl", set80211bgscanintvl), DEF_CMD_ARG("scanvalid", set80211scanvalid), DEF_CMD("quiet", 1, set80211quiet), DEF_CMD("-quiet", 0, set80211quiet), DEF_CMD_ARG("quiet_count", set80211quietcount), DEF_CMD_ARG("quiet_period", set80211quietperiod), DEF_CMD_ARG("quiet_dur", set80211quietduration), DEF_CMD_ARG("quiet_offset", set80211quietoffset), DEF_CMD_ARG("roam:rssi", set80211roamrssi), DEF_CMD_ARG("roam:rate", set80211roamrate), DEF_CMD_ARG("mcastrate", set80211mcastrate), DEF_CMD_ARG("ucastrate", set80211ucastrate), DEF_CMD_ARG("mgtrate", set80211mgtrate), DEF_CMD_ARG("mgmtrate", set80211mgtrate), DEF_CMD_ARG("maxretry", set80211maxretry), DEF_CMD_ARG("fragthreshold", set80211fragthreshold), DEF_CMD("burst", 1, set80211burst), DEF_CMD("-burst", 0, set80211burst), DEF_CMD_ARG("bmiss", set80211bmissthreshold), DEF_CMD_ARG("bmissthreshold", set80211bmissthreshold), DEF_CMD("shortgi", 1, set80211shortgi), DEF_CMD("-shortgi", 0, set80211shortgi), DEF_CMD("ampdurx", 2, set80211ampdu), DEF_CMD("-ampdurx", -2, set80211ampdu), DEF_CMD("ampdutx", 1, set80211ampdu), DEF_CMD("-ampdutx", -1, set80211ampdu), DEF_CMD("ampdu", 3, set80211ampdu), /* NB: tx+rx */ DEF_CMD("-ampdu", -3, set80211ampdu), DEF_CMD_ARG("ampdulimit", set80211ampdulimit), DEF_CMD_ARG("ampdudensity", set80211ampdudensity), DEF_CMD("amsdurx", 2, set80211amsdu), DEF_CMD("-amsdurx", -2, set80211amsdu), DEF_CMD("amsdutx", 1, set80211amsdu), DEF_CMD("-amsdutx", -1, set80211amsdu), DEF_CMD("amsdu", 3, set80211amsdu), /* NB: tx+rx */ DEF_CMD("-amsdu", -3, set80211amsdu), DEF_CMD_ARG("amsdulimit", set80211amsdulimit), DEF_CMD("puren", 1, set80211puren), DEF_CMD("-puren", 0, set80211puren), DEF_CMD("doth", 1, set80211doth), DEF_CMD("-doth", 0, set80211doth), DEF_CMD("dfs", 1, set80211dfs), DEF_CMD("-dfs", 0, set80211dfs), DEF_CMD("htcompat", 1, set80211htcompat), DEF_CMD("-htcompat", 0, set80211htcompat), DEF_CMD("dwds", 1, set80211dwds), DEF_CMD("-dwds", 0, set80211dwds), DEF_CMD("inact", 1, set80211inact), DEF_CMD("-inact", 0, set80211inact), DEF_CMD("tsn", 1, set80211tsn), DEF_CMD("-tsn", 0, set80211tsn), DEF_CMD_ARG("regdomain", set80211regdomain), DEF_CMD_ARG("country", set80211country), DEF_CMD("indoor", 'I', set80211location), DEF_CMD("-indoor", 'O', set80211location), DEF_CMD("outdoor", 'O', set80211location), DEF_CMD("-outdoor", 'I', set80211location), DEF_CMD("anywhere", ' ', set80211location), DEF_CMD("ecm", 1, set80211ecm), DEF_CMD("-ecm", 0, set80211ecm), DEF_CMD("dotd", 1, set80211dotd), DEF_CMD("-dotd", 0, set80211dotd), DEF_CMD_ARG("htprotmode", set80211htprotmode), DEF_CMD("ht20", 1, set80211htconf), DEF_CMD("-ht20", 0, set80211htconf), DEF_CMD("ht40", 3, set80211htconf), /* NB: 20+40 */ DEF_CMD("-ht40", 0, set80211htconf), DEF_CMD("ht", 3, set80211htconf), /* NB: 20+40 */ DEF_CMD("-ht", 0, set80211htconf), DEF_CMD("rifs", 1, set80211rifs), DEF_CMD("-rifs", 0, set80211rifs), DEF_CMD("smps", IEEE80211_HTCAP_SMPS_ENA, set80211smps), DEF_CMD("smpsdyn", IEEE80211_HTCAP_SMPS_DYNAMIC, set80211smps), DEF_CMD("-smps", IEEE80211_HTCAP_SMPS_OFF, set80211smps), /* XXX for testing */ DEF_CMD_ARG("chanswitch", set80211chanswitch), DEF_CMD_ARG("tdmaslot", set80211tdmaslot), DEF_CMD_ARG("tdmaslotcnt", set80211tdmaslotcnt), DEF_CMD_ARG("tdmaslotlen", set80211tdmaslotlen), DEF_CMD_ARG("tdmabintval", set80211tdmabintval), DEF_CMD_ARG("meshttl", set80211meshttl), DEF_CMD("meshforward", 1, set80211meshforward), DEF_CMD("-meshforward", 0, set80211meshforward), DEF_CMD("meshpeering", 1, set80211meshpeering), DEF_CMD("-meshpeering", 0, set80211meshpeering), DEF_CMD_ARG("meshmetric", set80211meshmetric), DEF_CMD_ARG("meshpath", set80211meshpath), DEF_CMD("meshrt:flush", IEEE80211_MESH_RTCMD_FLUSH, set80211meshrtcmd), DEF_CMD_ARG("meshrt:add", set80211addmeshrt), DEF_CMD_ARG("meshrt:del", set80211delmeshrt), DEF_CMD_ARG("hwmprootmode", set80211hwmprootmode), DEF_CMD_ARG("hwmpmaxhops", set80211hwmpmaxhops), /* vap cloning support */ DEF_CLONE_CMD_ARG("wlanaddr", set80211clone_wlanaddr), DEF_CLONE_CMD_ARG("wlanbssid", set80211clone_wlanbssid), DEF_CLONE_CMD_ARG("wlandev", set80211clone_wlandev), DEF_CLONE_CMD_ARG("wlanmode", set80211clone_wlanmode), DEF_CLONE_CMD("beacons", 1, set80211clone_beacons), DEF_CLONE_CMD("-beacons", 0, set80211clone_beacons), DEF_CLONE_CMD("bssid", 1, set80211clone_bssid), DEF_CLONE_CMD("-bssid", 0, set80211clone_bssid), DEF_CLONE_CMD("wdslegacy", 1, set80211clone_wdslegacy), DEF_CLONE_CMD("-wdslegacy", 0, set80211clone_wdslegacy), }; static struct afswtch af_ieee80211 = { .af_name = "af_ieee80211", .af_af = AF_UNSPEC, .af_other_status = ieee80211_status, }; static __constructor void ieee80211_ctor(void) { #define N(a) (sizeof(a) / sizeof(a[0])) int i; for (i = 0; i < N(ieee80211_cmds); i++) cmd_register(&ieee80211_cmds[i]); af_register(&af_ieee80211); clone_setdefcallback("wlan", wlan_create); #undef N } Index: head/sbin/ipfw/ipfw2.c =================================================================== --- head/sbin/ipfw/ipfw2.c (revision 229777) +++ head/sbin/ipfw/ipfw2.c (revision 229778) @@ -1,4013 +1,4013 @@ /* * Copyright (c) 2002-2003 Luigi Rizzo * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp * Copyright (c) 1994 Ugen J.S.Antsilevich * * Idea and grammar partially left from: * Copyright (c) 1993 Daniel Boulet * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * * NEW command line interface for IP firewall facility * * $FreeBSD$ */ #include #include #include #include #include #include "ipfw2.h" #include #include #include #include #include #include #include #include #include #include #include /* ctime */ #include /* _long_to_time */ #include #include #include #include /* only IFNAMSIZ */ #include #include /* only n_short, n_long */ #include #include #include #include #include struct cmdline_opts co; /* global options */ int resvd_set_number = RESVD_SET; #define GET_UINT_ARG(arg, min, max, tok, s_x) do { \ if (!av[0]) \ errx(EX_USAGE, "%s: missing argument", match_value(s_x, tok)); \ if (_substrcmp(*av, "tablearg") == 0) { \ arg = IP_FW_TABLEARG; \ break; \ } \ \ { \ long _xval; \ char *end; \ \ _xval = strtol(*av, &end, 10); \ \ if (!isdigit(**av) || *end != '\0' || (_xval == 0 && errno == EINVAL)) \ errx(EX_DATAERR, "%s: invalid argument: %s", \ match_value(s_x, tok), *av); \ \ if (errno == ERANGE || _xval < min || _xval > max) \ errx(EX_DATAERR, "%s: argument is out of range (%u..%u): %s", \ match_value(s_x, tok), min, max, *av); \ \ if (_xval == IP_FW_TABLEARG) \ errx(EX_DATAERR, "%s: illegal argument value: %s", \ match_value(s_x, tok), *av); \ arg = _xval; \ } \ } while (0) static void PRINT_UINT_ARG(const char *str, uint32_t arg) { if (str != NULL) printf("%s",str); if (arg == IP_FW_TABLEARG) printf("tablearg"); else printf("%u", arg); } static struct _s_x f_tcpflags[] = { { "syn", TH_SYN }, { "fin", TH_FIN }, { "ack", TH_ACK }, { "psh", TH_PUSH }, { "rst", TH_RST }, { "urg", TH_URG }, { "tcp flag", 0 }, { NULL, 0 } }; static struct _s_x f_tcpopts[] = { { "mss", IP_FW_TCPOPT_MSS }, { "maxseg", IP_FW_TCPOPT_MSS }, { "window", IP_FW_TCPOPT_WINDOW }, { "sack", IP_FW_TCPOPT_SACK }, { "ts", IP_FW_TCPOPT_TS }, { "timestamp", IP_FW_TCPOPT_TS }, { "cc", IP_FW_TCPOPT_CC }, { "tcp option", 0 }, { NULL, 0 } }; /* * IP options span the range 0 to 255 so we need to remap them * (though in fact only the low 5 bits are significant). */ static struct _s_x f_ipopts[] = { { "ssrr", IP_FW_IPOPT_SSRR}, { "lsrr", IP_FW_IPOPT_LSRR}, { "rr", IP_FW_IPOPT_RR}, { "ts", IP_FW_IPOPT_TS}, { "ip option", 0 }, { NULL, 0 } }; static struct _s_x f_iptos[] = { { "lowdelay", IPTOS_LOWDELAY}, { "throughput", IPTOS_THROUGHPUT}, { "reliability", IPTOS_RELIABILITY}, { "mincost", IPTOS_MINCOST}, { "congestion", IPTOS_ECN_CE}, { "ecntransport", IPTOS_ECN_ECT0}, { "ip tos option", 0}, { NULL, 0 } }; static struct _s_x limit_masks[] = { {"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT}, {"src-addr", DYN_SRC_ADDR}, {"src-port", DYN_SRC_PORT}, {"dst-addr", DYN_DST_ADDR}, {"dst-port", DYN_DST_PORT}, {NULL, 0} }; /* * we use IPPROTO_ETHERTYPE as a fake protocol id to call the print routines * This is only used in this code. */ #define IPPROTO_ETHERTYPE 0x1000 static struct _s_x ether_types[] = { /* * Note, we cannot use "-:&/" in the names because they are field * separators in the type specifications. Also, we use s = NULL as * end-delimiter, because a type of 0 can be legal. */ { "ip", 0x0800 }, { "ipv4", 0x0800 }, { "ipv6", 0x86dd }, { "arp", 0x0806 }, { "rarp", 0x8035 }, { "vlan", 0x8100 }, { "loop", 0x9000 }, { "trail", 0x1000 }, { "at", 0x809b }, { "atalk", 0x809b }, { "aarp", 0x80f3 }, { "pppoe_disc", 0x8863 }, { "pppoe_sess", 0x8864 }, { "ipx_8022", 0x00E0 }, { "ipx_8023", 0x0000 }, { "ipx_ii", 0x8137 }, { "ipx_snap", 0x8137 }, { "ipx", 0x8137 }, { "ns", 0x0600 }, { NULL, 0 } }; static struct _s_x rule_actions[] = { { "accept", TOK_ACCEPT }, { "pass", TOK_ACCEPT }, { "allow", TOK_ACCEPT }, { "permit", TOK_ACCEPT }, { "count", TOK_COUNT }, { "pipe", TOK_PIPE }, { "queue", TOK_QUEUE }, { "divert", TOK_DIVERT }, { "tee", TOK_TEE }, { "netgraph", TOK_NETGRAPH }, { "ngtee", TOK_NGTEE }, { "fwd", TOK_FORWARD }, { "forward", TOK_FORWARD }, { "skipto", TOK_SKIPTO }, { "deny", TOK_DENY }, { "drop", TOK_DENY }, { "reject", TOK_REJECT }, { "reset6", TOK_RESET6 }, { "reset", TOK_RESET }, { "unreach6", TOK_UNREACH6 }, { "unreach", TOK_UNREACH }, { "check-state", TOK_CHECKSTATE }, { "//", TOK_COMMENT }, { "nat", TOK_NAT }, { "reass", TOK_REASS }, { "setfib", TOK_SETFIB }, { "call", TOK_CALL }, { "return", TOK_RETURN }, { NULL, 0 } /* terminator */ }; static struct _s_x rule_action_params[] = { { "altq", TOK_ALTQ }, { "log", TOK_LOG }, { "tag", TOK_TAG }, { "untag", TOK_UNTAG }, { NULL, 0 } /* terminator */ }; /* * The 'lookup' instruction accepts one of the following arguments. * -1 is a terminator for the list. * Arguments are passed as v[1] in O_DST_LOOKUP options. */ static int lookup_key[] = { TOK_DSTIP, TOK_SRCIP, TOK_DSTPORT, TOK_SRCPORT, TOK_UID, TOK_JAIL, TOK_DSCP, -1 }; static struct _s_x rule_options[] = { { "tagged", TOK_TAGGED }, { "uid", TOK_UID }, { "gid", TOK_GID }, { "jail", TOK_JAIL }, { "in", TOK_IN }, { "limit", TOK_LIMIT }, { "keep-state", TOK_KEEPSTATE }, { "bridged", TOK_LAYER2 }, { "layer2", TOK_LAYER2 }, { "out", TOK_OUT }, { "diverted", TOK_DIVERTED }, { "diverted-loopback", TOK_DIVERTEDLOOPBACK }, { "diverted-output", TOK_DIVERTEDOUTPUT }, { "xmit", TOK_XMIT }, { "recv", TOK_RECV }, { "via", TOK_VIA }, { "fragment", TOK_FRAG }, { "frag", TOK_FRAG }, { "fib", TOK_FIB }, { "ipoptions", TOK_IPOPTS }, { "ipopts", TOK_IPOPTS }, { "iplen", TOK_IPLEN }, { "ipid", TOK_IPID }, { "ipprecedence", TOK_IPPRECEDENCE }, { "dscp", TOK_DSCP }, { "iptos", TOK_IPTOS }, { "ipttl", TOK_IPTTL }, { "ipversion", TOK_IPVER }, { "ipver", TOK_IPVER }, { "estab", TOK_ESTAB }, { "established", TOK_ESTAB }, { "setup", TOK_SETUP }, { "sockarg", TOK_SOCKARG }, { "tcpdatalen", TOK_TCPDATALEN }, { "tcpflags", TOK_TCPFLAGS }, { "tcpflgs", TOK_TCPFLAGS }, { "tcpoptions", TOK_TCPOPTS }, { "tcpopts", TOK_TCPOPTS }, { "tcpseq", TOK_TCPSEQ }, { "tcpack", TOK_TCPACK }, { "tcpwin", TOK_TCPWIN }, { "icmptype", TOK_ICMPTYPES }, { "icmptypes", TOK_ICMPTYPES }, { "dst-ip", TOK_DSTIP }, { "src-ip", TOK_SRCIP }, { "dst-port", TOK_DSTPORT }, { "src-port", TOK_SRCPORT }, { "proto", TOK_PROTO }, { "MAC", TOK_MAC }, { "mac", TOK_MAC }, { "mac-type", TOK_MACTYPE }, { "verrevpath", TOK_VERREVPATH }, { "versrcreach", TOK_VERSRCREACH }, { "antispoof", TOK_ANTISPOOF }, { "ipsec", TOK_IPSEC }, { "icmp6type", TOK_ICMP6TYPES }, { "icmp6types", TOK_ICMP6TYPES }, { "ext6hdr", TOK_EXT6HDR}, { "flow-id", TOK_FLOWID}, { "ipv6", TOK_IPV6}, { "ip6", TOK_IPV6}, { "ipv4", TOK_IPV4}, { "ip4", TOK_IPV4}, { "dst-ipv6", TOK_DSTIP6}, { "dst-ip6", TOK_DSTIP6}, { "src-ipv6", TOK_SRCIP6}, { "src-ip6", TOK_SRCIP6}, { "lookup", TOK_LOOKUP}, { "//", TOK_COMMENT }, { "not", TOK_NOT }, /* pseudo option */ { "!", /* escape ? */ TOK_NOT }, /* pseudo option */ { "or", TOK_OR }, /* pseudo option */ { "|", /* escape */ TOK_OR }, /* pseudo option */ { "{", TOK_STARTBRACE }, /* pseudo option */ { "(", TOK_STARTBRACE }, /* pseudo option */ { "}", TOK_ENDBRACE }, /* pseudo option */ { ")", TOK_ENDBRACE }, /* pseudo option */ { NULL, 0 } /* terminator */ }; /* * Helper routine to print a possibly unaligned uint64_t on * various platform. If width > 0, print the value with * the desired width, followed by a space; * otherwise, return the required width. */ int pr_u64(uint64_t *pd, int width) { #ifdef TCC #define U64_FMT "I64" #else #define U64_FMT "llu" #endif uint64_t u; unsigned long long d; bcopy (pd, &u, sizeof(u)); d = u; return (width > 0) ? printf("%*" U64_FMT " ", width, d) : snprintf(NULL, 0, "%" U64_FMT, d) ; #undef U64_FMT } void * safe_calloc(size_t number, size_t size) { void *ret = calloc(number, size); if (ret == NULL) err(EX_OSERR, "calloc"); return ret; } void * safe_realloc(void *ptr, size_t size) { void *ret = realloc(ptr, size); if (ret == NULL) err(EX_OSERR, "realloc"); return ret; } /* * conditionally runs the command. * Selected options or negative -> getsockopt */ int do_cmd(int optname, void *optval, uintptr_t optlen) { static int s = -1; /* the socket */ int i; if (co.test_only) return 0; if (s == -1) s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) err(EX_UNAVAILABLE, "socket"); if (optname == IP_FW_GET || optname == IP_DUMMYNET_GET || optname == IP_FW_ADD || optname == IP_FW_TABLE_LIST || optname == IP_FW_TABLE_GETSIZE || optname == IP_FW_NAT_GET_CONFIG || optname < 0 || optname == IP_FW_NAT_GET_LOG) { if (optname < 0) optname = -optname; i = getsockopt(s, IPPROTO_IP, optname, optval, (socklen_t *)optlen); } else { i = setsockopt(s, IPPROTO_IP, optname, optval, optlen); } return i; } /** * match_token takes a table and a string, returns the value associated * with the string (-1 in case of failure). */ int match_token(struct _s_x *table, char *string) { struct _s_x *pt; uint i = strlen(string); for (pt = table ; i && pt->s != NULL ; pt++) if (strlen(pt->s) == i && !bcmp(string, pt->s, i)) return pt->x; return -1; } /** * match_value takes a table and a value, returns the string associated * with the value (NULL in case of failure). */ char const * match_value(struct _s_x *p, int value) { for (; p->s != NULL; p++) if (p->x == value) return p->s; return NULL; } /* * _substrcmp takes two strings and returns 1 if they do not match, * and 0 if they match exactly or the first string is a sub-string * of the second. A warning is printed to stderr in the case that the * first string is a sub-string of the second. * * This function will be removed in the future through the usual * deprecation process. */ int _substrcmp(const char *str1, const char* str2) { if (strncmp(str1, str2, strlen(str1)) != 0) return 1; if (strlen(str1) != strlen(str2)) warnx("DEPRECATED: '%s' matched '%s' as a sub-string", str1, str2); return 0; } /* * _substrcmp2 takes three strings and returns 1 if the first two do not match, * and 0 if they match exactly or the second string is a sub-string * of the first. A warning is printed to stderr in the case that the * first string does not match the third. * - * This function exists to warn about the bizzare construction - * strncmp(str, "by", 2) which is used to allow people to use a shotcut + * This function exists to warn about the bizarre construction + * strncmp(str, "by", 2) which is used to allow people to use a shortcut * for "bytes". The problem is that in addition to accepting "by", * "byt", "byte", and "bytes", it also excepts "by_rabid_dogs" and any * other string beginning with "by". * * This function will be removed in the future through the usual * deprecation process. */ int _substrcmp2(const char *str1, const char* str2, const char* str3) { if (strncmp(str1, str2, strlen(str2)) != 0) return 1; if (strcmp(str1, str3) != 0) warnx("DEPRECATED: '%s' matched '%s'", str1, str3); return 0; } /* * prints one port, symbolic or numeric */ static void print_port(int proto, uint16_t port) { if (proto == IPPROTO_ETHERTYPE) { char const *s; if (co.do_resolv && (s = match_value(ether_types, port)) ) printf("%s", s); else printf("0x%04x", port); } else { struct servent *se = NULL; if (co.do_resolv) { struct protoent *pe = getprotobynumber(proto); se = getservbyport(htons(port), pe ? pe->p_name : NULL); } if (se) printf("%s", se->s_name); else printf("%d", port); } } static struct _s_x _port_name[] = { {"dst-port", O_IP_DSTPORT}, {"src-port", O_IP_SRCPORT}, {"ipid", O_IPID}, {"iplen", O_IPLEN}, {"ipttl", O_IPTTL}, {"mac-type", O_MAC_TYPE}, {"tcpdatalen", O_TCPDATALEN}, {"tagged", O_TAGGED}, {NULL, 0} }; /* * Print the values in a list 16-bit items of the types above. * XXX todo: add support for mask. */ static void print_newports(ipfw_insn_u16 *cmd, int proto, int opcode) { uint16_t *p = cmd->ports; int i; char const *sep; if (opcode != 0) { sep = match_value(_port_name, opcode); if (sep == NULL) sep = "???"; printf (" %s", sep); } sep = " "; for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) { printf("%s", sep); print_port(proto, p[0]); if (p[0] != p[1]) { printf("-"); print_port(proto, p[1]); } sep = ","; } } /* * Like strtol, but also translates service names into port numbers * for some protocols. * In particular: * proto == -1 disables the protocol check; * proto == IPPROTO_ETHERTYPE looks up an internal table * proto == matches the values there. * Returns *end == s in case the parameter is not found. */ static int strtoport(char *s, char **end, int base, int proto) { char *p, *buf; char *s1; int i; *end = s; /* default - not found */ if (*s == '\0') return 0; /* not found */ if (isdigit(*s)) return strtol(s, end, base); /* * find separator. '\\' escapes the next char. */ for (s1 = s; *s1 && (isalnum(*s1) || *s1 == '\\') ; s1++) if (*s1 == '\\' && s1[1] != '\0') s1++; buf = safe_calloc(s1 - s + 1, 1); /* * copy into a buffer skipping backslashes */ for (p = s, i = 0; p != s1 ; p++) if (*p != '\\') buf[i++] = *p; buf[i++] = '\0'; if (proto == IPPROTO_ETHERTYPE) { i = match_token(ether_types, buf); free(buf); if (i != -1) { /* found */ *end = s1; return i; } } else { struct protoent *pe = NULL; struct servent *se; if (proto != 0) pe = getprotobynumber(proto); setservent(1); se = getservbyname(buf, pe ? pe->p_name : NULL); free(buf); if (se != NULL) { *end = s1; return ntohs(se->s_port); } } return 0; /* not found */ } /* * Fill the body of the command with the list of port ranges. */ static int fill_newports(ipfw_insn_u16 *cmd, char *av, int proto) { uint16_t a, b, *p = cmd->ports; int i = 0; char *s = av; while (*s) { a = strtoport(av, &s, 0, proto); if (s == av) /* empty or invalid argument */ return (0); switch (*s) { case '-': /* a range */ av = s + 1; b = strtoport(av, &s, 0, proto); /* Reject expressions like '1-abc' or '1-2-3'. */ if (s == av || (*s != ',' && *s != '\0')) return (0); p[0] = a; p[1] = b; break; case ',': /* comma separated list */ case '\0': p[0] = p[1] = a; break; default: warnx("port list: invalid separator <%c> in <%s>", *s, av); return (0); } i++; p += 2; av = s + 1; } if (i > 0) { if (i + 1 > F_LEN_MASK) errx(EX_DATAERR, "too many ports/ranges\n"); cmd->o.len |= i + 1; /* leave F_NOT and F_OR untouched */ } return (i); } static struct _s_x icmpcodes[] = { { "net", ICMP_UNREACH_NET }, { "host", ICMP_UNREACH_HOST }, { "protocol", ICMP_UNREACH_PROTOCOL }, { "port", ICMP_UNREACH_PORT }, { "needfrag", ICMP_UNREACH_NEEDFRAG }, { "srcfail", ICMP_UNREACH_SRCFAIL }, { "net-unknown", ICMP_UNREACH_NET_UNKNOWN }, { "host-unknown", ICMP_UNREACH_HOST_UNKNOWN }, { "isolated", ICMP_UNREACH_ISOLATED }, { "net-prohib", ICMP_UNREACH_NET_PROHIB }, { "host-prohib", ICMP_UNREACH_HOST_PROHIB }, { "tosnet", ICMP_UNREACH_TOSNET }, { "toshost", ICMP_UNREACH_TOSHOST }, { "filter-prohib", ICMP_UNREACH_FILTER_PROHIB }, { "host-precedence", ICMP_UNREACH_HOST_PRECEDENCE }, { "precedence-cutoff", ICMP_UNREACH_PRECEDENCE_CUTOFF }, { NULL, 0 } }; static void fill_reject_code(u_short *codep, char *str) { int val; char *s; val = strtoul(str, &s, 0); if (s == str || *s != '\0' || val >= 0x100) val = match_token(icmpcodes, str); if (val < 0) errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str); *codep = val; return; } static void print_reject_code(uint16_t code) { char const *s = match_value(icmpcodes, code); if (s != NULL) printf("unreach %s", s); else printf("unreach %u", code); } /* * Returns the number of bits set (from left) in a contiguous bitmask, * or -1 if the mask is not contiguous. * XXX this needs a proper fix. * This effectively works on masks in big-endian (network) format. * when compiled on little endian architectures. * * First bit is bit 7 of the first byte -- note, for MAC addresses, * the first bit on the wire is bit 0 of the first byte. * len is the max length in bits. */ int contigmask(uint8_t *p, int len) { int i, n; for (i=0; iarg1 & 0xff; uint8_t clear = (cmd->arg1 >> 8) & 0xff; if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) { printf(" setup"); return; } printf(" %s ", name); for (i=0; list[i].x != 0; i++) { if (set & list[i].x) { set &= ~list[i].x; printf("%s%s", comma, list[i].s); comma = ","; } if (clear & list[i].x) { clear &= ~list[i].x; printf("%s!%s", comma, list[i].s); comma = ","; } } } /* * Print the ip address contained in a command. */ static void print_ip(ipfw_insn_ip *cmd, char const *s) { struct hostent *he = NULL; uint32_t len = F_LEN((ipfw_insn *)cmd); uint32_t *a = ((ipfw_insn_u32 *)cmd)->d; if (cmd->o.opcode == O_IP_DST_LOOKUP && len > F_INSN_SIZE(ipfw_insn_u32)) { uint32_t d = a[1]; const char *arg = ""; if (d < sizeof(lookup_key)/sizeof(lookup_key[0])) arg = match_value(rule_options, lookup_key[d]); printf("%s lookup %s %d", cmd->o.len & F_NOT ? " not": "", arg, cmd->o.arg1); return; } printf("%s%s ", cmd->o.len & F_NOT ? " not": "", s); if (cmd->o.opcode == O_IP_SRC_ME || cmd->o.opcode == O_IP_DST_ME) { printf("me"); return; } if (cmd->o.opcode == O_IP_SRC_LOOKUP || cmd->o.opcode == O_IP_DST_LOOKUP) { printf("table(%u", ((ipfw_insn *)cmd)->arg1); if (len == F_INSN_SIZE(ipfw_insn_u32)) printf(",%u", *a); printf(")"); return; } if (cmd->o.opcode == O_IP_SRC_SET || cmd->o.opcode == O_IP_DST_SET) { uint32_t x, *map = (uint32_t *)&(cmd->mask); int i, j; char comma = '{'; x = cmd->o.arg1 - 1; x = htonl( ~x ); cmd->addr.s_addr = htonl(cmd->addr.s_addr); printf("%s/%d", inet_ntoa(cmd->addr), contigmask((uint8_t *)&x, 32)); x = cmd->addr.s_addr = htonl(cmd->addr.s_addr); x &= 0xff; /* base */ /* * Print bits and ranges. * Locate first bit set (i), then locate first bit unset (j). * If we have 3+ consecutive bits set, then print them as a * range, otherwise only print the initial bit and rescan. */ for (i=0; i < cmd->o.arg1; i++) if (map[i/32] & (1<<(i & 31))) { for (j=i+1; j < cmd->o.arg1; j++) if (!(map[ j/32] & (1<<(j & 31)))) break; printf("%c%d", comma, i+x); if (j>i+2) { /* range has at least 3 elements */ printf("-%d", j-1+x); i = j-1; } comma = ','; } printf("}"); return; } /* * len == 2 indicates a single IP, whereas lists of 1 or more * addr/mask pairs have len = (2n+1). We convert len to n so we * use that to count the number of entries. */ for (len = len / 2; len > 0; len--, a += 2) { int mb = /* mask length */ (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) ? 32 : contigmask((uint8_t *)&(a[1]), 32); if (mb == 32 && co.do_resolv) he = gethostbyaddr((char *)&(a[0]), sizeof(u_long), AF_INET); if (he != NULL) /* resolved to name */ printf("%s", he->h_name); else if (mb == 0) /* any */ printf("any"); else { /* numeric IP followed by some kind of mask */ printf("%s", inet_ntoa( *((struct in_addr *)&a[0]) ) ); if (mb < 0) printf(":%s", inet_ntoa( *((struct in_addr *)&a[1]) ) ); else if (mb < 32) printf("/%d", mb); } if (len > 1) printf(","); } } /* * prints a MAC address/mask pair */ static void print_mac(uint8_t *addr, uint8_t *mask) { int l = contigmask(mask, 48); if (l == 0) printf(" any"); else { printf(" %02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); if (l == -1) printf("&%02x:%02x:%02x:%02x:%02x:%02x", mask[0], mask[1], mask[2], mask[3], mask[4], mask[5]); else if (l < 48) printf("/%d", l); } } static void fill_icmptypes(ipfw_insn_u32 *cmd, char *av) { uint8_t type; cmd->d[0] = 0; while (*av) { if (*av == ',') av++; type = strtoul(av, &av, 0); if (*av != ',' && *av != '\0') errx(EX_DATAERR, "invalid ICMP type"); if (type > 31) errx(EX_DATAERR, "ICMP type out of range"); cmd->d[0] |= 1 << type; } cmd->o.opcode = O_ICMPTYPE; cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); } static void print_icmptypes(ipfw_insn_u32 *cmd) { int i; char sep= ' '; printf(" icmptypes"); for (i = 0; i < 32; i++) { if ( (cmd->d[0] & (1 << (i))) == 0) continue; printf("%c%d", sep, i); sep = ','; } } /* * show_ipfw() prints the body of an ipfw rule. * Because the standard rule has at least proto src_ip dst_ip, we use * a helper function to produce these entries if not provided explicitly. * The first argument is the list of fields we have, the second is * the list of fields we want to be printed. * * Special cases if we have provided a MAC header: * + if the rule does not contain IP addresses/ports, do not print them; * + if the rule does not contain an IP proto, print "all" instead of "ip"; * * Once we have 'have_options', IP header fields are printed as options. */ #define HAVE_PROTO 0x0001 #define HAVE_SRCIP 0x0002 #define HAVE_DSTIP 0x0004 #define HAVE_PROTO4 0x0008 #define HAVE_PROTO6 0x0010 #define HAVE_IP 0x0100 #define HAVE_OPTIONS 0x8000 static void show_prerequisites(int *flags, int want, int cmd __unused) { if (co.comment_only) return; if ( (*flags & HAVE_IP) == HAVE_IP) *flags |= HAVE_OPTIONS; if ( !(*flags & HAVE_OPTIONS)) { if ( !(*flags & HAVE_PROTO) && (want & HAVE_PROTO)) { if ( (*flags & HAVE_PROTO4)) printf(" ip4"); else if ( (*flags & HAVE_PROTO6)) printf(" ip6"); else printf(" ip"); } if ( !(*flags & HAVE_SRCIP) && (want & HAVE_SRCIP)) printf(" from any"); if ( !(*flags & HAVE_DSTIP) && (want & HAVE_DSTIP)) printf(" to any"); } *flags |= want; } static void show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth) { static int twidth = 0; int l; ipfw_insn *cmd, *tagptr = NULL; const char *comment = NULL; /* ptr to comment if we have one */ int proto = 0; /* default */ int flags = 0; /* prerequisites */ ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */ ipfw_insn_altq *altqptr = NULL; /* set if we find an O_ALTQ */ int or_block = 0; /* we are in an or block */ uint32_t set_disable; bcopy(&rule->next_rule, &set_disable, sizeof(set_disable)); if (set_disable & (1 << rule->set)) { /* disabled */ if (!co.show_sets) return; else printf("# DISABLED "); } printf("%05u ", rule->rulenum); if (pcwidth > 0 || bcwidth > 0) { pr_u64(&rule->pcnt, pcwidth); pr_u64(&rule->bcnt, bcwidth); } if (co.do_time == 2) printf("%10u ", rule->timestamp); else if (co.do_time == 1) { char timestr[30]; time_t t = (time_t)0; if (twidth == 0) { strcpy(timestr, ctime(&t)); *strchr(timestr, '\n') = '\0'; twidth = strlen(timestr); } if (rule->timestamp) { t = _long_to_time(rule->timestamp); strcpy(timestr, ctime(&t)); *strchr(timestr, '\n') = '\0'; printf("%s ", timestr); } else { printf("%*s", twidth, " "); } } if (co.show_sets) printf("set %d ", rule->set); /* * print the optional "match probability" */ if (rule->cmd_len > 0) { cmd = rule->cmd ; if (cmd->opcode == O_PROB) { ipfw_insn_u32 *p = (ipfw_insn_u32 *)cmd; double d = 1.0 * p->d[0]; d = (d / 0x7fffffff); printf("prob %f ", d); } } /* * first print actions */ for (l = rule->cmd_len - rule->act_ofs, cmd = ACTION_PTR(rule); l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) { switch(cmd->opcode) { case O_CHECK_STATE: printf("check-state"); /* avoid printing anything else */ flags = HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP | HAVE_IP; break; case O_ACCEPT: printf("allow"); break; case O_COUNT: printf("count"); break; case O_DENY: printf("deny"); break; case O_REJECT: if (cmd->arg1 == ICMP_REJECT_RST) printf("reset"); else if (cmd->arg1 == ICMP_UNREACH_HOST) printf("reject"); else print_reject_code(cmd->arg1); break; case O_UNREACH6: if (cmd->arg1 == ICMP6_UNREACH_RST) printf("reset6"); else print_unreach6_code(cmd->arg1); break; case O_SKIPTO: PRINT_UINT_ARG("skipto ", cmd->arg1); break; case O_PIPE: PRINT_UINT_ARG("pipe ", cmd->arg1); break; case O_QUEUE: PRINT_UINT_ARG("queue ", cmd->arg1); break; case O_DIVERT: PRINT_UINT_ARG("divert ", cmd->arg1); break; case O_TEE: PRINT_UINT_ARG("tee ", cmd->arg1); break; case O_NETGRAPH: PRINT_UINT_ARG("netgraph ", cmd->arg1); break; case O_NGTEE: PRINT_UINT_ARG("ngtee ", cmd->arg1); break; case O_FORWARD_IP: { ipfw_insn_sa *s = (ipfw_insn_sa *)cmd; if (s->sa.sin_addr.s_addr == INADDR_ANY) { printf("fwd tablearg"); } else { printf("fwd %s", inet_ntoa(s->sa.sin_addr)); } if (s->sa.sin_port) printf(",%d", s->sa.sin_port); } break; case O_FORWARD_IP6: { char buf[4 + INET6_ADDRSTRLEN + 1]; ipfw_insn_sa6 *s = (ipfw_insn_sa6 *)cmd; printf("fwd %s", inet_ntop(AF_INET6, &s->sa.sin6_addr, buf, sizeof(buf))); if (s->sa.sin6_port) printf(",%d", s->sa.sin6_port); } break; case O_LOG: /* O_LOG is printed last */ logptr = (ipfw_insn_log *)cmd; break; case O_ALTQ: /* O_ALTQ is printed after O_LOG */ altqptr = (ipfw_insn_altq *)cmd; break; case O_TAG: tagptr = cmd; break; case O_NAT: if (cmd->arg1 != 0) PRINT_UINT_ARG("nat ", cmd->arg1); else printf("nat global"); break; case O_SETFIB: PRINT_UINT_ARG("setfib ", cmd->arg1); break; case O_REASS: printf("reass"); break; case O_CALLRETURN: if (cmd->len & F_NOT) printf("return"); else PRINT_UINT_ARG("call ", cmd->arg1); break; default: printf("** unrecognized action %d len %d ", cmd->opcode, cmd->len); } } if (logptr) { if (logptr->max_log > 0) printf(" log logamount %d", logptr->max_log); else printf(" log"); } #ifndef NO_ALTQ if (altqptr) { print_altq_cmd(altqptr); } #endif if (tagptr) { if (tagptr->len & F_NOT) PRINT_UINT_ARG(" untag ", tagptr->arg1); else PRINT_UINT_ARG(" tag ", tagptr->arg1); } /* * then print the body. */ for (l = rule->act_ofs, cmd = rule->cmd ; l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) { if ((cmd->len & F_OR) || (cmd->len & F_NOT)) continue; if (cmd->opcode == O_IP4) { flags |= HAVE_PROTO4; break; } else if (cmd->opcode == O_IP6) { flags |= HAVE_PROTO6; break; } } if (rule->_pad & 1) { /* empty rules before options */ if (!co.do_compact) { show_prerequisites(&flags, HAVE_PROTO, 0); printf(" from any to any"); } flags |= HAVE_IP | HAVE_OPTIONS | HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP; } if (co.comment_only) comment = "..."; for (l = rule->act_ofs, cmd = rule->cmd ; l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) { /* useful alias */ ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd; if (co.comment_only) { if (cmd->opcode != O_NOP) continue; printf(" // %s\n", (char *)(cmd + 1)); return; } show_prerequisites(&flags, 0, cmd->opcode); switch(cmd->opcode) { case O_PROB: break; /* done already */ case O_PROBE_STATE: break; /* no need to print anything here */ case O_IP_SRC: case O_IP_SRC_LOOKUP: case O_IP_SRC_MASK: case O_IP_SRC_ME: case O_IP_SRC_SET: show_prerequisites(&flags, HAVE_PROTO, 0); if (!(flags & HAVE_SRCIP)) printf(" from"); if ((cmd->len & F_OR) && !or_block) printf(" {"); print_ip((ipfw_insn_ip *)cmd, (flags & HAVE_OPTIONS) ? " src-ip" : ""); flags |= HAVE_SRCIP; break; case O_IP_DST: case O_IP_DST_LOOKUP: case O_IP_DST_MASK: case O_IP_DST_ME: case O_IP_DST_SET: show_prerequisites(&flags, HAVE_PROTO|HAVE_SRCIP, 0); if (!(flags & HAVE_DSTIP)) printf(" to"); if ((cmd->len & F_OR) && !or_block) printf(" {"); print_ip((ipfw_insn_ip *)cmd, (flags & HAVE_OPTIONS) ? " dst-ip" : ""); flags |= HAVE_DSTIP; break; case O_IP6_SRC: case O_IP6_SRC_MASK: case O_IP6_SRC_ME: show_prerequisites(&flags, HAVE_PROTO, 0); if (!(flags & HAVE_SRCIP)) printf(" from"); if ((cmd->len & F_OR) && !or_block) printf(" {"); print_ip6((ipfw_insn_ip6 *)cmd, (flags & HAVE_OPTIONS) ? " src-ip6" : ""); flags |= HAVE_SRCIP | HAVE_PROTO; break; case O_IP6_DST: case O_IP6_DST_MASK: case O_IP6_DST_ME: show_prerequisites(&flags, HAVE_PROTO|HAVE_SRCIP, 0); if (!(flags & HAVE_DSTIP)) printf(" to"); if ((cmd->len & F_OR) && !or_block) printf(" {"); print_ip6((ipfw_insn_ip6 *)cmd, (flags & HAVE_OPTIONS) ? " dst-ip6" : ""); flags |= HAVE_DSTIP; break; case O_FLOW6ID: print_flow6id( (ipfw_insn_u32 *) cmd ); flags |= HAVE_OPTIONS; break; case O_IP_DSTPORT: show_prerequisites(&flags, HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP | HAVE_IP, 0); case O_IP_SRCPORT: if (flags & HAVE_DSTIP) flags |= HAVE_IP; show_prerequisites(&flags, HAVE_PROTO | HAVE_SRCIP, 0); if ((cmd->len & F_OR) && !or_block) printf(" {"); if (cmd->len & F_NOT) printf(" not"); print_newports((ipfw_insn_u16 *)cmd, proto, (flags & HAVE_OPTIONS) ? cmd->opcode : 0); break; case O_PROTO: { struct protoent *pe = NULL; if ((cmd->len & F_OR) && !or_block) printf(" {"); if (cmd->len & F_NOT) printf(" not"); proto = cmd->arg1; pe = getprotobynumber(cmd->arg1); if ((flags & (HAVE_PROTO4 | HAVE_PROTO6)) && !(flags & HAVE_PROTO)) show_prerequisites(&flags, HAVE_PROTO | HAVE_IP | HAVE_SRCIP | HAVE_DSTIP | HAVE_OPTIONS, 0); if (flags & HAVE_OPTIONS) printf(" proto"); if (pe) printf(" %s", pe->p_name); else printf(" %u", cmd->arg1); } flags |= HAVE_PROTO; break; default: /*options ... */ if (!(cmd->len & (F_OR|F_NOT))) if (((cmd->opcode == O_IP6) && (flags & HAVE_PROTO6)) || ((cmd->opcode == O_IP4) && (flags & HAVE_PROTO4))) break; show_prerequisites(&flags, HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP | HAVE_IP | HAVE_OPTIONS, 0); if ((cmd->len & F_OR) && !or_block) printf(" {"); if (cmd->len & F_NOT && cmd->opcode != O_IN) printf(" not"); switch(cmd->opcode) { case O_MACADDR2: { ipfw_insn_mac *m = (ipfw_insn_mac *)cmd; printf(" MAC"); print_mac(m->addr, m->mask); print_mac(m->addr + 6, m->mask + 6); } break; case O_MAC_TYPE: print_newports((ipfw_insn_u16 *)cmd, IPPROTO_ETHERTYPE, cmd->opcode); break; case O_FRAG: printf(" frag"); break; case O_FIB: printf(" fib %u", cmd->arg1 ); break; case O_SOCKARG: printf(" sockarg"); break; case O_IN: printf(cmd->len & F_NOT ? " out" : " in"); break; case O_DIVERTED: switch (cmd->arg1) { case 3: printf(" diverted"); break; case 1: printf(" diverted-loopback"); break; case 2: printf(" diverted-output"); break; default: printf(" diverted-?<%u>", cmd->arg1); break; } break; case O_LAYER2: printf(" layer2"); break; case O_XMIT: case O_RECV: case O_VIA: { char const *s; ipfw_insn_if *cmdif = (ipfw_insn_if *)cmd; if (cmd->opcode == O_XMIT) s = "xmit"; else if (cmd->opcode == O_RECV) s = "recv"; else /* if (cmd->opcode == O_VIA) */ s = "via"; if (cmdif->name[0] == '\0') printf(" %s %s", s, inet_ntoa(cmdif->p.ip)); else printf(" %s %s", s, cmdif->name); break; } case O_IPID: if (F_LEN(cmd) == 1) printf(" ipid %u", cmd->arg1 ); else print_newports((ipfw_insn_u16 *)cmd, 0, O_IPID); break; case O_IPTTL: if (F_LEN(cmd) == 1) printf(" ipttl %u", cmd->arg1 ); else print_newports((ipfw_insn_u16 *)cmd, 0, O_IPTTL); break; case O_IPVER: printf(" ipver %u", cmd->arg1 ); break; case O_IPPRECEDENCE: printf(" ipprecedence %u", (cmd->arg1) >> 5 ); break; case O_IPLEN: if (F_LEN(cmd) == 1) printf(" iplen %u", cmd->arg1 ); else print_newports((ipfw_insn_u16 *)cmd, 0, O_IPLEN); break; case O_IPOPT: print_flags("ipoptions", cmd, f_ipopts); break; case O_IPTOS: print_flags("iptos", cmd, f_iptos); break; case O_ICMPTYPE: print_icmptypes((ipfw_insn_u32 *)cmd); break; case O_ESTAB: printf(" established"); break; case O_TCPDATALEN: if (F_LEN(cmd) == 1) printf(" tcpdatalen %u", cmd->arg1 ); else print_newports((ipfw_insn_u16 *)cmd, 0, O_TCPDATALEN); break; case O_TCPFLAGS: print_flags("tcpflags", cmd, f_tcpflags); break; case O_TCPOPTS: print_flags("tcpoptions", cmd, f_tcpopts); break; case O_TCPWIN: printf(" tcpwin %d", ntohs(cmd->arg1)); break; case O_TCPACK: printf(" tcpack %d", ntohl(cmd32->d[0])); break; case O_TCPSEQ: printf(" tcpseq %d", ntohl(cmd32->d[0])); break; case O_UID: { struct passwd *pwd = getpwuid(cmd32->d[0]); if (pwd) printf(" uid %s", pwd->pw_name); else printf(" uid %u", cmd32->d[0]); } break; case O_GID: { struct group *grp = getgrgid(cmd32->d[0]); if (grp) printf(" gid %s", grp->gr_name); else printf(" gid %u", cmd32->d[0]); } break; case O_JAIL: printf(" jail %d", cmd32->d[0]); break; case O_VERREVPATH: printf(" verrevpath"); break; case O_VERSRCREACH: printf(" versrcreach"); break; case O_ANTISPOOF: printf(" antispoof"); break; case O_IPSEC: printf(" ipsec"); break; case O_NOP: comment = (char *)(cmd + 1); break; case O_KEEP_STATE: printf(" keep-state"); break; case O_LIMIT: { struct _s_x *p = limit_masks; ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; uint8_t x = c->limit_mask; char const *comma = " "; printf(" limit"); for (; p->x != 0 ; p++) if ((x & p->x) == p->x) { x &= ~p->x; printf("%s%s", comma, p->s); comma = ","; } PRINT_UINT_ARG(" ", c->conn_limit); break; } case O_IP6: printf(" ip6"); break; case O_IP4: printf(" ip4"); break; case O_ICMP6TYPE: print_icmp6types((ipfw_insn_u32 *)cmd); break; case O_EXT_HDR: print_ext6hdr( (ipfw_insn *) cmd ); break; case O_TAGGED: if (F_LEN(cmd) == 1) PRINT_UINT_ARG(" tagged ", cmd->arg1); else print_newports((ipfw_insn_u16 *)cmd, 0, O_TAGGED); break; default: printf(" [opcode %d len %d]", cmd->opcode, cmd->len); } } if (cmd->len & F_OR) { printf(" or"); or_block = 1; } else if (or_block) { printf(" }"); or_block = 0; } } show_prerequisites(&flags, HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP | HAVE_IP, 0); if (comment) printf(" // %s", comment); printf("\n"); } static void show_dyn_ipfw(ipfw_dyn_rule *d, int pcwidth, int bcwidth) { struct protoent *pe; struct in_addr a; uint16_t rulenum; char buf[INET6_ADDRSTRLEN]; if (!co.do_expired) { if (!d->expire && !(d->dyn_type == O_LIMIT_PARENT)) return; } bcopy(&d->rule, &rulenum, sizeof(rulenum)); printf("%05d", rulenum); if (pcwidth > 0 || bcwidth > 0) { printf(" "); pr_u64(&d->pcnt, pcwidth); pr_u64(&d->bcnt, bcwidth); printf("(%ds)", d->expire); } switch (d->dyn_type) { case O_LIMIT_PARENT: printf(" PARENT %d", d->count); break; case O_LIMIT: printf(" LIMIT"); break; case O_KEEP_STATE: /* bidir, no mask */ printf(" STATE"); break; } if ((pe = getprotobynumber(d->id.proto)) != NULL) printf(" %s", pe->p_name); else printf(" proto %u", d->id.proto); if (d->id.addr_type == 4) { a.s_addr = htonl(d->id.src_ip); printf(" %s %d", inet_ntoa(a), d->id.src_port); a.s_addr = htonl(d->id.dst_ip); printf(" <-> %s %d", inet_ntoa(a), d->id.dst_port); } else if (d->id.addr_type == 6) { printf(" %s %d", inet_ntop(AF_INET6, &d->id.src_ip6, buf, sizeof(buf)), d->id.src_port); printf(" <-> %s %d", inet_ntop(AF_INET6, &d->id.dst_ip6, buf, sizeof(buf)), d->id.dst_port); } else printf(" UNKNOWN <-> UNKNOWN\n"); printf("\n"); } /* * This one handles all set-related commands * ipfw set { show | enable | disable } * ipfw set swap X Y * ipfw set move X to Y * ipfw set move rule X to Y */ void ipfw_sets_handler(char *av[]) { uint32_t set_disable, masks[2]; int i, nbytes; uint16_t rulenum; uint8_t cmd, new_set; av++; if (av[0] == NULL) errx(EX_USAGE, "set needs command"); if (_substrcmp(*av, "show") == 0) { void *data = NULL; char const *msg; int nalloc; nalloc = nbytes = sizeof(struct ip_fw); while (nbytes >= nalloc) { if (data) free(data); nalloc = nalloc * 2 + 200; nbytes = nalloc; data = safe_calloc(1, nbytes); if (do_cmd(IP_FW_GET, data, (uintptr_t)&nbytes) < 0) err(EX_OSERR, "getsockopt(IP_FW_GET)"); } bcopy(&((struct ip_fw *)data)->next_rule, &set_disable, sizeof(set_disable)); for (i = 0, msg = "disable" ; i < RESVD_SET; i++) if ((set_disable & (1< RESVD_SET) errx(EX_DATAERR, "invalid set number %s\n", av[0]); if (!isdigit(*(av[1])) || new_set > RESVD_SET) errx(EX_DATAERR, "invalid set number %s\n", av[1]); masks[0] = (4 << 24) | (new_set << 16) | (rulenum); i = do_cmd(IP_FW_DEL, masks, sizeof(uint32_t)); } else if (_substrcmp(*av, "move") == 0) { av++; if (av[0] && _substrcmp(*av, "rule") == 0) { cmd = 2; av++; } else cmd = 3; if (av[0] == NULL || av[1] == NULL || av[2] == NULL || av[3] != NULL || _substrcmp(av[1], "to") != 0) errx(EX_USAGE, "syntax: set move [rule] X to Y\n"); rulenum = atoi(av[0]); new_set = atoi(av[2]); if (!isdigit(*(av[0])) || (cmd == 3 && rulenum > RESVD_SET) || (cmd == 2 && rulenum == IPFW_DEFAULT_RULE) ) errx(EX_DATAERR, "invalid source number %s\n", av[0]); if (!isdigit(*(av[2])) || new_set > RESVD_SET) errx(EX_DATAERR, "invalid dest. set %s\n", av[1]); masks[0] = (cmd << 24) | (new_set << 16) | (rulenum); i = do_cmd(IP_FW_DEL, masks, sizeof(uint32_t)); } else if (_substrcmp(*av, "disable") == 0 || _substrcmp(*av, "enable") == 0 ) { int which = _substrcmp(*av, "enable") == 0 ? 1 : 0; av++; masks[0] = masks[1] = 0; while (av[0]) { if (isdigit(**av)) { i = atoi(*av); if (i < 0 || i > RESVD_SET) errx(EX_DATAERR, "invalid set number %d\n", i); masks[which] |= (1<= nalloc) { nalloc = nalloc * 2 + 200; nbytes = nalloc; data = safe_realloc(data, nbytes); if (do_cmd(ocmd, data, (uintptr_t)&nbytes) < 0) err(EX_OSERR, "getsockopt(IP_%s_GET)", co.do_pipe ? "DUMMYNET" : "FW"); } /* * Count static rules. They have variable size so we * need to scan the list to count them. */ for (nstat = 1, r = data, lim = (char *)data + nbytes; r->rulenum < IPFW_DEFAULT_RULE && (char *)r < lim; ++nstat, r = NEXT(r) ) ; /* nothing */ /* * Count dynamic rules. This is easier as they have * fixed size. */ r = NEXT(r); dynrules = (ipfw_dyn_rule *)r ; n = (char *)r - (char *)data; ndyn = (nbytes - n) / sizeof *dynrules; /* if showing stats, figure out column widths ahead of time */ bcwidth = pcwidth = 0; if (show_counters) { for (n = 0, r = data; n < nstat; n++, r = NEXT(r)) { /* skip rules from another set */ if (co.use_set && r->set != co.use_set - 1) continue; /* packet counter */ width = pr_u64(&r->pcnt, 0); if (width > pcwidth) pcwidth = width; /* byte counter */ width = pr_u64(&r->bcnt, 0); if (width > bcwidth) bcwidth = width; } } if (co.do_dynamic && ndyn) { for (n = 0, d = dynrules; n < ndyn; n++, d++) { if (co.use_set) { /* skip rules from another set */ bcopy((char *)&d->rule + sizeof(uint16_t), &set, sizeof(uint8_t)); if (set != co.use_set - 1) continue; } width = pr_u64(&d->pcnt, 0); if (width > pcwidth) pcwidth = width; width = pr_u64(&d->bcnt, 0); if (width > bcwidth) bcwidth = width; } } /* if no rule numbers were specified, list all rules */ if (ac == 0) { for (n = 0, r = data; n < nstat; n++, r = NEXT(r)) { if (co.use_set && r->set != co.use_set - 1) continue; show_ipfw(r, pcwidth, bcwidth); } if (co.do_dynamic && ndyn) { printf("## Dynamic rules (%d):\n", ndyn); for (n = 0, d = dynrules; n < ndyn; n++, d++) { if (co.use_set) { bcopy((char *)&d->rule + sizeof(uint16_t), &set, sizeof(uint8_t)); if (set != co.use_set - 1) continue; } show_dyn_ipfw(d, pcwidth, bcwidth); } } goto done; } /* display specific rules requested on command line */ for (lac = ac, lav = av; lac != 0; lac--) { /* convert command line rule # */ last = rnum = strtoul(*lav++, &endptr, 10); if (*endptr == '-') last = strtoul(endptr+1, &endptr, 10); if (*endptr) { exitval = EX_USAGE; warnx("invalid rule number: %s", *(lav - 1)); continue; } for (n = seen = 0, r = data; n < nstat; n++, r = NEXT(r) ) { if (r->rulenum > last) break; if (co.use_set && r->set != co.use_set - 1) continue; if (r->rulenum >= rnum && r->rulenum <= last) { show_ipfw(r, pcwidth, bcwidth); seen = 1; } } if (!seen) { /* give precedence to other error(s) */ if (exitval == EX_OK) exitval = EX_UNAVAILABLE; warnx("rule %lu does not exist", rnum); } } if (co.do_dynamic && ndyn) { printf("## Dynamic rules:\n"); for (lac = ac, lav = av; lac != 0; lac--) { last = rnum = strtoul(*lav++, &endptr, 10); if (*endptr == '-') last = strtoul(endptr+1, &endptr, 10); if (*endptr) /* already warned */ continue; for (n = 0, d = dynrules; n < ndyn; n++, d++) { uint16_t rulenum; bcopy(&d->rule, &rulenum, sizeof(rulenum)); if (rulenum > rnum) break; if (co.use_set) { bcopy((char *)&d->rule + sizeof(uint16_t), &set, sizeof(uint8_t)); if (set != co.use_set - 1) continue; } if (r->rulenum >= rnum && r->rulenum <= last) show_dyn_ipfw(d, pcwidth, bcwidth); } } } ac = 0; done: free(data); if (exitval != EX_OK) exit(exitval); #undef NEXT } static int lookup_host (char *host, struct in_addr *ipaddr) { struct hostent *he; if (!inet_aton(host, ipaddr)) { if ((he = gethostbyname(host)) == NULL) return(-1); *ipaddr = *(struct in_addr *)he->h_addr_list[0]; } return(0); } /* * fills the addr and mask fields in the instruction as appropriate from av. * Update length as appropriate. * The following formats are allowed: * me returns O_IP_*_ME * 1.2.3.4 single IP address * 1.2.3.4:5.6.7.8 address:mask * 1.2.3.4/24 address/mask * 1.2.3.4/26{1,6,5,4,23} set of addresses in a subnet * We can have multiple comma-separated address/mask entries. */ static void fill_ip(ipfw_insn_ip *cmd, char *av) { int len = 0; uint32_t *d = ((ipfw_insn_u32 *)cmd)->d; cmd->o.len &= ~F_LEN_MASK; /* zero len */ if (_substrcmp(av, "any") == 0) return; if (_substrcmp(av, "me") == 0) { cmd->o.len |= F_INSN_SIZE(ipfw_insn); return; } if (strncmp(av, "table(", 6) == 0) { char *p = strchr(av + 6, ','); if (p) *p++ = '\0'; cmd->o.opcode = O_IP_DST_LOOKUP; cmd->o.arg1 = strtoul(av + 6, NULL, 0); if (p) { cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); d[0] = strtoul(p, NULL, 0); } else cmd->o.len |= F_INSN_SIZE(ipfw_insn); return; } while (av) { /* * After the address we can have '/' or ':' indicating a mask, * ',' indicating another address follows, '{' indicating a * set of addresses of unspecified size. */ char *t = NULL, *p = strpbrk(av, "/:,{"); int masklen; char md, nd = '\0'; if (p) { md = *p; *p++ = '\0'; if ((t = strpbrk(p, ",{")) != NULL) { nd = *t; *t = '\0'; } } else md = '\0'; if (lookup_host(av, (struct in_addr *)&d[0]) != 0) errx(EX_NOHOST, "hostname ``%s'' unknown", av); switch (md) { case ':': if (!inet_aton(p, (struct in_addr *)&d[1])) errx(EX_DATAERR, "bad netmask ``%s''", p); break; case '/': masklen = atoi(p); if (masklen == 0) d[1] = htonl(0); /* mask */ else if (masklen > 32) errx(EX_DATAERR, "bad width ``%s''", p); else d[1] = htonl(~0 << (32 - masklen)); break; case '{': /* no mask, assume /24 and put back the '{' */ d[1] = htonl(~0 << (32 - 24)); *(--p) = md; break; case ',': /* single address plus continuation */ *(--p) = md; /* FALLTHROUGH */ case 0: /* initialization value */ default: d[1] = htonl(~0); /* force /32 */ break; } d[0] &= d[1]; /* mask base address with mask */ if (t) *t = nd; /* find next separator */ if (p) p = strpbrk(p, ",{"); if (p && *p == '{') { /* * We have a set of addresses. They are stored as follows: * arg1 is the set size (powers of 2, 2..256) * addr is the base address IN HOST FORMAT * mask.. is an array of arg1 bits (rounded up to * the next multiple of 32) with bits set * for each host in the map. */ uint32_t *map = (uint32_t *)&cmd->mask; int low, high; int i = contigmask((uint8_t *)&(d[1]), 32); if (len > 0) errx(EX_DATAERR, "address set cannot be in a list"); if (i < 24 || i > 31) errx(EX_DATAERR, "invalid set with mask %d\n", i); cmd->o.arg1 = 1<<(32-i); /* map length */ d[0] = ntohl(d[0]); /* base addr in host format */ cmd->o.opcode = O_IP_DST_SET; /* default */ cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + (cmd->o.arg1+31)/32; for (i = 0; i < (cmd->o.arg1+31)/32 ; i++) map[i] = 0; /* clear map */ av = p + 1; low = d[0] & 0xff; high = low + cmd->o.arg1 - 1; /* * Here, i stores the previous value when we specify a range * of addresses within a mask, e.g. 45-63. i = -1 means we * have no previous value. */ i = -1; /* previous value in a range */ while (isdigit(*av)) { char *s; int a = strtol(av, &s, 0); if (s == av) { /* no parameter */ if (*av != '}') errx(EX_DATAERR, "set not closed\n"); if (i != -1) errx(EX_DATAERR, "incomplete range %d-", i); break; } if (a < low || a > high) errx(EX_DATAERR, "addr %d out of range [%d-%d]\n", a, low, high); a -= low; if (i == -1) /* no previous in range */ i = a; else { /* check that range is valid */ if (i > a) errx(EX_DATAERR, "invalid range %d-%d", i+low, a+low); if (*s == '-') errx(EX_DATAERR, "double '-' in range"); } for (; i <= a; i++) map[i/32] |= 1<<(i & 31); i = -1; if (*s == '-') i = a; else if (*s == '}') break; av = s+1; } return; } av = p; if (av) /* then *av must be a ',' */ av++; /* Check this entry */ if (d[1] == 0) { /* "any", specified as x.x.x.x/0 */ /* * 'any' turns the entire list into a NOP. * 'not any' never matches, so it is removed from the * list unless it is the only item, in which case we * report an error. */ if (cmd->o.len & F_NOT) { /* "not any" never matches */ if (av == NULL && len == 0) /* only this entry */ errx(EX_DATAERR, "not any never matches"); } /* else do nothing and skip this entry */ return; } /* A single IP can be stored in an optimized format */ if (d[1] == (uint32_t)~0 && av == NULL && len == 0) { cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); return; } len += 2; /* two words... */ d += 2; } /* end while */ if (len + 1 > F_LEN_MASK) errx(EX_DATAERR, "address list too long"); cmd->o.len |= len+1; } /* n2mask sets n bits of the mask */ void n2mask(struct in6_addr *mask, int n) { static int minimask[9] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; u_char *p; memset(mask, 0, sizeof(struct in6_addr)); p = (u_char *) mask; for (; n > 0; p++, n -= 8) { if (n >= 8) *p = 0xff; else *p = minimask[n]; } return; } /* * helper function to process a set of flags and set bits in the * appropriate masks. */ static void fill_flags(ipfw_insn *cmd, enum ipfw_opcodes opcode, struct _s_x *flags, char *p) { uint8_t set=0, clear=0; while (p && *p) { char *q; /* points to the separator */ int val; uint8_t *which; /* mask we are working on */ if (*p == '!') { p++; which = &clear; } else which = &set; q = strchr(p, ','); if (q) *q++ = '\0'; val = match_token(flags, p); if (val <= 0) errx(EX_DATAERR, "invalid flag %s", p); *which |= (uint8_t)val; p = q; } cmd->opcode = opcode; cmd->len = (cmd->len & (F_NOT | F_OR)) | 1; cmd->arg1 = (set & 0xff) | ( (clear & 0xff) << 8); } void ipfw_delete(char *av[]) { uint32_t rulenum; int i; int exitval = EX_OK; int do_set = 0; av++; NEED1("missing rule specification"); if ( *av && _substrcmp(*av, "set") == 0) { /* Do not allow using the following syntax: * ipfw set N delete set M */ if (co.use_set) errx(EX_DATAERR, "invalid syntax"); do_set = 1; /* delete set */ av++; } /* Rule number */ while (*av && isdigit(**av)) { i = atoi(*av); av++; if (co.do_nat) { exitval = do_cmd(IP_FW_NAT_DEL, &i, sizeof i); if (exitval) { exitval = EX_UNAVAILABLE; warn("rule %u not available", i); } } else if (co.do_pipe) { exitval = ipfw_delete_pipe(co.do_pipe, i); } else { if (co.use_set) rulenum = (i & 0xffff) | (5 << 24) | ((co.use_set - 1) << 16); else rulenum = (i & 0xffff) | (do_set << 24); i = do_cmd(IP_FW_DEL, &rulenum, sizeof rulenum); if (i) { exitval = EX_UNAVAILABLE; warn("rule %u: setsockopt(IP_FW_DEL)", rulenum); } } } if (exitval != EX_OK) exit(exitval); } /* * fill the interface structure. We do not check the name as we can * create interfaces dynamically, so checking them at insert time * makes relatively little sense. * Interface names containing '*', '?', or '[' are assumed to be shell * patterns which match interfaces. */ static void fill_iface(ipfw_insn_if *cmd, char *arg) { cmd->name[0] = '\0'; cmd->o.len |= F_INSN_SIZE(ipfw_insn_if); /* Parse the interface or address */ if (strcmp(arg, "any") == 0) cmd->o.len = 0; /* effectively ignore this command */ else if (!isdigit(*arg)) { strlcpy(cmd->name, arg, sizeof(cmd->name)); cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0; } else if (!inet_aton(arg, &cmd->p.ip)) errx(EX_DATAERR, "bad ip address ``%s''", arg); } static void get_mac_addr_mask(const char *p, uint8_t *addr, uint8_t *mask) { int i; size_t l; char *ap, *ptr, *optr; struct ether_addr *mac; const char *macset = "0123456789abcdefABCDEF:"; if (strcmp(p, "any") == 0) { for (i = 0; i < ETHER_ADDR_LEN; i++) addr[i] = mask[i] = 0; return; } optr = ptr = strdup(p); if ((ap = strsep(&ptr, "&/")) != NULL && *ap != 0) { l = strlen(ap); if (strspn(ap, macset) != l || (mac = ether_aton(ap)) == NULL) errx(EX_DATAERR, "Incorrect MAC address"); bcopy(mac, addr, ETHER_ADDR_LEN); } else errx(EX_DATAERR, "Incorrect MAC address"); if (ptr != NULL) { /* we have mask? */ if (p[ptr - optr - 1] == '/') { /* mask len */ long ml = strtol(ptr, &ap, 10); if (*ap != 0 || ml > ETHER_ADDR_LEN * 8 || ml < 0) errx(EX_DATAERR, "Incorrect mask length"); for (i = 0; ml > 0 && i < ETHER_ADDR_LEN; ml -= 8, i++) mask[i] = (ml >= 8) ? 0xff: (~0) << (8 - ml); } else { /* mask */ l = strlen(ptr); if (strspn(ptr, macset) != l || (mac = ether_aton(ptr)) == NULL) errx(EX_DATAERR, "Incorrect mask"); bcopy(mac, mask, ETHER_ADDR_LEN); } } else { /* default mask: ff:ff:ff:ff:ff:ff */ for (i = 0; i < ETHER_ADDR_LEN; i++) mask[i] = 0xff; } for (i = 0; i < ETHER_ADDR_LEN; i++) addr[i] &= mask[i]; free(optr); } /* * helper function, updates the pointer to cmd with the length * of the current command, and also cleans up the first word of * the new command in case it has been clobbered before. */ static ipfw_insn * next_cmd(ipfw_insn *cmd) { cmd += F_LEN(cmd); bzero(cmd, sizeof(*cmd)); return cmd; } /* * Takes arguments and copies them into a comment */ static void fill_comment(ipfw_insn *cmd, char **av) { int i, l; char *p = (char *)(cmd + 1); cmd->opcode = O_NOP; cmd->len = (cmd->len & (F_NOT | F_OR)); /* Compute length of comment string. */ for (i = 0, l = 0; av[i] != NULL; i++) l += strlen(av[i]) + 1; if (l == 0) return; if (l > 84) errx(EX_DATAERR, "comment too long (max 80 chars)"); l = 1 + (l+3)/4; cmd->len = (cmd->len & (F_NOT | F_OR)) | l; for (i = 0; av[i] != NULL; i++) { strcpy(p, av[i]); p += strlen(av[i]); *p++ = ' '; } *(--p) = '\0'; } /* * A function to fill simple commands of size 1. * Existing flags are preserved. */ static void fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, int flags, uint16_t arg) { cmd->opcode = opcode; cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | 1; cmd->arg1 = arg; } /* * Fetch and add the MAC address and type, with masks. This generates one or * two microinstructions, and returns the pointer to the last one. */ static ipfw_insn * add_mac(ipfw_insn *cmd, char *av[]) { ipfw_insn_mac *mac; if ( ( av[0] == NULL ) || ( av[1] == NULL ) ) errx(EX_DATAERR, "MAC dst src"); cmd->opcode = O_MACADDR2; cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac); mac = (ipfw_insn_mac *)cmd; get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */ get_mac_addr_mask(av[1], &(mac->addr[ETHER_ADDR_LEN]), &(mac->mask[ETHER_ADDR_LEN])); /* src */ return cmd; } static ipfw_insn * add_mactype(ipfw_insn *cmd, char *av) { if (!av) errx(EX_DATAERR, "missing MAC type"); if (strcmp(av, "any") != 0) { /* we have a non-null type */ fill_newports((ipfw_insn_u16 *)cmd, av, IPPROTO_ETHERTYPE); cmd->opcode = O_MAC_TYPE; return cmd; } else return NULL; } static ipfw_insn * add_proto0(ipfw_insn *cmd, char *av, u_char *protop) { struct protoent *pe; char *ep; int proto; proto = strtol(av, &ep, 10); if (*ep != '\0' || proto <= 0) { if ((pe = getprotobyname(av)) == NULL) return NULL; proto = pe->p_proto; } fill_cmd(cmd, O_PROTO, 0, proto); *protop = proto; return cmd; } static ipfw_insn * add_proto(ipfw_insn *cmd, char *av, u_char *protop) { u_char proto = IPPROTO_IP; if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0) ; /* do not set O_IP4 nor O_IP6 */ else if (strcmp(av, "ip4") == 0) /* explicit "just IPv4" rule */ fill_cmd(cmd, O_IP4, 0, 0); else if (strcmp(av, "ip6") == 0) { /* explicit "just IPv6" rule */ proto = IPPROTO_IPV6; fill_cmd(cmd, O_IP6, 0, 0); } else return add_proto0(cmd, av, protop); *protop = proto; return cmd; } static ipfw_insn * add_proto_compat(ipfw_insn *cmd, char *av, u_char *protop) { u_char proto = IPPROTO_IP; if (_substrcmp(av, "all") == 0 || strcmp(av, "ip") == 0) ; /* do not set O_IP4 nor O_IP6 */ else if (strcmp(av, "ipv4") == 0 || strcmp(av, "ip4") == 0) /* explicit "just IPv4" rule */ fill_cmd(cmd, O_IP4, 0, 0); else if (strcmp(av, "ipv6") == 0 || strcmp(av, "ip6") == 0) { /* explicit "just IPv6" rule */ proto = IPPROTO_IPV6; fill_cmd(cmd, O_IP6, 0, 0); } else return add_proto0(cmd, av, protop); *protop = proto; return cmd; } static ipfw_insn * add_srcip(ipfw_insn *cmd, char *av) { fill_ip((ipfw_insn_ip *)cmd, av); if (cmd->opcode == O_IP_DST_SET) /* set */ cmd->opcode = O_IP_SRC_SET; else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ cmd->opcode = O_IP_SRC_LOOKUP; else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ cmd->opcode = O_IP_SRC_ME; else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ cmd->opcode = O_IP_SRC; else /* addr/mask */ cmd->opcode = O_IP_SRC_MASK; return cmd; } static ipfw_insn * add_dstip(ipfw_insn *cmd, char *av) { fill_ip((ipfw_insn_ip *)cmd, av); if (cmd->opcode == O_IP_DST_SET) /* set */ ; else if (cmd->opcode == O_IP_DST_LOOKUP) /* table */ ; else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ cmd->opcode = O_IP_DST_ME; else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ cmd->opcode = O_IP_DST; else /* addr/mask */ cmd->opcode = O_IP_DST_MASK; return cmd; } static ipfw_insn * add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode) { /* XXX "any" is trapped before. Perhaps "to" */ if (_substrcmp(av, "any") == 0) { return NULL; } else if (fill_newports((ipfw_insn_u16 *)cmd, av, proto)) { /* XXX todo: check that we have a protocol with ports */ cmd->opcode = opcode; return cmd; } return NULL; } static ipfw_insn * add_src(ipfw_insn *cmd, char *av, u_char proto) { struct in6_addr a; char *host, *ch; ipfw_insn *ret = NULL; if ((host = strdup(av)) == NULL) return NULL; if ((ch = strrchr(host, '/')) != NULL) *ch = '\0'; if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 || inet_pton(AF_INET6, host, &a) == 1) ret = add_srcip6(cmd, av); /* XXX: should check for IPv4, not !IPv6 */ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || inet_pton(AF_INET6, host, &a) != 1)) ret = add_srcip(cmd, av); if (ret == NULL && strcmp(av, "any") != 0) ret = cmd; free(host); return ret; } static ipfw_insn * add_dst(ipfw_insn *cmd, char *av, u_char proto) { struct in6_addr a; char *host, *ch; ipfw_insn *ret = NULL; if ((host = strdup(av)) == NULL) return NULL; if ((ch = strrchr(host, '/')) != NULL) *ch = '\0'; if (proto == IPPROTO_IPV6 || strcmp(av, "me6") == 0 || inet_pton(AF_INET6, host, &a) == 1) ret = add_dstip6(cmd, av); /* XXX: should check for IPv4, not !IPv6 */ if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 || inet_pton(AF_INET6, host, &a) != 1)) ret = add_dstip(cmd, av); if (ret == NULL && strcmp(av, "any") != 0) ret = cmd; free(host); return ret; } /* * Parse arguments and assemble the microinstructions which make up a rule. * Rules are added into the 'rulebuf' and then copied in the correct order * into the actual rule. * * The syntax for a rule starts with the action, followed by * optional action parameters, and the various match patterns. * In the assembled microcode, the first opcode must be an O_PROBE_STATE * (generated if the rule includes a keep-state option), then the * various match patterns, log/altq actions, and the actual action. * */ void ipfw_add(char *av[]) { /* * rules are added into the 'rulebuf' and then copied in * the correct order into the actual rule. * Some things that need to go out of order (prob, action etc.) * go into actbuf[]. */ static uint32_t rulebuf[255], actbuf[255], cmdbuf[255]; ipfw_insn *src, *dst, *cmd, *action, *prev=NULL; ipfw_insn *first_cmd; /* first match pattern */ struct ip_fw *rule; /* * various flags used to record that we entered some fields. */ ipfw_insn *have_state = NULL; /* check-state or keep-state */ ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL; size_t len; int i; int open_par = 0; /* open parenthesis ( */ /* proto is here because it is used to fetch ports */ u_char proto = IPPROTO_IP; /* default protocol */ double match_prob = 1; /* match probability, default is always match */ bzero(actbuf, sizeof(actbuf)); /* actions go here */ bzero(cmdbuf, sizeof(cmdbuf)); bzero(rulebuf, sizeof(rulebuf)); rule = (struct ip_fw *)rulebuf; cmd = (ipfw_insn *)cmdbuf; action = (ipfw_insn *)actbuf; av++; /* [rule N] -- Rule number optional */ if (av[0] && isdigit(**av)) { rule->rulenum = atoi(*av); av++; } /* [set N] -- set number (0..RESVD_SET), optional */ if (av[0] && av[1] && _substrcmp(*av, "set") == 0) { int set = strtoul(av[1], NULL, 10); if (set < 0 || set > RESVD_SET) errx(EX_DATAERR, "illegal set %s", av[1]); rule->set = set; av += 2; } /* [prob D] -- match probability, optional */ if (av[0] && av[1] && _substrcmp(*av, "prob") == 0) { match_prob = strtod(av[1], NULL); if (match_prob <= 0 || match_prob > 1) errx(EX_DATAERR, "illegal match prob. %s", av[1]); av += 2; } /* action -- mandatory */ NEED1("missing action"); i = match_token(rule_actions, *av); av++; action->len = 1; /* default */ switch(i) { case TOK_CHECKSTATE: have_state = action; action->opcode = O_CHECK_STATE; break; case TOK_ACCEPT: action->opcode = O_ACCEPT; break; case TOK_DENY: action->opcode = O_DENY; action->arg1 = 0; break; case TOK_REJECT: action->opcode = O_REJECT; action->arg1 = ICMP_UNREACH_HOST; break; case TOK_RESET: action->opcode = O_REJECT; action->arg1 = ICMP_REJECT_RST; break; case TOK_RESET6: action->opcode = O_UNREACH6; action->arg1 = ICMP6_UNREACH_RST; break; case TOK_UNREACH: action->opcode = O_REJECT; NEED1("missing reject code"); fill_reject_code(&action->arg1, *av); av++; break; case TOK_UNREACH6: action->opcode = O_UNREACH6; NEED1("missing unreach code"); fill_unreach6_code(&action->arg1, *av); av++; break; case TOK_COUNT: action->opcode = O_COUNT; break; case TOK_NAT: action->opcode = O_NAT; action->len = F_INSN_SIZE(ipfw_insn_nat); if (_substrcmp(*av, "global") == 0) { action->arg1 = 0; av++; break; } else goto chkarg; case TOK_QUEUE: action->opcode = O_QUEUE; goto chkarg; case TOK_PIPE: action->opcode = O_PIPE; goto chkarg; case TOK_SKIPTO: action->opcode = O_SKIPTO; goto chkarg; case TOK_NETGRAPH: action->opcode = O_NETGRAPH; goto chkarg; case TOK_NGTEE: action->opcode = O_NGTEE; goto chkarg; case TOK_DIVERT: action->opcode = O_DIVERT; goto chkarg; case TOK_TEE: action->opcode = O_TEE; goto chkarg; case TOK_CALL: action->opcode = O_CALLRETURN; chkarg: if (!av[0]) errx(EX_USAGE, "missing argument for %s", *(av - 1)); if (isdigit(**av)) { action->arg1 = strtoul(*av, NULL, 10); if (action->arg1 <= 0 || action->arg1 >= IP_FW_TABLEARG) errx(EX_DATAERR, "illegal argument for %s", *(av - 1)); } else if (_substrcmp(*av, "tablearg") == 0) { action->arg1 = IP_FW_TABLEARG; } else if (i == TOK_DIVERT || i == TOK_TEE) { struct servent *s; setservent(1); s = getservbyname(av[0], "divert"); if (s != NULL) action->arg1 = ntohs(s->s_port); else errx(EX_DATAERR, "illegal divert/tee port"); } else errx(EX_DATAERR, "illegal argument for %s", *(av - 1)); av++; break; case TOK_FORWARD: { /* * Locate the address-port separator (':' or ','). * Could be one of the following: * hostname:port * IPv4 a.b.c.d,port * IPv4 a.b.c.d:port * IPv6 w:x:y::z,port * The ':' can only be used with hostname and IPv4 address. * XXX-BZ Should we also support [w:x:y::z]:port? */ struct sockaddr_storage result; struct addrinfo *res; char *s, *end; int family; u_short port_number; NEED1("missing forward address[:port]"); /* * locate the address-port separator (':' or ',') */ s = strchr(*av, ','); if (s == NULL) { /* Distinguish between IPv4:port and IPv6 cases. */ s = strchr(*av, ':'); if (s && strchr(s+1, ':')) s = NULL; /* no port */ } port_number = 0; if (s != NULL) { /* Terminate host portion and set s to start of port. */ *(s++) = '\0'; i = strtoport(s, &end, 0 /* base */, 0 /* proto */); if (s == end) errx(EX_DATAERR, "illegal forwarding port ``%s''", s); port_number = (u_short)i; } if (_substrcmp(*av, "tablearg") == 0) { family = PF_INET; ((struct sockaddr_in*)&result)->sin_addr.s_addr = INADDR_ANY; } else { - /* + /* * Resolve the host name or address to a family and a - * network representation of the addres. + * network representation of the address. */ if (getaddrinfo(*av, NULL, NULL, &res)) errx(EX_DATAERR, NULL); /* Just use the first host in the answer. */ family = res->ai_family; memcpy(&result, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); } if (family == PF_INET) { ipfw_insn_sa *p = (ipfw_insn_sa *)action; action->opcode = O_FORWARD_IP; action->len = F_INSN_SIZE(ipfw_insn_sa); /* * In the kernel we assume AF_INET and use only * sin_port and sin_addr. Remember to set sin_len as * the routing code seems to use it too. */ p->sa.sin_len = sizeof(struct sockaddr_in); p->sa.sin_family = AF_INET; p->sa.sin_port = port_number; p->sa.sin_addr.s_addr = ((struct sockaddr_in *)&result)->sin_addr.s_addr; } else if (family == PF_INET6) { ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action; action->opcode = O_FORWARD_IP6; action->len = F_INSN_SIZE(ipfw_insn_sa6); p->sa.sin6_len = sizeof(struct sockaddr_in6); p->sa.sin6_family = AF_INET6; p->sa.sin6_port = port_number; p->sa.sin6_flowinfo = 0; p->sa.sin6_scope_id = 0; /* No table support for v6 yet. */ bcopy(&((struct sockaddr_in6*)&result)->sin6_addr, &p->sa.sin6_addr, sizeof(p->sa.sin6_addr)); } else { errx(EX_DATAERR, "Invalid address family in forward action"); } av++; break; } case TOK_COMMENT: /* pretend it is a 'count' rule followed by the comment */ action->opcode = O_COUNT; av--; /* go back... */ break; case TOK_SETFIB: { int numfibs; size_t intsize = sizeof(int); action->opcode = O_SETFIB; NEED1("missing fib number"); if (_substrcmp(*av, "tablearg") == 0) { action->arg1 = IP_FW_TABLEARG; } else { action->arg1 = strtoul(*av, NULL, 10); if (sysctlbyname("net.fibs", &numfibs, &intsize, NULL, 0) == -1) errx(EX_DATAERR, "fibs not suported.\n"); if (action->arg1 >= numfibs) /* Temporary */ errx(EX_DATAERR, "fib too large.\n"); } av++; break; } case TOK_REASS: action->opcode = O_REASS; break; case TOK_RETURN: fill_cmd(action, O_CALLRETURN, F_NOT, 0); break; default: errx(EX_DATAERR, "invalid action %s\n", av[-1]); } action = next_cmd(action); /* * [altq queuename] -- altq tag, optional * [log [logamount N]] -- log, optional * * If they exist, it go first in the cmdbuf, but then it is * skipped in the copy section to the end of the buffer. */ while (av[0] != NULL && (i = match_token(rule_action_params, *av)) != -1) { av++; switch (i) { case TOK_LOG: { ipfw_insn_log *c = (ipfw_insn_log *)cmd; int l; if (have_log) errx(EX_DATAERR, "log cannot be specified more than once"); have_log = (ipfw_insn *)c; cmd->len = F_INSN_SIZE(ipfw_insn_log); cmd->opcode = O_LOG; if (av[0] && _substrcmp(*av, "logamount") == 0) { av++; NEED1("logamount requires argument"); l = atoi(*av); if (l < 0) errx(EX_DATAERR, "logamount must be positive"); c->max_log = l; av++; } else { len = sizeof(c->max_log); if (sysctlbyname("net.inet.ip.fw.verbose_limit", &c->max_log, &len, NULL, 0) == -1) errx(1, "sysctlbyname(\"%s\")", "net.inet.ip.fw.verbose_limit"); } } break; #ifndef NO_ALTQ case TOK_ALTQ: { ipfw_insn_altq *a = (ipfw_insn_altq *)cmd; NEED1("missing altq queue name"); if (have_altq) errx(EX_DATAERR, "altq cannot be specified more than once"); have_altq = (ipfw_insn *)a; cmd->len = F_INSN_SIZE(ipfw_insn_altq); cmd->opcode = O_ALTQ; a->qid = altq_name_to_qid(*av); av++; } break; #endif case TOK_TAG: case TOK_UNTAG: { uint16_t tag; if (have_tag) errx(EX_USAGE, "tag and untag cannot be " "specified more than once"); GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, i, rule_action_params); have_tag = cmd; fill_cmd(cmd, O_TAG, (i == TOK_TAG) ? 0: F_NOT, tag); av++; break; } default: abort(); } cmd = next_cmd(cmd); } if (have_state) /* must be a check-state, we are done */ goto done; #define OR_START(target) \ if (av[0] && (*av[0] == '(' || *av[0] == '{')) { \ if (open_par) \ errx(EX_USAGE, "nested \"(\" not allowed\n"); \ prev = NULL; \ open_par = 1; \ if ( (av[0])[1] == '\0') { \ av++; \ } else \ (*av)++; \ } \ target: \ #define CLOSE_PAR \ if (open_par) { \ if (av[0] && ( \ strcmp(*av, ")") == 0 || \ strcmp(*av, "}") == 0)) { \ prev = NULL; \ open_par = 0; \ av++; \ } else \ errx(EX_USAGE, "missing \")\"\n"); \ } #define NOT_BLOCK \ if (av[0] && _substrcmp(*av, "not") == 0) { \ if (cmd->len & F_NOT) \ errx(EX_USAGE, "double \"not\" not allowed\n"); \ cmd->len |= F_NOT; \ av++; \ } #define OR_BLOCK(target) \ if (av[0] && _substrcmp(*av, "or") == 0) { \ if (prev == NULL || open_par == 0) \ errx(EX_DATAERR, "invalid OR block"); \ prev->len |= F_OR; \ av++; \ goto target; \ } \ CLOSE_PAR; first_cmd = cmd; #if 0 /* * MAC addresses, optional. * If we have this, we skip the part "proto from src to dst" * and jump straight to the option parsing. */ NOT_BLOCK; NEED1("missing protocol"); if (_substrcmp(*av, "MAC") == 0 || _substrcmp(*av, "mac") == 0) { av++; /* the "MAC" keyword */ add_mac(cmd, av); /* exits in case of errors */ cmd = next_cmd(cmd); av += 2; /* dst-mac and src-mac */ NOT_BLOCK; NEED1("missing mac type"); if (add_mactype(cmd, av[0])) cmd = next_cmd(cmd); av++; /* any or mac-type */ goto read_options; } #endif /* * protocol, mandatory */ OR_START(get_proto); NOT_BLOCK; NEED1("missing protocol"); if (add_proto_compat(cmd, *av, &proto)) { av++; if (F_LEN(cmd) != 0) { prev = cmd; cmd = next_cmd(cmd); } } else if (first_cmd != cmd) { errx(EX_DATAERR, "invalid protocol ``%s''", *av); } else goto read_options; OR_BLOCK(get_proto); /* * "from", mandatory */ if ((av[0] == NULL) || _substrcmp(*av, "from") != 0) errx(EX_USAGE, "missing ``from''"); av++; /* * source IP, mandatory */ OR_START(source_ip); NOT_BLOCK; /* optional "not" */ NEED1("missing source address"); if (add_src(cmd, *av, proto)) { av++; if (F_LEN(cmd) != 0) { /* ! any */ prev = cmd; cmd = next_cmd(cmd); } } else errx(EX_USAGE, "bad source address %s", *av); OR_BLOCK(source_ip); /* * source ports, optional */ NOT_BLOCK; /* optional "not" */ if ( av[0] != NULL ) { if (_substrcmp(*av, "any") == 0 || add_ports(cmd, *av, proto, O_IP_SRCPORT)) { av++; if (F_LEN(cmd) != 0) cmd = next_cmd(cmd); } } /* * "to", mandatory */ if ( (av[0] == NULL) || _substrcmp(*av, "to") != 0 ) errx(EX_USAGE, "missing ``to''"); av++; /* * destination, mandatory */ OR_START(dest_ip); NOT_BLOCK; /* optional "not" */ NEED1("missing dst address"); if (add_dst(cmd, *av, proto)) { av++; if (F_LEN(cmd) != 0) { /* ! any */ prev = cmd; cmd = next_cmd(cmd); } } else errx( EX_USAGE, "bad destination address %s", *av); OR_BLOCK(dest_ip); /* * dest. ports, optional */ NOT_BLOCK; /* optional "not" */ if (av[0]) { if (_substrcmp(*av, "any") == 0 || add_ports(cmd, *av, proto, O_IP_DSTPORT)) { av++; if (F_LEN(cmd) != 0) cmd = next_cmd(cmd); } } read_options: if (av[0] && first_cmd == cmd) { /* * nothing specified so far, store in the rule to ease * printout later. */ rule->_pad = 1; } prev = NULL; while ( av[0] != NULL ) { char *s; ipfw_insn_u32 *cmd32; /* alias for cmd */ s = *av; cmd32 = (ipfw_insn_u32 *)cmd; if (*s == '!') { /* alternate syntax for NOT */ if (cmd->len & F_NOT) errx(EX_USAGE, "double \"not\" not allowed\n"); cmd->len = F_NOT; s++; } i = match_token(rule_options, s); av++; switch(i) { case TOK_NOT: if (cmd->len & F_NOT) errx(EX_USAGE, "double \"not\" not allowed\n"); cmd->len = F_NOT; break; case TOK_OR: if (open_par == 0 || prev == NULL) errx(EX_USAGE, "invalid \"or\" block\n"); prev->len |= F_OR; break; case TOK_STARTBRACE: if (open_par) errx(EX_USAGE, "+nested \"(\" not allowed\n"); open_par = 1; break; case TOK_ENDBRACE: if (!open_par) errx(EX_USAGE, "+missing \")\"\n"); open_par = 0; prev = NULL; break; case TOK_IN: fill_cmd(cmd, O_IN, 0, 0); break; case TOK_OUT: cmd->len ^= F_NOT; /* toggle F_NOT */ fill_cmd(cmd, O_IN, 0, 0); break; case TOK_DIVERTED: fill_cmd(cmd, O_DIVERTED, 0, 3); break; case TOK_DIVERTEDLOOPBACK: fill_cmd(cmd, O_DIVERTED, 0, 1); break; case TOK_DIVERTEDOUTPUT: fill_cmd(cmd, O_DIVERTED, 0, 2); break; case TOK_FRAG: fill_cmd(cmd, O_FRAG, 0, 0); break; case TOK_LAYER2: fill_cmd(cmd, O_LAYER2, 0, 0); break; case TOK_XMIT: case TOK_RECV: case TOK_VIA: NEED1("recv, xmit, via require interface name" " or address"); fill_iface((ipfw_insn_if *)cmd, av[0]); av++; if (F_LEN(cmd) == 0) /* not a valid address */ break; if (i == TOK_XMIT) cmd->opcode = O_XMIT; else if (i == TOK_RECV) cmd->opcode = O_RECV; else if (i == TOK_VIA) cmd->opcode = O_VIA; break; case TOK_ICMPTYPES: NEED1("icmptypes requires list of types"); fill_icmptypes((ipfw_insn_u32 *)cmd, *av); av++; break; case TOK_ICMP6TYPES: NEED1("icmptypes requires list of types"); fill_icmp6types((ipfw_insn_icmp6 *)cmd, *av); av++; break; case TOK_IPTTL: NEED1("ipttl requires TTL"); if (strpbrk(*av, "-,")) { if (!add_ports(cmd, *av, 0, O_IPTTL)) errx(EX_DATAERR, "invalid ipttl %s", *av); } else fill_cmd(cmd, O_IPTTL, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_IPID: NEED1("ipid requires id"); if (strpbrk(*av, "-,")) { if (!add_ports(cmd, *av, 0, O_IPID)) errx(EX_DATAERR, "invalid ipid %s", *av); } else fill_cmd(cmd, O_IPID, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_IPLEN: NEED1("iplen requires length"); if (strpbrk(*av, "-,")) { if (!add_ports(cmd, *av, 0, O_IPLEN)) errx(EX_DATAERR, "invalid ip len %s", *av); } else fill_cmd(cmd, O_IPLEN, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_IPVER: NEED1("ipver requires version"); fill_cmd(cmd, O_IPVER, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_IPPRECEDENCE: NEED1("ipprecedence requires value"); fill_cmd(cmd, O_IPPRECEDENCE, 0, (strtoul(*av, NULL, 0) & 7) << 5); av++; break; case TOK_IPOPTS: NEED1("missing argument for ipoptions"); fill_flags(cmd, O_IPOPT, f_ipopts, *av); av++; break; case TOK_IPTOS: NEED1("missing argument for iptos"); fill_flags(cmd, O_IPTOS, f_iptos, *av); av++; break; case TOK_UID: NEED1("uid requires argument"); { char *end; uid_t uid; struct passwd *pwd; cmd->opcode = O_UID; uid = strtoul(*av, &end, 0); pwd = (*end == '\0') ? getpwuid(uid) : getpwnam(*av); if (pwd == NULL) errx(EX_DATAERR, "uid \"%s\" nonexistent", *av); cmd32->d[0] = pwd->pw_uid; cmd->len |= F_INSN_SIZE(ipfw_insn_u32); av++; } break; case TOK_GID: NEED1("gid requires argument"); { char *end; gid_t gid; struct group *grp; cmd->opcode = O_GID; gid = strtoul(*av, &end, 0); grp = (*end == '\0') ? getgrgid(gid) : getgrnam(*av); if (grp == NULL) errx(EX_DATAERR, "gid \"%s\" nonexistent", *av); cmd32->d[0] = grp->gr_gid; cmd->len |= F_INSN_SIZE(ipfw_insn_u32); av++; } break; case TOK_JAIL: NEED1("jail requires argument"); { char *end; int jid; cmd->opcode = O_JAIL; jid = (int)strtol(*av, &end, 0); if (jid < 0 || *end != '\0') errx(EX_DATAERR, "jail requires prison ID"); cmd32->d[0] = (uint32_t)jid; cmd->len |= F_INSN_SIZE(ipfw_insn_u32); av++; } break; case TOK_ESTAB: fill_cmd(cmd, O_ESTAB, 0, 0); break; case TOK_SETUP: fill_cmd(cmd, O_TCPFLAGS, 0, (TH_SYN) | ( (TH_ACK) & 0xff) <<8 ); break; case TOK_TCPDATALEN: NEED1("tcpdatalen requires length"); if (strpbrk(*av, "-,")) { if (!add_ports(cmd, *av, 0, O_TCPDATALEN)) errx(EX_DATAERR, "invalid tcpdata len %s", *av); } else fill_cmd(cmd, O_TCPDATALEN, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_TCPOPTS: NEED1("missing argument for tcpoptions"); fill_flags(cmd, O_TCPOPTS, f_tcpopts, *av); av++; break; case TOK_TCPSEQ: case TOK_TCPACK: NEED1("tcpseq/tcpack requires argument"); cmd->len = F_INSN_SIZE(ipfw_insn_u32); cmd->opcode = (i == TOK_TCPSEQ) ? O_TCPSEQ : O_TCPACK; cmd32->d[0] = htonl(strtoul(*av, NULL, 0)); av++; break; case TOK_TCPWIN: NEED1("tcpwin requires length"); fill_cmd(cmd, O_TCPWIN, 0, htons(strtoul(*av, NULL, 0))); av++; break; case TOK_TCPFLAGS: NEED1("missing argument for tcpflags"); cmd->opcode = O_TCPFLAGS; fill_flags(cmd, O_TCPFLAGS, f_tcpflags, *av); av++; break; case TOK_KEEPSTATE: if (open_par) errx(EX_USAGE, "keep-state cannot be part " "of an or block"); if (have_state) errx(EX_USAGE, "only one of keep-state " "and limit is allowed"); have_state = cmd; fill_cmd(cmd, O_KEEP_STATE, 0, 0); break; case TOK_LIMIT: { ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; int val; if (open_par) errx(EX_USAGE, "limit cannot be part of an or block"); if (have_state) errx(EX_USAGE, "only one of keep-state and " "limit is allowed"); have_state = cmd; cmd->len = F_INSN_SIZE(ipfw_insn_limit); cmd->opcode = O_LIMIT; c->limit_mask = c->conn_limit = 0; while ( av[0] != NULL ) { if ((val = match_token(limit_masks, *av)) <= 0) break; c->limit_mask |= val; av++; } if (c->limit_mask == 0) errx(EX_USAGE, "limit: missing limit mask"); GET_UINT_ARG(c->conn_limit, IPFW_ARG_MIN, IPFW_ARG_MAX, TOK_LIMIT, rule_options); av++; break; } case TOK_PROTO: NEED1("missing protocol"); if (add_proto(cmd, *av, &proto)) { av++; } else errx(EX_DATAERR, "invalid protocol ``%s''", *av); break; case TOK_SRCIP: NEED1("missing source IP"); if (add_srcip(cmd, *av)) { av++; } break; case TOK_DSTIP: NEED1("missing destination IP"); if (add_dstip(cmd, *av)) { av++; } break; case TOK_SRCIP6: NEED1("missing source IP6"); if (add_srcip6(cmd, *av)) { av++; } break; case TOK_DSTIP6: NEED1("missing destination IP6"); if (add_dstip6(cmd, *av)) { av++; } break; case TOK_SRCPORT: NEED1("missing source port"); if (_substrcmp(*av, "any") == 0 || add_ports(cmd, *av, proto, O_IP_SRCPORT)) { av++; } else errx(EX_DATAERR, "invalid source port %s", *av); break; case TOK_DSTPORT: NEED1("missing destination port"); if (_substrcmp(*av, "any") == 0 || add_ports(cmd, *av, proto, O_IP_DSTPORT)) { av++; } else errx(EX_DATAERR, "invalid destination port %s", *av); break; case TOK_MAC: if (add_mac(cmd, av)) av += 2; break; case TOK_MACTYPE: NEED1("missing mac type"); if (!add_mactype(cmd, *av)) errx(EX_DATAERR, "invalid mac type %s", *av); av++; break; case TOK_VERREVPATH: fill_cmd(cmd, O_VERREVPATH, 0, 0); break; case TOK_VERSRCREACH: fill_cmd(cmd, O_VERSRCREACH, 0, 0); break; case TOK_ANTISPOOF: fill_cmd(cmd, O_ANTISPOOF, 0, 0); break; case TOK_IPSEC: fill_cmd(cmd, O_IPSEC, 0, 0); break; case TOK_IPV6: fill_cmd(cmd, O_IP6, 0, 0); break; case TOK_IPV4: fill_cmd(cmd, O_IP4, 0, 0); break; case TOK_EXT6HDR: fill_ext6hdr( cmd, *av ); av++; break; case TOK_FLOWID: if (proto != IPPROTO_IPV6 ) errx( EX_USAGE, "flow-id filter is active " "only for ipv6 protocol\n"); fill_flow6( (ipfw_insn_u32 *) cmd, *av ); av++; break; case TOK_COMMENT: fill_comment(cmd, av); av[0]=NULL; break; case TOK_TAGGED: if (av[0] && strpbrk(*av, "-,")) { if (!add_ports(cmd, *av, 0, O_TAGGED)) errx(EX_DATAERR, "tagged: invalid tag" " list: %s", *av); } else { uint16_t tag; GET_UINT_ARG(tag, IPFW_ARG_MIN, IPFW_ARG_MAX, TOK_TAGGED, rule_options); fill_cmd(cmd, O_TAGGED, 0, tag); } av++; break; case TOK_FIB: NEED1("fib requires fib number"); fill_cmd(cmd, O_FIB, 0, strtoul(*av, NULL, 0)); av++; break; case TOK_SOCKARG: fill_cmd(cmd, O_SOCKARG, 0, 0); break; case TOK_LOOKUP: { ipfw_insn_u32 *c = (ipfw_insn_u32 *)cmd; char *p; int j; if (!av[0] || !av[1]) errx(EX_USAGE, "format: lookup argument tablenum"); cmd->opcode = O_IP_DST_LOOKUP; cmd->len |= F_INSN_SIZE(ipfw_insn) + 2; i = match_token(rule_options, *av); for (j = 0; lookup_key[j] >= 0 ; j++) { if (i == lookup_key[j]) break; } if (lookup_key[j] <= 0) errx(EX_USAGE, "format: cannot lookup on %s", *av); __PAST_END(c->d, 1) = j; // i converted to option av++; cmd->arg1 = strtoul(*av, &p, 0); if (p && *p) errx(EX_USAGE, "format: lookup argument tablenum"); av++; } break; default: errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s); } if (F_LEN(cmd) > 0) { /* prepare to advance */ prev = cmd; cmd = next_cmd(cmd); } } done: /* * Now copy stuff into the rule. * If we have a keep-state option, the first instruction * must be a PROBE_STATE (which is generated here). * If we have a LOG option, it was stored as the first command, * and now must be moved to the top of the action part. */ dst = (ipfw_insn *)rule->cmd; /* * First thing to write into the command stream is the match probability. */ if (match_prob != 1) { /* 1 means always match */ dst->opcode = O_PROB; dst->len = 2; *((int32_t *)(dst+1)) = (int32_t)(match_prob * 0x7fffffff); dst += dst->len; } /* * generate O_PROBE_STATE if necessary */ if (have_state && have_state->opcode != O_CHECK_STATE) { fill_cmd(dst, O_PROBE_STATE, 0, 0); dst = next_cmd(dst); } /* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG */ for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) { i = F_LEN(src); switch (src->opcode) { case O_LOG: case O_KEEP_STATE: case O_LIMIT: case O_ALTQ: case O_TAG: break; default: bcopy(src, dst, i * sizeof(uint32_t)); dst += i; } } /* * put back the have_state command as last opcode */ if (have_state && have_state->opcode != O_CHECK_STATE) { i = F_LEN(have_state); bcopy(have_state, dst, i * sizeof(uint32_t)); dst += i; } /* * start action section */ rule->act_ofs = dst - rule->cmd; /* put back O_LOG, O_ALTQ, O_TAG if necessary */ if (have_log) { i = F_LEN(have_log); bcopy(have_log, dst, i * sizeof(uint32_t)); dst += i; } if (have_altq) { i = F_LEN(have_altq); bcopy(have_altq, dst, i * sizeof(uint32_t)); dst += i; } if (have_tag) { i = F_LEN(have_tag); bcopy(have_tag, dst, i * sizeof(uint32_t)); dst += i; } /* * copy all other actions */ for (src = (ipfw_insn *)actbuf; src != action; src += i) { i = F_LEN(src); bcopy(src, dst, i * sizeof(uint32_t)); dst += i; } rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd); i = (char *)dst - (char *)rule; if (do_cmd(IP_FW_ADD, rule, (uintptr_t)&i) == -1) err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD"); if (!co.do_quiet) show_ipfw(rule, 0, 0); } /* * clear the counters or the log counters. */ void ipfw_zero(int ac, char *av[], int optname /* 0 = IP_FW_ZERO, 1 = IP_FW_RESETLOG */) { uint32_t arg, saved_arg; int failed = EX_OK; char const *errstr; char const *name = optname ? "RESETLOG" : "ZERO"; optname = optname ? IP_FW_RESETLOG : IP_FW_ZERO; av++; ac--; if (!ac) { /* clear all entries */ if (do_cmd(optname, NULL, 0) < 0) err(EX_UNAVAILABLE, "setsockopt(IP_FW_%s)", name); if (!co.do_quiet) printf("%s.\n", optname == IP_FW_ZERO ? "Accounting cleared":"Logging counts reset"); return; } while (ac) { /* Rule number */ if (isdigit(**av)) { arg = strtonum(*av, 0, 0xffff, &errstr); if (errstr) errx(EX_DATAERR, "invalid rule number %s\n", *av); saved_arg = arg; if (co.use_set) arg |= (1 << 24) | ((co.use_set - 1) << 16); av++; ac--; if (do_cmd(optname, &arg, sizeof(arg))) { warn("rule %u: setsockopt(IP_FW_%s)", saved_arg, name); failed = EX_UNAVAILABLE; } else if (!co.do_quiet) printf("Entry %d %s.\n", saved_arg, optname == IP_FW_ZERO ? "cleared" : "logging count reset"); } else { errx(EX_USAGE, "invalid rule number ``%s''", *av); } } if (failed != EX_OK) exit(failed); } void ipfw_flush(int force) { int cmd = co.do_pipe ? IP_DUMMYNET_FLUSH : IP_FW_FLUSH; if (!force && !co.do_quiet) { /* need to ask user */ int c; printf("Are you sure? [yn] "); fflush(stdout); do { c = toupper(getc(stdin)); while (c != '\n' && getc(stdin) != '\n') if (feof(stdin)) return; /* and do not flush */ } while (c != 'Y' && c != 'N'); printf("\n"); if (c == 'N') /* user said no */ return; } if (co.do_pipe) { dummynet_flush(); return; } /* `ipfw set N flush` - is the same that `ipfw delete set N` */ if (co.use_set) { uint32_t arg = ((co.use_set - 1) & 0xffff) | (1 << 24); if (do_cmd(IP_FW_DEL, &arg, sizeof(arg)) < 0) err(EX_UNAVAILABLE, "setsockopt(IP_FW_DEL)"); } else if (do_cmd(cmd, NULL, 0) < 0) err(EX_UNAVAILABLE, "setsockopt(IP_%s_FLUSH)", co.do_pipe ? "DUMMYNET" : "FW"); if (!co.do_quiet) printf("Flushed all %s.\n", co.do_pipe ? "pipes" : "rules"); } static void table_list(ipfw_table_entry ent, int need_header); /* * This one handles all table-related commands * ipfw table N add addr[/masklen] [value] * ipfw table N delete addr[/masklen] * ipfw table {N | all} flush * ipfw table {N | all} list */ void ipfw_table_handler(int ac, char *av[]) { ipfw_table_entry ent; int do_add; int is_all; size_t len; char *p; uint32_t a; uint32_t tables_max; len = sizeof(tables_max); if (sysctlbyname("net.inet.ip.fw.tables_max", &tables_max, &len, NULL, 0) == -1) { #ifdef IPFW_TABLES_MAX warn("Warn: Failed to get the max tables number via sysctl. " "Using the compiled in defaults. \nThe reason was"); tables_max = IPFW_TABLES_MAX; #else errx(1, "Failed sysctlbyname(\"net.inet.ip.fw.tables_max\")"); #endif } ac--; av++; if (ac && isdigit(**av)) { ent.tbl = atoi(*av); is_all = 0; ac--; av++; } else if (ac && _substrcmp(*av, "all") == 0) { ent.tbl = 0; is_all = 1; ac--; av++; } else errx(EX_USAGE, "table number or 'all' keyword required"); if (ent.tbl >= tables_max) errx(EX_USAGE, "The table number exceeds the maximum allowed " "value (%d)", tables_max - 1); NEED1("table needs command"); if (is_all && _substrcmp(*av, "list") != 0 && _substrcmp(*av, "flush") != 0) errx(EX_USAGE, "table number required"); if (_substrcmp(*av, "add") == 0 || _substrcmp(*av, "delete") == 0) { do_add = **av == 'a'; ac--; av++; if (!ac) errx(EX_USAGE, "IP address required"); p = strchr(*av, '/'); if (p) { *p++ = '\0'; ent.masklen = atoi(p); if (ent.masklen > 32) errx(EX_DATAERR, "bad width ``%s''", p); } else ent.masklen = 32; if (lookup_host(*av, (struct in_addr *)&ent.addr) != 0) errx(EX_NOHOST, "hostname ``%s'' unknown", *av); ac--; av++; if (do_add && ac) { unsigned int tval; /* isdigit is a bit of a hack here.. */ if (strchr(*av, (int)'.') == NULL && isdigit(**av)) { ent.value = strtoul(*av, NULL, 0); } else { if (lookup_host(*av, (struct in_addr *)&tval) == 0) { /* The value must be stored in host order * * so that the values < 65k can be distinguished */ ent.value = ntohl(tval); } else { errx(EX_NOHOST, "hostname ``%s'' unknown", *av); } } } else ent.value = 0; if (do_cmd(do_add ? IP_FW_TABLE_ADD : IP_FW_TABLE_DEL, &ent, sizeof(ent)) < 0) { /* If running silent, don't bomb out on these errors. */ if (!(co.do_quiet && (errno == (do_add ? EEXIST : ESRCH)))) err(EX_OSERR, "setsockopt(IP_FW_TABLE_%s)", do_add ? "ADD" : "DEL"); /* In silent mode, react to a failed add by deleting */ if (do_add) { do_cmd(IP_FW_TABLE_DEL, &ent, sizeof(ent)); if (do_cmd(IP_FW_TABLE_ADD, &ent, sizeof(ent)) < 0) err(EX_OSERR, "setsockopt(IP_FW_TABLE_ADD)"); } } } else if (_substrcmp(*av, "flush") == 0) { a = is_all ? tables_max : (uint32_t)(ent.tbl + 1); do { if (do_cmd(IP_FW_TABLE_FLUSH, &ent.tbl, sizeof(ent.tbl)) < 0) err(EX_OSERR, "setsockopt(IP_FW_TABLE_FLUSH)"); } while (++ent.tbl < a); } else if (_substrcmp(*av, "list") == 0) { a = is_all ? tables_max : (uint32_t)(ent.tbl + 1); do { table_list(ent, is_all); } while (++ent.tbl < a); } else errx(EX_USAGE, "invalid table command %s", *av); } static void table_list(ipfw_table_entry ent, int need_header) { ipfw_table *tbl; socklen_t l; uint32_t a; a = ent.tbl; l = sizeof(a); if (do_cmd(IP_FW_TABLE_GETSIZE, &a, (uintptr_t)&l) < 0) err(EX_OSERR, "getsockopt(IP_FW_TABLE_GETSIZE)"); /* If a is zero we have nothing to do, the table is empty. */ if (a == 0) return; l = sizeof(*tbl) + a * sizeof(ipfw_table_entry); tbl = safe_calloc(1, l); tbl->tbl = ent.tbl; if (do_cmd(IP_FW_TABLE_LIST, tbl, (uintptr_t)&l) < 0) err(EX_OSERR, "getsockopt(IP_FW_TABLE_LIST)"); if (tbl->cnt && need_header) printf("---table(%d)---\n", tbl->tbl); for (a = 0; a < tbl->cnt; a++) { unsigned int tval; tval = tbl->ent[a].value; if (co.do_value_as_ip) { char tbuf[128]; strncpy(tbuf, inet_ntoa(*(struct in_addr *) &tbl->ent[a].addr), 127); /* inet_ntoa expects network order */ tval = htonl(tval); printf("%s/%u %s\n", tbuf, tbl->ent[a].masklen, inet_ntoa(*(struct in_addr *)&tval)); } else { printf("%s/%u %u\n", inet_ntoa(*(struct in_addr *)&tbl->ent[a].addr), tbl->ent[a].masklen, tval); } } free(tbl); } Index: head/sbin/ipfw/main.c =================================================================== --- head/sbin/ipfw/main.c (revision 229777) +++ head/sbin/ipfw/main.c (revision 229778) @@ -1,624 +1,624 @@ /* * Copyright (c) 2002-2003,2010 Luigi Rizzo * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp * Copyright (c) 1994 Ugen J.S.Antsilevich * * Idea and grammar partially left from: * Copyright (c) 1993 Daniel Boulet * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * * Command line interface for IP firewall facility * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include "ipfw2.h" static void help(void) { fprintf(stderr, "ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n" "\tipfw [-abcdefhnNqStTv] \n\n" "where is one of the following:\n\n" "add [num] [set N] [prob x] RULE-BODY\n" "{pipe|queue} N config PIPE-BODY\n" "[pipe|queue] {zero|delete|show} [N{,N}]\n" "nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|reset|\n" " reverse|proxy_only|redirect_addr linkspec|\n" " redirect_port linkspec|redirect_proto linkspec}\n" "set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n" "set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n" "table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n" "table all {flush | list}\n" "\n" "RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n" "ACTION: check-state | allow | count | deny | unreach{,6} CODE |\n" " skipto N | {divert|tee} PORT | forward ADDR |\n" " pipe N | queue N | nat N | setfib FIB | reass\n" "PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n" "ADDR: [ MAC dst src ether_type ] \n" " [ ip from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n" " [ ipv6|ip6 from IP6ADDR [ PORT ] to IP6ADDR [ PORTLIST ] ]\n" "IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n" "IP6ADDR: [not] { any | me | me6 | ip6/bits | IP6LIST }\n" "IP6LIST: { ip6 | ip6/bits }[,IP6LIST]\n" "IPLIST: { ip | ip/bits | ip:mask }[,IPLIST]\n" "OPTION_LIST: OPTION [OPTION_LIST]\n" "OPTION: bridged | diverted | diverted-loopback | diverted-output |\n" " {dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n" " {dst-port|src-port} LIST |\n" " estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n" " iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n" " ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n" " icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n" " mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n" " setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n" " tcpdatalen LIST | verrevpath | versrcreach | antispoof\n" ); exit(0); } /* * Called with the arguments, including program name because getopt * wants it to be present. * Returns 0 if successful, 1 if empty command, errx() in case of errors. * First thing we do is process parameters creating an argv[] array * which includes the program name and a NULL entry at the end. * If we are called with a single string, we split it on whitespace. * Also, arguments with a trailing ',' are joined to the next one. * The pointers (av[]) and data are in a single chunk of memory. * av[0] points to the original program name, all other entries * point into the allocated chunk. */ static int ipfw_main(int oldac, char **oldav) { int ch, ac; const char *errstr; char **av, **save_av; int do_acct = 0; /* Show packet/byte count */ int try_next = 0; /* set if pipe cmd not found */ int av_size; /* compute the av size */ char *av_p; /* used to build the av list */ #define WHITESP " \t\f\v\n\r" if (oldac < 2) return 1; /* need at least one argument */ if (oldac == 2) { /* * If we are called with one argument, try to split it into * words for subsequent parsing. Spaces after a ',' are * removed by copying the string in-place. */ char *arg = oldav[1]; /* The string is the first arg. */ int l = strlen(arg); int copy = 0; /* 1 if we need to copy, 0 otherwise */ int i, j; for (i = j = 0; i < l; i++) { if (arg[i] == '#') /* comment marker */ break; if (copy) { arg[j++] = arg[i]; copy = !strchr("," WHITESP, arg[i]); } else { copy = !strchr(WHITESP, arg[i]); if (copy) arg[j++] = arg[i]; } } if (!copy && j > 0) /* last char was a 'blank', remove it */ j--; l = j; /* the new argument length */ arg[j++] = '\0'; if (l == 0) /* empty string! */ return 1; /* * First, count number of arguments. Because of the previous * processing, this is just the number of blanks plus 1. */ for (i = 0, ac = 1; i < l; i++) if (strchr(WHITESP, arg[i]) != NULL) ac++; /* * Allocate the argument list structure as a single block * of memory, containing pointers and the argument * strings. We include one entry for the program name * because getopt expects it, and a NULL at the end * to simplify further parsing. */ ac++; /* add 1 for the program name */ av_size = (ac+1) * sizeof(char *) + l + 1; av = safe_calloc(av_size, 1); /* * Init the argument pointer to the end of the array * and copy arguments from arg[] to av[]. For each one, * j is the initial character, i is the one past the end. */ av_p = (char *)&av[ac+1]; for (ac = 1, i = j = 0; i < l; i++) { if (strchr(WHITESP, arg[i]) != NULL || i == l-1) { if (i == l-1) i++; bcopy(arg+j, av_p, i-j); av[ac] = av_p; - av_p += i-j; /* the lenght of the string */ + av_p += i-j; /* the length of the string */ *av_p++ = '\0'; ac++; j = i + 1; } } } else { /* * If an argument ends with ',' join with the next one. */ int first, i, l=0; /* * Allocate the argument list structure as a single block * of memory, containing both pointers and the argument * strings. We include some space for the program name * because getopt expects it. * We add an extra pointer to the end of the array, * to make simpler further parsing. */ for (i=0; i= 2 && !strcmp(av[1], "sysctl")) { char *s; int i; if (ac != 3) { printf( "sysctl emulation usage:\n" " ipfw sysctl name[=value]\n" " ipfw sysctl -a\n"); return 0; } s = strchr(av[2], '='); if (s == NULL) { s = !strcmp(av[2], "-a") ? NULL : av[2]; sysctlbyname(s, NULL, NULL, NULL, 0); } else { /* ipfw sysctl x.y.z=value */ /* assume an INT value, will extend later */ if (s[1] == '\0') { printf("ipfw sysctl: missing value\n\n"); return 0; } *s = '\0'; i = strtol(s+1, NULL, 0); sysctlbyname(av[2], NULL, NULL, &i, sizeof(int)); } return 0; } #endif /* Save arguments for final freeing of memory. */ save_av = av; optind = optreset = 1; /* restart getopt() */ while ((ch = getopt(ac, av, "abcdefhinNp:qs:STtv")) != -1) switch (ch) { case 'a': do_acct = 1; break; case 'b': co.comment_only = 1; co.do_compact = 1; break; case 'c': co.do_compact = 1; break; case 'd': co.do_dynamic = 1; break; case 'e': co.do_expired = 1; break; case 'f': co.do_force = 1; break; case 'h': /* help */ free(save_av); help(); break; /* NOTREACHED */ case 'i': co.do_value_as_ip = 1; break; case 'n': co.test_only = 1; break; case 'N': co.do_resolv = 1; break; case 'p': errx(EX_USAGE, "An absolute pathname must be used " "with -p option."); /* NOTREACHED */ case 'q': co.do_quiet = 1; break; case 's': /* sort */ co.do_sort = atoi(optarg); break; case 'S': co.show_sets = 1; break; case 't': co.do_time = 1; break; case 'T': co.do_time = 2; /* numeric timestamp */ break; case 'v': /* verbose */ co.verbose = 1; break; default: free(save_av); return 1; } ac -= optind; av += optind; NEED1("bad arguments, for usage summary ``ipfw''"); /* * An undocumented behaviour of ipfw1 was to allow rule numbers first, * e.g. "100 add allow ..." instead of "add 100 allow ...". * In case, swap first and second argument to get the normal form. */ if (ac > 1 && isdigit(*av[0])) { char *p = av[0]; av[0] = av[1]; av[1] = p; } /* * Optional: pipe, queue or nat. */ co.do_nat = 0; co.do_pipe = 0; co.use_set = 0; if (!strncmp(*av, "nat", strlen(*av))) co.do_nat = 1; else if (!strncmp(*av, "pipe", strlen(*av))) co.do_pipe = 1; else if (_substrcmp(*av, "queue") == 0) co.do_pipe = 2; else if (_substrcmp(*av, "flowset") == 0) co.do_pipe = 2; else if (_substrcmp(*av, "sched") == 0) co.do_pipe = 3; else if (!strncmp(*av, "set", strlen(*av))) { if (ac > 1 && isdigit(av[1][0])) { co.use_set = strtonum(av[1], 0, resvd_set_number, &errstr); if (errstr) errx(EX_DATAERR, "invalid set number %s\n", av[1]); ac -= 2; av += 2; co.use_set++; } } if (co.do_pipe || co.do_nat) { ac--; av++; } NEED1("missing command"); /* * For pipes, queues and nats we normally say 'nat|pipe NN config' * but the code is easier to parse as 'nat|pipe config NN' * so we swap the two arguments. */ if ((co.do_pipe || co.do_nat) && ac > 1 && isdigit(*av[0])) { char *p = av[0]; av[0] = av[1]; av[1] = p; } if (co.use_set == 0) { if (_substrcmp(*av, "add") == 0) ipfw_add(av); else if (co.do_nat && _substrcmp(*av, "show") == 0) ipfw_show_nat(ac, av); else if (co.do_pipe && _substrcmp(*av, "config") == 0) ipfw_config_pipe(ac, av); else if (co.do_nat && _substrcmp(*av, "config") == 0) ipfw_config_nat(ac, av); else if (_substrcmp(*av, "set") == 0) ipfw_sets_handler(av); else if (_substrcmp(*av, "table") == 0) ipfw_table_handler(ac, av); else if (_substrcmp(*av, "enable") == 0) ipfw_sysctl_handler(av, 1); else if (_substrcmp(*av, "disable") == 0) ipfw_sysctl_handler(av, 0); else try_next = 1; } if (co.use_set || try_next) { if (_substrcmp(*av, "delete") == 0) ipfw_delete(av); else if (_substrcmp(*av, "flush") == 0) ipfw_flush(co.do_force); else if (_substrcmp(*av, "zero") == 0) ipfw_zero(ac, av, 0 /* IP_FW_ZERO */); else if (_substrcmp(*av, "resetlog") == 0) ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */); else if (_substrcmp(*av, "print") == 0 || _substrcmp(*av, "list") == 0) ipfw_list(ac, av, do_acct); else if (_substrcmp(*av, "show") == 0) ipfw_list(ac, av, 1 /* show counters */); else errx(EX_USAGE, "bad command `%s'", *av); } /* Free memory allocated in the argument parsing. */ free(save_av); return 0; } static void ipfw_readfile(int ac, char *av[]) { #define MAX_ARGS 32 char buf[4096]; char *progname = av[0]; /* original program name */ const char *cmd = NULL; /* preprocessor name, if any */ const char *filename = av[ac-1]; /* file to read */ int c, lineno=0; FILE *f = NULL; pid_t preproc = 0; while ((c = getopt(ac, av, "cfNnp:qS")) != -1) { switch(c) { case 'c': co.do_compact = 1; break; case 'f': co.do_force = 1; break; case 'N': co.do_resolv = 1; break; case 'n': co.test_only = 1; break; case 'p': /* * ipfw -p cmd [args] filename * * We are done with getopt(). All arguments * except the filename go to the preprocessor, * so we need to do the following: * - check that a filename is actually present; * - advance av by optind-1 to skip arguments * already processed; * - decrease ac by optind, to remove the args * already processed and the final filename; * - set the last entry in av[] to NULL so * popen() can detect the end of the array; * - set optind=ac to let getopt() terminate. */ if (optind == ac) errx(EX_USAGE, "no filename argument"); cmd = optarg; av[ac-1] = NULL; av += optind - 1; ac -= optind; optind = ac; break; case 'q': co.do_quiet = 1; break; case 'S': co.show_sets = 1; break; default: errx(EX_USAGE, "bad arguments, for usage" " summary ``ipfw''"); } } if (cmd == NULL && ac != optind + 1) errx(EX_USAGE, "extraneous filename arguments %s", av[ac-1]); if ((f = fopen(filename, "r")) == NULL) err(EX_UNAVAILABLE, "fopen: %s", filename); if (cmd != NULL) { /* pipe through preprocessor */ int pipedes[2]; if (pipe(pipedes) == -1) err(EX_OSERR, "cannot create pipe"); preproc = fork(); if (preproc == -1) err(EX_OSERR, "cannot fork"); if (preproc == 0) { /* * Child, will run the preprocessor with the * file on stdin and the pipe on stdout. */ if (dup2(fileno(f), 0) == -1 || dup2(pipedes[1], 1) == -1) err(EX_OSERR, "dup2()"); fclose(f); close(pipedes[1]); close(pipedes[0]); execvp(cmd, av); err(EX_OSERR, "execvp(%s) failed", cmd); } else { /* parent, will reopen f as the pipe */ fclose(f); close(pipedes[1]); if ((f = fdopen(pipedes[0], "r")) == NULL) { int savederrno = errno; (void)kill(preproc, SIGTERM); errno = savederrno; err(EX_OSERR, "fdopen()"); } } } while (fgets(buf, sizeof(buf), f)) { /* read commands */ char linename[20]; char *args[2]; lineno++; snprintf(linename, sizeof(linename), "Line %d", lineno); setprogname(linename); /* XXX */ args[0] = progname; args[1] = buf; ipfw_main(2, args); } fclose(f); if (cmd != NULL) { int status; if (waitpid(preproc, &status, 0) == -1) errx(EX_OSERR, "waitpid()"); if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK) errx(EX_UNAVAILABLE, "preprocessor exited with status %d", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) errx(EX_UNAVAILABLE, "preprocessor exited with signal %d", WTERMSIG(status)); } } int main(int ac, char *av[]) { #if defined(_WIN32) && defined(TCC) { WSADATA wsaData; int ret=0; unsigned short wVersionRequested = MAKEWORD(2, 2); ret = WSAStartup(wVersionRequested, &wsaData); if (ret != 0) { /* Tell the user that we could not find a usable */ /* Winsock DLL. */ printf("WSAStartup failed with error: %d\n", ret); return 1; } } #endif /* * If the last argument is an absolute pathname, interpret it * as a file to be preprocessed. */ if (ac > 1 && av[ac - 1][0] == '/') { if (access(av[ac - 1], R_OK) == 0) ipfw_readfile(ac, av); else err(EX_USAGE, "pathname: %s", av[ac - 1]); } else { if (ipfw_main(ac, av)) { errx(EX_USAGE, "usage: ipfw [options]\n" "do \"ipfw -h\" or \"man ipfw\" for details"); } } return EX_OK; } Index: head/sbin/mdmfs/mdmfs.c =================================================================== --- head/sbin/mdmfs/mdmfs.c (revision 229777) +++ head/sbin/mdmfs/mdmfs.c (revision 229778) @@ -1,696 +1,696 @@ /* * Copyright (c) 2001 Dima Dorfman. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * mdmfs (md/MFS) is a wrapper around mdconfig(8), * newfs(8), and mount(8) that mimics the command line option set of * the deprecated mount_mfs(8). */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { false, true } bool; struct mtpt_info { uid_t mi_uid; bool mi_have_uid; gid_t mi_gid; bool mi_have_gid; mode_t mi_mode; bool mi_have_mode; bool mi_forced_pw; }; static bool debug; /* Emit debugging information? */ static bool loudsubs; /* Suppress output from helper programs? */ static bool norun; /* Actually run the helper programs? */ static int unit; /* The unit we're working with. */ static const char *mdname; /* Name of memory disk device (e.g., "md"). */ static const char *mdsuffix; /* Suffix of memory disk device (e.g., ".uzip"). */ static size_t mdnamelen; /* Length of mdname. */ static const char *path_mdconfig =_PATH_MDCONFIG; static void argappend(char **, const char *, ...) __printflike(2, 3); static void debugprintf(const char *, ...) __printflike(1, 2); static void do_mdconfig_attach(const char *, const enum md_types); static void do_mdconfig_attach_au(const char *, const enum md_types); static void do_mdconfig_detach(void); static void do_mount(const char *, const char *); static void do_mtptsetup(const char *, struct mtpt_info *); static void do_newfs(const char *); static void extract_ugid(const char *, struct mtpt_info *); static int run(int *, const char *, ...) __printflike(2, 3); static void usage(void); int main(int argc, char **argv) { struct mtpt_info mi; /* Mountpoint info. */ char *mdconfig_arg, *newfs_arg, /* Args to helper programs. */ *mount_arg; enum md_types mdtype; /* The type of our memory disk. */ bool have_mdtype; bool detach, softdep, autounit, newfs; char *mtpoint, *unitstr; char *p; int ch; void *set; unsigned long ul; /* Misc. initialization. */ (void)memset(&mi, '\0', sizeof(mi)); detach = true; softdep = true; autounit = false; newfs = true; have_mdtype = false; mdtype = MD_SWAP; mdname = MD_NAME; mdnamelen = strlen(mdname); /* * Can't set these to NULL. They may be passed to the * respective programs without modification. I.e., we may not * receive any command-line options which will caused them to * be modified. */ mdconfig_arg = strdup(""); newfs_arg = strdup(""); mount_arg = strdup(""); /* If we were started as mount_mfs or mfs, imply -C. */ if (strcmp(getprogname(), "mount_mfs") == 0 || strcmp(getprogname(), "mfs") == 0) { /* Make compatibility assumptions. */ mi.mi_mode = 01777; mi.mi_have_mode = true; } while ((ch = getopt(argc, argv, "a:b:Cc:Dd:E:e:F:f:hi:LlMm:NnO:o:Pp:Ss:tUv:w:X")) != -1) switch (ch) { case 'a': argappend(&newfs_arg, "-a %s", optarg); break; case 'b': argappend(&newfs_arg, "-b %s", optarg); break; case 'C': /* Ignored for compatibility. */ break; case 'c': argappend(&newfs_arg, "-c %s", optarg); break; case 'D': detach = false; break; case 'd': argappend(&newfs_arg, "-d %s", optarg); break; case 'E': path_mdconfig = optarg; break; case 'e': argappend(&newfs_arg, "-e %s", optarg); break; case 'F': if (have_mdtype) usage(); mdtype = MD_VNODE; have_mdtype = true; argappend(&mdconfig_arg, "-f %s", optarg); break; case 'f': argappend(&newfs_arg, "-f %s", optarg); break; case 'h': usage(); break; case 'i': argappend(&newfs_arg, "-i %s", optarg); break; case 'L': loudsubs = true; break; case 'l': argappend(&newfs_arg, "-l"); break; case 'M': if (have_mdtype) usage(); mdtype = MD_MALLOC; have_mdtype = true; break; case 'm': argappend(&newfs_arg, "-m %s", optarg); break; case 'N': norun = true; break; case 'n': argappend(&newfs_arg, "-n"); break; case 'O': argappend(&newfs_arg, "-o %s", optarg); break; case 'o': argappend(&mount_arg, "-o %s", optarg); break; case 'P': newfs = false; break; case 'p': if ((set = setmode(optarg)) == NULL) usage(); mi.mi_mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO); mi.mi_have_mode = true; mi.mi_forced_pw = true; free(set); break; case 'S': softdep = false; break; case 's': argappend(&mdconfig_arg, "-s %s", optarg); break; case 't': argappend(&newfs_arg, "-t"); break; case 'U': softdep = true; break; case 'v': argappend(&newfs_arg, "-O %s", optarg); break; case 'w': extract_ugid(optarg, &mi); mi.mi_forced_pw = true; break; case 'X': debug = true; break; default: usage(); } argc -= optind; argv += optind; if (argc < 2) usage(); /* Derive 'unit' (global). */ unitstr = argv[0]; if (strncmp(unitstr, "/dev/", 5) == 0) unitstr += 5; if (strncmp(unitstr, mdname, mdnamelen) == 0) unitstr += mdnamelen; if (!isdigit(*unitstr)) { autounit = true; unit = -1; mdsuffix = unitstr; } else { ul = strtoul(unitstr, &p, 10); if (ul == ULONG_MAX) errx(1, "bad device unit: %s", unitstr); unit = ul; mdsuffix = p; /* can be empty */ } mtpoint = argv[1]; if (!have_mdtype) mdtype = MD_SWAP; if (softdep) argappend(&newfs_arg, "-U"); if (mdtype != MD_VNODE && !newfs) errx(1, "-P requires a vnode-backed disk"); /* Do the work. */ if (detach && !autounit) do_mdconfig_detach(); if (autounit) do_mdconfig_attach_au(mdconfig_arg, mdtype); else do_mdconfig_attach(mdconfig_arg, mdtype); if (newfs) do_newfs(newfs_arg); do_mount(mount_arg, mtpoint); do_mtptsetup(mtpoint, &mi); return (0); } /* * Append the expansion of 'fmt' to the buffer pointed to by '*dstp'; * reallocate as required. */ static void argappend(char **dstp, const char *fmt, ...) { char *old, *new; va_list ap; old = *dstp; assert(old != NULL); va_start(ap, fmt); if (vasprintf(&new, fmt,ap) == -1) errx(1, "vasprintf"); va_end(ap); *dstp = new; if (asprintf(&new, "%s %s", old, new) == -1) errx(1, "asprintf"); free(*dstp); free(old); *dstp = new; } /* * If run-time debugging is enabled, print the expansion of 'fmt'. * Otherwise, do nothing. */ static void debugprintf(const char *fmt, ...) { va_list ap; if (!debug) return; fprintf(stderr, "DEBUG: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); fflush(stderr); } /* * Attach a memory disk with a known unit. */ static void do_mdconfig_attach(const char *args, const enum md_types mdtype) { int rv; const char *ta; /* Type arg. */ switch (mdtype) { case MD_SWAP: ta = "-t swap"; break; case MD_VNODE: ta = "-t vnode"; break; case MD_MALLOC: ta = "-t malloc"; break; default: abort(); } rv = run(NULL, "%s -a %s%s -u %s%d", path_mdconfig, ta, args, mdname, unit); if (rv) errx(1, "mdconfig (attach) exited with error code %d", rv); } /* * Attach a memory disk with an unknown unit; use autounit. */ static void do_mdconfig_attach_au(const char *args, const enum md_types mdtype) { const char *ta; /* Type arg. */ char *linep, *linebuf; /* Line pointer, line buffer. */ int fd; /* Standard output of mdconfig invocation. */ FILE *sfd; int rv; char *p; size_t linelen; unsigned long ul; switch (mdtype) { case MD_SWAP: ta = "-t swap"; break; case MD_VNODE: ta = "-t vnode"; break; case MD_MALLOC: ta = "-t malloc"; break; default: abort(); } rv = run(&fd, "%s -a %s%s", path_mdconfig, ta, args); if (rv) errx(1, "mdconfig (attach) exited with error code %d", rv); /* Receive the unit number. */ if (norun) { /* Since we didn't run, we can't read. Fake it. */ unit = 0; return; } sfd = fdopen(fd, "r"); if (sfd == NULL) err(1, "fdopen"); linep = fgetln(sfd, &linelen); if (linep == NULL && linelen < mdnamelen + 1) errx(1, "unexpected output from mdconfig (attach)"); /* If the output format changes, we want to know about it. */ assert(strncmp(linep, mdname, mdnamelen) == 0); linebuf = malloc(linelen - mdnamelen + 1); assert(linebuf != NULL); /* Can't use strlcpy because linep is not NULL-terminated. */ strncpy(linebuf, linep + mdnamelen, linelen); linebuf[linelen] = '\0'; ul = strtoul(linebuf, &p, 10); if (ul == ULONG_MAX || *p != '\n') errx(1, "unexpected output from mdconfig (attach)"); unit = ul; fclose(sfd); close(fd); } /* * Detach a memory disk. */ static void do_mdconfig_detach(void) { int rv; rv = run(NULL, "%s -d -u %s%d", path_mdconfig, mdname, unit); if (rv && debug) /* This is allowed to fail. */ warnx("mdconfig (detach) exited with error code %d (ignored)", rv); } /* * Mount the configured memory disk. */ static void do_mount(const char *args, const char *mtpoint) { int rv; rv = run(NULL, "%s%s /dev/%s%d%s %s", _PATH_MOUNT, args, mdname, unit, mdsuffix, mtpoint); if (rv) errx(1, "mount exited with error code %d", rv); } /* * Various configuration of the mountpoint. Mostly, enact 'mip'. */ static void do_mtptsetup(const char *mtpoint, struct mtpt_info *mip) { struct statfs sfs; if (!mip->mi_have_mode && !mip->mi_have_uid && !mip->mi_have_gid) return; if (!norun) { if (statfs(mtpoint, &sfs) == -1) { warn("statfs: %s", mtpoint); return; } if ((sfs.f_flags & MNT_RDONLY) != 0) { if (mip->mi_forced_pw) { warnx( "Not changing mode/owner of %s since it is read-only", mtpoint); } else { debugprintf( "Not changing mode/owner of %s since it is read-only", mtpoint); } return; } } if (mip->mi_have_mode) { debugprintf("changing mode of %s to %o.", mtpoint, mip->mi_mode); if (!norun) if (chmod(mtpoint, mip->mi_mode) == -1) err(1, "chmod: %s", mtpoint); } /* * We have to do these separately because the user may have * only specified one of them. */ if (mip->mi_have_uid) { debugprintf("changing owner (user) or %s to %u.", mtpoint, mip->mi_uid); if (!norun) if (chown(mtpoint, mip->mi_uid, -1) == -1) err(1, "chown %s to %u (user)", mtpoint, mip->mi_uid); } if (mip->mi_have_gid) { debugprintf("changing owner (group) or %s to %u.", mtpoint, mip->mi_gid); if (!norun) if (chown(mtpoint, -1, mip->mi_gid) == -1) err(1, "chown %s to %u (group)", mtpoint, mip->mi_gid); } } /* * Put a file system on the memory disk. */ static void do_newfs(const char *args) { int rv; rv = run(NULL, "%s%s /dev/%s%d", _PATH_NEWFS, args, mdname, unit); if (rv) errx(1, "newfs exited with error code %d", rv); } /* * 'str' should be a user and group name similar to the last argument * to chown(1); i.e., a user, followed by a colon, followed by a * group. The user and group in 'str' may be either a [ug]id or a * name. Upon return, the uid and gid fields in 'mip' will contain * the uid and gid of the user and group name in 'str', respectively. * * In other words, this derives a user and group id from a string * formatted like the last argument to chown(1). * * Notice: At this point we don't support only a username or only a * group name. do_mtptsetup already does, so when this feature is * desired, this is the only routine that needs to be changed. */ static void extract_ugid(const char *str, struct mtpt_info *mip) { char *ug; /* Writable 'str'. */ char *user, *group; /* Result of extracton. */ struct passwd *pw; struct group *gr; char *p; uid_t *uid; gid_t *gid; uid = &mip->mi_uid; gid = &mip->mi_gid; mip->mi_have_uid = mip->mi_have_gid = false; /* Extract the user and group from 'str'. Format above. */ ug = strdup(str); assert(ug != NULL); group = ug; user = strsep(&group, ":"); if (user == NULL || group == NULL || *user == '\0' || *group == '\0') usage(); /* Derive uid. */ *uid = strtoul(user, &p, 10); if (*uid == (uid_t)ULONG_MAX) usage(); if (*p != '\0') { pw = getpwnam(user); if (pw == NULL) errx(1, "invalid user: %s", user); *uid = pw->pw_uid; } mip->mi_have_uid = true; /* Derive gid. */ *gid = strtoul(group, &p, 10); if (*gid == (gid_t)ULONG_MAX) usage(); if (*p != '\0') { gr = getgrnam(group); if (gr == NULL) errx(1, "invalid group: %s", group); *gid = gr->gr_gid; } mip->mi_have_gid = true; free(ug); } /* * Run a process with command name and arguments pointed to by the * formatted string 'cmdline'. Since system(3) is not used, the first * space-delimited token of 'cmdline' must be the full pathname of the * program to run. The return value is the return code of the process * spawned. If 'ofd' is non-NULL, it is set to the standard output of * the program spawned (i.e., you can read from ofd and get the output * of the program). */ static int run(int *ofd, const char *cmdline, ...) { char **argv, **argvp; /* Result of splitting 'cmd'. */ int argc; char *cmd; /* Expansion of 'cmdline'. */ int pid, status; /* Child info. */ int pfd[2]; /* Pipe to the child. */ int nfd; /* Null (/dev/null) file descriptor. */ bool dup2dn; /* Dup /dev/null to stdout? */ va_list ap; char *p; int rv, i; dup2dn = true; va_start(ap, cmdline); rv = vasprintf(&cmd, cmdline, ap); if (rv == -1) err(1, "vasprintf"); va_end(ap); /* Split up 'cmd' into 'argv' for use with execve. */ for (argc = 1, p = cmd; (p = strchr(p, ' ')) != NULL; p++) argc++; /* 'argc' generation loop. */ argv = (char **)malloc(sizeof(*argv) * (argc + 1)); assert(argv != NULL); for (p = cmd, argvp = argv; (*argvp = strsep(&p, " ")) != NULL;) if (**argvp != '\0') if (++argvp >= &argv[argc]) { *argvp = NULL; break; } assert(*argv); /* The argv array ends up NULL-terminated here. */ /* Make sure the above loop works as expected. */ if (debug) { /* * We can't, but should, use debugprintf here. First, * it appends a trailing newline to the output, and * second it prepends "DEBUG: " to the output. The * former is a problem for this would-be first call, * and the latter for the would-be call inside the * loop. */ (void)fprintf(stderr, "DEBUG: running:"); - /* Should be equivilent to 'cmd' (before strsep, of course). */ + /* Should be equivalent to 'cmd' (before strsep, of course). */ for (i = 0; argv[i] != NULL; i++) (void)fprintf(stderr, " %s", argv[i]); (void)fprintf(stderr, "\n"); } /* Create a pipe if necessary and fork the helper program. */ if (ofd != NULL) { if (pipe(&pfd[0]) == -1) err(1, "pipe"); *ofd = pfd[0]; dup2dn = false; } pid = fork(); switch (pid) { case 0: /* XXX can we call err() in here? */ if (norun) _exit(0); if (ofd != NULL) if (dup2(pfd[1], STDOUT_FILENO) < 0) err(1, "dup2"); if (!loudsubs) { nfd = open(_PATH_DEVNULL, O_RDWR); if (nfd == -1) err(1, "open: %s", _PATH_DEVNULL); if (dup2(nfd, STDIN_FILENO) < 0) err(1, "dup2"); if (dup2dn) if (dup2(nfd, STDOUT_FILENO) < 0) err(1, "dup2"); if (dup2(nfd, STDERR_FILENO) < 0) err(1, "dup2"); } (void)execv(argv[0], argv); warn("exec: %s", argv[0]); _exit(-1); case -1: err(1, "fork"); } free(cmd); free(argv); while (waitpid(pid, &status, 0) != pid) ; return (WEXITSTATUS(status)); } static void usage(void) { fprintf(stderr, "usage: %s [-DLlMNnPStUX] [-a maxcontig] [-b block-size]\n" "\t[-c blocks-per-cylinder-group][-d max-extent-size] [-E path-mdconfig]\n" "\t[-e maxbpg] [-F file] [-f frag-size] [-i bytes] [-m percent-free]\n" "\t[-O optimization] [-o mount-options]\n" "\t[-p permissions] [-s size] [-v version] [-w user:group]\n" "\tmd-device mount-point\n", getprogname()); exit(1); } Index: head/sbin/mount_nfs/mount_nfs.c =================================================================== --- head/sbin/mount_nfs/mount_nfs.c (revision 229777) +++ head/sbin/mount_nfs/mount_nfs.c (revision 229778) @@ -1,1221 +1,1221 @@ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1992, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)mount_nfs.c 8.11 (Berkeley) 5/4/95"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mntopts.h" #include "mounttab.h" /* Table for af,sotype -> netid conversions. */ struct nc_protos { const char *netid; int af; int sotype; } nc_protos[] = { {"udp", AF_INET, SOCK_DGRAM}, {"tcp", AF_INET, SOCK_STREAM}, {"udp6", AF_INET6, SOCK_DGRAM}, {"tcp6", AF_INET6, SOCK_STREAM}, {NULL, 0, 0} }; struct nfhret { u_long stat; long vers; long auth; long fhsize; u_char nfh[NFS3_FHSIZE]; }; #define BGRND 1 #define ISBGRND 2 #define OF_NOINET4 4 #define OF_NOINET6 8 int retrycnt = -1; int opflags = 0; int nfsproto = IPPROTO_TCP; int mnttcp_ok = 1; int noconn = 0; char *portspec = NULL; /* Server nfs port; NULL means look up via rpcbind. */ struct sockaddr *addr; int addrlen = 0; u_char *fh = NULL; int fhsize = 0; int secflavor = -1; int got_principal = 0; enum mountmode { ANY, V2, V3, V4 } mountmode = ANY; /* Return codes for nfs_tryproto. */ enum tryret { TRYRET_SUCCESS, TRYRET_TIMEOUT, /* No response received. */ TRYRET_REMOTEERR, /* Error received from remote server. */ TRYRET_LOCALERR /* Local failure. */ }; static int fallback_mount(struct iovec *iov, int iovlen, int mntflags); static int sec_name_to_num(char *sec); static char *sec_num_to_name(int num); static int getnfsargs(char *, struct iovec **iov, int *iovlen); /* void set_rpc_maxgrouplist(int); */ static struct netconfig *getnetconf_cached(const char *netid); static const char *netidbytype(int af, int sotype); static void usage(void) __dead2; static int xdr_dir(XDR *, char *); static int xdr_fh(XDR *, struct nfhret *); static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr, struct iovec **iov, int *iovlen); static enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr); int main(int argc, char *argv[]) { int c; struct iovec *iov; int mntflags, num, iovlen; int osversion; char *name, *p, *spec, *fstype; char mntpath[MAXPATHLEN], errmsg[255]; char hostname[MAXHOSTNAMELEN + 1], *gssname, gssn[MAXHOSTNAMELEN + 50]; mntflags = 0; iov = NULL; iovlen = 0; memset(errmsg, 0, sizeof(errmsg)); gssname = NULL; fstype = strrchr(argv[0], '_'); if (fstype == NULL) errx(EX_USAGE, "argv[0] must end in _fstype"); ++fstype; while ((c = getopt(argc, argv, "23a:bcdD:g:I:iLlNo:PR:r:sTt:w:x:U")) != -1) switch (c) { case '2': mountmode = V2; break; case '3': mountmode = V3; break; case 'a': printf("-a deprecated, use -o readahead=\n"); build_iovec(&iov, &iovlen, "readahead", optarg, (size_t)-1); break; case 'b': opflags |= BGRND; break; case 'c': printf("-c deprecated, use -o noconn\n"); build_iovec(&iov, &iovlen, "noconn", NULL, 0); noconn = 1; break; case 'D': printf("-D deprecated, use -o deadthresh=\n"); build_iovec(&iov, &iovlen, "deadthresh", optarg, (size_t)-1); break; case 'd': printf("-d deprecated, use -o dumbtimer"); build_iovec(&iov, &iovlen, "dumbtimer", NULL, 0); break; case 'g': printf("-g deprecated, use -o maxgroups"); num = strtol(optarg, &p, 10); if (*p || num <= 0) errx(1, "illegal -g value -- %s", optarg); //set_rpc_maxgrouplist(num); build_iovec(&iov, &iovlen, "maxgroups", optarg, (size_t)-1); break; case 'I': printf("-I deprecated, use -o readdirsize=\n"); build_iovec(&iov, &iovlen, "readdirsize", optarg, (size_t)-1); break; case 'i': printf("-i deprecated, use -o intr\n"); build_iovec(&iov, &iovlen, "intr", NULL, 0); break; case 'L': printf("-L deprecated, use -o nolockd\n"); build_iovec(&iov, &iovlen, "nolockd", NULL, 0); break; case 'l': printf("-l deprecated, -o rdirplus\n"); build_iovec(&iov, &iovlen, "rdirplus", NULL, 0); break; case 'N': printf("-N deprecated, do not specify -o resvport\n"); break; case 'o': { int pass_flag_to_nmount; char *opt = optarg; while (opt) { char *pval = NULL; char *pnextopt = NULL; char *val = ""; pass_flag_to_nmount = 1; pnextopt = strchr(opt, ','); if (pnextopt != NULL) { *pnextopt = '\0'; pnextopt++; } pval = strchr(opt, '='); if (pval != NULL) { *pval = '\0'; val = pval + 1; } if (strcmp(opt, "bg") == 0) { opflags |= BGRND; pass_flag_to_nmount=0; } else if (strcmp(opt, "fg") == 0) { /* same as not specifying -o bg */ pass_flag_to_nmount=0; } else if (strcmp(opt, "gssname") == 0) { pass_flag_to_nmount = 0; gssname = val; } else if (strcmp(opt, "mntudp") == 0) { mnttcp_ok = 0; nfsproto = IPPROTO_UDP; } else if (strcmp(opt, "udp") == 0) { nfsproto = IPPROTO_UDP; } else if (strcmp(opt, "tcp") == 0) { nfsproto = IPPROTO_TCP; } else if (strcmp(opt, "noinet4") == 0) { pass_flag_to_nmount=0; opflags |= OF_NOINET4; } else if (strcmp(opt, "noinet6") == 0) { pass_flag_to_nmount=0; opflags |= OF_NOINET6; } else if (strcmp(opt, "noconn") == 0) { noconn = 1; } else if (strcmp(opt, "nfsv2") == 0) { pass_flag_to_nmount=0; mountmode = V2; } else if (strcmp(opt, "nfsv3") == 0) { mountmode = V3; } else if (strcmp(opt, "nfsv4") == 0) { pass_flag_to_nmount=0; mountmode = V4; fstype = "nfs"; nfsproto = IPPROTO_TCP; if (portspec == NULL) portspec = "2049"; } else if (strcmp(opt, "port") == 0) { pass_flag_to_nmount=0; asprintf(&portspec, "%d", atoi(val)); if (portspec == NULL) err(1, "asprintf"); } else if (strcmp(opt, "principal") == 0) { got_principal = 1; } else if (strcmp(opt, "sec") == 0) { /* * Don't add this option to * the iovec yet - we will * negotiate which sec flavor * to use with the remote * mountd. */ pass_flag_to_nmount=0; secflavor = sec_name_to_num(val); if (secflavor < 0) { errx(1, "illegal sec value -- %s", val); } } else if (strcmp(opt, "retrycnt") == 0) { pass_flag_to_nmount=0; num = strtol(val, &p, 10); if (*p || num < 0) errx(1, "illegal retrycnt value -- %s", val); retrycnt = num; } else if (strcmp(opt, "maxgroups") == 0) { num = strtol(val, &p, 10); if (*p || num <= 0) errx(1, "illegal maxgroups value -- %s", val); //set_rpc_maxgrouplist(num); } if (pass_flag_to_nmount) build_iovec(&iov, &iovlen, opt, val, strlen(val) + 1); opt = pnextopt; } } break; case 'P': /* obsolete for -o noresvport now default */ printf("-P deprecated, use -o noresvport\n"); build_iovec(&iov, &iovlen, "noresvport", NULL, 0); break; case 'R': printf("-R deprecated, use -o retrycnt=\n"); num = strtol(optarg, &p, 10); if (*p || num < 0) errx(1, "illegal -R value -- %s", optarg); retrycnt = num; break; case 'r': printf("-r deprecated, use -o rsize=\n"); build_iovec(&iov, &iovlen, "rsize", optarg, (size_t)-1); break; case 's': printf("-s deprecated, use -o soft\n"); build_iovec(&iov, &iovlen, "soft", NULL, 0); break; case 'T': nfsproto = IPPROTO_TCP; printf("-T deprecated, use -o tcp\n"); break; case 't': printf("-t deprecated, use -o timeout=\n"); build_iovec(&iov, &iovlen, "timeout", optarg, (size_t)-1); break; case 'w': printf("-w deprecated, use -o wsize=\n"); build_iovec(&iov, &iovlen, "wsize", optarg, (size_t)-1); break; case 'x': printf("-x deprecated, use -o retrans=\n"); build_iovec(&iov, &iovlen, "retrans", optarg, (size_t)-1); break; case 'U': printf("-U deprecated, use -o mntudp\n"); mnttcp_ok = 0; nfsproto = IPPROTO_UDP; build_iovec(&iov, &iovlen, "mntudp", NULL, 0); break; default: usage(); break; } argc -= optind; argv += optind; if (argc != 2) { usage(); /* NOTREACHED */ } spec = *argv++; name = *argv; if (retrycnt == -1) /* The default is to keep retrying forever. */ retrycnt = 0; /* * If the fstye is "oldnfs", run the old NFS client unless the * "nfsv4" option was specified. */ if (strcmp(fstype, "nfs") == 0) { if (modfind("nfscl") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfscl") < 0 || modfind("nfscl") < 0) errx(1, "nfscl is not available"); } } /* * Add the fqdn to the gssname, as required. */ if (gssname != NULL) { if (strchr(gssname, '@') == NULL && gethostname(hostname, MAXHOSTNAMELEN) == 0) { snprintf(gssn, sizeof (gssn), "%s@%s", gssname, hostname); gssname = gssn; } build_iovec(&iov, &iovlen, "gssname", gssname, strlen(gssname) + 1); } if (!getnfsargs(spec, &iov, &iovlen)) exit(1); /* resolve the mountpoint with realpath(3) */ (void)checkpath(name, mntpath); build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); /* * XXX: * Backwards compatibility routines for older kernels. * Remove this and fallback_mount() code when we do not need to support * NFS mounts against older kernels which still need * struct nfs_args to be passed in via nmount(). */ osversion = getosreldate(); if (osversion >= 702100) { if (nmount(iov, iovlen, mntflags)) err(1, "%s, %s", mntpath, errmsg); } else { if (fallback_mount(iov, iovlen, mntflags)) err(1, "%s, %s", mntpath, errmsg); } exit(0); } static int findopt(struct iovec *iov, int iovlen, const char *name, char **valuep, int *lenp) { int i; for (i = 0; i < iovlen/2; i++, iov += 2) { if (strcmp(name, iov[0].iov_base) == 0) { if (valuep) *valuep = iov[1].iov_base; if (lenp) *lenp = iov[1].iov_len; return (0); } } return (ENOENT); } static void copyopt(struct iovec **newiov, int *newiovlen, struct iovec *iov, int iovlen, const char *name) { char *value; int len; if (findopt(iov, iovlen, name, &value, &len) == 0) build_iovec(newiov, newiovlen, name, value, len); } /* * XXX: This function is provided for backwards * compatibility with older kernels which did not support * passing NFS mount options to nmount() as individual * parameters. It should be eventually be removed. */ static int fallback_mount(struct iovec *iov, int iovlen, int mntflags) { struct nfs_args args = { .version = NFS_ARGSVERSION, .addr = NULL, .addrlen = sizeof (struct sockaddr_in), .sotype = SOCK_STREAM, .proto = 0, .fh = NULL, .fhsize = 0, .flags = NFSMNT_RESVPORT, .wsize = NFS_WSIZE, .rsize = NFS_RSIZE, .readdirsize = NFS_READDIRSIZE, .timeo = 10, .retrans = NFS_RETRANS, .maxgrouplist = NFS_MAXGRPS, .readahead = NFS_DEFRAHEAD, .wcommitsize = 0, /* was: NQ_DEFLEASE */ .deadthresh = NFS_MAXDEADTHRESH, /* was: NQ_DEADTHRESH */ .hostname = NULL, /* args version 4 */ .acregmin = NFS_MINATTRTIMO, .acregmax = NFS_MAXATTRTIMO, .acdirmin = NFS_MINDIRATTRTIMO, .acdirmax = NFS_MAXDIRATTRTIMO, }; int ret; char *opt; struct iovec *newiov; int newiovlen; if (findopt(iov, iovlen, "dumbtimer", NULL, NULL) == 0) args.flags |= NFSMNT_DUMBTIMR; if (findopt(iov, iovlen, "noconn", NULL, NULL) == 0) args.flags |= NFSMNT_NOCONN; if (findopt(iov, iovlen, "conn", NULL, NULL) == 0) args.flags |= NFSMNT_NOCONN; if (findopt(iov, iovlen, "nolockd", NULL, NULL) == 0) args.flags |= NFSMNT_NOLOCKD; if (findopt(iov, iovlen, "lockd", NULL, NULL) == 0) args.flags &= ~NFSMNT_NOLOCKD; if (findopt(iov, iovlen, "intr", NULL, NULL) == 0) args.flags |= NFSMNT_INT; if (findopt(iov, iovlen, "rdirplus", NULL, NULL) == 0) args.flags |= NFSMNT_RDIRPLUS; if (findopt(iov, iovlen, "resvport", NULL, NULL) == 0) args.flags |= NFSMNT_RESVPORT; if (findopt(iov, iovlen, "noresvport", NULL, NULL) == 0) args.flags &= ~NFSMNT_RESVPORT; if (findopt(iov, iovlen, "soft", NULL, NULL) == 0) args.flags |= NFSMNT_SOFT; if (findopt(iov, iovlen, "hard", NULL, NULL) == 0) args.flags &= ~NFSMNT_SOFT; if (findopt(iov, iovlen, "mntudp", NULL, NULL) == 0) args.sotype = SOCK_DGRAM; if (findopt(iov, iovlen, "udp", NULL, NULL) == 0) args.sotype = SOCK_DGRAM; if (findopt(iov, iovlen, "tcp", NULL, NULL) == 0) args.sotype = SOCK_STREAM; if (findopt(iov, iovlen, "nfsv3", NULL, NULL) == 0) args.flags |= NFSMNT_NFSV3; if (findopt(iov, iovlen, "readdirsize", &opt, NULL) == 0) { if (opt == NULL) { errx(1, "illegal readdirsize"); } ret = sscanf(opt, "%d", &args.readdirsize); if (ret != 1 || args.readdirsize <= 0) { errx(1, "illegal readdirsize: %s", opt); } args.flags |= NFSMNT_READDIRSIZE; } if (findopt(iov, iovlen, "readahead", &opt, NULL) == 0) { if (opt == NULL) { errx(1, "illegal readahead"); } ret = sscanf(opt, "%d", &args.readahead); if (ret != 1 || args.readahead <= 0) { errx(1, "illegal readahead: %s", opt); } args.flags |= NFSMNT_READAHEAD; } if (findopt(iov, iovlen, "wsize", &opt, NULL) == 0) { if (opt == NULL) { errx(1, "illegal wsize"); } ret = sscanf(opt, "%d", &args.wsize); if (ret != 1 || args.wsize <= 0) { errx(1, "illegal wsize: %s", opt); } args.flags |= NFSMNT_WSIZE; } if (findopt(iov, iovlen, "rsize", &opt, NULL) == 0) { if (opt == NULL) { errx(1, "illegal rsize"); } ret = sscanf(opt, "%d", &args.rsize); if (ret != 1 || args.rsize <= 0) { errx(1, "illegal wsize: %s", opt); } args.flags |= NFSMNT_RSIZE; } if (findopt(iov, iovlen, "retrans", &opt, NULL) == 0) { if (opt == NULL) { errx(1, "illegal retrans"); } ret = sscanf(opt, "%d", &args.retrans); if (ret != 1 || args.retrans <= 0) { errx(1, "illegal retrans: %s", opt); } args.flags |= NFSMNT_RETRANS; } if (findopt(iov, iovlen, "acregmin", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.acregmin); if (ret != 1 || args.acregmin < 0) { errx(1, "illegal acregmin: %s", opt); } args.flags |= NFSMNT_ACREGMIN; } if (findopt(iov, iovlen, "acregmax", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.acregmax); if (ret != 1 || args.acregmax < 0) { errx(1, "illegal acregmax: %s", opt); } args.flags |= NFSMNT_ACREGMAX; } if (findopt(iov, iovlen, "acdirmin", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.acdirmin); if (ret != 1 || args.acdirmin < 0) { errx(1, "illegal acdirmin: %s", opt); } args.flags |= NFSMNT_ACDIRMIN; } if (findopt(iov, iovlen, "acdirmax", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.acdirmax); if (ret != 1 || args.acdirmax < 0) { errx(1, "illegal acdirmax: %s", opt); } args.flags |= NFSMNT_ACDIRMAX; } if (findopt(iov, iovlen, "wcommitsize", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.wcommitsize); if (ret != 1 || args.wcommitsize < 0) { errx(1, "illegal wcommitsize: %s", opt); } args.flags |= NFSMNT_WCOMMITSIZE; } if (findopt(iov, iovlen, "deadthresh", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.deadthresh); if (ret != 1 || args.deadthresh <= 0) { errx(1, "illegal deadthresh: %s", opt); } args.flags |= NFSMNT_DEADTHRESH; } if (findopt(iov, iovlen, "timeout", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.timeo); if (ret != 1 || args.timeo <= 0) { errx(1, "illegal timeout: %s", opt); } args.flags |= NFSMNT_TIMEO; } if (findopt(iov, iovlen, "maxgroups", &opt, NULL) == 0) { ret = sscanf(opt, "%d", &args.maxgrouplist); if (ret != 1 || args.timeo <= 0) { errx(1, "illegal maxgroups: %s", opt); } args.flags |= NFSMNT_MAXGRPS; } if (findopt(iov, iovlen, "addr", &opt, &args.addrlen) == 0) { args.addr = (struct sockaddr *) opt; } if (findopt(iov, iovlen, "fh", &opt, &args.fhsize) == 0) { args.fh = opt; } if (findopt(iov, iovlen, "hostname", &args.hostname, NULL) == 0) { } if (args.hostname == NULL) { errx(1, "Invalid hostname"); } newiov = NULL; newiovlen = 0; build_iovec(&newiov, &newiovlen, "nfs_args", &args, sizeof(args)); copyopt(&newiov, &newiovlen, iov, iovlen, "fstype"); copyopt(&newiov, &newiovlen, iov, iovlen, "fspath"); copyopt(&newiov, &newiovlen, iov, iovlen, "errmsg"); return nmount(newiov, newiovlen, mntflags); } static int sec_name_to_num(char *sec) { if (!strcmp(sec, "krb5")) return (RPCSEC_GSS_KRB5); if (!strcmp(sec, "krb5i")) return (RPCSEC_GSS_KRB5I); if (!strcmp(sec, "krb5p")) return (RPCSEC_GSS_KRB5P); if (!strcmp(sec, "sys")) return (AUTH_SYS); return (-1); } static char * sec_num_to_name(int flavor) { switch (flavor) { case RPCSEC_GSS_KRB5: return ("krb5"); case RPCSEC_GSS_KRB5I: return ("krb5i"); case RPCSEC_GSS_KRB5P: return ("krb5p"); case AUTH_SYS: return ("sys"); } return (NULL); } static int getnfsargs(char *spec, struct iovec **iov, int *iovlen) { struct addrinfo hints, *ai_nfs, *ai; enum tryret ret; int ecode, speclen, remoteerr, offset, have_bracket = 0; char *hostp, *delimp, *errstr; size_t len; static char nam[MNAMELEN + 1], pname[MAXHOSTNAMELEN + 5]; if (*spec == '[' && (delimp = strchr(spec + 1, ']')) != NULL && *(delimp + 1) == ':') { hostp = spec + 1; spec = delimp + 2; have_bracket = 1; } else if ((delimp = strrchr(spec, ':')) != NULL) { hostp = spec; spec = delimp + 1; } else if ((delimp = strrchr(spec, '@')) != NULL) { warnx("path@server syntax is deprecated, use server:path"); hostp = delimp + 1; } else { warnx("no : nfs-name"); return (0); } *delimp = '\0'; /* * If there has been a trailing slash at mounttime it seems * that some mountd implementations fail to remove the mount * entries from their mountlist while unmounting. */ for (speclen = strlen(spec); speclen > 1 && spec[speclen - 1] == '/'; speclen--) spec[speclen - 1] = '\0'; if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) { warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG)); return (0); } /* Make both '@' and ':' notations equal */ if (*hostp != '\0') { len = strlen(hostp); offset = 0; if (have_bracket) nam[offset++] = '['; memmove(nam + offset, hostp, len); if (have_bracket) nam[len + offset++] = ']'; nam[len + offset++] = ':'; memmove(nam + len + offset, spec, speclen); nam[len + speclen + offset] = '\0'; } /* * Handle an internet host address. */ memset(&hints, 0, sizeof hints); hints.ai_flags = AI_NUMERICHOST; if (nfsproto == IPPROTO_TCP) hints.ai_socktype = SOCK_STREAM; else if (nfsproto == IPPROTO_UDP) hints.ai_socktype = SOCK_DGRAM; if (getaddrinfo(hostp, portspec, &hints, &ai_nfs) != 0) { hints.ai_flags = AI_CANONNAME; if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs)) != 0) { if (portspec == NULL) errx(1, "%s: %s", hostp, gai_strerror(ecode)); else errx(1, "%s:%s: %s", hostp, portspec, gai_strerror(ecode)); return (0); } /* * For a Kerberized nfs mount where the "principal" * argument has not been set, add it here. */ if (got_principal == 0 && secflavor >= 0 && secflavor != AUTH_SYS && ai_nfs->ai_canonname != NULL) { snprintf(pname, sizeof (pname), "nfs@%s", ai_nfs->ai_canonname); build_iovec(iov, iovlen, "principal", pname, strlen(pname) + 1); } } ret = TRYRET_LOCALERR; for (;;) { /* * Try each entry returned by getaddrinfo(). Note the - * occurence of remote errors by setting `remoteerr'. + * occurrence of remote errors by setting `remoteerr'. */ remoteerr = 0; for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) { if ((ai->ai_family == AF_INET6) && (opflags & OF_NOINET6)) continue; if ((ai->ai_family == AF_INET) && (opflags & OF_NOINET4)) continue; ret = nfs_tryproto(ai, hostp, spec, &errstr, iov, iovlen); if (ret == TRYRET_SUCCESS) break; if (ret != TRYRET_LOCALERR) remoteerr = 1; if ((opflags & ISBGRND) == 0) fprintf(stderr, "%s\n", errstr); } if (ret == TRYRET_SUCCESS) break; /* Exit if all errors were local. */ if (!remoteerr) exit(1); /* * If retrycnt == 0, we are to keep retrying forever. * Otherwise decrement it, and exit if it hits zero. */ if (retrycnt != 0 && --retrycnt == 0) exit(1); if ((opflags & (BGRND | ISBGRND)) == BGRND) { warnx("Cannot immediately mount %s:%s, backgrounding", hostp, spec); opflags |= ISBGRND; if (daemon(0, 0) != 0) err(1, "daemon"); } sleep(60); } freeaddrinfo(ai_nfs); build_iovec(iov, iovlen, "hostname", nam, (size_t)-1); /* Add mounted file system to PATH_MOUNTTAB */ if (!add_mtab(hostp, spec)) warnx("can't update %s for %s:%s", PATH_MOUNTTAB, hostp, spec); return (1); } /* * Try to set up the NFS arguments according to the address * family, protocol (and possibly port) specified in `ai'. * * Returns TRYRET_SUCCESS if successful, or: * TRYRET_TIMEOUT The server did not respond. * TRYRET_REMOTEERR The server reported an error. * TRYRET_LOCALERR Local failure. * * In all error cases, *errstr will be set to a statically-allocated string * describing the error. */ static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr, struct iovec **iov, int *iovlen) { static char errbuf[256]; struct sockaddr_storage nfs_ss; struct netbuf nfs_nb; struct nfhret nfhret; struct timeval try; struct rpc_err rpcerr; CLIENT *clp; struct netconfig *nconf, *nconf_mnt; const char *netid, *netid_mnt; char *secname; int doconnect, nfsvers, mntvers, sotype; enum clnt_stat stat; enum mountmode trymntmode; sotype = 0; trymntmode = mountmode; errbuf[0] = '\0'; *errstr = errbuf; if (nfsproto == IPPROTO_TCP) sotype = SOCK_STREAM; else if (nfsproto == IPPROTO_UDP) sotype = SOCK_DGRAM; if ((netid = netidbytype(ai->ai_family, sotype)) == NULL) { snprintf(errbuf, sizeof errbuf, "af %d sotype %d not supported", ai->ai_family, sotype); return (TRYRET_LOCALERR); } if ((nconf = getnetconf_cached(netid)) == NULL) { snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror()); return (TRYRET_LOCALERR); } /* The RPCPROG_MNT netid may be different. */ if (mnttcp_ok) { netid_mnt = netid; nconf_mnt = nconf; } else { if ((netid_mnt = netidbytype(ai->ai_family, SOCK_DGRAM)) == NULL) { snprintf(errbuf, sizeof errbuf, "af %d sotype SOCK_DGRAM not supported", ai->ai_family); return (TRYRET_LOCALERR); } if ((nconf_mnt = getnetconf_cached(netid_mnt)) == NULL) { snprintf(errbuf, sizeof errbuf, "%s: %s", netid_mnt, nc_sperror()); return (TRYRET_LOCALERR); } } tryagain: if (trymntmode == V4) { nfsvers = 4; } else if (trymntmode == V2) { nfsvers = 2; mntvers = 1; } else { nfsvers = 3; mntvers = 3; } if (portspec != NULL) { /* `ai' contains the complete nfsd sockaddr. */ nfs_nb.buf = ai->ai_addr; nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen; } else { /* Ask the remote rpcbind. */ nfs_nb.buf = &nfs_ss; nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss; if (!rpcb_getaddr(NFS_PROGRAM, nfsvers, nconf, &nfs_nb, hostp)) { if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { trymntmode = V2; goto tryagain; } snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_spcreateerror("RPCPROG_NFS")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } } /* Check that the server (nfsd) responds on the port we have chosen. */ clp = clnt_tli_create(RPC_ANYFD, nconf, &nfs_nb, NFS_PROGRAM, nfsvers, 0, 0); if (clp == NULL) { snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_spcreateerror("nfsd: RPCPROG_NFS")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } if (sotype == SOCK_DGRAM && noconn == 0) { /* * Use connect(), to match what the kernel does. This * catches cases where the server responds from the * wrong source address. */ doconnect = 1; if (!clnt_control(clp, CLSET_CONNECT, (char *)&doconnect)) { clnt_destroy(clp); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: CLSET_CONNECT failed", netid, hostp, spec); return (TRYRET_LOCALERR); } } try.tv_sec = 10; try.tv_usec = 0; stat = clnt_call(clp, NFSPROC_NULL, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL, try); if (stat != RPC_SUCCESS) { if (stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { clnt_destroy(clp); trymntmode = V2; goto tryagain; } clnt_geterr(clp, &rpcerr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_sperror(clp, "NFSPROC_NULL")); clnt_destroy(clp); return (returncode(stat, &rpcerr)); } clnt_destroy(clp); /* * For NFSv4, there is no mount protocol. */ if (trymntmode == V4) { /* * Store the server address in nfsargsp, making * sure to copy any locally allocated structures. */ addrlen = nfs_nb.len; addr = malloc(addrlen); if (addr == NULL) err(1, "malloc"); bcopy(nfs_nb.buf, addr, addrlen); build_iovec(iov, iovlen, "addr", addr, addrlen); secname = sec_num_to_name(secflavor); if (secname != NULL) build_iovec(iov, iovlen, "sec", secname, (size_t)-1); build_iovec(iov, iovlen, "nfsv4", NULL, 0); build_iovec(iov, iovlen, "dirpath", spec, (size_t)-1); return (TRYRET_SUCCESS); } /* Send the MOUNTPROC_MNT RPC to get the root filehandle. */ try.tv_sec = 10; try.tv_usec = 0; clp = clnt_tp_create(hostp, MOUNTPROG, mntvers, nconf_mnt); if (clp == NULL) { snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, clnt_spcreateerror("RPCMNT: clnt_create")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } clp->cl_auth = authsys_create_default(); nfhret.auth = secflavor; nfhret.vers = mntvers; stat = clnt_call(clp, MOUNTPROC_MNT, (xdrproc_t)xdr_dir, spec, (xdrproc_t)xdr_fh, &nfhret, try); auth_destroy(clp->cl_auth); if (stat != RPC_SUCCESS) { if (stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { clnt_destroy(clp); trymntmode = V2; goto tryagain; } clnt_geterr(clp, &rpcerr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, clnt_sperror(clp, "RPCPROG_MNT")); clnt_destroy(clp); return (returncode(stat, &rpcerr)); } clnt_destroy(clp); if (nfhret.stat != 0) { snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, strerror(nfhret.stat)); return (TRYRET_REMOTEERR); } /* * Store the filehandle and server address in nfsargsp, making * sure to copy any locally allocated structures. */ addrlen = nfs_nb.len; addr = malloc(addrlen); fhsize = nfhret.fhsize; fh = malloc(fhsize); if (addr == NULL || fh == NULL) err(1, "malloc"); bcopy(nfs_nb.buf, addr, addrlen); bcopy(nfhret.nfh, fh, fhsize); build_iovec(iov, iovlen, "addr", addr, addrlen); build_iovec(iov, iovlen, "fh", fh, fhsize); secname = sec_num_to_name(nfhret.auth); if (secname) build_iovec(iov, iovlen, "sec", secname, (size_t)-1); if (nfsvers == 3) build_iovec(iov, iovlen, "nfsv3", NULL, 0); return (TRYRET_SUCCESS); } /* * Catagorise a RPC return status and error into an `enum tryret' * return code. */ static enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr) { switch (stat) { case RPC_TIMEDOUT: return (TRYRET_TIMEOUT); case RPC_PMAPFAILURE: case RPC_PROGNOTREGISTERED: case RPC_PROGVERSMISMATCH: /* XXX, these can be local or remote. */ case RPC_CANTSEND: case RPC_CANTRECV: return (TRYRET_REMOTEERR); case RPC_SYSTEMERROR: switch (rpcerr->re_errno) { case ETIMEDOUT: return (TRYRET_TIMEOUT); case ENOMEM: break; default: return (TRYRET_REMOTEERR); } /* FALLTHROUGH */ default: break; } return (TRYRET_LOCALERR); } /* * Look up a netid based on an address family and socket type. * `af' is the address family, and `sotype' is SOCK_DGRAM or SOCK_STREAM. * * XXX there should be a library function for this. */ static const char * netidbytype(int af, int sotype) { struct nc_protos *p; for (p = nc_protos; p->netid != NULL; p++) { if (af != p->af || sotype != p->sotype) continue; return (p->netid); } return (NULL); } /* * Look up a netconfig entry based on a netid, and cache the result so * that we don't need to remember to call freenetconfigent(). * * Otherwise it behaves just like getnetconfigent(), so nc_*error() * work on failure. */ static struct netconfig * getnetconf_cached(const char *netid) { static struct nc_entry { struct netconfig *nconf; struct nc_entry *next; } *head; struct nc_entry *p; struct netconfig *nconf; for (p = head; p != NULL; p = p->next) if (strcmp(netid, p->nconf->nc_netid) == 0) return (p->nconf); if ((nconf = getnetconfigent(netid)) == NULL) return (NULL); if ((p = malloc(sizeof(*p))) == NULL) err(1, "malloc"); p->nconf = nconf; p->next = head; head = p; return (p->nconf); } /* * xdr routines for mount rpc's */ static int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } static int xdr_fh(XDR *xdrsp, struct nfhret *np) { int i; long auth, authcnt, authfnd = 0; if (!xdr_u_long(xdrsp, &np->stat)) return (0); if (np->stat) return (1); switch (np->vers) { case 1: np->fhsize = NFS_FHSIZE; return (xdr_opaque(xdrsp, (caddr_t)np->nfh, NFS_FHSIZE)); case 3: if (!xdr_long(xdrsp, &np->fhsize)) return (0); if (np->fhsize <= 0 || np->fhsize > NFS3_FHSIZE) return (0); if (!xdr_opaque(xdrsp, (caddr_t)np->nfh, np->fhsize)) return (0); if (!xdr_long(xdrsp, &authcnt)) return (0); for (i = 0; i < authcnt; i++) { if (!xdr_long(xdrsp, &auth)) return (0); if (np->auth == -1) { np->auth = auth; authfnd++; } else if (auth == np->auth) { authfnd++; } } /* * Some servers, such as DEC's OSF/1 return a nil authenticator * list to indicate RPCAUTH_UNIX. */ if (authcnt == 0 && np->auth == -1) np->auth = AUTH_SYS; if (!authfnd && (authcnt > 0 || np->auth != AUTH_SYS)) np->stat = EAUTH; return (1); }; return (0); } static void usage(void) { (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", "usage: mount_nfs [-23bcdiLlNPsTU] [-a maxreadahead] [-D deadthresh]", " [-g maxgroups] [-I readdirsize] [-o options] [-R retrycnt]", " [-r readsize] [-t timeout] [-w writesize] [-x retrans]", " rhost:path node"); exit(1); } Index: head/sbin/ping6/ping6.c =================================================================== --- head/sbin/ping6/ping6.c (revision 229777) +++ head/sbin/ping6/ping6.c (revision 229778) @@ -1,2794 +1,2794 @@ /* $KAME: ping6.c,v 1.169 2003/07/25 06:01:47 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* BSDI ping.c,v 2.3 1996/01/21 17:56:50 jch Exp */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Muuss. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1989, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)ping.c 8.1 (Berkeley) 6/5/93"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /* * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility, * measure round-trip-delays and packet loss across network paths. * * Author - * Mike Muuss * U. S. Army Ballistic Research Laboratory * December, 1983 * * Status - * Public Domain. Distribution Unlimited. * Bugs - * More statistics could always be gathered. * This program has to run SUID to ROOT to access the ICMP socket. */ /* * NOTE: * USE_SIN6_SCOPE_ID assumes that sin6_scope_id has the same semantics * as IPV6_PKTINFO. Some people object it (sin6_scope_id specifies *link* * while IPV6_PKTINFO specifies *interface*. Link is defined as collection of * network attached to 1 or more interfaces) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #ifdef IPSEC #include #include #endif #include struct tv32 { u_int32_t tv32_sec; u_int32_t tv32_usec; }; #define MAXPACKETLEN 131072 #define IP6LEN 40 #define ICMP6ECHOLEN 8 /* icmp echo header len excluding time */ #define ICMP6ECHOTMLEN sizeof(struct tv32) #define ICMP6_NIQLEN (ICMP6ECHOLEN + 8) # define CONTROLLEN 10240 /* ancillary data buffer size RFC3542 20.1 */ /* FQDN case, 64 bits of nonce + 32 bits ttl */ #define ICMP6_NIRLEN (ICMP6ECHOLEN + 12) #define EXTRA 256 /* for AH and various other headers. weird. */ #define DEFDATALEN ICMP6ECHOTMLEN #define MAXDATALEN MAXPACKETLEN - IP6LEN - ICMP6ECHOLEN #define NROUTES 9 /* number of record route slots */ #define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ #define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ #define SET(bit) (A(bit) |= B(bit)) #define CLR(bit) (A(bit) &= (~B(bit))) #define TST(bit) (A(bit) & B(bit)) #define F_FLOOD 0x0001 #define F_INTERVAL 0x0002 #define F_PINGFILLED 0x0008 #define F_QUIET 0x0010 #define F_RROUTE 0x0020 #define F_SO_DEBUG 0x0040 #define F_VERBOSE 0x0100 #ifdef IPSEC #ifdef IPSEC_POLICY_IPSEC #define F_POLICY 0x0400 #else #define F_AUTHHDR 0x0200 #define F_ENCRYPT 0x0400 #endif /*IPSEC_POLICY_IPSEC*/ #endif /*IPSEC*/ #define F_NODEADDR 0x0800 #define F_FQDN 0x1000 #define F_INTERFACE 0x2000 #define F_SRCADDR 0x4000 #define F_HOSTNAME 0x10000 #define F_FQDNOLD 0x20000 #define F_NIGROUP 0x40000 #define F_SUPTYPES 0x80000 #define F_NOMINMTU 0x100000 #define F_ONCE 0x200000 #define F_AUDIBLE 0x400000 #define F_MISSED 0x800000 #define F_DONTFRAG 0x1000000 #define F_NOUSERDATA (F_NODEADDR | F_FQDN | F_FQDNOLD | F_SUPTYPES) u_int options; #define IN6LEN sizeof(struct in6_addr) #define SA6LEN sizeof(struct sockaddr_in6) #define DUMMY_PORT 10101 #define SIN6(s) ((struct sockaddr_in6 *)(s)) /* * MAX_DUP_CHK is the number of bits in received table, i.e. the maximum * number of received sequence numbers we can keep track of. Change 128 * to 8192 for complete accuracy... */ #define MAX_DUP_CHK (8 * 8192) int mx_dup_ck = MAX_DUP_CHK; char rcvd_tbl[MAX_DUP_CHK / 8]; struct addrinfo *res = NULL; struct sockaddr_in6 dst; /* who to ping6 */ struct sockaddr_in6 src; /* src addr of this packet */ socklen_t srclen; int datalen = DEFDATALEN; int s; /* socket file descriptor */ u_char outpack[MAXPACKETLEN]; char BSPACE = '\b'; /* characters written for flood */ char BBELL = '\a'; /* characters written for AUDIBLE */ char DOT = '.'; char *hostname; int ident; /* process id to identify our packets */ u_int8_t nonce[8]; /* nonce field for node information */ int hoplimit = -1; /* hoplimit */ int pathmtu = 0; /* path MTU for the destination. 0 = unspec. */ u_char *packet = NULL; #ifdef HAVE_POLL_H struct pollfd fdmaskp[1]; #else fd_set *fdmaskp = NULL; int fdmasks; #endif /* counters */ long nmissedmax; /* max value of ntransmitted - nreceived - 1 */ long npackets; /* max packets to transmit */ long nreceived; /* # of packets we got back */ long nrepeats; /* number of duplicates */ long ntransmitted; /* sequence # for outbound packets = #sent */ struct timeval interval = {1, 0}; /* interval between packets */ /* timing */ int timing; /* flag to do timing */ double tmin = 999999999.0; /* minimum round trip time */ double tmax = 0.0; /* maximum round trip time */ double tsum = 0.0; /* sum of all times, for doing average */ double tsumsq = 0.0; /* sum of all times squared, for std. dev. */ /* for node addresses */ u_short naflags; /* for ancillary data(advanced API) */ struct msghdr smsghdr; struct iovec smsgiov; char *scmsg = 0; volatile sig_atomic_t seenalrm; volatile sig_atomic_t seenint; #ifdef SIGINFO volatile sig_atomic_t seeninfo; #endif int main(int, char *[]); void fill(char *, char *); int get_hoplim(struct msghdr *); int get_pathmtu(struct msghdr *); struct in6_pktinfo *get_rcvpktinfo(struct msghdr *); void onsignal(int); void retransmit(void); void onint(int); size_t pingerlen(void); int pinger(void); const char *pr_addr(struct sockaddr *, int); void pr_icmph(struct icmp6_hdr *, u_char *); void pr_iph(struct ip6_hdr *); void pr_suptypes(struct icmp6_nodeinfo *, size_t); void pr_nodeaddr(struct icmp6_nodeinfo *, int); int myechoreply(const struct icmp6_hdr *); int mynireply(const struct icmp6_nodeinfo *); char *dnsdecode(const u_char **, const u_char *, const u_char *, char *, size_t); void pr_pack(u_char *, int, struct msghdr *); void pr_exthdrs(struct msghdr *); void pr_ip6opt(void *, size_t); void pr_rthdr(void *, size_t); int pr_bitrange(u_int32_t, int, int); void pr_retip(struct ip6_hdr *, u_char *); void summary(void); void tvsub(struct timeval *, struct timeval *); int setpolicy(int, char *); char *nigroup(char *); void usage(void); int main(int argc, char *argv[]) { struct itimerval itimer; struct sockaddr_in6 from; #ifndef HAVE_ARC4RANDOM struct timeval seed; #endif #ifdef HAVE_POLL_H int timeout; #else struct timeval timeout, *tv; #endif struct addrinfo hints; int cc, i; int ch, hold, packlen, preload, optval, ret_ga; u_char *datap; char *e, *target, *ifname = NULL, *gateway = NULL; int ip6optlen = 0; struct cmsghdr *scmsgp = NULL; /* For control (ancillary) data received from recvmsg() */ struct cmsghdr cm[CONTROLLEN]; #if defined(SO_SNDBUF) && defined(SO_RCVBUF) u_long lsockbufsize; int sockbufsize = 0; #endif int usepktinfo = 0; struct in6_pktinfo *pktinfo = NULL; #ifdef USE_RFC2292BIS struct ip6_rthdr *rthdr = NULL; #endif #ifdef IPSEC_POLICY_IPSEC char *policy_in = NULL; char *policy_out = NULL; #endif double intval; size_t rthlen; #ifdef IPV6_USE_MIN_MTU int mflag = 0; #endif /* just to be sure */ memset(&smsghdr, 0, sizeof(smsghdr)); memset(&smsgiov, 0, sizeof(smsgiov)); preload = 0; datap = &outpack[ICMP6ECHOLEN + ICMP6ECHOTMLEN]; #ifndef IPSEC #define ADDOPTS #else #ifdef IPSEC_POLICY_IPSEC #define ADDOPTS "P:" #else #define ADDOPTS "AE" #endif /*IPSEC_POLICY_IPSEC*/ #endif while ((ch = getopt(argc, argv, "a:b:c:DdfHg:h:I:i:l:mnNop:qrRS:s:tvwW" ADDOPTS)) != -1) { #undef ADDOPTS switch (ch) { case 'a': { char *cp; options &= ~F_NOUSERDATA; options |= F_NODEADDR; for (cp = optarg; *cp != '\0'; cp++) { switch (*cp) { case 'a': naflags |= NI_NODEADDR_FLAG_ALL; break; case 'c': case 'C': naflags |= NI_NODEADDR_FLAG_COMPAT; break; case 'l': case 'L': naflags |= NI_NODEADDR_FLAG_LINKLOCAL; break; case 's': case 'S': naflags |= NI_NODEADDR_FLAG_SITELOCAL; break; case 'g': case 'G': naflags |= NI_NODEADDR_FLAG_GLOBAL; break; case 'A': /* experimental. not in the spec */ #ifdef NI_NODEADDR_FLAG_ANYCAST naflags |= NI_NODEADDR_FLAG_ANYCAST; break; #else errx(1, "-a A is not supported on the platform"); /*NOTREACHED*/ #endif default: usage(); /*NOTREACHED*/ } } break; } case 'b': #if defined(SO_SNDBUF) && defined(SO_RCVBUF) errno = 0; e = NULL; lsockbufsize = strtoul(optarg, &e, 10); sockbufsize = lsockbufsize; if (errno || !*optarg || *e || sockbufsize != lsockbufsize) errx(1, "invalid socket buffer size"); #else errx(1, "-b option ignored: SO_SNDBUF/SO_RCVBUF socket options not supported"); #endif break; case 'c': npackets = strtol(optarg, &e, 10); if (npackets <= 0 || *optarg == '\0' || *e != '\0') errx(1, "illegal number of packets -- %s", optarg); break; case 'D': options |= F_DONTFRAG; break; case 'd': options |= F_SO_DEBUG; break; case 'f': if (getuid()) { errno = EPERM; errx(1, "Must be superuser to flood ping"); } options |= F_FLOOD; setbuf(stdout, (char *)NULL); break; case 'g': gateway = optarg; break; case 'H': options |= F_HOSTNAME; break; case 'h': /* hoplimit */ hoplimit = strtol(optarg, &e, 10); if (*optarg == '\0' || *e != '\0') errx(1, "illegal hoplimit %s", optarg); if (255 < hoplimit || hoplimit < -1) errx(1, "illegal hoplimit -- %s", optarg); break; case 'I': ifname = optarg; options |= F_INTERFACE; #ifndef USE_SIN6_SCOPE_ID usepktinfo++; #endif break; case 'i': /* wait between sending packets */ intval = strtod(optarg, &e); if (*optarg == '\0' || *e != '\0') errx(1, "illegal timing interval %s", optarg); if (intval < 1 && getuid()) { errx(1, "%s: only root may use interval < 1s", strerror(EPERM)); } interval.tv_sec = (long)intval; interval.tv_usec = (long)((intval - interval.tv_sec) * 1000000); if (interval.tv_sec < 0) errx(1, "illegal timing interval %s", optarg); /* less than 1/hz does not make sense */ if (interval.tv_sec == 0 && interval.tv_usec < 1) { warnx("too small interval, raised to .000001"); interval.tv_usec = 1; } options |= F_INTERVAL; break; case 'l': if (getuid()) { errno = EPERM; errx(1, "Must be superuser to preload"); } preload = strtol(optarg, &e, 10); if (preload < 0 || *optarg == '\0' || *e != '\0') errx(1, "illegal preload value -- %s", optarg); break; case 'm': #ifdef IPV6_USE_MIN_MTU mflag++; break; #else errx(1, "-%c is not supported on this platform", ch); /*NOTREACHED*/ #endif case 'n': options &= ~F_HOSTNAME; break; case 'N': options |= F_NIGROUP; break; case 'o': options |= F_ONCE; break; case 'p': /* fill buffer with user pattern */ options |= F_PINGFILLED; fill((char *)datap, optarg); break; case 'q': options |= F_QUIET; break; case 'r': options |= F_AUDIBLE; break; case 'R': options |= F_MISSED; break; case 'S': memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_NUMERICHOST; /* allow hostname? */ hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_RAW; hints.ai_protocol = IPPROTO_ICMPV6; ret_ga = getaddrinfo(optarg, NULL, &hints, &res); if (ret_ga) { errx(1, "invalid source address: %s", gai_strerror(ret_ga)); } /* * res->ai_family must be AF_INET6 and res->ai_addrlen * must be sizeof(src). */ memcpy(&src, res->ai_addr, res->ai_addrlen); srclen = res->ai_addrlen; freeaddrinfo(res); res = NULL; options |= F_SRCADDR; break; case 's': /* size of packet to send */ datalen = strtol(optarg, &e, 10); if (datalen <= 0 || *optarg == '\0' || *e != '\0') errx(1, "illegal datalen value -- %s", optarg); if (datalen > MAXDATALEN) { errx(1, "datalen value too large, maximum is %d", MAXDATALEN); } break; case 't': options &= ~F_NOUSERDATA; options |= F_SUPTYPES; break; case 'v': options |= F_VERBOSE; break; case 'w': options &= ~F_NOUSERDATA; options |= F_FQDN; break; case 'W': options &= ~F_NOUSERDATA; options |= F_FQDNOLD; break; #ifdef IPSEC #ifdef IPSEC_POLICY_IPSEC case 'P': options |= F_POLICY; if (!strncmp("in", optarg, 2)) { if ((policy_in = strdup(optarg)) == NULL) errx(1, "strdup"); } else if (!strncmp("out", optarg, 3)) { if ((policy_out = strdup(optarg)) == NULL) errx(1, "strdup"); } else errx(1, "invalid security policy"); break; #else case 'A': options |= F_AUTHHDR; break; case 'E': options |= F_ENCRYPT; break; #endif /*IPSEC_POLICY_IPSEC*/ #endif /*IPSEC*/ default: usage(); /*NOTREACHED*/ } } argc -= optind; argv += optind; if (argc < 1) { usage(); /*NOTREACHED*/ } if (argc > 1) { #ifdef IPV6_RECVRTHDR /* 2292bis */ rthlen = CMSG_SPACE(inet6_rth_space(IPV6_RTHDR_TYPE_0, argc - 1)); #else /* RFC2292 */ rthlen = inet6_rthdr_space(IPV6_RTHDR_TYPE_0, argc - 1); #endif if (rthlen == 0) { errx(1, "too many intermediate hops"); /*NOTREACHED*/ } ip6optlen += rthlen; } if (options & F_NIGROUP) { target = nigroup(argv[argc - 1]); if (target == NULL) { usage(); /*NOTREACHED*/ } } else target = argv[argc - 1]; /* getaddrinfo */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_CANONNAME; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_RAW; hints.ai_protocol = IPPROTO_ICMPV6; ret_ga = getaddrinfo(target, NULL, &hints, &res); if (ret_ga) errx(1, "%s", gai_strerror(ret_ga)); if (res->ai_canonname) hostname = res->ai_canonname; else hostname = target; if (!res->ai_addr) errx(1, "getaddrinfo failed"); (void)memcpy(&dst, res->ai_addr, res->ai_addrlen); if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) err(1, "socket"); /* set the source address if specified. */ if ((options & F_SRCADDR) && bind(s, (struct sockaddr *)&src, srclen) != 0) { err(1, "bind"); } /* set the gateway (next hop) if specified */ if (gateway) { struct addrinfo ghints, *gres; int error; memset(&ghints, 0, sizeof(ghints)); ghints.ai_family = AF_INET6; ghints.ai_socktype = SOCK_RAW; ghints.ai_protocol = IPPROTO_ICMPV6; error = getaddrinfo(gateway, NULL, &hints, &gres); if (error) { errx(1, "getaddrinfo for the gateway %s: %s", gateway, gai_strerror(error)); } if (gres->ai_next && (options & F_VERBOSE)) warnx("gateway resolves to multiple addresses"); if (setsockopt(s, IPPROTO_IPV6, IPV6_NEXTHOP, gres->ai_addr, gres->ai_addrlen)) { err(1, "setsockopt(IPV6_NEXTHOP)"); } freeaddrinfo(gres); } /* * let the kerel pass extension headers of incoming packets, * for privileged socket options */ if ((options & F_VERBOSE) != 0) { int opton = 1; #ifdef IPV6_RECVHOPOPTS if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_RECVHOPOPTS)"); #else /* old adv. API */ if (setsockopt(s, IPPROTO_IPV6, IPV6_HOPOPTS, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_HOPOPTS)"); #endif #ifdef IPV6_RECVDSTOPTS if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_RECVDSTOPTS)"); #else /* old adv. API */ if (setsockopt(s, IPPROTO_IPV6, IPV6_DSTOPTS, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_DSTOPTS)"); #endif #ifdef IPV6_RECVRTHDRDSTOPTS if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVRTHDRDSTOPTS, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_RECVRTHDRDSTOPTS)"); #endif } /* revoke root privilege */ seteuid(getuid()); setuid(getuid()); if ((options & F_FLOOD) && (options & F_INTERVAL)) errx(1, "-f and -i incompatible options"); if ((options & F_NOUSERDATA) == 0) { if (datalen >= sizeof(struct tv32)) { /* we can time transfer */ timing = 1; } else timing = 0; /* in F_VERBOSE case, we may get non-echoreply packets*/ if (options & F_VERBOSE) packlen = 2048 + IP6LEN + ICMP6ECHOLEN + EXTRA; else packlen = datalen + IP6LEN + ICMP6ECHOLEN + EXTRA; } else { /* suppress timing for node information query */ timing = 0; datalen = 2048; packlen = 2048 + IP6LEN + ICMP6ECHOLEN + EXTRA; } if (!(packet = (u_char *)malloc((u_int)packlen))) err(1, "Unable to allocate packet"); if (!(options & F_PINGFILLED)) for (i = ICMP6ECHOLEN; i < packlen; ++i) *datap++ = i; ident = getpid() & 0xFFFF; #ifndef HAVE_ARC4RANDOM gettimeofday(&seed, NULL); srand((unsigned int)(seed.tv_sec ^ seed.tv_usec ^ (long)ident)); memset(nonce, 0, sizeof(nonce)); for (i = 0; i < sizeof(nonce); i += sizeof(int)) *((int *)&nonce[i]) = rand(); #else memset(nonce, 0, sizeof(nonce)); for (i = 0; i < sizeof(nonce); i += sizeof(u_int32_t)) *((u_int32_t *)&nonce[i]) = arc4random(); #endif optval = 1; if (options & F_DONTFRAG) if (setsockopt(s, IPPROTO_IPV6, IPV6_DONTFRAG, &optval, sizeof(optval)) == -1) err(1, "IPV6_DONTFRAG"); hold = 1; if (options & F_SO_DEBUG) (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, (char *)&hold, sizeof(hold)); optval = IPV6_DEFHLIM; if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr)) if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &optval, sizeof(optval)) == -1) err(1, "IPV6_MULTICAST_HOPS"); #ifdef IPV6_USE_MIN_MTU if (mflag != 1) { optval = mflag > 1 ? 0 : 1; if (setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &optval, sizeof(optval)) == -1) err(1, "setsockopt(IPV6_USE_MIN_MTU)"); } #ifdef IPV6_RECVPATHMTU else { optval = 1; if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPATHMTU, &optval, sizeof(optval)) == -1) err(1, "setsockopt(IPV6_RECVPATHMTU)"); } #endif /* IPV6_RECVPATHMTU */ #endif /* IPV6_USE_MIN_MTU */ #ifdef IPSEC #ifdef IPSEC_POLICY_IPSEC if (options & F_POLICY) { if (setpolicy(s, policy_in) < 0) errx(1, "%s", ipsec_strerror()); if (setpolicy(s, policy_out) < 0) errx(1, "%s", ipsec_strerror()); } #else if (options & F_AUTHHDR) { optval = IPSEC_LEVEL_REQUIRE; #ifdef IPV6_AUTH_TRANS_LEVEL if (setsockopt(s, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL, &optval, sizeof(optval)) == -1) err(1, "setsockopt(IPV6_AUTH_TRANS_LEVEL)"); #else /* old def */ if (setsockopt(s, IPPROTO_IPV6, IPV6_AUTH_LEVEL, &optval, sizeof(optval)) == -1) err(1, "setsockopt(IPV6_AUTH_LEVEL)"); #endif } if (options & F_ENCRYPT) { optval = IPSEC_LEVEL_REQUIRE; if (setsockopt(s, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL, &optval, sizeof(optval)) == -1) err(1, "setsockopt(IPV6_ESP_TRANS_LEVEL)"); } #endif /*IPSEC_POLICY_IPSEC*/ #endif #ifdef ICMP6_FILTER { struct icmp6_filter filt; if (!(options & F_VERBOSE)) { ICMP6_FILTER_SETBLOCKALL(&filt); if ((options & F_FQDN) || (options & F_FQDNOLD) || (options & F_NODEADDR) || (options & F_SUPTYPES)) ICMP6_FILTER_SETPASS(ICMP6_NI_REPLY, &filt); else ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt); } else { ICMP6_FILTER_SETPASSALL(&filt); } if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) < 0) err(1, "setsockopt(ICMP6_FILTER)"); } #endif /*ICMP6_FILTER*/ /* let the kerel pass extension headers of incoming packets */ if ((options & F_VERBOSE) != 0) { int opton = 1; #ifdef IPV6_RECVRTHDR if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVRTHDR, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_RECVRTHDR)"); #else /* old adv. API */ if (setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, &opton, sizeof(opton))) err(1, "setsockopt(IPV6_RTHDR)"); #endif } /* optval = 1; if (IN6_IS_ADDR_MULTICAST(&dst.sin6_addr)) if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &optval, sizeof(optval)) == -1) err(1, "IPV6_MULTICAST_LOOP"); */ /* Specify the outgoing interface and/or the source address */ if (usepktinfo) ip6optlen += CMSG_SPACE(sizeof(struct in6_pktinfo)); if (hoplimit != -1) ip6optlen += CMSG_SPACE(sizeof(int)); /* set IP6 packet options */ if (ip6optlen) { if ((scmsg = (char *)malloc(ip6optlen)) == 0) errx(1, "can't allocate enough memory"); smsghdr.msg_control = (caddr_t)scmsg; smsghdr.msg_controllen = ip6optlen; scmsgp = (struct cmsghdr *)scmsg; } if (usepktinfo) { pktinfo = (struct in6_pktinfo *)(CMSG_DATA(scmsgp)); memset(pktinfo, 0, sizeof(*pktinfo)); scmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); scmsgp->cmsg_level = IPPROTO_IPV6; scmsgp->cmsg_type = IPV6_PKTINFO; scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp); } /* set the outgoing interface */ if (ifname) { #ifndef USE_SIN6_SCOPE_ID /* pktinfo must have already been allocated */ if ((pktinfo->ipi6_ifindex = if_nametoindex(ifname)) == 0) errx(1, "%s: invalid interface name", ifname); #else if ((dst.sin6_scope_id = if_nametoindex(ifname)) == 0) errx(1, "%s: invalid interface name", ifname); #endif } if (hoplimit != -1) { scmsgp->cmsg_len = CMSG_LEN(sizeof(int)); scmsgp->cmsg_level = IPPROTO_IPV6; scmsgp->cmsg_type = IPV6_HOPLIMIT; *(int *)(CMSG_DATA(scmsgp)) = hoplimit; scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp); } if (argc > 1) { /* some intermediate addrs are specified */ int hops, error; #ifdef USE_RFC2292BIS int rthdrlen; #endif #ifdef USE_RFC2292BIS rthdrlen = inet6_rth_space(IPV6_RTHDR_TYPE_0, argc - 1); scmsgp->cmsg_len = CMSG_LEN(rthdrlen); scmsgp->cmsg_level = IPPROTO_IPV6; scmsgp->cmsg_type = IPV6_RTHDR; rthdr = (struct ip6_rthdr *)CMSG_DATA(scmsgp); rthdr = inet6_rth_init((void *)rthdr, rthdrlen, IPV6_RTHDR_TYPE_0, argc - 1); if (rthdr == NULL) errx(1, "can't initialize rthdr"); #else /* old advanced API */ if ((scmsgp = (struct cmsghdr *)inet6_rthdr_init(scmsgp, IPV6_RTHDR_TYPE_0)) == 0) errx(1, "can't initialize rthdr"); #endif /* USE_RFC2292BIS */ for (hops = 0; hops < argc - 1; hops++) { struct addrinfo *iaip; if ((error = getaddrinfo(argv[hops], NULL, &hints, &iaip))) errx(1, "%s", gai_strerror(error)); if (SIN6(iaip->ai_addr)->sin6_family != AF_INET6) errx(1, "bad addr family of an intermediate addr"); #ifdef USE_RFC2292BIS if (inet6_rth_add(rthdr, &(SIN6(iaip->ai_addr))->sin6_addr)) errx(1, "can't add an intermediate node"); #else /* old advanced API */ if (inet6_rthdr_add(scmsgp, &(SIN6(iaip->ai_addr))->sin6_addr, IPV6_RTHDR_LOOSE)) errx(1, "can't add an intermediate node"); #endif /* USE_RFC2292BIS */ freeaddrinfo(iaip); } #ifndef USE_RFC2292BIS if (inet6_rthdr_lasthop(scmsgp, IPV6_RTHDR_LOOSE)) errx(1, "can't set the last flag"); #endif scmsgp = CMSG_NXTHDR(&smsghdr, scmsgp); } if (!(options & F_SRCADDR)) { /* * get the source address. XXX since we revoked the root * privilege, we cannot use a raw socket for this. */ int dummy; socklen_t len = sizeof(src); if ((dummy = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) err(1, "UDP socket"); src.sin6_family = AF_INET6; src.sin6_addr = dst.sin6_addr; src.sin6_port = ntohs(DUMMY_PORT); src.sin6_scope_id = dst.sin6_scope_id; #ifdef USE_RFC2292BIS if (pktinfo && setsockopt(dummy, IPPROTO_IPV6, IPV6_PKTINFO, (void *)pktinfo, sizeof(*pktinfo))) err(1, "UDP setsockopt(IPV6_PKTINFO)"); if (hoplimit != -1 && setsockopt(dummy, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (void *)&hoplimit, sizeof(hoplimit))) err(1, "UDP setsockopt(IPV6_UNICAST_HOPS)"); if (hoplimit != -1 && setsockopt(dummy, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void *)&hoplimit, sizeof(hoplimit))) err(1, "UDP setsockopt(IPV6_MULTICAST_HOPS)"); if (rthdr && setsockopt(dummy, IPPROTO_IPV6, IPV6_RTHDR, (void *)rthdr, (rthdr->ip6r_len + 1) << 3)) err(1, "UDP setsockopt(IPV6_RTHDR)"); #else /* old advanced API */ if (smsghdr.msg_control && setsockopt(dummy, IPPROTO_IPV6, IPV6_PKTOPTIONS, (void *)smsghdr.msg_control, smsghdr.msg_controllen)) err(1, "UDP setsockopt(IPV6_PKTOPTIONS)"); #endif if (connect(dummy, (struct sockaddr *)&src, len) < 0) err(1, "UDP connect"); if (getsockname(dummy, (struct sockaddr *)&src, &len) < 0) err(1, "getsockname"); close(dummy); } #if defined(SO_SNDBUF) && defined(SO_RCVBUF) if (sockbufsize) { if (datalen > sockbufsize) warnx("you need -b to increase socket buffer size"); if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbufsize, sizeof(sockbufsize)) < 0) err(1, "setsockopt(SO_SNDBUF)"); if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbufsize, sizeof(sockbufsize)) < 0) err(1, "setsockopt(SO_RCVBUF)"); } else { if (datalen > 8 * 1024) /*XXX*/ warnx("you need -b to increase socket buffer size"); /* * When pinging the broadcast address, you can get a lot of * answers. Doing something so evil is useful if you are trying * to stress the ethernet, or just want to fill the arp cache * to get some stuff for /etc/ethers. */ hold = 48 * 1024; setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&hold, sizeof(hold)); } #endif optval = 1; #ifndef USE_SIN6_SCOPE_ID #ifdef IPV6_RECVPKTINFO if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &optval, sizeof(optval)) < 0) warn("setsockopt(IPV6_RECVPKTINFO)"); /* XXX err? */ #else /* old adv. API */ if (setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &optval, sizeof(optval)) < 0) warn("setsockopt(IPV6_PKTINFO)"); /* XXX err? */ #endif #endif /* USE_SIN6_SCOPE_ID */ #ifdef IPV6_RECVHOPLIMIT if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &optval, sizeof(optval)) < 0) warn("setsockopt(IPV6_RECVHOPLIMIT)"); /* XXX err? */ #else /* old adv. API */ if (setsockopt(s, IPPROTO_IPV6, IPV6_HOPLIMIT, &optval, sizeof(optval)) < 0) warn("setsockopt(IPV6_HOPLIMIT)"); /* XXX err? */ #endif printf("PING6(%lu=40+8+%lu bytes) ", (unsigned long)(40 + pingerlen()), (unsigned long)(pingerlen() - 8)); printf("%s --> ", pr_addr((struct sockaddr *)&src, sizeof(src))); printf("%s\n", pr_addr((struct sockaddr *)&dst, sizeof(dst))); while (preload--) /* Fire off them quickies. */ (void)pinger(); (void)signal(SIGINT, onsignal); #ifdef SIGINFO (void)signal(SIGINFO, onsignal); #endif if ((options & F_FLOOD) == 0) { (void)signal(SIGALRM, onsignal); itimer.it_interval = interval; itimer.it_value = interval; (void)setitimer(ITIMER_REAL, &itimer, NULL); if (ntransmitted == 0) retransmit(); } #ifndef HAVE_POLL_H fdmasks = howmany(s + 1, NFDBITS) * sizeof(fd_mask); if ((fdmaskp = malloc(fdmasks)) == NULL) err(1, "malloc"); #endif seenalrm = seenint = 0; #ifdef SIGINFO seeninfo = 0; #endif for (;;) { struct msghdr m; struct iovec iov[2]; /* signal handling */ if (seenalrm) { /* last packet sent, timeout reached? */ if (npackets && ntransmitted >= npackets) break; retransmit(); seenalrm = 0; continue; } if (seenint) { onint(SIGINT); seenint = 0; continue; } #ifdef SIGINFO if (seeninfo) { summary(); seeninfo = 0; continue; } #endif if (options & F_FLOOD) { (void)pinger(); #ifdef HAVE_POLL_H timeout = 10; #else timeout.tv_sec = 0; timeout.tv_usec = 10000; tv = &timeout; #endif } else { #ifdef HAVE_POLL_H timeout = INFTIM; #else tv = NULL; #endif } #ifdef HAVE_POLL_H fdmaskp[0].fd = s; fdmaskp[0].events = POLLIN; cc = poll(fdmaskp, 1, timeout); #else memset(fdmaskp, 0, fdmasks); FD_SET(s, fdmaskp); cc = select(s + 1, fdmaskp, NULL, NULL, tv); #endif if (cc < 0) { if (errno != EINTR) { #ifdef HAVE_POLL_H warn("poll"); #else warn("select"); #endif sleep(1); } continue; } else if (cc == 0) continue; m.msg_name = (caddr_t)&from; m.msg_namelen = sizeof(from); memset(&iov, 0, sizeof(iov)); iov[0].iov_base = (caddr_t)packet; iov[0].iov_len = packlen; m.msg_iov = iov; m.msg_iovlen = 1; memset(cm, 0, CONTROLLEN); m.msg_control = (void *)cm; m.msg_controllen = CONTROLLEN; cc = recvmsg(s, &m, 0); if (cc < 0) { if (errno != EINTR) { warn("recvmsg"); sleep(1); } continue; } else if (cc == 0) { int mtu; /* * receive control messages only. Process the - * exceptions (currently the only possiblity is + * exceptions (currently the only possibility is * a path MTU notification.) */ if ((mtu = get_pathmtu(&m)) > 0) { if ((options & F_VERBOSE) != 0) { printf("new path MTU (%d) is " "notified\n", mtu); } } continue; } else { /* * an ICMPv6 message (probably an echoreply) arrived. */ pr_pack(packet, cc, &m); } if (((options & F_ONCE) != 0 && nreceived > 0) || (npackets > 0 && nreceived >= npackets)) break; if (ntransmitted - nreceived - 1 > nmissedmax) { nmissedmax = ntransmitted - nreceived - 1; if (options & F_MISSED) (void)write(STDOUT_FILENO, &BBELL, 1); } } summary(); if (res != NULL) freeaddrinfo(res); if(packet != NULL) free(packet); #ifndef HAVE_POLL_H if(fdmaskp != NULL) free(fdmaskp); #endif exit(nreceived == 0 ? 2 : 0); } void onsignal(int sig) { switch (sig) { case SIGALRM: seenalrm++; break; case SIGINT: seenint++; break; #ifdef SIGINFO case SIGINFO: seeninfo++; break; #endif } } /* * retransmit -- * This routine transmits another ping6. */ void retransmit(void) { struct itimerval itimer; if (pinger() == 0) return; /* * If we're not transmitting any more packets, change the timer * to wait two round-trip times if we've received any packets or * ten seconds if we haven't. */ #define MAXWAIT 10 if (nreceived) { itimer.it_value.tv_sec = 2 * tmax / 1000; if (itimer.it_value.tv_sec == 0) itimer.it_value.tv_sec = 1; } else itimer.it_value.tv_sec = MAXWAIT; itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; itimer.it_value.tv_usec = 0; (void)signal(SIGALRM, onsignal); (void)setitimer(ITIMER_REAL, &itimer, NULL); } /* * pinger -- * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet * will be added on by the kernel. The ID field is our UNIX process ID, * and the sequence number is an ascending integer. The first 8 bytes * of the data portion are used to hold a UNIX "timeval" struct in VAX * byte-order, to compute the round-trip time. */ size_t pingerlen(void) { size_t l; if (options & F_FQDN) l = ICMP6_NIQLEN + sizeof(dst.sin6_addr); else if (options & F_FQDNOLD) l = ICMP6_NIQLEN; else if (options & F_NODEADDR) l = ICMP6_NIQLEN + sizeof(dst.sin6_addr); else if (options & F_SUPTYPES) l = ICMP6_NIQLEN; else l = ICMP6ECHOLEN + datalen; return l; } int pinger(void) { struct icmp6_hdr *icp; struct iovec iov[2]; int i, cc; struct icmp6_nodeinfo *nip; int seq; if (npackets && ntransmitted >= npackets) return(-1); /* no more transmission */ icp = (struct icmp6_hdr *)outpack; nip = (struct icmp6_nodeinfo *)outpack; memset(icp, 0, sizeof(*icp)); icp->icmp6_cksum = 0; seq = ntransmitted++; CLR(seq % mx_dup_ck); if (options & F_FQDN) { icp->icmp6_type = ICMP6_NI_QUERY; icp->icmp6_code = ICMP6_NI_SUBJ_IPV6; nip->ni_qtype = htons(NI_QTYPE_FQDN); nip->ni_flags = htons(0); memcpy(nip->icmp6_ni_nonce, nonce, sizeof(nip->icmp6_ni_nonce)); *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq); memcpy(&outpack[ICMP6_NIQLEN], &dst.sin6_addr, sizeof(dst.sin6_addr)); cc = ICMP6_NIQLEN + sizeof(dst.sin6_addr); datalen = 0; } else if (options & F_FQDNOLD) { /* packet format in 03 draft - no Subject data on queries */ icp->icmp6_type = ICMP6_NI_QUERY; icp->icmp6_code = 0; /* code field is always 0 */ nip->ni_qtype = htons(NI_QTYPE_FQDN); nip->ni_flags = htons(0); memcpy(nip->icmp6_ni_nonce, nonce, sizeof(nip->icmp6_ni_nonce)); *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq); cc = ICMP6_NIQLEN; datalen = 0; } else if (options & F_NODEADDR) { icp->icmp6_type = ICMP6_NI_QUERY; icp->icmp6_code = ICMP6_NI_SUBJ_IPV6; nip->ni_qtype = htons(NI_QTYPE_NODEADDR); nip->ni_flags = naflags; memcpy(nip->icmp6_ni_nonce, nonce, sizeof(nip->icmp6_ni_nonce)); *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq); memcpy(&outpack[ICMP6_NIQLEN], &dst.sin6_addr, sizeof(dst.sin6_addr)); cc = ICMP6_NIQLEN + sizeof(dst.sin6_addr); datalen = 0; } else if (options & F_SUPTYPES) { icp->icmp6_type = ICMP6_NI_QUERY; icp->icmp6_code = ICMP6_NI_SUBJ_FQDN; /*empty*/ nip->ni_qtype = htons(NI_QTYPE_SUPTYPES); /* we support compressed bitmap */ nip->ni_flags = NI_SUPTYPE_FLAG_COMPRESS; memcpy(nip->icmp6_ni_nonce, nonce, sizeof(nip->icmp6_ni_nonce)); *(u_int16_t *)nip->icmp6_ni_nonce = ntohs(seq); cc = ICMP6_NIQLEN; datalen = 0; } else { icp->icmp6_type = ICMP6_ECHO_REQUEST; icp->icmp6_code = 0; icp->icmp6_id = htons(ident); icp->icmp6_seq = ntohs(seq); if (timing) { struct timeval tv; struct tv32 *tv32; (void)gettimeofday(&tv, NULL); tv32 = (struct tv32 *)&outpack[ICMP6ECHOLEN]; tv32->tv32_sec = htonl(tv.tv_sec); tv32->tv32_usec = htonl(tv.tv_usec); } cc = ICMP6ECHOLEN + datalen; } #ifdef DIAGNOSTIC if (pingerlen() != cc) errx(1, "internal error; length mismatch"); #endif smsghdr.msg_name = (caddr_t)&dst; smsghdr.msg_namelen = sizeof(dst); memset(&iov, 0, sizeof(iov)); iov[0].iov_base = (caddr_t)outpack; iov[0].iov_len = cc; smsghdr.msg_iov = iov; smsghdr.msg_iovlen = 1; i = sendmsg(s, &smsghdr, 0); if (i < 0 || i != cc) { if (i < 0) warn("sendmsg"); (void)printf("ping6: wrote %s %d chars, ret=%d\n", hostname, cc, i); } if (!(options & F_QUIET) && options & F_FLOOD) (void)write(STDOUT_FILENO, &DOT, 1); return(0); } int myechoreply(const struct icmp6_hdr *icp) { if (ntohs(icp->icmp6_id) == ident) return 1; else return 0; } int mynireply(const struct icmp6_nodeinfo *nip) { if (memcmp(nip->icmp6_ni_nonce + sizeof(u_int16_t), nonce + sizeof(u_int16_t), sizeof(nonce) - sizeof(u_int16_t)) == 0) return 1; else return 0; } char * dnsdecode(const u_char **sp, const u_char *ep, const u_char *base, char *buf, size_t bufsiz) /*base for compressed name*/ { int i; const u_char *cp; char cresult[MAXDNAME + 1]; const u_char *comp; int l; cp = *sp; *buf = '\0'; if (cp >= ep) return NULL; while (cp < ep) { i = *cp; if (i == 0 || cp != *sp) { if (strlcat((char *)buf, ".", bufsiz) >= bufsiz) return NULL; /*result overrun*/ } if (i == 0) break; cp++; if ((i & 0xc0) == 0xc0 && cp - base > (i & 0x3f)) { /* DNS compression */ if (!base) return NULL; comp = base + (i & 0x3f); if (dnsdecode(&comp, cp, base, cresult, sizeof(cresult)) == NULL) return NULL; if (strlcat(buf, cresult, bufsiz) >= bufsiz) return NULL; /*result overrun*/ break; } else if ((i & 0x3f) == i) { if (i > ep - cp) return NULL; /*source overrun*/ while (i-- > 0 && cp < ep) { l = snprintf(cresult, sizeof(cresult), isprint(*cp) ? "%c" : "\\%03o", *cp & 0xff); if (l >= sizeof(cresult) || l < 0) return NULL; if (strlcat(buf, cresult, bufsiz) >= bufsiz) return NULL; /*result overrun*/ cp++; } } else return NULL; /*invalid label*/ } if (i != 0) return NULL; /*not terminated*/ cp++; *sp = cp; return buf; } /* * pr_pack -- * Print out the packet, if it came from us. This logic is necessary * because ALL readers of the ICMP socket get a copy of ALL ICMP packets * which arrive ('tis only fair). This permits multiple copies of this * program to be run without having intermingled output (or statistics!). */ void pr_pack(u_char *buf, int cc, struct msghdr *mhdr) { #define safeputc(c) printf((isprint((c)) ? "%c" : "\\%03o"), c) struct icmp6_hdr *icp; struct icmp6_nodeinfo *ni; int i; int hoplim; struct sockaddr *from; int fromlen; u_char *cp = NULL, *dp, *end = buf + cc; struct in6_pktinfo *pktinfo = NULL; struct timeval tv, tp; struct tv32 *tpp; double triptime = 0; int dupflag; size_t off; int oldfqdn; u_int16_t seq; char dnsname[MAXDNAME + 1]; (void)gettimeofday(&tv, NULL); if (!mhdr || !mhdr->msg_name || mhdr->msg_namelen != sizeof(struct sockaddr_in6) || ((struct sockaddr *)mhdr->msg_name)->sa_family != AF_INET6) { if (options & F_VERBOSE) warnx("invalid peername"); return; } from = (struct sockaddr *)mhdr->msg_name; fromlen = mhdr->msg_namelen; if (cc < sizeof(struct icmp6_hdr)) { if (options & F_VERBOSE) warnx("packet too short (%d bytes) from %s", cc, pr_addr(from, fromlen)); return; } if (((mhdr->msg_flags & MSG_CTRUNC) != 0) && (options & F_VERBOSE) != 0) warnx("some control data discarded, insufficient buffer size"); icp = (struct icmp6_hdr *)buf; ni = (struct icmp6_nodeinfo *)buf; off = 0; if ((hoplim = get_hoplim(mhdr)) == -1) { warnx("failed to get receiving hop limit"); return; } if ((pktinfo = get_rcvpktinfo(mhdr)) == NULL) { warnx("failed to get receiving packet information"); return; } if (icp->icmp6_type == ICMP6_ECHO_REPLY && myechoreply(icp)) { seq = ntohs(icp->icmp6_seq); ++nreceived; if (timing) { tpp = (struct tv32 *)(icp + 1); tp.tv_sec = ntohl(tpp->tv32_sec); tp.tv_usec = ntohl(tpp->tv32_usec); tvsub(&tv, &tp); triptime = ((double)tv.tv_sec) * 1000.0 + ((double)tv.tv_usec) / 1000.0; tsum += triptime; tsumsq += triptime * triptime; if (triptime < tmin) tmin = triptime; if (triptime > tmax) tmax = triptime; } if (TST(seq % mx_dup_ck)) { ++nrepeats; --nreceived; dupflag = 1; } else { SET(seq % mx_dup_ck); dupflag = 0; } if (options & F_QUIET) return; if (options & F_FLOOD) (void)write(STDOUT_FILENO, &BSPACE, 1); else { if (options & F_AUDIBLE) (void)write(STDOUT_FILENO, &BBELL, 1); (void)printf("%d bytes from %s, icmp_seq=%u", cc, pr_addr(from, fromlen), seq); (void)printf(" hlim=%d", hoplim); if ((options & F_VERBOSE) != 0) { struct sockaddr_in6 dstsa; memset(&dstsa, 0, sizeof(dstsa)); dstsa.sin6_family = AF_INET6; dstsa.sin6_len = sizeof(dstsa); dstsa.sin6_scope_id = pktinfo->ipi6_ifindex; dstsa.sin6_addr = pktinfo->ipi6_addr; (void)printf(" dst=%s", pr_addr((struct sockaddr *)&dstsa, sizeof(dstsa))); } if (timing) (void)printf(" time=%.3f ms", triptime); if (dupflag) (void)printf("(DUP!)"); /* check the data */ cp = buf + off + ICMP6ECHOLEN + ICMP6ECHOTMLEN; dp = outpack + ICMP6ECHOLEN + ICMP6ECHOTMLEN; for (i = 8; cp < end; ++i, ++cp, ++dp) { if (*cp != *dp) { (void)printf("\nwrong data byte #%d should be 0x%x but was 0x%x", i, *dp, *cp); break; } } } } else if (icp->icmp6_type == ICMP6_NI_REPLY && mynireply(ni)) { seq = ntohs(*(u_int16_t *)ni->icmp6_ni_nonce); ++nreceived; if (TST(seq % mx_dup_ck)) { ++nrepeats; --nreceived; dupflag = 1; } else { SET(seq % mx_dup_ck); dupflag = 0; } if (options & F_QUIET) return; (void)printf("%d bytes from %s: ", cc, pr_addr(from, fromlen)); switch (ntohs(ni->ni_code)) { case ICMP6_NI_SUCCESS: break; case ICMP6_NI_REFUSED: printf("refused, type 0x%x", ntohs(ni->ni_type)); goto fqdnend; case ICMP6_NI_UNKNOWN: printf("unknown, type 0x%x", ntohs(ni->ni_type)); goto fqdnend; default: printf("unknown code 0x%x, type 0x%x", ntohs(ni->ni_code), ntohs(ni->ni_type)); goto fqdnend; } switch (ntohs(ni->ni_qtype)) { case NI_QTYPE_NOOP: printf("NodeInfo NOOP"); break; case NI_QTYPE_SUPTYPES: pr_suptypes(ni, end - (u_char *)ni); break; case NI_QTYPE_NODEADDR: pr_nodeaddr(ni, end - (u_char *)ni); break; case NI_QTYPE_FQDN: default: /* XXX: for backward compatibility */ cp = (u_char *)ni + ICMP6_NIRLEN; if (buf[off + ICMP6_NIRLEN] == cc - off - ICMP6_NIRLEN - 1) oldfqdn = 1; else oldfqdn = 0; if (oldfqdn) { cp++; /* skip length */ while (cp < end) { safeputc(*cp & 0xff); cp++; } } else { i = 0; while (cp < end) { if (dnsdecode((const u_char **)&cp, end, (const u_char *)(ni + 1), dnsname, sizeof(dnsname)) == NULL) { printf("???"); break; } /* * name-lookup special handling for * truncated name */ if (cp + 1 <= end && !*cp && strlen(dnsname) > 0) { dnsname[strlen(dnsname) - 1] = '\0'; cp++; } printf("%s%s", i > 0 ? "," : "", dnsname); } } if (options & F_VERBOSE) { int32_t ttl; int comma = 0; (void)printf(" ("); /*)*/ switch (ni->ni_code) { case ICMP6_NI_REFUSED: (void)printf("refused"); comma++; break; case ICMP6_NI_UNKNOWN: (void)printf("unknown qtype"); comma++; break; } if ((end - (u_char *)ni) < ICMP6_NIRLEN) { /* case of refusion, unknown */ /*(*/ putchar(')'); goto fqdnend; } ttl = (int32_t)ntohl(*(u_long *)&buf[off+ICMP6ECHOLEN+8]); if (comma) printf(","); if (!(ni->ni_flags & NI_FQDN_FLAG_VALIDTTL)) { (void)printf("TTL=%d:meaningless", (int)ttl); } else { if (ttl < 0) { (void)printf("TTL=%d:invalid", ttl); } else (void)printf("TTL=%d", ttl); } comma++; if (oldfqdn) { if (comma) printf(","); printf("03 draft"); comma++; } else { cp = (u_char *)ni + ICMP6_NIRLEN; if (cp == end) { if (comma) printf(","); printf("no name"); comma++; } } if (buf[off + ICMP6_NIRLEN] != cc - off - ICMP6_NIRLEN - 1 && oldfqdn) { if (comma) printf(","); (void)printf("invalid namelen:%d/%lu", buf[off + ICMP6_NIRLEN], (u_long)cc - off - ICMP6_NIRLEN - 1); comma++; } /*(*/ putchar(')'); } fqdnend: ; } } else { /* We've got something other than an ECHOREPLY */ if (!(options & F_VERBOSE)) return; (void)printf("%d bytes from %s: ", cc, pr_addr(from, fromlen)); pr_icmph(icp, end); } if (!(options & F_FLOOD)) { (void)putchar('\n'); if (options & F_VERBOSE) pr_exthdrs(mhdr); (void)fflush(stdout); } #undef safeputc } void pr_exthdrs(struct msghdr *mhdr) { ssize_t bufsize; void *bufp; struct cmsghdr *cm; bufsize = 0; bufp = mhdr->msg_control; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; bufsize = CONTROLLEN - ((caddr_t)CMSG_DATA(cm) - (caddr_t)bufp); if (bufsize <= 0) continue; switch (cm->cmsg_type) { case IPV6_HOPOPTS: printf(" HbH Options: "); pr_ip6opt(CMSG_DATA(cm), (size_t)bufsize); break; case IPV6_DSTOPTS: #ifdef IPV6_RTHDRDSTOPTS case IPV6_RTHDRDSTOPTS: #endif printf(" Dst Options: "); pr_ip6opt(CMSG_DATA(cm), (size_t)bufsize); break; case IPV6_RTHDR: printf(" Routing: "); pr_rthdr(CMSG_DATA(cm), (size_t)bufsize); break; } } } #ifdef USE_RFC2292BIS void pr_ip6opt(void *extbuf, size_t bufsize) { struct ip6_hbh *ext; int currentlen; u_int8_t type; socklen_t extlen, len, origextlen; void *databuf; size_t offset; u_int16_t value2; u_int32_t value4; ext = (struct ip6_hbh *)extbuf; extlen = (ext->ip6h_len + 1) * 8; printf("nxt %u, len %u (%lu bytes)\n", ext->ip6h_nxt, (unsigned int)ext->ip6h_len, (unsigned long)extlen); /* * Bounds checking on the ancillary data buffer: * subtract the size of a cmsg structure from the buffer size. */ if (bufsize < (extlen + CMSG_SPACE(0))) { origextlen = extlen; extlen = bufsize - CMSG_SPACE(0); warnx("options truncated, showing only %u (total=%u)", (unsigned int)(extlen / 8 - 1), (unsigned int)(ext->ip6h_len)); } currentlen = 0; while (1) { currentlen = inet6_opt_next(extbuf, extlen, currentlen, &type, &len, &databuf); if (currentlen == -1) break; switch (type) { /* * Note that inet6_opt_next automatically skips any padding * optins. */ case IP6OPT_JUMBO: offset = 0; offset = inet6_opt_get_val(databuf, offset, &value4, sizeof(value4)); printf(" Jumbo Payload Opt: Length %u\n", (u_int32_t)ntohl(value4)); break; case IP6OPT_ROUTER_ALERT: offset = 0; offset = inet6_opt_get_val(databuf, offset, &value2, sizeof(value2)); printf(" Router Alert Opt: Type %u\n", ntohs(value2)); break; default: printf(" Received Opt %u len %lu\n", type, (unsigned long)len); break; } } return; } #else /* !USE_RFC2292BIS */ /* ARGSUSED */ void pr_ip6opt(void *extbuf, size_t bufsize __unused) { putchar('\n'); return; } #endif /* USE_RFC2292BIS */ #ifdef USE_RFC2292BIS void pr_rthdr(void *extbuf, size_t bufsize) { struct in6_addr *in6; char ntopbuf[INET6_ADDRSTRLEN]; struct ip6_rthdr *rh = (struct ip6_rthdr *)extbuf; int i, segments, origsegs, rthsize, size0, size1; /* print fixed part of the header */ printf("nxt %u, len %u (%d bytes), type %u, ", rh->ip6r_nxt, rh->ip6r_len, (rh->ip6r_len + 1) << 3, rh->ip6r_type); if ((segments = inet6_rth_segments(extbuf)) >= 0) { printf("%d segments, ", segments); printf("%d left\n", rh->ip6r_segleft); } else { printf("segments unknown, "); printf("%d left\n", rh->ip6r_segleft); return; } /* * Bounds checking on the ancillary data buffer. When calculating * the number of items to show keep in mind: * - The size of the cmsg structure * - The size of one segment (the size of a Type 0 routing header) * - When dividing add a fudge factor of one in case the * dividend is not evenly divisible by the divisor */ rthsize = (rh->ip6r_len + 1) * 8; if (bufsize < (rthsize + CMSG_SPACE(0))) { origsegs = segments; size0 = inet6_rth_space(IPV6_RTHDR_TYPE_0, 0); size1 = inet6_rth_space(IPV6_RTHDR_TYPE_0, 1); segments -= (rthsize - (bufsize - CMSG_SPACE(0))) / (size1 - size0) + 1; warnx("segments truncated, showing only %d (total=%d)", segments, origsegs); } for (i = 0; i < segments; i++) { in6 = inet6_rth_getaddr(extbuf, i); if (in6 == NULL) printf(" [%d]\n", i); else { if (!inet_ntop(AF_INET6, in6, ntopbuf, sizeof(ntopbuf))) strlcpy(ntopbuf, "?", sizeof(ntopbuf)); printf(" [%d]%s\n", i, ntopbuf); } } return; } #else /* !USE_RFC2292BIS */ /* ARGSUSED */ void pr_rthdr(void *extbuf, size_t bufsize __unused) { putchar('\n'); return; } #endif /* USE_RFC2292BIS */ int pr_bitrange(u_int32_t v, int soff, int ii) { int off; int i; off = 0; while (off < 32) { /* shift till we have 0x01 */ if ((v & 0x01) == 0) { if (ii > 1) printf("-%u", soff + off - 1); ii = 0; switch (v & 0x0f) { case 0x00: v >>= 4; off += 4; continue; case 0x08: v >>= 3; off += 3; continue; case 0x04: case 0x0c: v >>= 2; off += 2; continue; default: v >>= 1; off += 1; continue; } } /* we have 0x01 with us */ for (i = 0; i < 32 - off; i++) { if ((v & (0x01 << i)) == 0) break; } if (!ii) printf(" %u", soff + off); ii += i; v >>= i; off += i; } return ii; } void pr_suptypes(struct icmp6_nodeinfo *ni, size_t nilen) /* ni->qtype must be SUPTYPES */ { size_t clen; u_int32_t v; const u_char *cp, *end; u_int16_t cur; struct cbit { u_int16_t words; /*32bit count*/ u_int16_t skip; } cbit; #define MAXQTYPES (1 << 16) size_t off; int b; cp = (u_char *)(ni + 1); end = ((u_char *)ni) + nilen; cur = 0; b = 0; printf("NodeInfo Supported Qtypes"); if (options & F_VERBOSE) { if (ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS) printf(", compressed bitmap"); else printf(", raw bitmap"); } while (cp < end) { clen = (size_t)(end - cp); if ((ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS) == 0) { if (clen == 0 || clen > MAXQTYPES / 8 || clen % sizeof(v)) { printf("???"); return; } } else { if (clen < sizeof(cbit) || clen % sizeof(v)) return; memcpy(&cbit, cp, sizeof(cbit)); if (sizeof(cbit) + ntohs(cbit.words) * sizeof(v) > clen) return; cp += sizeof(cbit); clen = ntohs(cbit.words) * sizeof(v); if (cur + clen * 8 + (u_long)ntohs(cbit.skip) * 32 > MAXQTYPES) return; } for (off = 0; off < clen; off += sizeof(v)) { memcpy(&v, cp + off, sizeof(v)); v = (u_int32_t)ntohl(v); b = pr_bitrange(v, (int)(cur + off * 8), b); } /* flush the remaining bits */ b = pr_bitrange(0, (int)(cur + off * 8), b); cp += clen; cur += clen * 8; if ((ni->ni_flags & NI_SUPTYPE_FLAG_COMPRESS) != 0) cur += ntohs(cbit.skip) * 32; } } void pr_nodeaddr(struct icmp6_nodeinfo *ni, int nilen) /* ni->qtype must be NODEADDR */ { u_char *cp = (u_char *)(ni + 1); char ntop_buf[INET6_ADDRSTRLEN]; int withttl = 0; nilen -= sizeof(struct icmp6_nodeinfo); if (options & F_VERBOSE) { switch (ni->ni_code) { case ICMP6_NI_REFUSED: (void)printf("refused"); break; case ICMP6_NI_UNKNOWN: (void)printf("unknown qtype"); break; } if (ni->ni_flags & NI_NODEADDR_FLAG_TRUNCATE) (void)printf(" truncated"); } putchar('\n'); if (nilen <= 0) printf(" no address\n"); /* * In icmp-name-lookups 05 and later, TTL of each returned address * is contained in the resposne. We try to detect the version * by the length of the data, but note that the detection algorithm * is incomplete. We assume the latest draft by default. */ if (nilen % (sizeof(u_int32_t) + sizeof(struct in6_addr)) == 0) withttl = 1; while (nilen > 0) { u_int32_t ttl; if (withttl) { /* XXX: alignment? */ ttl = (u_int32_t)ntohl(*(u_int32_t *)cp); cp += sizeof(u_int32_t); nilen -= sizeof(u_int32_t); } if (inet_ntop(AF_INET6, cp, ntop_buf, sizeof(ntop_buf)) == NULL) strlcpy(ntop_buf, "?", sizeof(ntop_buf)); printf(" %s", ntop_buf); if (withttl) { if (ttl == 0xffffffff) { /* * XXX: can this convention be applied to all * type of TTL (i.e. non-ND TTL)? */ printf("(TTL=infty)"); } else printf("(TTL=%u)", ttl); } putchar('\n'); nilen -= sizeof(struct in6_addr); cp += sizeof(struct in6_addr); } } int get_hoplim(struct msghdr *mhdr) { struct cmsghdr *cm; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) { if (cm->cmsg_len == 0) return(-1); if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_HOPLIMIT && cm->cmsg_len == CMSG_LEN(sizeof(int))) return(*(int *)CMSG_DATA(cm)); } return(-1); } struct in6_pktinfo * get_rcvpktinfo(struct msghdr *mhdr) { struct cmsghdr *cm; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) { if (cm->cmsg_len == 0) return(NULL); if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_PKTINFO && cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) return((struct in6_pktinfo *)CMSG_DATA(cm)); } return(NULL); } int get_pathmtu(struct msghdr *mhdr) { #ifdef IPV6_RECVPATHMTU struct cmsghdr *cm; struct ip6_mtuinfo *mtuctl = NULL; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) { if (cm->cmsg_len == 0) return(0); if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_PATHMTU && cm->cmsg_len == CMSG_LEN(sizeof(struct ip6_mtuinfo))) { mtuctl = (struct ip6_mtuinfo *)CMSG_DATA(cm); /* * If the notified destination is different from * the one we are pinging, just ignore the info. * We check the scope ID only when both notified value * and our own value have non-0 values, because we may * have used the default scope zone ID for sending, * in which case the scope ID value is 0. */ if (!IN6_ARE_ADDR_EQUAL(&mtuctl->ip6m_addr.sin6_addr, &dst.sin6_addr) || (mtuctl->ip6m_addr.sin6_scope_id && dst.sin6_scope_id && mtuctl->ip6m_addr.sin6_scope_id != dst.sin6_scope_id)) { if ((options & F_VERBOSE) != 0) { printf("path MTU for %s is notified. " "(ignored)\n", pr_addr((struct sockaddr *)&mtuctl->ip6m_addr, sizeof(mtuctl->ip6m_addr))); } return(0); } /* * Ignore an invalid MTU. XXX: can we just believe * the kernel check? */ if (mtuctl->ip6m_mtu < IPV6_MMTU) return(0); /* notification for our destination. return the MTU. */ return((int)mtuctl->ip6m_mtu); } } #endif return(0); } /* * tvsub -- * Subtract 2 timeval structs: out = out - in. Out is assumed to * be >= in. */ void tvsub(struct timeval *out, struct timeval *in) { if ((out->tv_usec -= in->tv_usec) < 0) { --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } /* * onint -- * SIGINT handler. */ /* ARGSUSED */ void onint(int notused __unused) { summary(); if (res != NULL) freeaddrinfo(res); if(packet != NULL) free(packet); #ifndef HAVE_POLL_H if(fdmaskp != NULL) free(fdmaskp); #endif (void)signal(SIGINT, SIG_DFL); (void)kill(getpid(), SIGINT); /* NOTREACHED */ exit(1); } /* * summary -- * Print out statistics. */ void summary(void) { (void)printf("\n--- %s ping6 statistics ---\n", hostname); (void)printf("%ld packets transmitted, ", ntransmitted); (void)printf("%ld packets received, ", nreceived); if (nrepeats) (void)printf("+%ld duplicates, ", nrepeats); if (ntransmitted) { if (nreceived > ntransmitted) (void)printf("-- somebody's duplicating packets!"); else (void)printf("%.1f%% packet loss", ((((double)ntransmitted - nreceived) * 100.0) / ntransmitted)); } (void)putchar('\n'); if (nreceived && timing) { /* Only display average to microseconds */ double num = nreceived + nrepeats; double avg = tsum / num; double dev = sqrt(tsumsq / num - avg * avg); (void)printf( "round-trip min/avg/max/std-dev = %.3f/%.3f/%.3f/%.3f ms\n", tmin, avg, tmax, dev); (void)fflush(stdout); } (void)fflush(stdout); } /*subject type*/ static const char *niqcode[] = { "IPv6 address", "DNS label", /*or empty*/ "IPv4 address", }; /*result code*/ static const char *nircode[] = { "Success", "Refused", "Unknown", }; /* * pr_icmph -- * Print a descriptive string about an ICMP header. */ void pr_icmph(struct icmp6_hdr *icp, u_char *end) { char ntop_buf[INET6_ADDRSTRLEN]; struct nd_redirect *red; struct icmp6_nodeinfo *ni; char dnsname[MAXDNAME + 1]; const u_char *cp; size_t l; switch (icp->icmp6_type) { case ICMP6_DST_UNREACH: switch (icp->icmp6_code) { case ICMP6_DST_UNREACH_NOROUTE: (void)printf("No Route to Destination\n"); break; case ICMP6_DST_UNREACH_ADMIN: (void)printf("Destination Administratively " "Unreachable\n"); break; case ICMP6_DST_UNREACH_BEYONDSCOPE: (void)printf("Destination Unreachable Beyond Scope\n"); break; case ICMP6_DST_UNREACH_ADDR: (void)printf("Destination Host Unreachable\n"); break; case ICMP6_DST_UNREACH_NOPORT: (void)printf("Destination Port Unreachable\n"); break; default: (void)printf("Destination Unreachable, Bad Code: %d\n", icp->icmp6_code); break; } /* Print returned IP header information */ pr_retip((struct ip6_hdr *)(icp + 1), end); break; case ICMP6_PACKET_TOO_BIG: (void)printf("Packet too big mtu = %d\n", (int)ntohl(icp->icmp6_mtu)); pr_retip((struct ip6_hdr *)(icp + 1), end); break; case ICMP6_TIME_EXCEEDED: switch (icp->icmp6_code) { case ICMP6_TIME_EXCEED_TRANSIT: (void)printf("Time to live exceeded\n"); break; case ICMP6_TIME_EXCEED_REASSEMBLY: (void)printf("Frag reassembly time exceeded\n"); break; default: (void)printf("Time exceeded, Bad Code: %d\n", icp->icmp6_code); break; } pr_retip((struct ip6_hdr *)(icp + 1), end); break; case ICMP6_PARAM_PROB: (void)printf("Parameter problem: "); switch (icp->icmp6_code) { case ICMP6_PARAMPROB_HEADER: (void)printf("Erroneous Header "); break; case ICMP6_PARAMPROB_NEXTHEADER: (void)printf("Unknown Nextheader "); break; case ICMP6_PARAMPROB_OPTION: (void)printf("Unrecognized Option "); break; default: (void)printf("Bad code(%d) ", icp->icmp6_code); break; } (void)printf("pointer = 0x%02x\n", (u_int32_t)ntohl(icp->icmp6_pptr)); pr_retip((struct ip6_hdr *)(icp + 1), end); break; case ICMP6_ECHO_REQUEST: (void)printf("Echo Request"); /* XXX ID + Seq + Data */ break; case ICMP6_ECHO_REPLY: (void)printf("Echo Reply"); /* XXX ID + Seq + Data */ break; case ICMP6_MEMBERSHIP_QUERY: (void)printf("Listener Query"); break; case ICMP6_MEMBERSHIP_REPORT: (void)printf("Listener Report"); break; case ICMP6_MEMBERSHIP_REDUCTION: (void)printf("Listener Done"); break; case ND_ROUTER_SOLICIT: (void)printf("Router Solicitation"); break; case ND_ROUTER_ADVERT: (void)printf("Router Advertisement"); break; case ND_NEIGHBOR_SOLICIT: (void)printf("Neighbor Solicitation"); break; case ND_NEIGHBOR_ADVERT: (void)printf("Neighbor Advertisement"); break; case ND_REDIRECT: red = (struct nd_redirect *)icp; (void)printf("Redirect\n"); if (!inet_ntop(AF_INET6, &red->nd_rd_dst, ntop_buf, sizeof(ntop_buf))) strlcpy(ntop_buf, "?", sizeof(ntop_buf)); (void)printf("Destination: %s", ntop_buf); if (!inet_ntop(AF_INET6, &red->nd_rd_target, ntop_buf, sizeof(ntop_buf))) strlcpy(ntop_buf, "?", sizeof(ntop_buf)); (void)printf(" New Target: %s", ntop_buf); break; case ICMP6_NI_QUERY: (void)printf("Node Information Query"); /* XXX ID + Seq + Data */ ni = (struct icmp6_nodeinfo *)icp; l = end - (u_char *)(ni + 1); printf(", "); switch (ntohs(ni->ni_qtype)) { case NI_QTYPE_NOOP: (void)printf("NOOP"); break; case NI_QTYPE_SUPTYPES: (void)printf("Supported qtypes"); break; case NI_QTYPE_FQDN: (void)printf("DNS name"); break; case NI_QTYPE_NODEADDR: (void)printf("nodeaddr"); break; case NI_QTYPE_IPV4ADDR: (void)printf("IPv4 nodeaddr"); break; default: (void)printf("unknown qtype"); break; } if (options & F_VERBOSE) { switch (ni->ni_code) { case ICMP6_NI_SUBJ_IPV6: if (l == sizeof(struct in6_addr) && inet_ntop(AF_INET6, ni + 1, ntop_buf, sizeof(ntop_buf)) != NULL) { (void)printf(", subject=%s(%s)", niqcode[ni->ni_code], ntop_buf); } else { #if 1 /* backward compat to -W */ (void)printf(", oldfqdn"); #else (void)printf(", invalid"); #endif } break; case ICMP6_NI_SUBJ_FQDN: if (end == (u_char *)(ni + 1)) { (void)printf(", no subject"); break; } printf(", subject=%s", niqcode[ni->ni_code]); cp = (const u_char *)(ni + 1); if (dnsdecode(&cp, end, NULL, dnsname, sizeof(dnsname)) != NULL) printf("(%s)", dnsname); else printf("(invalid)"); break; case ICMP6_NI_SUBJ_IPV4: if (l == sizeof(struct in_addr) && inet_ntop(AF_INET, ni + 1, ntop_buf, sizeof(ntop_buf)) != NULL) { (void)printf(", subject=%s(%s)", niqcode[ni->ni_code], ntop_buf); } else (void)printf(", invalid"); break; default: (void)printf(", invalid"); break; } } break; case ICMP6_NI_REPLY: (void)printf("Node Information Reply"); /* XXX ID + Seq + Data */ ni = (struct icmp6_nodeinfo *)icp; printf(", "); switch (ntohs(ni->ni_qtype)) { case NI_QTYPE_NOOP: (void)printf("NOOP"); break; case NI_QTYPE_SUPTYPES: (void)printf("Supported qtypes"); break; case NI_QTYPE_FQDN: (void)printf("DNS name"); break; case NI_QTYPE_NODEADDR: (void)printf("nodeaddr"); break; case NI_QTYPE_IPV4ADDR: (void)printf("IPv4 nodeaddr"); break; default: (void)printf("unknown qtype"); break; } if (options & F_VERBOSE) { if (ni->ni_code > sizeof(nircode) / sizeof(nircode[0])) printf(", invalid"); else printf(", %s", nircode[ni->ni_code]); } break; default: (void)printf("Bad ICMP type: %d", icp->icmp6_type); } } /* * pr_iph -- * Print an IP6 header. */ void pr_iph(struct ip6_hdr *ip6) { u_int32_t flow = ip6->ip6_flow & IPV6_FLOWLABEL_MASK; u_int8_t tc; char ntop_buf[INET6_ADDRSTRLEN]; tc = *(&ip6->ip6_vfc + 1); /* XXX */ tc = (tc >> 4) & 0x0f; tc |= (ip6->ip6_vfc << 4); printf("Vr TC Flow Plen Nxt Hlim\n"); printf(" %1x %02x %05x %04x %02x %02x\n", (ip6->ip6_vfc & IPV6_VERSION_MASK) >> 4, tc, (u_int32_t)ntohl(flow), ntohs(ip6->ip6_plen), ip6->ip6_nxt, ip6->ip6_hlim); if (!inet_ntop(AF_INET6, &ip6->ip6_src, ntop_buf, sizeof(ntop_buf))) strlcpy(ntop_buf, "?", sizeof(ntop_buf)); printf("%s->", ntop_buf); if (!inet_ntop(AF_INET6, &ip6->ip6_dst, ntop_buf, sizeof(ntop_buf))) strlcpy(ntop_buf, "?", sizeof(ntop_buf)); printf("%s\n", ntop_buf); } /* * pr_addr -- * Return an ascii host address as a dotted quad and optionally with * a hostname. */ const char * pr_addr(struct sockaddr *addr, int addrlen) { static char buf[NI_MAXHOST]; int flag = 0; if ((options & F_HOSTNAME) == 0) flag |= NI_NUMERICHOST; if (getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0, flag) == 0) return (buf); else return "?"; } /* * pr_retip -- * Dump some info on a returned (via ICMPv6) IPv6 packet. */ void pr_retip(struct ip6_hdr *ip6, u_char *end) { u_char *cp = (u_char *)ip6, nh; int hlen; if (end - (u_char *)ip6 < sizeof(*ip6)) { printf("IP6"); goto trunc; } pr_iph(ip6); hlen = sizeof(*ip6); nh = ip6->ip6_nxt; cp += hlen; while (end - cp >= 8) { switch (nh) { case IPPROTO_HOPOPTS: printf("HBH "); hlen = (((struct ip6_hbh *)cp)->ip6h_len+1) << 3; nh = ((struct ip6_hbh *)cp)->ip6h_nxt; break; case IPPROTO_DSTOPTS: printf("DSTOPT "); hlen = (((struct ip6_dest *)cp)->ip6d_len+1) << 3; nh = ((struct ip6_dest *)cp)->ip6d_nxt; break; case IPPROTO_FRAGMENT: printf("FRAG "); hlen = sizeof(struct ip6_frag); nh = ((struct ip6_frag *)cp)->ip6f_nxt; break; case IPPROTO_ROUTING: printf("RTHDR "); hlen = (((struct ip6_rthdr *)cp)->ip6r_len+1) << 3; nh = ((struct ip6_rthdr *)cp)->ip6r_nxt; break; #ifdef IPSEC case IPPROTO_AH: printf("AH "); hlen = (((struct ah *)cp)->ah_len+2) << 2; nh = ((struct ah *)cp)->ah_nxt; break; #endif case IPPROTO_ICMPV6: printf("ICMP6: type = %d, code = %d\n", *cp, *(cp + 1)); return; case IPPROTO_ESP: printf("ESP\n"); return; case IPPROTO_TCP: printf("TCP: from port %u, to port %u (decimal)\n", (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3))); return; case IPPROTO_UDP: printf("UDP: from port %u, to port %u (decimal)\n", (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3))); return; default: printf("Unknown Header(%d)\n", nh); return; } if ((cp += hlen) >= end) goto trunc; } if (end - cp < 8) goto trunc; putchar('\n'); return; trunc: printf("...\n"); return; } void fill(char *bp, char *patp) { int ii, jj, kk; int pat[16]; char *cp; for (cp = patp; *cp; cp++) if (!isxdigit(*cp)) errx(1, "patterns must be specified as hex digits"); ii = sscanf(patp, "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", &pat[0], &pat[1], &pat[2], &pat[3], &pat[4], &pat[5], &pat[6], &pat[7], &pat[8], &pat[9], &pat[10], &pat[11], &pat[12], &pat[13], &pat[14], &pat[15]); /* xxx */ if (ii > 0) for (kk = 0; kk <= MAXDATALEN - (8 + sizeof(struct tv32) + ii); kk += ii) for (jj = 0; jj < ii; ++jj) bp[jj + kk] = pat[jj]; if (!(options & F_QUIET)) { (void)printf("PATTERN: 0x"); for (jj = 0; jj < ii; ++jj) (void)printf("%02x", bp[jj] & 0xFF); (void)printf("\n"); } } #ifdef IPSEC #ifdef IPSEC_POLICY_IPSEC int setpolicy(int so __unused, char *policy) { char *buf; if (policy == NULL) return 0; /* ignore */ buf = ipsec_set_policy(policy, strlen(policy)); if (buf == NULL) errx(1, "%s", ipsec_strerror()); if (setsockopt(s, IPPROTO_IPV6, IPV6_IPSEC_POLICY, buf, ipsec_get_policylen(buf)) < 0) warnx("Unable to set IPsec policy"); free(buf); return 0; } #endif #endif char * nigroup(char *name) { char *p; char *q; MD5_CTX ctxt; u_int8_t digest[16]; u_int8_t c; size_t l; char hbuf[NI_MAXHOST]; struct in6_addr in6; p = strchr(name, '.'); if (!p) p = name + strlen(name); l = p - name; if (l > 63 || l > sizeof(hbuf) - 1) return NULL; /*label too long*/ strncpy(hbuf, name, l); hbuf[(int)l] = '\0'; for (q = name; *q; q++) { if (isupper(*(unsigned char *)q)) *q = tolower(*(unsigned char *)q); } /* generate 8 bytes of pseudo-random value. */ memset(&ctxt, 0, sizeof(ctxt)); MD5Init(&ctxt); c = l & 0xff; MD5Update(&ctxt, &c, sizeof(c)); MD5Update(&ctxt, (unsigned char *)name, l); MD5Final(digest, &ctxt); if (inet_pton(AF_INET6, "ff02::2:0000:0000", &in6) != 1) return NULL; /*XXX*/ bcopy(digest, &in6.s6_addr[12], 4); if (inet_ntop(AF_INET6, &in6, hbuf, sizeof(hbuf)) == NULL) return NULL; return strdup(hbuf); } void usage(void) { (void)fprintf(stderr, #if defined(IPSEC) && !defined(IPSEC_POLICY_IPSEC) "A" #endif "usage: ping6 [-" "Dd" #if defined(IPSEC) && !defined(IPSEC_POLICY_IPSEC) "E" #endif "fH" #ifdef IPV6_USE_MIN_MTU "m" #endif "nNoqrRtvwW] " "[-a addrtype] [-b bufsiz] [-c count] [-g gateway]\n" " [-h hoplimit] [-I interface] [-i wait] [-l preload]" #if defined(IPSEC) && defined(IPSEC_POLICY_IPSEC) " [-P policy]" #endif "\n" " [-p pattern] [-S sourceaddr] [-s packetsize] " "[hops ...] host\n"); exit(1); } Index: head/sbin/reboot/nextboot.8 =================================================================== --- head/sbin/reboot/nextboot.8 (revision 229777) +++ head/sbin/reboot/nextboot.8 (revision 229778) @@ -1,125 +1,125 @@ .\" Copyright (c) 2002 Gordon Tetlow .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd November 4, 2002 .Dt NEXTBOOT 8 .Os .Sh NAME .Nm nextboot .Nd "specify an alternate kernel and boot flags for the next reboot" .Sh SYNOPSIS .Nm .Op Fl f .Op Fl o Ar options .Fl k Ar kernel .Nm .Fl D .Sh DESCRIPTION The .Nm utility allows specifying an alternate kernel and/or boot flags for the next time the machine is booted. Once the .Xr loader 8 loads in the new kernel information, it is deleted so in case the new kernel hangs the machine, once it is rebooted, the machine will automatically revert to its previous configuration. .Pp The options are as follows: .Bl -tag -width ".Fl o Ar options" .It Fl D Invoking .Nm with this option removes an existing .Nm configuration. .It Fl f This option disables the sanity checking which checks if the kernel really exists before writing the .Nm configuration. .It Fl k Ar kernel This option specifies a kernel directory relative to .Pa /boot to load the kernel and any modules from. .It Fl o Ar options This option allows the passing of kernel flags for the next boot. .El .Sh FILES .Bl -tag -width ".Pa /boot/nextboot.conf" -compact .It Pa /boot/nextboot.conf The configuration file that the .Nm configuration is written into. .El .Sh EXAMPLES To boot the .Pa GENERIC kernel with the .Nm command: .Pp .Dl "nextboot -k GENERIC" .Pp To enable into single user mode with the normal kernel: .Pp .Dl "nextboot -o ""-s"" -k kernel" .Pp To remove an existing nextboot configuration: .Pp .Dl "nextboot -D" .Sh SEE ALSO .Xr boot 8 , .Xr loader 8 .Sh HISTORY The original .Nm manual page first appeared in .Fx 2.2 . It used a very different interface to achieve similar results. .Pp The current incarnation of .Nm appeared in .Fx 5.0 . .Sh AUTHORS This manual page was written by .An Gordon Tetlow Aq gordon@FreeBSD.org . .Sh BUGS The .Nm code is implemented in the .Xr loader 8 . -It is not the most throughly tested code. +It is not the most thoroughly tested code. It is also my first attempt to write in Forth. .Pp Finally, it does some evil things like writing to the file system before it has been checked. If it scrambles your file system, do not blame me. Index: head/sbin/routed/main.c =================================================================== --- head/sbin/routed/main.c (revision 229777) +++ head/sbin/routed/main.c (revision 229778) @@ -1,965 +1,965 @@ /* * Copyright (c) 1983, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include "defs.h" #include "pathnames.h" #ifdef sgi #include "math.h" #endif #include #include #include __COPYRIGHT("@(#) Copyright (c) 1983, 1988, 1993 " "The Regents of the University of California." " All rights reserved."); #ifdef __NetBSD__ __RCSID("$NetBSD$"); #include #elif defined(__FreeBSD__) __RCSID("$FreeBSD$"); #else __RCSID("$Revision: 2.31 $"); #ident "$Revision: 2.31 $" #endif pid_t mypid; naddr myaddr; /* system address */ static char myname[MAXHOSTNAMELEN+1]; static int verbose; int supplier; /* supply or broadcast updates */ int supplier_set; static int ipforwarding = 1; /* kernel forwarding on */ static int default_gateway; /* 1=advertise default */ static int background = 1; int ridhosts; /* 1=reduce host routes */ int mhome; /* 1=want multi-homed host route */ int advertise_mhome; /* 1=must continue advertising it */ int auth_ok = 1; /* 1=ignore auth if we do not care */ struct timeval epoch; /* when started */ struct timeval clk; static struct timeval prev_clk; static int usec_fudge; struct timeval now; /* current idea of time */ time_t now_stale; time_t now_expire; time_t now_garbage; static struct timeval next_bcast; /* next general broadcast */ struct timeval no_flash = { /* inhibit flash update */ EPOCH+SUPPLY_INTERVAL, 0 }; static struct timeval flush_kern_timer; static fd_set fdbits; static int sock_max; int rip_sock = -1; /* RIP socket */ const struct interface *rip_sock_mcast; /* current multicast interface */ int rt_sock; /* routing socket */ int rt_sock_seqno; static int get_rip_sock(naddr, int); static void timevalsub(struct timeval *, struct timeval *, struct timeval *); static void sigalrm(int s UNUSED); static void sigterm(int sig); int main(int argc, char *argv[]) { int n, mib[4], off; size_t len; char *p, *q; const char *cp; struct timeval wtime, t2; time_t dt; fd_set ibits; naddr p_net, p_mask; struct interface *ifp; struct parm parm; char *tracename = 0; /* Some shells are badly broken and send SIGHUP to backgrounded * processes. */ signal(SIGHUP, SIG_IGN); openlog("routed", LOG_PID, LOG_DAEMON); ftrace = stdout; gettimeofday(&clk, 0); prev_clk = clk; epoch = clk; epoch.tv_sec -= EPOCH; now.tv_sec = EPOCH; now_stale = EPOCH - STALE_TIME; now_expire = EPOCH - EXPIRE_TIME; now_garbage = EPOCH - GARBAGE_TIME; wtime.tv_sec = 0; (void)gethostname(myname, sizeof(myname)-1); (void)gethost(myname, &myaddr); while ((n = getopt(argc, argv, "sqdghmAtvT:F:P:")) != -1) { switch (n) { case 's': supplier = 1; supplier_set = 1; break; case 'q': supplier = 0; supplier_set = 1; break; case 'd': background = 0; break; case 'g': memset(&parm, 0, sizeof(parm)); parm.parm_d_metric = 1; cp = check_parms(&parm); if (cp != 0) msglog("bad -g: %s", cp); else default_gateway = 1; break; case 'h': /* suppress extra host routes */ ridhosts = 1; break; case 'm': /* advertise host route */ mhome = 1; /* on multi-homed hosts */ break; case 'A': /* Ignore authentication if we do not care. * Crazy as it is, that is what RFC 1723 requires. */ auth_ok = 0; break; case 't': new_tracelevel++; break; case 'T': tracename = optarg; break; case 'F': /* minimal routes for SLIP */ n = FAKE_METRIC; p = strchr(optarg,','); if (p && *p != '\0') { n = (int)strtoul(p+1, &q, 0); if (*q == '\0' && n <= HOPCNT_INFINITY-1 && n >= 1) *p = '\0'; } if (!getnet(optarg, &p_net, &p_mask)) { msglog("bad network; \"-F %s\"", optarg); break; } memset(&parm, 0, sizeof(parm)); parm.parm_net = p_net; parm.parm_mask = p_mask; parm.parm_d_metric = n; cp = check_parms(&parm); if (cp != 0) msglog("bad -F: %s", cp); break; case 'P': /* handle arbitrary parameters. */ q = strdup(optarg); cp = parse_parms(q, 0); if (cp != 0) msglog("%s in \"-P %s\"", cp, optarg); free(q); break; case 'v': /* display version */ verbose++; msglog("version 2.31"); break; default: goto usage; } } argc -= optind; argv += optind; if (tracename == 0 && argc >= 1) { tracename = *argv++; argc--; } if (tracename != 0 && tracename[0] == '\0') goto usage; if (argc != 0) { usage: logbad(0, "usage: routed [-sqdghmAtv] [-T tracefile]" " [-F net[,metric]] [-P parms]"); } if (geteuid() != 0) { if (verbose) exit(0); logbad(0, "requires UID 0"); } mib[0] = CTL_NET; mib[1] = PF_INET; mib[2] = IPPROTO_IP; mib[3] = IPCTL_FORWARDING; len = sizeof(ipforwarding); if (sysctl(mib, 4, &ipforwarding, &len, 0, 0) < 0) LOGERR("sysctl(IPCTL_FORWARDING)"); if (!ipforwarding) { if (supplier) msglog("-s incompatible with ipforwarding=0"); if (default_gateway) { msglog("-g incompatible with ipforwarding=0"); default_gateway = 0; } supplier = 0; supplier_set = 1; } if (default_gateway) { if (supplier_set && !supplier) { msglog("-g and -q incompatible"); } else { supplier = 1; supplier_set = 1; } } signal(SIGALRM, sigalrm); if (!background) signal(SIGHUP, sigterm); /* SIGHUP fatal during debugging */ signal(SIGTERM, sigterm); signal(SIGINT, sigterm); signal(SIGUSR1, sigtrace_on); signal(SIGUSR2, sigtrace_off); /* get into the background */ #ifdef sgi if (0 > _daemonize(background ? 0 : (_DF_NOCHDIR|_DF_NOFORK), STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)) BADERR(0, "_daemonize()"); #else if (background && daemon(0, 1) < 0) BADERR(0,"daemon()"); #endif #if defined(__NetBSD__) pidfile(0); #endif mypid = getpid(); #ifdef __FreeBSD__ srandomdev(); #else srandom((int)(clk.tv_sec ^ clk.tv_usec ^ mypid)); #endif /* prepare socket connected to the kernel. */ rt_sock = socket(AF_ROUTE, SOCK_RAW, 0); if (rt_sock < 0) BADERR(1,"rt_sock = socket()"); if (fcntl(rt_sock, F_SETFL, O_NONBLOCK) == -1) logbad(1, "fcntl(rt_sock) O_NONBLOCK: %s", strerror(errno)); off = 0; if (setsockopt(rt_sock, SOL_SOCKET,SO_USELOOPBACK, &off,sizeof(off)) < 0) LOGERR("setsockopt(SO_USELOOPBACK,0)"); fix_select(); if (tracename != 0) { strncpy(inittracename, tracename, sizeof(inittracename)-1); set_tracefile(inittracename, "%s", -1); } else { tracelevel_msg("%s", -1); /* turn on tracing to stdio */ } bufinit(); /* initialize radix tree */ rtinit(); /* Pick a random part of the second for our output to minimize * collisions. * * Start broadcasting after hearing from other routers, and * at a random time so a bunch of systems do not get synchronized * after a power failure. */ intvl_random(&next_bcast, EPOCH+MIN_WAITTIME, EPOCH+SUPPLY_INTERVAL); age_timer.tv_usec = next_bcast.tv_usec; age_timer.tv_sec = EPOCH+MIN_WAITTIME; rdisc_timer = next_bcast; ifinit_timer.tv_usec = next_bcast.tv_usec; /* Collect an initial view of the world by checking the interface * configuration and the kludge file. */ gwkludge(); ifinit(); /* Ask for routes */ rip_query(); rdisc_sol(); /* Now turn off stdio if not tracing */ if (new_tracelevel == 0) trace_close(background); /* Loop forever, listening and broadcasting. */ for (;;) { prev_clk = clk; gettimeofday(&clk, 0); if (prev_clk.tv_sec == clk.tv_sec && prev_clk.tv_usec == clk.tv_usec+usec_fudge) { /* Much of `routed` depends on time always advancing. * On systems that do not guarantee that gettimeofday() * produces unique timestamps even if called within * a single tick, use trickery like that in classic * BSD kernels. */ clk.tv_usec += ++usec_fudge; } else { usec_fudge = 0; timevalsub(&t2, &clk, &prev_clk); if (t2.tv_sec < 0 || t2.tv_sec > wtime.tv_sec + 5) { /* Deal with time changes before other * housekeeping to keep everything straight. */ dt = t2.tv_sec; if (dt > 0) dt -= wtime.tv_sec; trace_act("time changed by %d sec", (int)dt); epoch.tv_sec += dt; } } timevalsub(&now, &clk, &epoch); now_stale = now.tv_sec - STALE_TIME; now_expire = now.tv_sec - EXPIRE_TIME; now_garbage = now.tv_sec - GARBAGE_TIME; /* deal with signals that should affect tracing */ set_tracelevel(); if (stopint != 0) { rip_bcast(0); rdisc_adv(); trace_off("exiting with signal %d", stopint); exit(stopint | 128); } /* look for new or dead interfaces */ timevalsub(&wtime, &ifinit_timer, &now); if (wtime.tv_sec <= 0) { wtime.tv_sec = 0; ifinit(); rip_query(); continue; } - /* Check the kernel table occassionally for mysteriously + /* Check the kernel table occasionally for mysteriously * evaporated routes */ timevalsub(&t2, &flush_kern_timer, &now); if (t2.tv_sec <= 0) { flush_kern(); flush_kern_timer.tv_sec = (now.tv_sec + CHECK_QUIET_INTERVAL); continue; } if (timercmp(&t2, &wtime, <)) wtime = t2; /* If it is time, then broadcast our routes. */ if (supplier || advertise_mhome) { timevalsub(&t2, &next_bcast, &now); if (t2.tv_sec <= 0) { /* Synchronize the aging and broadcast * timers to minimize awakenings */ age(0); rip_bcast(0); /* It is desirable to send routing updates * regularly. So schedule the next update * 30 seconds after the previous one was * scheduled, instead of 30 seconds after * the previous update was finished. * Even if we just started after discovering * a 2nd interface or were otherwise delayed, - * pick a 30-second aniversary of the + * pick a 30-second anniversary of the * original broadcast time. */ n = 1 + (0-t2.tv_sec)/SUPPLY_INTERVAL; next_bcast.tv_sec += n*SUPPLY_INTERVAL; continue; } if (timercmp(&t2, &wtime, <)) wtime = t2; } /* If we need a flash update, either do it now or * set the delay to end when it is time. * * If we are within MIN_WAITTIME seconds of a full update, * do not bother. */ if (need_flash && supplier && no_flash.tv_sec+MIN_WAITTIME < next_bcast.tv_sec) { /* accurate to the millisecond */ if (!timercmp(&no_flash, &now, >)) rip_bcast(1); timevalsub(&t2, &no_flash, &now); if (timercmp(&t2, &wtime, <)) wtime = t2; } /* trigger the main aging timer. */ timevalsub(&t2, &age_timer, &now); if (t2.tv_sec <= 0) { age(0); continue; } if (timercmp(&t2, &wtime, <)) wtime = t2; /* update the kernel routing table */ timevalsub(&t2, &need_kern, &now); if (t2.tv_sec <= 0) { age(0); continue; } if (timercmp(&t2, &wtime, <)) wtime = t2; /* take care of router discovery, * but do it in the correct the millisecond */ if (!timercmp(&rdisc_timer, &now, >)) { rdisc_age(0); continue; } timevalsub(&t2, &rdisc_timer, &now); if (timercmp(&t2, &wtime, <)) wtime = t2; /* wait for input or a timer to expire. */ trace_flush(); ibits = fdbits; n = select(sock_max, &ibits, 0, 0, &wtime); if (n <= 0) { if (n < 0 && errno != EINTR && errno != EAGAIN) BADERR(1,"select"); continue; } if (FD_ISSET(rt_sock, &ibits)) { read_rt(); n--; } if (rdisc_sock >= 0 && FD_ISSET(rdisc_sock, &ibits)) { read_d(); n--; } if (rip_sock >= 0 && FD_ISSET(rip_sock, &ibits)) { read_rip(rip_sock, 0); n--; } LIST_FOREACH(ifp, &ifnet, int_list) { if (n <= 0) break; if (ifp->int_rip_sock >= 0 && FD_ISSET(ifp->int_rip_sock, &ibits)) { read_rip(ifp->int_rip_sock, ifp); n--; } } } } /* ARGSUSED */ static void sigalrm(int s UNUSED) { /* Historically, SIGALRM would cause the daemon to check for * new and broken interfaces. */ ifinit_timer.tv_sec = now.tv_sec; trace_act("SIGALRM"); } /* watch for fatal signals */ static void sigterm(int sig) { stopint = sig; (void)signal(sig, SIG_DFL); /* catch it only once */ } void fix_select(void) { struct interface *ifp; FD_ZERO(&fdbits); sock_max = 0; FD_SET(rt_sock, &fdbits); if (sock_max <= rt_sock) sock_max = rt_sock+1; if (rip_sock >= 0) { FD_SET(rip_sock, &fdbits); if (sock_max <= rip_sock) sock_max = rip_sock+1; } LIST_FOREACH(ifp, &ifnet, int_list) { if (ifp->int_rip_sock >= 0) { FD_SET(ifp->int_rip_sock, &fdbits); if (sock_max <= ifp->int_rip_sock) sock_max = ifp->int_rip_sock+1; } } if (rdisc_sock >= 0) { FD_SET(rdisc_sock, &fdbits); if (sock_max <= rdisc_sock) sock_max = rdisc_sock+1; } } void fix_sock(int sock, const char *name) { int on; #define MIN_SOCKBUF (4*1024) static int rbuf; if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) logbad(1, "fcntl(%s) O_NONBLOCK: %s", name, strerror(errno)); on = 1; if (setsockopt(sock, SOL_SOCKET,SO_BROADCAST, &on,sizeof(on)) < 0) msglog("setsockopt(%s,SO_BROADCAST): %s", name, strerror(errno)); #ifdef USE_PASSIFNAME on = 1; if (setsockopt(sock, SOL_SOCKET, SO_PASSIFNAME, &on,sizeof(on)) < 0) msglog("setsockopt(%s,SO_PASSIFNAME): %s", name, strerror(errno)); #endif if (rbuf >= MIN_SOCKBUF) { if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rbuf, sizeof(rbuf)) < 0) msglog("setsockopt(%s,SO_RCVBUF=%d): %s", name, rbuf, strerror(errno)); } else { for (rbuf = 60*1024; ; rbuf -= 4096) { if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rbuf, sizeof(rbuf)) == 0) { trace_act("RCVBUF=%d", rbuf); break; } if (rbuf < MIN_SOCKBUF) { msglog("setsockopt(%s,SO_RCVBUF = %d): %s", name, rbuf, strerror(errno)); break; } } } } /* get a rip socket */ static int /* <0 or file descriptor */ get_rip_sock(naddr addr, int serious) /* 1=failure to bind is serious */ { struct sockaddr_in rsin; unsigned char ttl; int s; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) BADERR(1,"rip_sock = socket()"); memset(&rsin, 0, sizeof(rsin)); #ifdef _HAVE_SIN_LEN rsin.sin_len = sizeof(rsin); #endif rsin.sin_family = AF_INET; rsin.sin_port = htons(RIP_PORT); rsin.sin_addr.s_addr = addr; if (bind(s, (struct sockaddr *)&rsin, sizeof(rsin)) < 0) { if (serious) BADERR(errno != EADDRINUSE, "bind(rip_sock)"); return -1; } fix_sock(s,"rip_sock"); ttl = 1; if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) DBGERR(1,"rip_sock setsockopt(IP_MULTICAST_TTL)"); return s; } /* turn off main RIP socket */ void rip_off(void) { struct interface *ifp; naddr addr; if (rip_sock >= 0 && !mhome) { trace_act("turn off RIP"); (void)close(rip_sock); rip_sock = -1; /* get non-broadcast sockets to listen to queries. */ LIST_FOREACH(ifp, &ifnet, int_list) { if (ifp->int_state & IS_REMOTE) continue; if (ifp->int_rip_sock < 0) { addr = ((ifp->int_if_flags & IFF_POINTOPOINT) ? ifp->int_dstaddr : ifp->int_addr); ifp->int_rip_sock = get_rip_sock(addr, 0); } } fix_select(); age(0); } } /* turn on RIP multicast input via an interface */ static void rip_mcast_on(struct interface *ifp) { struct group_req gr; struct sockaddr_in *sin; if (!IS_RIP_IN_OFF(ifp->int_state) && (ifp->int_if_flags & IFF_MULTICAST) && !(ifp->int_state & IS_ALIAS)) { memset(&gr, 0, sizeof(gr)); gr.gr_interface = ifp->int_index; sin = (struct sockaddr_in *)&gr.gr_group; sin->sin_family = AF_INET; #ifdef _HAVE_SIN_LEN sin->sin_len = sizeof(struct sockaddr_in); #endif sin->sin_addr.s_addr = htonl(INADDR_RIP_GROUP); if (setsockopt(rip_sock, IPPROTO_IP, MCAST_JOIN_GROUP, &gr, sizeof(gr)) < 0) LOGERR("setsockopt(MCAST_JOIN_GROUP RIP)"); } } /* Prepare socket used for RIP. */ void rip_on(struct interface *ifp) { /* If the main RIP socket is already alive, only start receiving * multicasts for this interface. */ if (rip_sock >= 0) { if (ifp != 0) rip_mcast_on(ifp); return; } /* If the main RIP socket is off and it makes sense to turn it on, * then turn it on for all of the interfaces. * It makes sense if either router discovery is off, or if * router discover is on and at most one interface is doing RIP. */ if (rip_interfaces > 0 && (!rdisc_ok || rip_interfaces > 1)) { trace_act("turn on RIP"); /* Close all of the query sockets so that we can open * the main socket. SO_REUSEPORT is not a solution, * since that would let two daemons bind to the broadcast * socket. */ LIST_FOREACH(ifp, &ifnet, int_list) { if (ifp->int_rip_sock >= 0) { (void)close(ifp->int_rip_sock); ifp->int_rip_sock = -1; } } rip_sock = get_rip_sock(INADDR_ANY, 1); rip_sock_mcast = 0; /* Do not advertise anything until we have heard something */ if (next_bcast.tv_sec < now.tv_sec+MIN_WAITTIME) next_bcast.tv_sec = now.tv_sec+MIN_WAITTIME; LIST_FOREACH(ifp, &ifnet, int_list) { ifp->int_query_time = NEVER; rip_mcast_on(ifp); } ifinit_timer.tv_sec = now.tv_sec; } else if (ifp != 0 && !(ifp->int_state & IS_REMOTE) && ifp->int_rip_sock < 0) { /* RIP is off, so ensure there are sockets on which * to listen for queries. */ ifp->int_rip_sock = get_rip_sock(ifp->int_addr, 0); } fix_select(); } /* die if malloc(3) fails */ void * rtmalloc(size_t size, const char *msg) { void *p = malloc(size); if (p == 0) logbad(1,"malloc(%lu) failed in %s", (u_long)size, msg); return p; } /* get a random instant in an interval */ void intvl_random(struct timeval *tp, /* put value here */ u_long lo, /* value is after this second */ u_long hi) /* and before this */ { tp->tv_sec = (time_t)(hi == lo ? lo : (lo + random() % ((hi - lo)))); tp->tv_usec = random() % 1000000; } void timevaladd(struct timeval *t1, struct timeval *t2) { t1->tv_sec += t2->tv_sec; if ((t1->tv_usec += t2->tv_usec) >= 1000000) { t1->tv_sec++; t1->tv_usec -= 1000000; } } /* t1 = t2 - t3 */ static void timevalsub(struct timeval *t1, struct timeval *t2, struct timeval *t3) { t1->tv_sec = t2->tv_sec - t3->tv_sec; if ((t1->tv_usec = t2->tv_usec - t3->tv_usec) < 0) { t1->tv_sec--; t1->tv_usec += 1000000; } } /* put a message into the system log */ void msglog(const char *p, ...) { va_list args; trace_flush(); va_start(args, p); vsyslog(LOG_ERR, p, args); va_end(args); if (ftrace != 0) { if (ftrace == stdout) (void)fputs("routed: ", ftrace); va_start(args, p); (void)vfprintf(ftrace, p, args); va_end(args); (void)fputc('\n', ftrace); } } /* Put a message about a bad system into the system log if * we have not complained about it recently. * * It is desirable to complain about all bad systems, but not too often. * In the worst case, it is not practical to keep track of all bad systems. * For example, there can be many systems with the wrong password. */ void msglim(struct msg_limit *lim, naddr addr, const char *p, ...) { va_list args; int i; struct msg_sub *ms1, *ms; const char *p1; /* look for the oldest slot in the table * or the slot for the bad router. */ ms = ms1 = lim->subs; for (i = MSG_SUBJECT_N; ; i--, ms1++) { if (i == 0) { /* Reuse a slot at most once every 10 minutes. */ if (lim->reuse > now.tv_sec) { ms = 0; } else { ms = ms1; lim->reuse = now.tv_sec + 10*60; } break; } if (ms->addr == addr) { /* Repeat a complaint about a given system at * most once an hour. */ if (ms->until > now.tv_sec) ms = 0; break; } if (ms->until < ms1->until) ms = ms1; } if (ms != 0) { ms->addr = addr; ms->until = now.tv_sec + 60*60; /* 60 minutes */ trace_flush(); for (p1 = p; *p1 == ' '; p1++) continue; va_start(args, p); vsyslog(LOG_ERR, p1, args); va_end(args); } /* always display the message if tracing */ if (ftrace != 0) { va_start(args, p); (void)vfprintf(ftrace, p, args); va_end(args); (void)fputc('\n', ftrace); } } void logbad(int dump, const char *p, ...) { va_list args; trace_flush(); va_start(args, p); vsyslog(LOG_ERR, p, args); va_end(args); (void)fputs("routed: ", stderr); va_start(args, p); (void)vfprintf(stderr, p, args); va_end(args); (void)fputs("; giving up\n",stderr); (void)fflush(stderr); if (dump) abort(); exit(1); } Index: head/sbin/routed/radix.c =================================================================== --- head/sbin/routed/radix.c (revision 229777) +++ head/sbin/routed/radix.c (revision 229778) @@ -1,897 +1,897 @@ /* * Copyright (c) 1988, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)radix.c 8.4 (Berkeley) 11/2/94 * * $FreeBSD$ */ /* * Routines to build and maintain radix trees for routing lookups. */ #include "defs.h" #ifdef __NetBSD__ __RCSID("$NetBSD$"); #elif defined(__FreeBSD__) __RCSID("$FreeBSD$"); #else __RCSID("$Revision: 2.23 $"); #ident "$Revision: 2.23 $" #endif #define log(x, msg) syslog(x, msg) #define panic(s) {log(LOG_ERR,s); exit(1);} #define min(a,b) (((a)<(b))?(a):(b)) int max_keylen; static struct radix_mask *rn_mkfreelist; static struct radix_node_head *mask_rnhead; static char *addmask_key; static const uint8_t normal_chars[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; static char *rn_zeros, *rn_ones; #define rn_masktop (mask_rnhead->rnh_treetop) #define Bcmp(a, b, l) (l == 0 ? 0 \ : memcmp((caddr_t)(a), (caddr_t)(b), (size_t)l)) static int rn_satisfies_leaf(char *, struct radix_node *, int); static struct radix_node *rn_addmask(void *n_arg, int search, int skip); static struct radix_node *rn_addroute(void *v_arg, void *n_arg, struct radix_node_head *head, struct radix_node treenodes[2]); static struct radix_node *rn_match(void *v_arg, struct radix_node_head *head); /* * The data structure for the keys is a radix tree with one way * branching removed. The index rn_b at an internal node n represents a bit * position to be tested. The tree is arranged so that all descendants * of a node n have keys whose bits all agree up to position rn_b - 1. * (We say the index of n is rn_b.) * * There is at least one descendant which has a one bit at position rn_b, * and at least one with a zero there. * * A route is determined by a pair of key and mask. We require that the * bit-wise logical and of the key and mask to be the key. * We define the index of a route to associated with the mask to be * the first bit number in the mask where 0 occurs (with bit number 0 * representing the highest order bit). * * We say a mask is normal if every bit is 0, past the index of the mask. * If a node n has a descendant (k, m) with index(m) == index(n) == rn_b, * and m is a normal mask, then the route applies to every descendant of n. * If the index(m) < rn_b, this implies the trailing last few bits of k * before bit b are all 0, (and hence consequently true of every descendant * of n), so the route applies to all descendants of the node as well. * * Similar logic shows that a non-normal mask m such that * index(m) <= index(n) could potentially apply to many children of n. * Thus, for each non-host route, we attach its mask to a list at an internal * node as high in the tree as we can go. * * The present version of the code makes use of normal routes in short- - * circuiting an explict mask and compare operation when testing whether + * circuiting an explicit mask and compare operation when testing whether * a key satisfies a normal route, and also in remembering the unique leaf * that governs a subtree. */ static struct radix_node * rn_search(void *v_arg, struct radix_node *head) { struct radix_node *x; caddr_t v; for (x = head, v = v_arg; x->rn_b >= 0;) { if (x->rn_bmask & v[x->rn_off]) x = x->rn_r; else x = x->rn_l; } return (x); } static struct radix_node * rn_search_m(void *v_arg, struct radix_node *head, void *m_arg) { struct radix_node *x; caddr_t v = v_arg, m = m_arg; for (x = head; x->rn_b >= 0;) { if ((x->rn_bmask & m[x->rn_off]) && (x->rn_bmask & v[x->rn_off])) x = x->rn_r; else x = x->rn_l; } return x; } static int rn_refines(void* m_arg, void *n_arg) { caddr_t m = m_arg, n = n_arg; caddr_t lim, lim2 = lim = n + *(u_char *)n; int longer = (*(u_char *)n++) - (int)(*(u_char *)m++); int masks_are_equal = 1; if (longer > 0) lim -= longer; while (n < lim) { if (*n & ~(*m)) return 0; if (*n++ != *m++) masks_are_equal = 0; } while (n < lim2) if (*n++) return 0; if (masks_are_equal && (longer < 0)) for (lim2 = m - longer; m < lim2; ) if (*m++) return 1; return (!masks_are_equal); } static struct radix_node * rn_lookup(void *v_arg, void *m_arg, struct radix_node_head *head) { struct radix_node *x; caddr_t netmask = 0; if (m_arg) { if ((x = rn_addmask(m_arg, 1, head->rnh_treetop->rn_off)) == 0) return (0); netmask = x->rn_key; } x = rn_match(v_arg, head); if (x && netmask) { while (x && x->rn_mask != netmask) x = x->rn_dupedkey; } return x; } static int rn_satisfies_leaf(char *trial, struct radix_node *leaf, int skip) { char *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask; char *cplim; int length = min(*(u_char *)cp, *(u_char *)cp2); if (cp3 == 0) cp3 = rn_ones; else length = min(length, *(u_char *)cp3); cplim = cp + length; cp3 += skip; cp2 += skip; for (cp += skip; cp < cplim; cp++, cp2++, cp3++) if ((*cp ^ *cp2) & *cp3) return 0; return 1; } static struct radix_node * rn_match(void *v_arg, struct radix_node_head *head) { caddr_t v = v_arg; struct radix_node *t = head->rnh_treetop, *x; caddr_t cp = v, cp2; caddr_t cplim; struct radix_node *saved_t, *top = t; int off = t->rn_off, vlen = *(u_char *)cp, matched_off; int test, b, rn_b; /* * Open code rn_search(v, top) to avoid overhead of extra * subroutine call. */ for (; t->rn_b >= 0; ) { if (t->rn_bmask & cp[t->rn_off]) t = t->rn_r; else t = t->rn_l; } /* * See if we match exactly as a host destination * or at least learn how many bits match, for normal mask finesse. * * It doesn't hurt us to limit how many bytes to check * to the length of the mask, since if it matches we had a genuine * match and the leaf we have is the most specific one anyway; * if it didn't match with a shorter length it would fail * with a long one. This wins big for class B&C netmasks which * are probably the most common case... */ if (t->rn_mask) vlen = *(u_char *)t->rn_mask; cp += off; cp2 = t->rn_key + off; cplim = v + vlen; for (; cp < cplim; cp++, cp2++) if (*cp != *cp2) goto on1; /* * This extra grot is in case we are explicitly asked * to look up the default. Ugh! * Or 255.255.255.255 * * In this case, we have a complete match of the key. Unless * the node is one of the roots, we are finished. - * If it is the zeros root, then take what we have, prefering + * If it is the zeros root, then take what we have, preferring * any real data. * If it is the ones root, then pretend the target key was followed * by a byte of zeros. */ if (!(t->rn_flags & RNF_ROOT)) return t; /* not a root */ if (t->rn_dupedkey) { t = t->rn_dupedkey; return t; /* have some real data */ } if (*(cp-1) == 0) return t; /* not the ones root */ b = 0; /* fake a zero after 255.255.255.255 */ goto on2; on1: test = (*cp ^ *cp2) & 0xff; /* find first bit that differs */ for (b = 7; (test >>= 1) > 0;) b--; on2: matched_off = cp - v; b += matched_off << 3; rn_b = -1 - b; /* * If there is a host route in a duped-key chain, it will be first. */ if ((saved_t = t)->rn_mask == 0) t = t->rn_dupedkey; for (; t; t = t->rn_dupedkey) { /* * Even if we don't match exactly as a host, * we may match if the leaf we wound up at is * a route to a net. */ if (t->rn_flags & RNF_NORMAL) { if (rn_b <= t->rn_b) return t; } else if (rn_satisfies_leaf(v, t, matched_off)) { return t; } } t = saved_t; /* start searching up the tree */ do { struct radix_mask *m; t = t->rn_p; if ((m = t->rn_mklist)) { /* * If non-contiguous masks ever become important * we can restore the masking and open coding of * the search and satisfaction test and put the * calculation of "off" back before the "do". */ do { if (m->rm_flags & RNF_NORMAL) { if (rn_b <= m->rm_b) return (m->rm_leaf); } else { off = min(t->rn_off, matched_off); x = rn_search_m(v, t, m->rm_mask); while (x && x->rn_mask != m->rm_mask) x = x->rn_dupedkey; if (x && rn_satisfies_leaf(v, x, off)) return x; } } while ((m = m->rm_mklist)); } } while (t != top); return 0; } #ifdef RN_DEBUG int rn_nodenum; struct radix_node *rn_clist; int rn_saveinfo; int rn_debug = 1; #endif static struct radix_node * rn_newpair(void *v, int b, struct radix_node nodes[2]) { struct radix_node *tt = nodes, *t = tt + 1; t->rn_b = b; t->rn_bmask = 0x80 >> (b & 7); t->rn_l = tt; t->rn_off = b >> 3; tt->rn_b = -1; tt->rn_key = (caddr_t)v; tt->rn_p = t; tt->rn_flags = t->rn_flags = RNF_ACTIVE; #ifdef RN_DEBUG tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++; tt->rn_twin = t; tt->rn_ybro = rn_clist; rn_clist = tt; #endif return t; } static struct radix_node * rn_insert(void* v_arg, struct radix_node_head *head, int *dupentry, struct radix_node nodes[2]) { caddr_t v = v_arg; struct radix_node *top = head->rnh_treetop; int head_off = top->rn_off, vlen = (int)*((u_char *)v); struct radix_node *t = rn_search(v_arg, top); caddr_t cp = v + head_off; int b; struct radix_node *tt; /* * Find first bit at which v and t->rn_key differ */ { caddr_t cp2 = t->rn_key + head_off; int cmp_res; caddr_t cplim = v + vlen; while (cp < cplim) if (*cp2++ != *cp++) goto on1; /* handle adding 255.255.255.255 */ if (!(t->rn_flags & RNF_ROOT) || *(cp2-1) == 0) { *dupentry = 1; return t; } on1: *dupentry = 0; cmp_res = (cp[-1] ^ cp2[-1]) & 0xff; for (b = (cp - v) << 3; cmp_res; b--) cmp_res >>= 1; } { struct radix_node *p, *x = top; cp = v; do { p = x; if (cp[x->rn_off] & x->rn_bmask) x = x->rn_r; else x = x->rn_l; } while ((unsigned)b > (unsigned)x->rn_b); #ifdef RN_DEBUG if (rn_debug) log(LOG_DEBUG, "rn_insert: Going In:\n"), traverse(p); #endif t = rn_newpair(v_arg, b, nodes); tt = t->rn_l; if ((cp[p->rn_off] & p->rn_bmask) == 0) p->rn_l = t; else p->rn_r = t; x->rn_p = t; t->rn_p = p; /* frees x, p as temp vars below */ if ((cp[t->rn_off] & t->rn_bmask) == 0) { t->rn_r = x; } else { t->rn_r = tt; t->rn_l = x; } #ifdef RN_DEBUG if (rn_debug) log(LOG_DEBUG, "rn_insert: Coming Out:\n"), traverse(p); #endif } return (tt); } static struct radix_node * rn_addmask(void *n_arg, int search, int skip) { caddr_t netmask = (caddr_t)n_arg; struct radix_node *x; caddr_t cp, cplim; int b = 0, mlen, j; int maskduplicated, m0, isnormal; struct radix_node *saved_x; static int last_zeroed = 0; if ((mlen = *(u_char *)netmask) > max_keylen) mlen = max_keylen; if (skip == 0) skip = 1; if (mlen <= skip) return (mask_rnhead->rnh_nodes); if (skip > 1) Bcopy(rn_ones + 1, addmask_key + 1, skip - 1); if ((m0 = mlen) > skip) Bcopy(netmask + skip, addmask_key + skip, mlen - skip); /* * Trim trailing zeroes. */ for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;) cp--; mlen = cp - addmask_key; if (mlen <= skip) { if (m0 >= last_zeroed) last_zeroed = mlen; return (mask_rnhead->rnh_nodes); } if (m0 < last_zeroed) Bzero(addmask_key + m0, last_zeroed - m0); *addmask_key = last_zeroed = mlen; x = rn_search(addmask_key, rn_masktop); if (Bcmp(addmask_key, x->rn_key, mlen) != 0) x = 0; if (x || search) return (x); x = (struct radix_node *)rtmalloc(max_keylen + 2*sizeof(*x), "rn_addmask"); saved_x = x; Bzero(x, max_keylen + 2 * sizeof (*x)); netmask = cp = (caddr_t)(x + 2); Bcopy(addmask_key, cp, mlen); x = rn_insert(cp, mask_rnhead, &maskduplicated, x); if (maskduplicated) { log(LOG_ERR, "rn_addmask: mask impossibly already in tree"); Free(saved_x); return (x); } /* * Calculate index of mask, and check for normalcy. */ cplim = netmask + mlen; isnormal = 1; for (cp = netmask + skip; (cp < cplim) && *(u_char *)cp == 0xff;) cp++; if (cp != cplim) { for (j = 0x80; (j & *cp) != 0; j >>= 1) b++; if (*cp != normal_chars[b] || cp != (cplim - 1)) isnormal = 0; } b += (cp - netmask) << 3; x->rn_b = -1 - b; if (isnormal) x->rn_flags |= RNF_NORMAL; return (x); } static int /* XXX: arbitrary ordering for non-contiguous masks */ rn_lexobetter(void *m_arg, void *n_arg) { u_char *mp = m_arg, *np = n_arg, *lim; if (*mp > *np) return 1; /* not really, but need to check longer one first */ if (*mp == *np) for (lim = mp + *mp; mp < lim;) if (*mp++ > *np++) return 1; return 0; } static struct radix_mask * rn_new_radix_mask(struct radix_node *tt, struct radix_mask *next) { struct radix_mask *m; MKGet(m); if (m == 0) { log(LOG_ERR, "Mask for route not entered\n"); return (0); } Bzero(m, sizeof *m); m->rm_b = tt->rn_b; m->rm_flags = tt->rn_flags; if (tt->rn_flags & RNF_NORMAL) m->rm_leaf = tt; else m->rm_mask = tt->rn_mask; m->rm_mklist = next; tt->rn_mklist = m; return m; } static struct radix_node * rn_addroute(void *v_arg, void *n_arg, struct radix_node_head *head, struct radix_node treenodes[2]) { caddr_t v = (caddr_t)v_arg, netmask = (caddr_t)n_arg; struct radix_node *t, *x = 0, *tt; struct radix_node *saved_tt, *top = head->rnh_treetop; short b = 0, b_leaf = 0; int keyduplicated; caddr_t mmask; struct radix_mask *m, **mp; /* * In dealing with non-contiguous masks, there may be * many different routes which have the same mask. * We will find it useful to have a unique pointer to * the mask to speed avoiding duplicate references at * nodes and possibly save time in calculating indices. */ if (netmask) { if ((x = rn_addmask(netmask, 0, top->rn_off)) == 0) return (0); b_leaf = x->rn_b; b = -1 - x->rn_b; netmask = x->rn_key; } /* * Deal with duplicated keys: attach node to previous instance */ saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes); if (keyduplicated) { for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) { if (tt->rn_mask == netmask) return (0); if (netmask == 0 || (tt->rn_mask && ((b_leaf < tt->rn_b) || /* index(netmask) > node */ rn_refines(netmask, tt->rn_mask) || rn_lexobetter(netmask, tt->rn_mask)))) break; } /* * If the mask is not duplicated, we wouldn't * find it among possible duplicate key entries * anyway, so the above test doesn't hurt. * * We sort the masks for a duplicated key the same way as * in a masklist -- most specific to least specific. * This may require the unfortunate nuisance of relocating * the head of the list. */ if (tt == saved_tt) { struct radix_node *xx = x; /* link in at head of list */ (tt = treenodes)->rn_dupedkey = t; tt->rn_flags = t->rn_flags; tt->rn_p = x = t->rn_p; if (x->rn_l == t) x->rn_l = tt; else x->rn_r = tt; saved_tt = tt; x = xx; } else { (tt = treenodes)->rn_dupedkey = t->rn_dupedkey; t->rn_dupedkey = tt; } #ifdef RN_DEBUG t=tt+1; tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++; tt->rn_twin = t; tt->rn_ybro = rn_clist; rn_clist = tt; #endif tt->rn_key = (caddr_t) v; tt->rn_b = -1; tt->rn_flags = RNF_ACTIVE; } /* * Put mask in tree. */ if (netmask) { tt->rn_mask = netmask; tt->rn_b = x->rn_b; tt->rn_flags |= x->rn_flags & RNF_NORMAL; } t = saved_tt->rn_p; if (keyduplicated) goto on2; b_leaf = -1 - t->rn_b; if (t->rn_r == saved_tt) x = t->rn_l; else x = t->rn_r; /* Promote general routes from below */ if (x->rn_b < 0) { for (mp = &t->rn_mklist; x; x = x->rn_dupedkey) if (x->rn_mask && (x->rn_b >= b_leaf) && x->rn_mklist == 0) { if ((*mp = m = rn_new_radix_mask(x, 0))) mp = &m->rm_mklist; } } else if (x->rn_mklist) { /* * Skip over masks whose index is > that of new node */ for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) if (m->rm_b >= b_leaf) break; t->rn_mklist = m; *mp = 0; } on2: /* Add new route to highest possible ancestor's list */ if ((netmask == 0) || (b > t->rn_b )) return tt; /* can't lift at all */ b_leaf = tt->rn_b; do { x = t; t = t->rn_p; } while (b <= t->rn_b && x != top); /* * Search through routes associated with node to * insert new route according to index. * Need same criteria as when sorting dupedkeys to avoid * double loop on deletion. */ for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) { if (m->rm_b < b_leaf) continue; if (m->rm_b > b_leaf) break; if (m->rm_flags & RNF_NORMAL) { mmask = m->rm_leaf->rn_mask; if (tt->rn_flags & RNF_NORMAL) { log(LOG_ERR, "Non-unique normal route, mask not entered"); return tt; } } else mmask = m->rm_mask; if (mmask == netmask) { m->rm_refs++; tt->rn_mklist = m; return tt; } if (rn_refines(netmask, mmask) || rn_lexobetter(netmask, mmask)) break; } *mp = rn_new_radix_mask(tt, *mp); return tt; } static struct radix_node * rn_delete(void *v_arg, void *netmask_arg, struct radix_node_head *head) { struct radix_node *t, *p, *x, *tt; struct radix_mask *m, *saved_m, **mp; struct radix_node *dupedkey, *saved_tt, *top; caddr_t v, netmask; int b, head_off, vlen; v = v_arg; netmask = netmask_arg; x = head->rnh_treetop; tt = rn_search(v, x); head_off = x->rn_off; vlen = *(u_char *)v; saved_tt = tt; top = x; if (tt == 0 || Bcmp(v + head_off, tt->rn_key + head_off, vlen - head_off)) return (0); /* * Delete our route from mask lists. */ if (netmask) { if ((x = rn_addmask(netmask, 1, head_off)) == 0) return (0); netmask = x->rn_key; while (tt->rn_mask != netmask) if ((tt = tt->rn_dupedkey) == 0) return (0); } if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == 0) goto on1; if (tt->rn_flags & RNF_NORMAL) { if (m->rm_leaf != tt || m->rm_refs > 0) { log(LOG_ERR, "rn_delete: inconsistent annotation\n"); return 0; /* dangling ref could cause disaster */ } } else { if (m->rm_mask != tt->rn_mask) { log(LOG_ERR, "rn_delete: inconsistent annotation\n"); goto on1; } if (--m->rm_refs >= 0) goto on1; } b = -1 - tt->rn_b; t = saved_tt->rn_p; if (b > t->rn_b) goto on1; /* Wasn't lifted at all */ do { x = t; t = t->rn_p; } while (b <= t->rn_b && x != top); for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) if (m == saved_m) { *mp = m->rm_mklist; MKFree(m); break; } if (m == 0) { log(LOG_ERR, "rn_delete: couldn't find our annotation\n"); if (tt->rn_flags & RNF_NORMAL) return (0); /* Dangling ref to us */ } on1: /* * Eliminate us from tree */ if (tt->rn_flags & RNF_ROOT) return (0); #ifdef RN_DEBUG /* Get us out of the creation list */ for (t = rn_clist; t && t->rn_ybro != tt; t = t->rn_ybro) {} if (t) t->rn_ybro = tt->rn_ybro; #endif t = tt->rn_p; if ((dupedkey = saved_tt->rn_dupedkey)) { if (tt == saved_tt) { x = dupedkey; x->rn_p = t; if (t->rn_l == tt) t->rn_l = x; else t->rn_r = x; } else { for (x = p = saved_tt; p && p->rn_dupedkey != tt;) p = p->rn_dupedkey; if (p) p->rn_dupedkey = tt->rn_dupedkey; else log(LOG_ERR, "rn_delete: couldn't find us\n"); } t = tt + 1; if (t->rn_flags & RNF_ACTIVE) { #ifndef RN_DEBUG *++x = *t; p = t->rn_p; #else b = t->rn_info; *++x = *t; t->rn_info = b; p = t->rn_p; #endif if (p->rn_l == t) p->rn_l = x; else p->rn_r = x; x->rn_l->rn_p = x; x->rn_r->rn_p = x; } goto out; } if (t->rn_l == tt) x = t->rn_r; else x = t->rn_l; p = t->rn_p; if (p->rn_r == t) p->rn_r = x; else p->rn_l = x; x->rn_p = p; /* * Demote routes attached to us. */ if (t->rn_mklist) { if (x->rn_b >= 0) { for (mp = &x->rn_mklist; (m = *mp);) mp = &m->rm_mklist; *mp = t->rn_mklist; } else { /* If there are any key,mask pairs in a sibling duped-key chain, some subset will appear sorted in the same order attached to our mklist */ for (m = t->rn_mklist; m && x; x = x->rn_dupedkey) if (m == x->rn_mklist) { struct radix_mask *mm = m->rm_mklist; x->rn_mklist = 0; if (--(m->rm_refs) < 0) MKFree(m); m = mm; } if (m) syslog(LOG_ERR, "%s 0x%lx at 0x%lx\n", "rn_delete: Orphaned Mask", (unsigned long)m, (unsigned long)x); } } /* * We may be holding an active internal node in the tree. */ x = tt + 1; if (t != x) { #ifndef RN_DEBUG *t = *x; #else b = t->rn_info; *t = *x; t->rn_info = b; #endif t->rn_l->rn_p = t; t->rn_r->rn_p = t; p = x->rn_p; if (p->rn_l == x) p->rn_l = t; else p->rn_r = t; } out: tt->rn_flags &= ~RNF_ACTIVE; tt[1].rn_flags &= ~RNF_ACTIVE; return (tt); } int rn_walktree(struct radix_node_head *h, int (*f)(struct radix_node *, struct walkarg *), struct walkarg *w) { int error; struct radix_node *base, *next; struct radix_node *rn = h->rnh_treetop; /* * This gets complicated because we may delete the node * while applying the function f to it, so we need to calculate * the successor node in advance. */ /* First time through node, go left */ while (rn->rn_b >= 0) rn = rn->rn_l; for (;;) { base = rn; /* If at right child go back up, otherwise, go right */ while (rn->rn_p->rn_r == rn && (rn->rn_flags & RNF_ROOT) == 0) rn = rn->rn_p; /* Find the next *leaf* since next node might vanish, too */ for (rn = rn->rn_p->rn_r; rn->rn_b >= 0;) rn = rn->rn_l; next = rn; /* Process leaves */ while ((rn = base)) { base = rn->rn_dupedkey; if (!(rn->rn_flags & RNF_ROOT) && (error = (*f)(rn, w))) return (error); } rn = next; if (rn->rn_flags & RNF_ROOT) return (0); } /* NOTREACHED */ } int rn_inithead(struct radix_node_head **head, int off) { struct radix_node_head *rnh; struct radix_node *t, *tt, *ttt; if (*head) return (1); rnh = (struct radix_node_head *)rtmalloc(sizeof(*rnh), "rn_inithead"); Bzero(rnh, sizeof (*rnh)); *head = rnh; t = rn_newpair(rn_zeros, off, rnh->rnh_nodes); ttt = rnh->rnh_nodes + 2; t->rn_r = ttt; t->rn_p = t; tt = t->rn_l; tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE; tt->rn_b = -1 - off; *ttt = *tt; ttt->rn_key = rn_ones; rnh->rnh_addaddr = rn_addroute; rnh->rnh_deladdr = rn_delete; rnh->rnh_matchaddr = rn_match; rnh->rnh_lookup = rn_lookup; rnh->rnh_walktree = rn_walktree; rnh->rnh_treetop = t; return (1); } void rn_init(void) { char *cp, *cplim; if (max_keylen == 0) { printf("rn_init: radix functions require max_keylen be set\n"); return; } rn_zeros = (char *)rtmalloc(3 * max_keylen, "rn_init"); Bzero(rn_zeros, 3 * max_keylen); rn_ones = cp = rn_zeros + max_keylen; addmask_key = cplim = rn_ones + max_keylen; while (cp < cplim) *cp++ = -1; if (rn_inithead(&mask_rnhead, 0) == 0) panic("rn_init 2"); } Index: head/sbin/routed/table.c =================================================================== --- head/sbin/routed/table.c (revision 229777) +++ head/sbin/routed/table.c (revision 229778) @@ -1,2155 +1,2155 @@ /* * Copyright (c) 1983, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include "defs.h" #ifdef __NetBSD__ __RCSID("$NetBSD$"); #elif defined(__FreeBSD__) __RCSID("$FreeBSD$"); #else __RCSID("$Revision: 2.27 $"); #ident "$Revision: 2.27 $" #endif static struct rt_spare *rts_better(struct rt_entry *); static struct rt_spare rts_empty = {0,0,0,HOPCNT_INFINITY,0,0,0}; static void set_need_flash(void); #ifdef _HAVE_SIN_LEN static void masktrim(struct sockaddr_in *ap); #else static void masktrim(struct sockaddr_in_new *ap); #endif static void rtbad(struct rt_entry *); struct radix_node_head *rhead; /* root of the radix tree */ int need_flash = 1; /* flash update needed * start =1 to suppress the 1st */ struct timeval age_timer; /* next check of old routes */ struct timeval need_kern = { /* need to update kernel table */ EPOCH+MIN_WAITTIME-1, 0 }; int stopint; int total_routes; /* zap any old routes through this gateway */ static naddr age_bad_gate; /* It is desirable to "aggregate" routes, to combine differing routes of * the same metric and next hop into a common route with a smaller netmask * or to suppress redundant routes, routes that add no information to * routes with smaller netmasks. * * A route is redundant if and only if any and all routes with smaller * but matching netmasks and nets are the same. Since routes are * kept sorted in the radix tree, redundant routes always come second. * * There are two kinds of aggregations. First, two routes of the same bit * mask and differing only in the least significant bit of the network * number can be combined into a single route with a coarser mask. * * Second, a route can be suppressed in favor of another route with a more * coarse mask provided no incompatible routes with intermediate masks * are present. The second kind of aggregation involves suppressing routes. * A route must not be suppressed if an incompatible route exists with * an intermediate mask, since the suppressed route would be covered * by the intermediate. * * This code relies on the radix tree walk encountering routes * sorted first by address, with the smallest address first. */ static struct ag_info ag_slots[NUM_AG_SLOTS], *ag_avail, *ag_corsest, *ag_finest; /* #define DEBUG_AG */ #ifdef DEBUG_AG #define CHECK_AG() {int acnt = 0; struct ag_info *cag; \ for (cag = ag_avail; cag != 0; cag = cag->ag_fine) \ acnt++; \ for (cag = ag_corsest; cag != 0; cag = cag->ag_fine) \ acnt++; \ if (acnt != NUM_AG_SLOTS) { \ (void)fflush(stderr); \ abort(); \ } \ } #else #define CHECK_AG() #endif /* Output the contents of an aggregation table slot. * This function must always be immediately followed with the deletion * of the target slot. */ static void ag_out(struct ag_info *ag, void (*out)(struct ag_info *)) { struct ag_info *ag_cors; naddr bit; /* Forget it if this route should not be output for split-horizon. */ if (ag->ag_state & AGS_SPLIT_HZ) return; /* If we output both the even and odd twins, then the immediate parent, * if it is present, is redundant, unless the parent manages to * aggregate into something coarser. * On successive calls, this code detects the even and odd twins, * and marks the parent. * * Note that the order in which the radix tree code emits routes * ensures that the twins are seen before the parent is emitted. */ ag_cors = ag->ag_cors; if (ag_cors != 0 && ag_cors->ag_mask == ag->ag_mask<<1 && ag_cors->ag_dst_h == (ag->ag_dst_h & ag_cors->ag_mask)) { ag_cors->ag_state |= ((ag_cors->ag_dst_h == ag->ag_dst_h) ? AGS_REDUN0 : AGS_REDUN1); } /* Skip it if this route is itself redundant. * * It is ok to change the contents of the slot here, since it is * always deleted next. */ if (ag->ag_state & AGS_REDUN0) { if (ag->ag_state & AGS_REDUN1) return; /* quit if fully redundant */ /* make it finer if it is half-redundant */ bit = (-ag->ag_mask) >> 1; ag->ag_dst_h |= bit; ag->ag_mask |= bit; } else if (ag->ag_state & AGS_REDUN1) { /* make it finer if it is half-redundant */ bit = (-ag->ag_mask) >> 1; ag->ag_mask |= bit; } out(ag); } static void ag_del(struct ag_info *ag) { CHECK_AG(); if (ag->ag_cors == 0) ag_corsest = ag->ag_fine; else ag->ag_cors->ag_fine = ag->ag_fine; if (ag->ag_fine == 0) ag_finest = ag->ag_cors; else ag->ag_fine->ag_cors = ag->ag_cors; ag->ag_fine = ag_avail; ag_avail = ag; CHECK_AG(); } /* Flush routes waiting for aggregation. * This must not suppress a route unless it is known that among all * routes with coarser masks that match it, the one with the longest * mask is appropriate. This is ensured by scanning the routes * in lexical order, and with the most restrictive mask first * among routes to the same destination. */ void ag_flush(naddr lim_dst_h, /* flush routes to here */ naddr lim_mask, /* matching this mask */ void (*out)(struct ag_info *)) { struct ag_info *ag, *ag_cors; naddr dst_h; for (ag = ag_finest; ag != 0 && ag->ag_mask >= lim_mask; ag = ag_cors) { ag_cors = ag->ag_cors; /* work on only the specified routes */ dst_h = ag->ag_dst_h; if ((dst_h & lim_mask) != lim_dst_h) continue; if (!(ag->ag_state & AGS_SUPPRESS)) ag_out(ag, out); else for ( ; ; ag_cors = ag_cors->ag_cors) { /* Look for a route that can suppress the * current route */ if (ag_cors == 0) { /* failed, so output it and look for * another route to work on */ ag_out(ag, out); break; } if ((dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h) { /* We found a route with a coarser mask that * aggregates the current target. * * If it has a different next hop, it * cannot replace the target, so output * the target. */ if (ag->ag_gate != ag_cors->ag_gate && !(ag->ag_state & AGS_FINE_GATE) && !(ag_cors->ag_state & AGS_CORS_GATE)) { ag_out(ag, out); break; } /* If the coarse route has a good enough * metric, it suppresses the target. * If the suppressed target was redundant, * then mark the suppressor redundant. */ if (ag_cors->ag_pref <= ag->ag_pref) { if (AG_IS_REDUN(ag->ag_state) && ag_cors->ag_mask==ag->ag_mask<<1) { if (ag_cors->ag_dst_h == dst_h) ag_cors->ag_state |= AGS_REDUN0; else ag_cors->ag_state |= AGS_REDUN1; } if (ag->ag_tag != ag_cors->ag_tag) ag_cors->ag_tag = 0; if (ag->ag_nhop != ag_cors->ag_nhop) ag_cors->ag_nhop = 0; break; } } } /* That route has either been output or suppressed */ ag_cors = ag->ag_cors; ag_del(ag); } CHECK_AG(); } /* Try to aggregate a route with previous routes. */ void ag_check(naddr dst, naddr mask, naddr gate, naddr nhop, char metric, char pref, u_int new_seqno, u_short tag, u_short state, void (*out)(struct ag_info *)) /* output using this */ { struct ag_info *ag, *nag, *ag_cors; naddr xaddr; int x; dst = ntohl(dst); /* Punt non-contiguous subnet masks. * * (X & -X) contains a single bit if and only if X is a power of 2. * (X + (X & -X)) == 0 if and only if X is a power of 2. */ if ((mask & -mask) + mask != 0) { struct ag_info nc_ag; nc_ag.ag_dst_h = dst; nc_ag.ag_mask = mask; nc_ag.ag_gate = gate; nc_ag.ag_nhop = nhop; nc_ag.ag_metric = metric; nc_ag.ag_pref = pref; nc_ag.ag_tag = tag; nc_ag.ag_state = state; nc_ag.ag_seqno = new_seqno; out(&nc_ag); return; } /* Search for the right slot in the aggregation table. */ ag_cors = 0; ag = ag_corsest; while (ag != 0) { if (ag->ag_mask >= mask) break; /* Suppress old routes (i.e. combine with compatible routes * with coarser masks) as we look for the right slot in the * aggregation table for the new route. * A route to an address less than the current destination * will not be affected by the current route or any route * seen hereafter. That means it is safe to suppress it. * This check keeps poor routes (e.g. with large hop counts) * from preventing suppression of finer routes. */ if (ag_cors != 0 && ag->ag_dst_h < dst && (ag->ag_state & AGS_SUPPRESS) && ag_cors->ag_pref <= ag->ag_pref && (ag->ag_dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h && (ag_cors->ag_gate == ag->ag_gate || (ag->ag_state & AGS_FINE_GATE) || (ag_cors->ag_state & AGS_CORS_GATE))) { /* If the suppressed target was redundant, * then mark the suppressor redundant. */ if (AG_IS_REDUN(ag->ag_state) && ag_cors->ag_mask == ag->ag_mask<<1) { if (ag_cors->ag_dst_h == dst) ag_cors->ag_state |= AGS_REDUN0; else ag_cors->ag_state |= AGS_REDUN1; } if (ag->ag_tag != ag_cors->ag_tag) ag_cors->ag_tag = 0; if (ag->ag_nhop != ag_cors->ag_nhop) ag_cors->ag_nhop = 0; ag_del(ag); CHECK_AG(); } else { ag_cors = ag; } ag = ag_cors->ag_fine; } /* If we find the even/odd twin of the new route, and if the * masks and so forth are equal, we can aggregate them. * We can probably promote one of the pair. * * Since the routes are encountered in lexical order, * the new route must be odd. However, the second or later * times around this loop, it could be the even twin promoted * from the even/odd pair of twins of the finer route. */ while (ag != 0 && ag->ag_mask == mask && ((ag->ag_dst_h ^ dst) & (mask<<1)) == 0) { /* Here we know the target route and the route in the current * slot have the same netmasks and differ by at most the * last bit. They are either for the same destination, or * for an even/odd pair of destinations. */ if (ag->ag_dst_h == dst) { /* We have two routes to the same destination. * Routes are encountered in lexical order, so a * route is never promoted until the parent route is * already present. So we know that the new route is * a promoted (or aggregated) pair and the route * already in the slot is the explicit route. * * Prefer the best route if their metrics differ, * or the aggregated one if not, following a sort * of longest-match rule. */ if (pref <= ag->ag_pref) { ag->ag_gate = gate; ag->ag_nhop = nhop; ag->ag_tag = tag; ag->ag_metric = metric; ag->ag_pref = pref; if (ag->ag_seqno < new_seqno) ag->ag_seqno = new_seqno; x = ag->ag_state; ag->ag_state = state; state = x; } /* Some bits are set if they are set on either route, * except when the route is for an interface. */ if (!(ag->ag_state & AGS_IF)) ag->ag_state |= (state & (AGS_AGGREGATE_EITHER | AGS_REDUN0 | AGS_REDUN1)); return; } /* If one of the routes can be promoted and the other can * be suppressed, it may be possible to combine them or * worthwhile to promote one. * * Any route that can be promoted is always * marked to be eligible to be suppressed. */ if (!((state & AGS_AGGREGATE) && (ag->ag_state & AGS_SUPPRESS)) && !((ag->ag_state & AGS_AGGREGATE) && (state & AGS_SUPPRESS))) break; /* A pair of even/odd twin routes can be combined * if either is redundant, or if they are via the * same gateway and have the same metric. */ if (AG_IS_REDUN(ag->ag_state) || AG_IS_REDUN(state) || (ag->ag_gate == gate && ag->ag_pref == pref && (state & ag->ag_state & AGS_AGGREGATE) != 0)) { /* We have both the even and odd pairs. * Since the routes are encountered in order, * the route in the slot must be the even twin. * * Combine and promote (aggregate) the pair of routes. */ if (new_seqno < ag->ag_seqno) new_seqno = ag->ag_seqno; if (!AG_IS_REDUN(state)) state &= ~AGS_REDUN1; if (AG_IS_REDUN(ag->ag_state)) state |= AGS_REDUN0; else state &= ~AGS_REDUN0; state |= (ag->ag_state & AGS_AGGREGATE_EITHER); if (ag->ag_tag != tag) tag = 0; if (ag->ag_nhop != nhop) nhop = 0; /* Get rid of the even twin that was already * in the slot. */ ag_del(ag); } else if (ag->ag_pref >= pref && (ag->ag_state & AGS_AGGREGATE)) { /* If we cannot combine the pair, maybe the route * with the worse metric can be promoted. * * Promote the old, even twin, by giving its slot * in the table to the new, odd twin. */ ag->ag_dst_h = dst; xaddr = ag->ag_gate; ag->ag_gate = gate; gate = xaddr; xaddr = ag->ag_nhop; ag->ag_nhop = nhop; nhop = xaddr; x = ag->ag_tag; ag->ag_tag = tag; tag = x; /* The promoted route is even-redundant only if the * even twin was fully redundant. It is not * odd-redundant because the odd-twin will still be * in the table. */ x = ag->ag_state; if (!AG_IS_REDUN(x)) x &= ~AGS_REDUN0; x &= ~AGS_REDUN1; ag->ag_state = state; state = x; x = ag->ag_metric; ag->ag_metric = metric; metric = x; x = ag->ag_pref; ag->ag_pref = pref; pref = x; /* take the newest sequence number */ if (new_seqno <= ag->ag_seqno) new_seqno = ag->ag_seqno; else ag->ag_seqno = new_seqno; } else { if (!(state & AGS_AGGREGATE)) break; /* cannot promote either twin */ /* Promote the new, odd twin by shaving its * mask and address. * The promoted route is odd-redundant only if the * odd twin was fully redundant. It is not * even-redundant because the even twin is still in * the table. */ if (!AG_IS_REDUN(state)) state &= ~AGS_REDUN1; state &= ~AGS_REDUN0; if (new_seqno < ag->ag_seqno) new_seqno = ag->ag_seqno; else ag->ag_seqno = new_seqno; } mask <<= 1; dst &= mask; if (ag_cors == 0) { ag = ag_corsest; break; } ag = ag_cors; ag_cors = ag->ag_cors; } /* When we can no longer promote and combine routes, * flush the old route in the target slot. Also flush * any finer routes that we know will never be aggregated by * the new route. * * In case we moved toward coarser masks, * get back where we belong */ if (ag != 0 && ag->ag_mask < mask) { ag_cors = ag; ag = ag->ag_fine; } /* Empty the target slot */ if (ag != 0 && ag->ag_mask == mask) { ag_flush(ag->ag_dst_h, ag->ag_mask, out); ag = (ag_cors == 0) ? ag_corsest : ag_cors->ag_fine; } #ifdef DEBUG_AG (void)fflush(stderr); if (ag == 0 && ag_cors != ag_finest) abort(); if (ag_cors == 0 && ag != ag_corsest) abort(); if (ag != 0 && ag->ag_cors != ag_cors) abort(); if (ag_cors != 0 && ag_cors->ag_fine != ag) abort(); CHECK_AG(); #endif /* Save the new route on the end of the table. */ nag = ag_avail; ag_avail = nag->ag_fine; nag->ag_dst_h = dst; nag->ag_mask = mask; nag->ag_gate = gate; nag->ag_nhop = nhop; nag->ag_metric = metric; nag->ag_pref = pref; nag->ag_tag = tag; nag->ag_state = state; nag->ag_seqno = new_seqno; nag->ag_fine = ag; if (ag != 0) ag->ag_cors = nag; else ag_finest = nag; nag->ag_cors = ag_cors; if (ag_cors == 0) ag_corsest = nag; else ag_cors->ag_fine = nag; CHECK_AG(); } static const char * rtm_type_name(u_char type) { static const char * const rtm_types[] = { "RTM_ADD", "RTM_DELETE", "RTM_CHANGE", "RTM_GET", "RTM_LOSING", "RTM_REDIRECT", "RTM_MISS", "RTM_LOCK", "RTM_OLDADD", "RTM_OLDDEL", "RTM_RESOLVE", "RTM_NEWADDR", "RTM_DELADDR", #ifdef RTM_OIFINFO "RTM_OIFINFO", #endif "RTM_IFINFO", "RTM_NEWMADDR", "RTM_DELMADDR" }; #define NEW_RTM_PAT "RTM type %#x" static char name0[sizeof(NEW_RTM_PAT)+2]; if (type > sizeof(rtm_types)/sizeof(rtm_types[0]) || type == 0) { snprintf(name0, sizeof(name0), NEW_RTM_PAT, type); return name0; } else { return rtm_types[type-1]; } #undef NEW_RTM_PAT } /* Trim a mask in a sockaddr * Produce a length of 0 for an address of 0. * Otherwise produce the index of the first zero byte. */ void #ifdef _HAVE_SIN_LEN masktrim(struct sockaddr_in *ap) #else masktrim(struct sockaddr_in_new *ap) #endif { char *cp; if (ap->sin_addr.s_addr == 0) { ap->sin_len = 0; return; } cp = (char *)(&ap->sin_addr.s_addr+1); while (*--cp == 0) continue; ap->sin_len = cp - (char*)ap + 1; } /* Tell the kernel to add, delete or change a route */ static void rtioctl(int action, /* RTM_DELETE, etc */ naddr dst, naddr gate, naddr mask, int metric, int flags) { struct { struct rt_msghdr w_rtm; struct sockaddr_in w_dst; struct sockaddr_in w_gate; #ifdef _HAVE_SA_LEN struct sockaddr_in w_mask; #else struct sockaddr_in_new w_mask; #endif } w; long cc; # define PAT " %-10s %s metric=%d flags=%#x" # define ARGS rtm_type_name(action), rtname(dst,mask,gate), metric, flags again: memset(&w, 0, sizeof(w)); w.w_rtm.rtm_msglen = sizeof(w); w.w_rtm.rtm_version = RTM_VERSION; w.w_rtm.rtm_type = action; w.w_rtm.rtm_flags = flags; w.w_rtm.rtm_seq = ++rt_sock_seqno; w.w_rtm.rtm_addrs = RTA_DST|RTA_GATEWAY; if (metric != 0 || action == RTM_CHANGE) { w.w_rtm.rtm_rmx.rmx_hopcount = metric; w.w_rtm.rtm_inits |= RTV_HOPCOUNT; } w.w_dst.sin_family = AF_INET; w.w_dst.sin_addr.s_addr = dst; w.w_gate.sin_family = AF_INET; w.w_gate.sin_addr.s_addr = gate; #ifdef _HAVE_SA_LEN w.w_dst.sin_len = sizeof(w.w_dst); w.w_gate.sin_len = sizeof(w.w_gate); #endif if (mask == HOST_MASK) { w.w_rtm.rtm_flags |= RTF_HOST; w.w_rtm.rtm_msglen -= sizeof(w.w_mask); } else { w.w_rtm.rtm_addrs |= RTA_NETMASK; w.w_mask.sin_addr.s_addr = htonl(mask); #ifdef _HAVE_SA_LEN masktrim(&w.w_mask); if (w.w_mask.sin_len == 0) w.w_mask.sin_len = sizeof(long); w.w_rtm.rtm_msglen -= (sizeof(w.w_mask) - w.w_mask.sin_len); #endif } #ifndef NO_INSTALL cc = write(rt_sock, &w, w.w_rtm.rtm_msglen); if (cc < 0) { if (errno == ESRCH && (action == RTM_CHANGE || action == RTM_DELETE)) { trace_act("route disappeared before" PAT, ARGS); if (action == RTM_CHANGE) { action = RTM_ADD; goto again; } return; } msglog("write(rt_sock)" PAT ": %s", ARGS, strerror(errno)); return; } else if (cc != w.w_rtm.rtm_msglen) { msglog("write(rt_sock) wrote %ld instead of %d for" PAT, cc, w.w_rtm.rtm_msglen, ARGS); return; } #endif if (TRACEKERNEL) trace_misc("write kernel" PAT, ARGS); #undef PAT #undef ARGS } #define KHASH_SIZE 71 /* should be prime */ #define KHASH(a,m) khash_bins[((a) ^ (m)) % KHASH_SIZE] static struct khash { struct khash *k_next; naddr k_dst; naddr k_mask; naddr k_gate; short k_metric; u_short k_state; #define KS_NEW 0x001 #define KS_DELETE 0x002 /* need to delete the route */ #define KS_ADD 0x004 /* add to the kernel */ #define KS_CHANGE 0x008 /* tell kernel to change the route */ #define KS_DEL_ADD 0x010 /* delete & add to change the kernel */ #define KS_STATIC 0x020 /* Static flag in kernel */ #define KS_GATEWAY 0x040 /* G flag in kernel */ #define KS_DYNAMIC 0x080 /* result of redirect */ #define KS_DELETED 0x100 /* already deleted from kernel */ #define KS_CHECK 0x200 time_t k_keep; #define K_KEEP_LIM 30 time_t k_redirect_time; /* when redirected route 1st seen */ } *khash_bins[KHASH_SIZE]; static struct khash* kern_find(naddr dst, naddr mask, struct khash ***ppk) { struct khash *k, **pk; for (pk = &KHASH(dst,mask); (k = *pk) != 0; pk = &k->k_next) { if (k->k_dst == dst && k->k_mask == mask) break; } if (ppk != 0) *ppk = pk; return k; } static struct khash* kern_add(naddr dst, naddr mask) { struct khash *k, **pk; k = kern_find(dst, mask, &pk); if (k != 0) return k; k = (struct khash *)rtmalloc(sizeof(*k), "kern_add"); memset(k, 0, sizeof(*k)); k->k_dst = dst; k->k_mask = mask; k->k_state = KS_NEW; k->k_keep = now.tv_sec; *pk = k; return k; } /* If a kernel route has a non-zero metric, check that it is still in the * daemon table, and not deleted by interfaces coming and going. */ static void kern_check_static(struct khash *k, struct interface *ifp) { struct rt_entry *rt; struct rt_spare new; if (k->k_metric == 0) return; memset(&new, 0, sizeof(new)); new.rts_ifp = ifp; new.rts_gate = k->k_gate; new.rts_router = (ifp != 0) ? ifp->int_addr : loopaddr; new.rts_metric = k->k_metric; new.rts_time = now.tv_sec; rt = rtget(k->k_dst, k->k_mask); if (rt != 0) { if (!(rt->rt_state & RS_STATIC)) rtchange(rt, rt->rt_state | RS_STATIC, &new, 0); } else { rtadd(k->k_dst, k->k_mask, RS_STATIC, &new); } } /* operate on a kernel entry */ static void kern_ioctl(struct khash *k, int action, /* RTM_DELETE, etc */ int flags) { switch (action) { case RTM_DELETE: k->k_state &= ~KS_DYNAMIC; if (k->k_state & KS_DELETED) return; k->k_state |= KS_DELETED; break; case RTM_ADD: k->k_state &= ~KS_DELETED; break; case RTM_CHANGE: if (k->k_state & KS_DELETED) { action = RTM_ADD; k->k_state &= ~KS_DELETED; } break; } rtioctl(action, k->k_dst, k->k_gate, k->k_mask, k->k_metric, flags); } /* add a route the kernel told us */ static void rtm_add(struct rt_msghdr *rtm, struct rt_addrinfo *info, time_t keep) { struct khash *k; struct interface *ifp; naddr mask; if (rtm->rtm_flags & RTF_HOST) { mask = HOST_MASK; } else if (INFO_MASK(info) != 0) { mask = ntohl(S_ADDR(INFO_MASK(info))); } else { msglog("ignore %s without mask", rtm_type_name(rtm->rtm_type)); return; } k = kern_add(S_ADDR(INFO_DST(info)), mask); if (k->k_state & KS_NEW) k->k_keep = now.tv_sec+keep; if (INFO_GATE(info) == 0) { trace_act("note %s without gateway", rtm_type_name(rtm->rtm_type)); k->k_metric = HOPCNT_INFINITY; } else if (INFO_GATE(info)->sa_family != AF_INET) { trace_act("note %s with gateway AF=%d", rtm_type_name(rtm->rtm_type), INFO_GATE(info)->sa_family); k->k_metric = HOPCNT_INFINITY; } else { k->k_gate = S_ADDR(INFO_GATE(info)); k->k_metric = rtm->rtm_rmx.rmx_hopcount; if (k->k_metric < 0) k->k_metric = 0; else if (k->k_metric > HOPCNT_INFINITY-1) k->k_metric = HOPCNT_INFINITY-1; } k->k_state &= ~(KS_DELETE | KS_ADD | KS_CHANGE | KS_DEL_ADD | KS_DELETED | KS_GATEWAY | KS_STATIC | KS_NEW | KS_CHECK); if (rtm->rtm_flags & RTF_GATEWAY) k->k_state |= KS_GATEWAY; if (rtm->rtm_flags & RTF_STATIC) k->k_state |= KS_STATIC; if (0 != (rtm->rtm_flags & (RTF_DYNAMIC | RTF_MODIFIED))) { if (INFO_AUTHOR(info) != 0 && INFO_AUTHOR(info)->sa_family == AF_INET) ifp = iflookup(S_ADDR(INFO_AUTHOR(info))); else ifp = 0; if (supplier && (ifp == 0 || !(ifp->int_state & IS_REDIRECT_OK))) { /* Routers are not supposed to listen to redirects, * so delete it if it came via an unknown interface * or the interface does not have special permission. */ k->k_state &= ~KS_DYNAMIC; k->k_state |= KS_DELETE; LIM_SEC(need_kern, 0); trace_act("mark for deletion redirected %s --> %s" " via %s", addrname(k->k_dst, k->k_mask, 0), naddr_ntoa(k->k_gate), ifp ? ifp->int_name : "unknown interface"); } else { k->k_state |= KS_DYNAMIC; k->k_redirect_time = now.tv_sec; trace_act("accept redirected %s --> %s via %s", addrname(k->k_dst, k->k_mask, 0), naddr_ntoa(k->k_gate), ifp ? ifp->int_name : "unknown interface"); } return; } /* If it is not a static route, quit until the next comparison * between the kernel and daemon tables, when it will be deleted. */ if (!(k->k_state & KS_STATIC)) { k->k_state |= KS_DELETE; LIM_SEC(need_kern, k->k_keep); return; } /* Put static routes with real metrics into the daemon table so * they can be advertised. * * Find the interface toward the gateway. */ ifp = iflookup(k->k_gate); if (ifp == 0) msglog("static route %s --> %s impossibly lacks ifp", addrname(S_ADDR(INFO_DST(info)), mask, 0), naddr_ntoa(k->k_gate)); kern_check_static(k, ifp); } /* deal with packet loss */ static void rtm_lose(struct rt_msghdr *rtm, struct rt_addrinfo *info) { if (INFO_GATE(info) == 0 || INFO_GATE(info)->sa_family != AF_INET) { trace_act("ignore %s without gateway", rtm_type_name(rtm->rtm_type)); return; } if (rdisc_ok) rdisc_age(S_ADDR(INFO_GATE(info))); age(S_ADDR(INFO_GATE(info))); } /* Make the gateway slot of an info structure point to something * useful. If it is not already useful, but it specifies an interface, * then fill in the sockaddr_in provided and point it there. */ static int get_info_gate(struct sockaddr **sap, struct sockaddr_in *rsin) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)*sap; struct interface *ifp; if (sdl == 0) return 0; if ((sdl)->sdl_family == AF_INET) return 1; if ((sdl)->sdl_family != AF_LINK) return 0; ifp = ifwithindex(sdl->sdl_index, 1); if (ifp == 0) return 0; rsin->sin_addr.s_addr = ifp->int_addr; #ifdef _HAVE_SA_LEN rsin->sin_len = sizeof(*rsin); #endif rsin->sin_family = AF_INET; *sap = (struct sockaddr*)rsin; return 1; } /* Clean the kernel table by copying it to the daemon image. * Eventually the daemon will delete any extra routes. */ void flush_kern(void) { static char *sysctl_buf; static size_t sysctl_buf_size = 0; size_t needed; int mib[6]; char *next, *lim; struct rt_msghdr *rtm; struct sockaddr_in gate_sin; struct rt_addrinfo info; int i; struct khash *k; for (i = 0; i < KHASH_SIZE; i++) { for (k = khash_bins[i]; k != 0; k = k->k_next) { k->k_state |= KS_CHECK; } } mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; /* protocol */ mib[3] = 0; /* wildcard address family */ mib[4] = NET_RT_DUMP; mib[5] = 0; /* no flags */ for (;;) { if ((needed = sysctl_buf_size) != 0) { if (sysctl(mib, 6, sysctl_buf,&needed, 0, 0) >= 0) break; if (errno != ENOMEM && errno != EFAULT) BADERR(1,"flush_kern: sysctl(RT_DUMP)"); free(sysctl_buf); needed = 0; } if (sysctl(mib, 6, 0, &needed, 0, 0) < 0) BADERR(1,"flush_kern: sysctl(RT_DUMP) estimate"); /* Kludge around the habit of some systems, such as * BSD/OS 3.1, to not admit how many routes are in the * kernel, or at least to be quite wrong. */ needed += 50*(sizeof(*rtm)+5*sizeof(struct sockaddr)); sysctl_buf = rtmalloc(sysctl_buf_size = needed, "flush_kern sysctl(RT_DUMP)"); } lim = sysctl_buf + needed; for (next = sysctl_buf; next < lim; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; if (rtm->rtm_msglen == 0) { msglog("zero length kernel route at " " %#lx in buffer %#lx before %#lx", (u_long)rtm, (u_long)sysctl_buf, (u_long)lim); break; } rt_xaddrs(&info, (struct sockaddr *)(rtm+1), (struct sockaddr *)(next + rtm->rtm_msglen), rtm->rtm_addrs); if (INFO_DST(&info) == 0 || INFO_DST(&info)->sa_family != AF_INET) continue; #if defined (RTF_LLINFO) /* ignore ARP table entries on systems with a merged route * and ARP table. */ if (rtm->rtm_flags & RTF_LLINFO) continue; #endif #if defined(RTF_WASCLONED) && defined(__FreeBSD__) /* ignore cloned routes */ if (rtm->rtm_flags & RTF_WASCLONED) continue; #endif /* ignore multicast addresses */ if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info))))) continue; if (!get_info_gate(&INFO_GATE(&info), &gate_sin)) continue; /* Note static routes and interface routes, and also * preload the image of the kernel table so that * we can later clean it, as well as avoid making * unneeded changes. Keep the old kernel routes for a * few seconds to allow a RIP or router-discovery * response to be heard. */ rtm_add(rtm,&info,MIN_WAITTIME); } for (i = 0; i < KHASH_SIZE; i++) { for (k = khash_bins[i]; k != 0; k = k->k_next) { if (k->k_state & KS_CHECK) { msglog("%s --> %s disappeared from kernel", addrname(k->k_dst, k->k_mask, 0), naddr_ntoa(k->k_gate)); del_static(k->k_dst, k->k_mask, k->k_gate, 1); } } } } /* Listen to announcements from the kernel */ void read_rt(void) { long cc; struct interface *ifp; struct sockaddr_in gate_sin; naddr mask, gate; union { struct { struct rt_msghdr rtm; struct sockaddr addrs[RTAX_MAX]; } r; struct if_msghdr ifm; } m; char str[100], *strp; struct rt_addrinfo info; for (;;) { cc = read(rt_sock, &m, sizeof(m)); if (cc <= 0) { if (cc < 0 && errno != EWOULDBLOCK) LOGERR("read(rt_sock)"); return; } if (m.r.rtm.rtm_version != RTM_VERSION) { msglog("bogus routing message version %d", m.r.rtm.rtm_version); continue; } /* Ignore our own results. */ if (m.r.rtm.rtm_type <= RTM_CHANGE && m.r.rtm.rtm_pid == mypid) { static int complained = 0; if (!complained) { msglog("receiving our own change messages"); complained = 1; } continue; } if (m.r.rtm.rtm_type == RTM_IFINFO || m.r.rtm.rtm_type == RTM_NEWADDR || m.r.rtm.rtm_type == RTM_DELADDR) { ifp = ifwithindex(m.ifm.ifm_index, m.r.rtm.rtm_type != RTM_DELADDR); if (ifp == 0) trace_act("note %s with flags %#x" " for unknown interface index #%d", rtm_type_name(m.r.rtm.rtm_type), m.ifm.ifm_flags, m.ifm.ifm_index); else trace_act("note %s with flags %#x for %s", rtm_type_name(m.r.rtm.rtm_type), m.ifm.ifm_flags, ifp->int_name); /* After being informed of a change to an interface, * check them all now if the check would otherwise * be a long time from now, if the interface is * not known, or if the interface has been turned * off or on. */ if (ifinit_timer.tv_sec-now.tv_sec>=CHECK_BAD_INTERVAL || ifp == 0 || ((ifp->int_if_flags ^ m.ifm.ifm_flags) & IFF_UP) != 0) ifinit_timer.tv_sec = now.tv_sec; continue; } #ifdef RTM_OIFINFO if (m.r.rtm.rtm_type == RTM_OIFINFO) continue; /* ignore compat message */ #endif strcpy(str, rtm_type_name(m.r.rtm.rtm_type)); strp = &str[strlen(str)]; if (m.r.rtm.rtm_type <= RTM_CHANGE) strp += sprintf(strp," from pid %d",m.r.rtm.rtm_pid); rt_xaddrs(&info, m.r.addrs, &m.r.addrs[RTAX_MAX], m.r.rtm.rtm_addrs); if (INFO_DST(&info) == 0) { trace_act("ignore %s without dst", str); continue; } if (INFO_DST(&info)->sa_family != AF_INET) { trace_act("ignore %s for AF %d", str, INFO_DST(&info)->sa_family); continue; } mask = ((INFO_MASK(&info) != 0) ? ntohl(S_ADDR(INFO_MASK(&info))) : (m.r.rtm.rtm_flags & RTF_HOST) ? HOST_MASK : std_mask(S_ADDR(INFO_DST(&info)))); strp += sprintf(strp, ": %s", addrname(S_ADDR(INFO_DST(&info)), mask, 0)); if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info))))) { trace_act("ignore multicast %s", str); continue; } #if defined(RTF_LLINFO) if (m.r.rtm.rtm_flags & RTF_LLINFO) { trace_act("ignore ARP %s", str); continue; } #endif #if defined(RTF_WASCLONED) && defined(__FreeBSD__) if (m.r.rtm.rtm_flags & RTF_WASCLONED) { trace_act("ignore cloned %s", str); continue; } #endif if (get_info_gate(&INFO_GATE(&info), &gate_sin)) { gate = S_ADDR(INFO_GATE(&info)); strp += sprintf(strp, " --> %s", naddr_ntoa(gate)); } else { gate = 0; } if (INFO_AUTHOR(&info) != 0) strp += sprintf(strp, " by authority of %s", saddr_ntoa(INFO_AUTHOR(&info))); switch (m.r.rtm.rtm_type) { case RTM_ADD: case RTM_CHANGE: case RTM_REDIRECT: if (m.r.rtm.rtm_errno != 0) { trace_act("ignore %s with \"%s\" error", str, strerror(m.r.rtm.rtm_errno)); } else { trace_act("%s", str); rtm_add(&m.r.rtm,&info,0); } break; case RTM_DELETE: if (m.r.rtm.rtm_errno != 0 && m.r.rtm.rtm_errno != ESRCH) { trace_act("ignore %s with \"%s\" error", str, strerror(m.r.rtm.rtm_errno)); } else { trace_act("%s", str); del_static(S_ADDR(INFO_DST(&info)), mask, gate, 1); } break; case RTM_LOSING: trace_act("%s", str); rtm_lose(&m.r.rtm,&info); break; default: trace_act("ignore %s", str); break; } } } /* after aggregating, note routes that belong in the kernel */ static void kern_out(struct ag_info *ag) { struct khash *k; /* Do not install bad routes if they are not already present. * This includes routes that had RS_NET_SYN for interfaces that * recently died. */ if (ag->ag_metric == HOPCNT_INFINITY) { k = kern_find(htonl(ag->ag_dst_h), ag->ag_mask, 0); if (k == 0) return; } else { k = kern_add(htonl(ag->ag_dst_h), ag->ag_mask); } if (k->k_state & KS_NEW) { /* will need to add new entry to the kernel table */ k->k_state = KS_ADD; if (ag->ag_state & AGS_GATEWAY) k->k_state |= KS_GATEWAY; k->k_gate = ag->ag_gate; k->k_metric = ag->ag_metric; return; } if (k->k_state & KS_STATIC) return; /* modify existing kernel entry if necessary */ if (k->k_gate != ag->ag_gate || k->k_metric != ag->ag_metric) { /* Must delete bad interface routes etc. to change them. */ if (k->k_metric == HOPCNT_INFINITY) k->k_state |= KS_DEL_ADD; k->k_gate = ag->ag_gate; k->k_metric = ag->ag_metric; k->k_state |= KS_CHANGE; } /* If the daemon thinks the route should exist, forget * about any redirections. * If the daemon thinks the route should exist, eventually * override manual intervention by the operator. */ if ((k->k_state & (KS_DYNAMIC | KS_DELETED)) != 0) { k->k_state &= ~KS_DYNAMIC; k->k_state |= (KS_ADD | KS_DEL_ADD); } if ((k->k_state & KS_GATEWAY) && !(ag->ag_state & AGS_GATEWAY)) { k->k_state &= ~KS_GATEWAY; k->k_state |= (KS_ADD | KS_DEL_ADD); } else if (!(k->k_state & KS_GATEWAY) && (ag->ag_state & AGS_GATEWAY)) { k->k_state |= KS_GATEWAY; k->k_state |= (KS_ADD | KS_DEL_ADD); } /* Deleting-and-adding is necessary to change aspects of a route. * Just delete instead of deleting and then adding a bad route. * Otherwise, we want to keep the route in the kernel. */ if (k->k_metric == HOPCNT_INFINITY && (k->k_state & KS_DEL_ADD)) k->k_state |= KS_DELETE; else k->k_state &= ~KS_DELETE; #undef RT } /* ARGSUSED */ static int walk_kern(struct radix_node *rn, struct walkarg *argp UNUSED) { #define RT ((struct rt_entry *)rn) char metric, pref; u_int ags = 0; /* Do not install synthetic routes */ if (RT->rt_state & RS_NET_SYN) return 0; if (!(RT->rt_state & RS_IF)) { /* This is an ordinary route, not for an interface. */ /* aggregate, ordinary good routes without regard to * their metric */ pref = 1; ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_AGGREGATE); /* Do not install host routes directly to hosts, to avoid * interfering with ARP entries in the kernel table. */ if (RT_ISHOST(RT) && ntohl(RT->rt_dst) == RT->rt_gate) return 0; } else { /* This is an interface route. * Do not install routes for "external" remote interfaces. */ if (RT->rt_ifp != 0 && (RT->rt_ifp->int_state & IS_EXTERNAL)) return 0; /* Interfaces should override received routes. */ pref = 0; ags |= (AGS_IF | AGS_CORS_GATE); /* If it is not an interface, or an alias for an interface, * it must be a "gateway." * * If it is a "remote" interface, it is also a "gateway" to * the kernel if is not an alias. */ if (RT->rt_ifp == 0 || (RT->rt_ifp->int_state & IS_REMOTE)) ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_AGGREGATE); } /* If RIP is off and IRDP is on, let the route to the discovered * route suppress any RIP routes. Eventually the RIP routes * will time-out and be deleted. This reaches the steady-state * quicker. */ if ((RT->rt_state & RS_RDISC) && rip_sock < 0) ags |= AGS_CORS_GATE; metric = RT->rt_metric; if (metric == HOPCNT_INFINITY) { /* if the route is dead, so try hard to aggregate. */ pref = HOPCNT_INFINITY; ags |= (AGS_FINE_GATE | AGS_SUPPRESS); ags &= ~(AGS_IF | AGS_CORS_GATE); } ag_check(RT->rt_dst, RT->rt_mask, RT->rt_gate, 0, metric,pref, 0, 0, ags, kern_out); return 0; #undef RT } /* Update the kernel table to match the daemon table. */ static void fix_kern(void) { int i; struct khash *k, **pk; need_kern = age_timer; /* Walk daemon table, updating the copy of the kernel table. */ (void)rn_walktree(rhead, walk_kern, 0); ag_flush(0,0,kern_out); for (i = 0; i < KHASH_SIZE; i++) { for (pk = &khash_bins[i]; (k = *pk) != 0; ) { /* Do not touch static routes */ if (k->k_state & KS_STATIC) { kern_check_static(k,0); pk = &k->k_next; continue; } /* check hold on routes deleted by the operator */ if (k->k_keep > now.tv_sec) { /* ensure we check when the hold is over */ LIM_SEC(need_kern, k->k_keep); /* mark for the next cycle */ k->k_state |= KS_DELETE; pk = &k->k_next; continue; } if ((k->k_state & KS_DELETE) && !(k->k_state & KS_DYNAMIC)) { kern_ioctl(k, RTM_DELETE, 0); *pk = k->k_next; free(k); continue; } if (k->k_state & KS_DEL_ADD) kern_ioctl(k, RTM_DELETE, 0); if (k->k_state & KS_ADD) { kern_ioctl(k, RTM_ADD, ((0 != (k->k_state & (KS_GATEWAY | KS_DYNAMIC))) ? RTF_GATEWAY : 0)); } else if (k->k_state & KS_CHANGE) { kern_ioctl(k, RTM_CHANGE, ((0 != (k->k_state & (KS_GATEWAY | KS_DYNAMIC))) ? RTF_GATEWAY : 0)); } k->k_state &= ~(KS_ADD|KS_CHANGE|KS_DEL_ADD); /* Mark this route to be deleted in the next cycle. * This deletes routes that disappear from the * daemon table, since the normal aging code * will clear the bit for routes that have not * disappeared from the daemon table. */ k->k_state |= KS_DELETE; pk = &k->k_next; } } } /* Delete a static route in the image of the kernel table. */ void del_static(naddr dst, naddr mask, naddr gate, int gone) { struct khash *k; struct rt_entry *rt; /* Just mark it in the table to be deleted next time the kernel * table is updated. * If it has already been deleted, mark it as such, and set its * keep-timer so that it will not be deleted again for a while. * This lets the operator delete a route added by the daemon * and add a replacement. */ k = kern_find(dst, mask, 0); if (k != 0 && (gate == 0 || k->k_gate == gate)) { k->k_state &= ~(KS_STATIC | KS_DYNAMIC | KS_CHECK); k->k_state |= KS_DELETE; if (gone) { k->k_state |= KS_DELETED; k->k_keep = now.tv_sec + K_KEEP_LIM; } } rt = rtget(dst, mask); if (rt != 0 && (rt->rt_state & RS_STATIC)) rtbad(rt); } /* Delete all routes generated from ICMP Redirects that use a given gateway, * as well as old redirected routes. */ void del_redirects(naddr bad_gate, time_t old) { int i; struct khash *k; for (i = 0; i < KHASH_SIZE; i++) { for (k = khash_bins[i]; k != 0; k = k->k_next) { if (!(k->k_state & KS_DYNAMIC) || (k->k_state & KS_STATIC)) continue; if (k->k_gate != bad_gate && k->k_redirect_time > old && !supplier) continue; k->k_state |= KS_DELETE; k->k_state &= ~KS_DYNAMIC; need_kern.tv_sec = now.tv_sec; trace_act("mark redirected %s --> %s for deletion", addrname(k->k_dst, k->k_mask, 0), naddr_ntoa(k->k_gate)); } } } /* Start the daemon tables. */ extern int max_keylen; void rtinit(void) { int i; struct ag_info *ag; /* Initialize the radix trees */ max_keylen = sizeof(struct sockaddr_in); rn_init(); rn_inithead(&rhead, 32); /* mark all of the slots in the table free */ ag_avail = ag_slots; for (ag = ag_slots, i = 1; i < NUM_AG_SLOTS; i++) { ag->ag_fine = ag+1; ag++; } } #ifdef _HAVE_SIN_LEN static struct sockaddr_in dst_sock = {sizeof(dst_sock), AF_INET, 0, {0}, {0}}; static struct sockaddr_in mask_sock = {sizeof(mask_sock), AF_INET, 0, {0}, {0}}; #else static struct sockaddr_in_new dst_sock = {_SIN_ADDR_SIZE, AF_INET}; static struct sockaddr_in_new mask_sock = {_SIN_ADDR_SIZE, AF_INET}; #endif static void set_need_flash(void) { if (!need_flash) { need_flash = 1; /* Do not send the flash update immediately. Wait a little * while to hear from other routers. */ no_flash.tv_sec = now.tv_sec + MIN_WAITTIME; } } /* Get a particular routing table entry */ struct rt_entry * rtget(naddr dst, naddr mask) { struct rt_entry *rt; dst_sock.sin_addr.s_addr = dst; mask_sock.sin_addr.s_addr = htonl(mask); masktrim(&mask_sock); rt = (struct rt_entry *)rhead->rnh_lookup(&dst_sock,&mask_sock,rhead); if (!rt || rt->rt_dst != dst || rt->rt_mask != mask) return 0; return rt; } /* Find a route to dst as the kernel would. */ struct rt_entry * rtfind(naddr dst) { dst_sock.sin_addr.s_addr = dst; return (struct rt_entry *)rhead->rnh_matchaddr(&dst_sock, rhead); } /* add a route to the table */ void rtadd(naddr dst, naddr mask, u_int state, /* rt_state for the entry */ struct rt_spare *new) { struct rt_entry *rt; naddr smask; int i; struct rt_spare *rts; rt = (struct rt_entry *)rtmalloc(sizeof (*rt), "rtadd"); memset(rt, 0, sizeof(*rt)); for (rts = rt->rt_spares, i = NUM_SPARES; i != 0; i--, rts++) rts->rts_metric = HOPCNT_INFINITY; rt->rt_nodes->rn_key = (caddr_t)&rt->rt_dst_sock; rt->rt_dst = dst; rt->rt_dst_sock.sin_family = AF_INET; #ifdef _HAVE_SIN_LEN rt->rt_dst_sock.sin_len = dst_sock.sin_len; #endif if (mask != HOST_MASK) { smask = std_mask(dst); if ((smask & ~mask) == 0 && mask > smask) state |= RS_SUBNET; } mask_sock.sin_addr.s_addr = htonl(mask); masktrim(&mask_sock); rt->rt_mask = mask; rt->rt_state = state; rt->rt_spares[0] = *new; rt->rt_time = now.tv_sec; rt->rt_poison_metric = HOPCNT_INFINITY; rt->rt_seqno = update_seqno; if (++total_routes == MAX_ROUTES) msglog("have maximum (%d) routes", total_routes); if (TRACEACTIONS) trace_add_del("Add", rt); need_kern.tv_sec = now.tv_sec; set_need_flash(); if (0 == rhead->rnh_addaddr(&rt->rt_dst_sock, &mask_sock, rhead, rt->rt_nodes)) { msglog("rnh_addaddr() failed for %s mask=%#lx", naddr_ntoa(dst), (u_long)mask); free(rt); } } /* notice a changed route */ void rtchange(struct rt_entry *rt, u_int state, /* new state bits */ struct rt_spare *new, char *label) { if (rt->rt_metric != new->rts_metric) { /* Fix the kernel immediately if it seems the route * has gone bad, since there may be a working route that * aggregates this route. */ if (new->rts_metric == HOPCNT_INFINITY) { need_kern.tv_sec = now.tv_sec; if (new->rts_time >= now.tv_sec - EXPIRE_TIME) new->rts_time = now.tv_sec - EXPIRE_TIME; } rt->rt_seqno = update_seqno; set_need_flash(); } if (rt->rt_gate != new->rts_gate) { need_kern.tv_sec = now.tv_sec; rt->rt_seqno = update_seqno; set_need_flash(); } state |= (rt->rt_state & RS_SUBNET); /* Keep various things from deciding ageless routes are stale. */ if (!AGE_RT(state, new->rts_ifp)) new->rts_time = now.tv_sec; if (TRACEACTIONS) trace_change(rt, state, new, label ? label : "Chg "); rt->rt_state = state; rt->rt_spares[0] = *new; } /* check for a better route among the spares */ static struct rt_spare * rts_better(struct rt_entry *rt) { struct rt_spare *rts, *rts1; int i; /* find the best alternative among the spares */ rts = rt->rt_spares+1; for (i = NUM_SPARES, rts1 = rts+1; i > 2; i--, rts1++) { if (BETTER_LINK(rt,rts1,rts)) rts = rts1; } return rts; } /* switch to a backup route */ void rtswitch(struct rt_entry *rt, struct rt_spare *rts) { struct rt_spare swap; char label[10]; /* Do not change permanent routes */ if (0 != (rt->rt_state & (RS_MHOME | RS_STATIC | RS_RDISC | RS_NET_SYN | RS_IF))) return; /* find the best alternative among the spares */ if (rts == 0) rts = rts_better(rt); /* Do not bother if it is not worthwhile. */ if (!BETTER_LINK(rt, rts, rt->rt_spares)) return; swap = rt->rt_spares[0]; (void)sprintf(label, "Use #%d", (int)(rts - rt->rt_spares)); rtchange(rt, rt->rt_state & ~(RS_NET_SYN | RS_RDISC), rts, label); if (swap.rts_metric == HOPCNT_INFINITY) { *rts = rts_empty; } else { *rts = swap; } } void rtdelete(struct rt_entry *rt) { struct khash *k; if (TRACEACTIONS) trace_add_del("Del", rt); k = kern_find(rt->rt_dst, rt->rt_mask, 0); if (k != 0) { k->k_state |= KS_DELETE; need_kern.tv_sec = now.tv_sec; } dst_sock.sin_addr.s_addr = rt->rt_dst; mask_sock.sin_addr.s_addr = htonl(rt->rt_mask); masktrim(&mask_sock); if (rt != (struct rt_entry *)rhead->rnh_deladdr(&dst_sock, &mask_sock, rhead)) { msglog("rnh_deladdr() failed"); } else { free(rt); total_routes--; } } void rts_delete(struct rt_entry *rt, struct rt_spare *rts) { trace_upslot(rt, rts, &rts_empty); *rts = rts_empty; } /* Get rid of a bad route, and try to switch to a replacement. */ static void rtbad(struct rt_entry *rt) { struct rt_spare new; /* Poison the route */ new = rt->rt_spares[0]; new.rts_metric = HOPCNT_INFINITY; rtchange(rt, rt->rt_state & ~(RS_IF | RS_LOCAL | RS_STATIC), &new, 0); rtswitch(rt, 0); } /* Junk a RS_NET_SYN or RS_LOCAL route, * unless it is needed by another interface. */ void rtbad_sub(struct rt_entry *rt) { struct interface *ifp, *ifp1; struct intnet *intnetp; u_int state; ifp1 = 0; state = 0; if (rt->rt_state & RS_LOCAL) { /* Is this the route through loopback for the interface? * If so, see if it is used by any other interfaces, such * as a point-to-point interface with the same local address. */ LIST_FOREACH(ifp, &ifnet, int_list) { /* Retain it if another interface needs it. */ if (ifp->int_addr == rt->rt_ifp->int_addr) { state |= RS_LOCAL; ifp1 = ifp; break; } } } if (!(state & RS_LOCAL)) { /* Retain RIPv1 logical network route if there is another * interface that justifies it. */ if (rt->rt_state & RS_NET_SYN) { LIST_FOREACH(ifp, &ifnet, int_list) { if ((ifp->int_state & IS_NEED_NET_SYN) && rt->rt_mask == ifp->int_std_mask && rt->rt_dst == ifp->int_std_addr) { state |= RS_NET_SYN; ifp1 = ifp; break; } } } /* or if there is an authority route that needs it. */ for (intnetp = intnets; intnetp != 0; intnetp = intnetp->intnet_next) { if (intnetp->intnet_addr == rt->rt_dst && intnetp->intnet_mask == rt->rt_mask) { state |= (RS_NET_SYN | RS_NET_INT); break; } } } if (ifp1 != 0 || (state & RS_NET_SYN)) { struct rt_spare new = rt->rt_spares[0]; new.rts_ifp = ifp1; rtchange(rt, ((rt->rt_state & ~(RS_NET_SYN|RS_LOCAL)) | state), &new, 0); } else { rtbad(rt); } } /* Called while walking the table looking for sick interfaces * or after a time change. */ /* ARGSUSED */ int walk_bad(struct radix_node *rn, struct walkarg *argp UNUSED) { #define RT ((struct rt_entry *)rn) struct rt_spare *rts; int i; /* fix any spare routes through the interface */ rts = RT->rt_spares; for (i = NUM_SPARES; i != 1; i--) { rts++; if (rts->rts_metric < HOPCNT_INFINITY && (rts->rts_ifp == 0 || (rts->rts_ifp->int_state & IS_BROKE))) rts_delete(RT, rts); } /* Deal with the main route */ /* finished if it has been handled before or if its interface is ok */ if (RT->rt_ifp == 0 || !(RT->rt_ifp->int_state & IS_BROKE)) return 0; /* Bad routes for other than interfaces are easy. */ if (0 == (RT->rt_state & (RS_IF | RS_NET_SYN | RS_LOCAL))) { rtbad(RT); return 0; } rtbad_sub(RT); return 0; #undef RT } /* Check the age of an individual route. */ /* ARGSUSED */ static int walk_age(struct radix_node *rn, struct walkarg *argp UNUSED) { #define RT ((struct rt_entry *)rn) struct interface *ifp; struct rt_spare *rts; int i; /* age all of the spare routes, including the primary route * currently in use */ rts = RT->rt_spares; for (i = NUM_SPARES; i != 0; i--, rts++) { ifp = rts->rts_ifp; if (i == NUM_SPARES) { if (!AGE_RT(RT->rt_state, ifp)) { /* Keep various things from deciding ageless * routes are stale */ rts->rts_time = now.tv_sec; continue; } /* forget RIP routes after RIP has been turned off. */ if (rip_sock < 0) { rtdelete(RT); return 0; } } /* age failing routes */ if (age_bad_gate == rts->rts_gate && rts->rts_time >= now_stale) { rts->rts_time -= SUPPLY_INTERVAL; } /* trash the spare routes when they go bad */ if (rts->rts_metric < HOPCNT_INFINITY && now_garbage > rts->rts_time && i != NUM_SPARES) rts_delete(RT, rts); } /* finished if the active route is still fresh */ if (now_stale <= RT->rt_time) return 0; /* try to switch to an alternative */ rtswitch(RT, 0); - /* Delete a dead route after it has been publically mourned. */ + /* Delete a dead route after it has been publicly mourned. */ if (now_garbage > RT->rt_time) { rtdelete(RT); return 0; } /* Start poisoning a bad route before deleting it. */ if (now.tv_sec - RT->rt_time > EXPIRE_TIME) { struct rt_spare new = RT->rt_spares[0]; new.rts_metric = HOPCNT_INFINITY; rtchange(RT, RT->rt_state, &new, 0); } return 0; } /* Watch for dead routes and interfaces. */ void age(naddr bad_gate) { struct interface *ifp; int need_query = 0; /* If not listening to RIP, there is no need to age the routes in * the table. */ age_timer.tv_sec = (now.tv_sec + ((rip_sock < 0) ? NEVER : SUPPLY_INTERVAL)); /* Check for dead IS_REMOTE interfaces by timing their * transmissions. */ LIST_FOREACH(ifp, &ifnet, int_list) { if (!(ifp->int_state & IS_REMOTE)) continue; /* ignore unreachable remote interfaces */ if (!check_remote(ifp)) continue; /* Restore remote interface that has become reachable */ if (ifp->int_state & IS_BROKE) if_ok(ifp, "remote "); if (ifp->int_act_time != NEVER && now.tv_sec - ifp->int_act_time > EXPIRE_TIME) { msglog("remote interface %s to %s timed out after" " %ld:%ld", ifp->int_name, naddr_ntoa(ifp->int_dstaddr), (long)(now.tv_sec - ifp->int_act_time)/60, (long)(now.tv_sec - ifp->int_act_time)%60); if_sick(ifp); } /* If we have not heard from the other router * recently, ask it. */ if (now.tv_sec >= ifp->int_query_time) { ifp->int_query_time = NEVER; need_query = 1; } } /* Age routes. */ age_bad_gate = bad_gate; (void)rn_walktree(rhead, walk_age, 0); /* delete old redirected routes to keep the kernel table small * and prevent blackholes */ del_redirects(bad_gate, now.tv_sec-STALE_TIME); /* Update the kernel routing table. */ fix_kern(); /* poke reticent remote gateways */ if (need_query) rip_query(); }