Esempio n. 1
0
void
rtsol_input(int s)
{
	u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
	int l, ifindex = 0, *hlimp = NULL;
	ssize_t msglen;
	struct in6_pktinfo *pi = NULL;
	struct ifinfo *ifi = NULL;
	struct ra_opt *rao = NULL;
	struct icmp6_hdr *icp;
	struct nd_router_advert *nd_ra;
	struct cmsghdr *cm;
	struct rainfo *rai;
	char *raoptp;
	char *p;
	struct in6_addr *addr;
	struct nd_opt_hdr *ndo;
	struct nd_opt_rdnss *rdnss;
	struct nd_opt_dnssl *dnssl;
	size_t len;
	char nsbuf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1];
	char dname[NI_MAXHOST];
	struct timeval now;
	struct timeval lifetime;
	int newent_rai;
	int newent_rao;

	/* get message.  namelen and controllen must always be initialized. */
	rcvmhdr.msg_namelen = sizeof(from);
	rcvmhdr.msg_controllen = rcvcmsglen;
	if ((msglen = recvmsg(s, &rcvmhdr, 0)) < 0) {
		warnmsg(LOG_ERR, __func__, "recvmsg: %s", strerror(errno));
		return;
	}

	/* extract optional information via Advanced API */
	for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvmhdr); cm;
	    cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvmhdr, cm)) {
		if (cm->cmsg_level == IPPROTO_IPV6 &&
		    cm->cmsg_type == IPV6_PKTINFO &&
		    cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
			pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
			ifindex = pi->ipi6_ifindex;
		}
		if (cm->cmsg_level == IPPROTO_IPV6 &&
		    cm->cmsg_type == IPV6_HOPLIMIT &&
		    cm->cmsg_len == CMSG_LEN(sizeof(int)))
			hlimp = (int *)CMSG_DATA(cm);
	}

	if (ifindex == 0) {
		warnmsg(LOG_ERR, __func__,
		    "failed to get receiving interface");
		return;
	}
	if (hlimp == NULL) {
		warnmsg(LOG_ERR, __func__,
		    "failed to get receiving hop limit");
		return;
	}

	if ((size_t)msglen < sizeof(struct nd_router_advert)) {
		warnmsg(LOG_INFO, __func__,
		    "packet size(%zd) is too short", msglen);
		return;
	}

	icp = (struct icmp6_hdr *)rcvmhdr.msg_iov[0].iov_base;

	if (icp->icmp6_type != ND_ROUTER_ADVERT) {
		/*
		 * this should not happen because we configured a filter
		 * that only passes RAs on the receiving socket.
		 */
		warnmsg(LOG_ERR, __func__,
		    "invalid icmp type(%d) from %s on %s", icp->icmp6_type,
		    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
			sizeof(ntopbuf)),
		    if_indextoname(pi->ipi6_ifindex, ifnamebuf));
		return;
	}

	if (icp->icmp6_code != 0) {
		warnmsg(LOG_INFO, __func__,
		    "invalid icmp code(%d) from %s on %s", icp->icmp6_code,
		    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
			sizeof(ntopbuf)),
		    if_indextoname(pi->ipi6_ifindex, ifnamebuf));
		return;
	}

	if (*hlimp != 255) {
		warnmsg(LOG_INFO, __func__,
		    "invalid RA with hop limit(%d) from %s on %s",
		    *hlimp,
		    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
			sizeof(ntopbuf)),
		    if_indextoname(pi->ipi6_ifindex, ifnamebuf));
		return;
	}

	if (pi && !IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
		warnmsg(LOG_INFO, __func__,
		    "invalid RA with non link-local source from %s on %s",
		    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
			sizeof(ntopbuf)),
		    if_indextoname(pi->ipi6_ifindex, ifnamebuf));
		return;
	}

	/* xxx: more validation? */

	if ((ifi = find_ifinfo(pi->ipi6_ifindex)) == NULL) {
		warnmsg(LOG_INFO, __func__,
		    "received RA from %s on an unexpected IF(%s)",
		    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
			sizeof(ntopbuf)),
		    if_indextoname(pi->ipi6_ifindex, ifnamebuf));
		return;
	}

	warnmsg(LOG_DEBUG, __func__,
	    "received RA from %s on %s, state is %d",
	    inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, sizeof(ntopbuf)),
	    ifi->ifname, ifi->state);

	nd_ra = (struct nd_router_advert *)icp;

	/*
	 * Process the "O bit."
	 * If the value of OtherConfigFlag changes from FALSE to TRUE, the
	 * host should invoke the stateful autoconfiguration protocol,
	 * requesting information.
	 * [RFC 2462 Section 5.5.3]
	 */
	if (((nd_ra->nd_ra_flags_reserved) & ND_RA_FLAG_OTHER) &&
	    !ifi->otherconfig) {
		warnmsg(LOG_DEBUG, __func__,
		    "OtherConfigFlag on %s is turned on", ifi->ifname);
		ifi->otherconfig = 1;
		CALL_SCRIPT(OTHER, NULL);
	}
	gettimeofday(&now, NULL);
	newent_rai = 0;
	rai = find_rainfo(ifi, &from);
	if (rai == NULL) {
		ELM_MALLOC(rai, exit(1));
		rai->rai_ifinfo = ifi;
		TAILQ_INIT(&rai->rai_ra_opt);
		rai->rai_saddr.sin6_family = AF_INET6;
		rai->rai_saddr.sin6_len = sizeof(rai->rai_saddr);
		memcpy(&rai->rai_saddr.sin6_addr, &from.sin6_addr,
		    sizeof(rai->rai_saddr.sin6_addr));
		newent_rai = 1;
	}

#define	RA_OPT_NEXT_HDR(x)	(struct nd_opt_hdr *)((char *)x + \
				(((struct nd_opt_hdr *)x)->nd_opt_len * 8))
	/* Process RA options. */
	warnmsg(LOG_DEBUG, __func__, "Processing RA");
	raoptp = (char *)icp + sizeof(struct nd_router_advert);
	while (raoptp < (char *)icp + msglen) {
		ndo = (struct nd_opt_hdr *)raoptp;
		warnmsg(LOG_DEBUG, __func__, "ndo = %p", raoptp);
		warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_type = %d",
		    ndo->nd_opt_type);
		warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_len = %d",
		    ndo->nd_opt_len);

		switch (ndo->nd_opt_type) {
		case ND_OPT_RDNSS:
			rdnss = (struct nd_opt_rdnss *)raoptp;

			/* Optlen sanity check (Section 5.3.1 in RFC 6106) */
			if (rdnss->nd_opt_rdnss_len < 3) {
				warnmsg(LOG_INFO, __func__,
		    			"too short RDNSS option"
					"in RA from %s was ignored.",
					inet_ntop(AF_INET6, &from.sin6_addr,
					    ntopbuf, sizeof(ntopbuf)));
				break;
			}

			addr = (struct in6_addr *)(raoptp + sizeof(*rdnss));
			while ((char *)addr < (char *)RA_OPT_NEXT_HDR(raoptp)) {
				if (inet_ntop(AF_INET6, addr, ntopbuf,
					sizeof(ntopbuf)) == NULL) {
					warnmsg(LOG_INFO, __func__,
		    			    "an invalid address in RDNSS option"
					    " in RA from %s was ignored.",
					    inet_ntop(AF_INET6, &from.sin6_addr,
						ntopbuf, sizeof(ntopbuf)));
					addr++;
					continue;
				}
				if (IN6_IS_ADDR_LINKLOCAL(addr))
					/* XXX: % has to be escaped here */
					l = snprintf(nsbuf, sizeof(nsbuf),
					    "%s%c%s", ntopbuf,
					    SCOPE_DELIMITER,
					    ifi->ifname);
				else
					l = snprintf(nsbuf, sizeof(nsbuf),
					    "%s", ntopbuf);
				if (l < 0 || (size_t)l >= sizeof(nsbuf)) {
					warnmsg(LOG_ERR, __func__,
					    "address copying error in "
					    "RDNSS option: %d.", l);
					addr++;
					continue;
				}
				warnmsg(LOG_DEBUG, __func__, "nsbuf = %s",
				    nsbuf);

				newent_rao = 0;
				rao = find_raopt(rai, ndo->nd_opt_type, nsbuf,
				    strlen(nsbuf));
				if (rao == NULL) {
					ELM_MALLOC(rao, break);
					rao->rao_type = ndo->nd_opt_type;
					rao->rao_len = strlen(nsbuf);
					rao->rao_msg = strdup(nsbuf);
					if (rao->rao_msg == NULL) {
						warnmsg(LOG_ERR, __func__,
						    "strdup failed: %s",
						    strerror(errno));
						free(rao);
						addr++;
						continue;
					}
					newent_rao = 1;
				}
				/* Set expiration timer */
				memset(&rao->rao_expire, 0,
				    sizeof(rao->rao_expire));
				memset(&lifetime, 0, sizeof(lifetime));
				lifetime.tv_sec =
				    ntohl(rdnss->nd_opt_rdnss_lifetime);
				timeradd(&now, &lifetime, &rao->rao_expire);

				if (newent_rao)
					TAILQ_INSERT_TAIL(&rai->rai_ra_opt,
					    rao, rao_next);
				addr++;
			}
			break;
		case ND_OPT_DNSSL:
			dnssl = (struct nd_opt_dnssl *)raoptp;

			/* Optlen sanity check (Section 5.3.1 in RFC 6106) */
			if (dnssl->nd_opt_dnssl_len < 2) {
				warnmsg(LOG_INFO, __func__,
		    			"too short DNSSL option"
					"in RA from %s was ignored.",
					inet_ntop(AF_INET6, &from.sin6_addr,
					    ntopbuf, sizeof(ntopbuf)));
				break;
			}

			/*
			 * Ensure NUL-termination in DNSSL in case of
			 * malformed field.
			 */
			p = (char *)RA_OPT_NEXT_HDR(raoptp);
			*(p - 1) = '\0';

			p = raoptp + sizeof(*dnssl);
			while (1 < (len = dname_labeldec(dname, sizeof(dname),
			    p))) {
				/* length == 1 means empty string */
				warnmsg(LOG_DEBUG, __func__, "dname = %s",
				    dname);

				newent_rao = 0;
				rao = find_raopt(rai, ndo->nd_opt_type, dname,
				    strlen(dname));
				if (rao == NULL) {
					ELM_MALLOC(rao, break);
					rao->rao_type = ndo->nd_opt_type;
					rao->rao_len = strlen(dname);
					rao->rao_msg = strdup(dname);
					if (rao->rao_msg == NULL) {
						warnmsg(LOG_ERR, __func__,
						    "strdup failed: %s",
						    strerror(errno));
						free(rao);
						addr++;
						continue;
					}
					newent_rao = 1;
				}
				/* Set expiration timer */
				memset(&rao->rao_expire, 0,
				    sizeof(rao->rao_expire));
				memset(&lifetime, 0, sizeof(lifetime));
				lifetime.tv_sec =
				    ntohl(dnssl->nd_opt_dnssl_lifetime);
				timeradd(&now, &lifetime, &rao->rao_expire);

				if (newent_rao)
					TAILQ_INSERT_TAIL(&rai->rai_ra_opt,
					    rao, rao_next);
				p += len;
			}
			break;
		default:  
			/* nothing to do for other options */
			break;
		}
Esempio n. 2
0
static int
siop_intr(struct siop_adapter *adp)
{
	struct siop_xfer *siop_xfer = NULL;
	struct scsi_xfer *xs = NULL;
	u_long scriptaddr = local_to_PCI((u_long)adp->script);
	int offset, target, lun, tag, restart = 0, need_reset = 0;
	uint32_t dsa, irqcode;
	uint16_t sist;
	uint8_t dstat, sstat1, istat;

	istat = readb(adp->addr + SIOP_ISTAT);
	if ((istat & (ISTAT_INTF | ISTAT_DIP | ISTAT_SIP)) == 0)
		return 0;
	if (istat & ISTAT_INTF) {
		printf("INTRF\n");
		writeb(adp->addr + SIOP_ISTAT, ISTAT_INTF);
	}
	if ((istat & (ISTAT_DIP | ISTAT_SIP | ISTAT_ABRT)) ==
	    (ISTAT_DIP | ISTAT_ABRT))
		/* clear abort */
		writeb(adp->addr + SIOP_ISTAT, 0);
	/* use DSA to find the current siop_cmd */
	dsa = readl(adp->addr + SIOP_DSA);
	if (dsa >= local_to_PCI((u_long)adp->xfer) &&
	    dsa < local_to_PCI((u_long)adp->xfer) + SIOP_TABLE_SIZE) {
		dsa -= local_to_PCI((u_long)adp->xfer);
		siop_xfer = adp->xfer;
		_inv((u_long)siop_xfer, sizeof(*siop_xfer));

		xs = adp->xs;
	}

	if (istat & ISTAT_DIP)
		dstat = readb(adp->addr + SIOP_DSTAT);
	if (istat & ISTAT_SIP) {
		if (istat & ISTAT_DIP)
			delay(10);
		/*
		 * Can't read sist0 & sist1 independently, or we have to
		 * insert delay
		 */
		sist = readw(adp->addr + SIOP_SIST0);
		sstat1 = readb(adp->addr + SIOP_SSTAT1);

		if ((sist & SIST0_MA) && need_reset == 0) {
			if (siop_xfer) {
				int scratcha0;

				dstat = readb(adp->addr + SIOP_DSTAT);
				/*
				 * first restore DSA, in case we were in a S/G
				 * operation.
				 */
				writel(adp->addr + SIOP_DSA,
				    local_to_PCI((u_long)siop_xfer));
				scratcha0 = readb(adp->addr + SIOP_SCRATCHA);
				switch (sstat1 & SSTAT1_PHASE_MASK) {
				case SSTAT1_PHASE_STATUS:
				/*
				 * previous phase may be aborted for any reason
				 * ( for example, the target has less data to
				 * transfer than requested). Compute resid and
				 * just go to status, the command should
				 * terminate.
				 */
					if (scratcha0 & A_flag_data)
						siop_ma(adp, xs);
					else if ((dstat & DSTAT_DFE) == 0)
						siop_clearfifo(adp);
					CALL_SCRIPT(Ent_status);
					return 1;
				case SSTAT1_PHASE_MSGIN:
				/*
				 * target may be ready to disconnect
				 * Compute resid which would be used later
				 * if a save data pointer is needed.
				 */
					if (scratcha0 & A_flag_data)
						siop_ma(adp, xs);
					else if ((dstat & DSTAT_DFE) == 0)
						siop_clearfifo(adp);
					writeb(adp->addr + SIOP_SCRATCHA,
					    scratcha0 & ~A_flag_data);
					CALL_SCRIPT(Ent_msgin);
					return 1;
				}
				printf("unexpected phase mismatch %d\n",
				    sstat1 & SSTAT1_PHASE_MASK);
			} else
				printf("phase mismatch without command\n");
			need_reset = 1;
		}
		if (sist & (SIST1_STO << 8)) {
			/* selection time out, assume there's no device here */
			if (siop_xfer) {
				xs->error = XS_SELTIMEOUT;
				goto end;
			} else
				printf("selection timeout without command\n");
		}

		/* Else it's an unhandled exception (for now). */
		printf("unhandled scsi interrupt,"
		    " sist=0x%x sstat1=0x%x DSA=0x%x DSP=0x%lx\n",
		    sist, sstat1, dsa, 
		    readl(adp->addr + SIOP_DSP) - scriptaddr);
		if (siop_xfer) {
			xs->error = XS_SELTIMEOUT;
			goto end;
		}
		need_reset = 1;
	}
	if (need_reset) {
reset:
		printf("XXXXX: fatal error, need reset the bus...\n");
		return 1;
	}

//scintr:
	if ((istat & ISTAT_DIP) && (dstat & DSTAT_SIR)) { /* script interrupt */
		irqcode = readl(adp->addr + SIOP_DSPS);
		/*
		 * no command, or an inactive command is only valid for a
		 * reselect interrupt
		 */
		if ((irqcode & 0x80) == 0) {
			if (siop_xfer == NULL) {
				printf(
				    "script interrupt 0x%x with invalid DSA\n",
				    irqcode);
				goto reset;
			}
		}
		switch(irqcode) {
		case A_int_err:
			printf("error, DSP=0x%lx\n",
			    readl(adp->addr + SIOP_DSP) - scriptaddr);
			if (xs) {
				xs->error = XS_SELTIMEOUT;
				goto end;
			} else {
				goto reset;
			}
		case A_int_reseltarg:
			printf("reselect with invalid target\n");
			goto reset;
		case A_int_resellun:
			target = readb(adp->addr + SIOP_SCRATCHA) & 0xf;
			lun = readb(adp->addr + SIOP_SCRATCHA + 1);
			tag = readb(adp->addr + SIOP_SCRATCHA + 2);
			if (target != adp->xs->target ||
			    lun != adp->xs->lun ||
			    tag != 0) {
				printf("unknwon resellun:"
				    " target %d lun %d tag %d\n",
				    target, lun, tag);
				goto reset;
			}
			siop_xfer = adp->xfer;
			dsa = local_to_PCI((u_long)siop_xfer);
			writel(adp->addr + SIOP_DSP,
			    dsa + sizeof(struct siop_common_xfer) +
			    Ent_ldsa_reload_dsa);
			_wbinv((u_long)siop_xfer, sizeof(*siop_xfer));
			return 1;
		case A_int_reseltag:
			printf("reselect with invalid tag\n");
			goto reset;
		case A_int_disc:
			offset = readb(adp->addr + SIOP_SCRATCHA + 1);
			siop_sdp(adp, siop_xfer, xs, offset);
#if 0
			/* we start again with no offset */
			siop_cmd->saved_offset = SIOP_NOOFFSET;
#endif
			_wbinv((u_long)siop_xfer, sizeof(*siop_xfer));
			CALL_SCRIPT(Ent_script_sched);
			return 1;
		case A_int_resfail:
			printf("reselect failed\n");
			return  1;
		case A_int_done:
			if (xs == NULL) {
				printf("done without command, DSA=0x%lx\n",
				    local_to_PCI((u_long)adp->xfer));
				return 1;
			}
			/* update resid.  */
			offset = readb(adp->addr + SIOP_SCRATCHA + 1);
#if 0
			/*
			 * if we got a disconnect between the last data phase
			 * and the status phase, offset will be 0. In this
			 * case, siop_cmd->saved_offset will have the proper
			 * value if it got updated by the controller
			 */
			if (offset == 0 &&
			    siop_cmd->saved_offset != SIOP_NOOFFSET)
				offset = siop_cmd->saved_offset;
#endif
			siop_update_resid(adp, siop_xfer, xs, offset);
			goto end;
		default:
			printf("unknown irqcode %x\n", irqcode);
			if (xs) {
				xs->error = XS_SELTIMEOUT;
				goto end;
			}
			goto reset;
		}
		return 1;
	}
	/* We just should't get there */
	panic("siop_intr: I shouldn't be there !");

	return 1;

end:
	/*
	 * restart the script now if command completed properly
	 * Otherwise wait for siop_scsicmd_end(), we may need to cleanup the
	 * queue
	 */
	xs->status = ctoh32(siop_xfer->siop_tables.status);
	if (xs->status == SCSI_OK)
		writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched);
	else
		restart = 1;
	siop_scsicmd_end(adp, xs);
	if (restart)
		writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched);

	return 1;
}