- struct in6_pktinfo *pktinfo;
-
- if (*pktopt == NULL) {
- *pktopt = _MALLOC(sizeof(struct ip6_pktopts), M_IP6OPT,
- M_WAITOK);
- bzero(*pktopt, sizeof(struct ip6_pktopts));
- (*pktopt)->ip6po_hlim = -1;
- }
- opt = *pktopt;
-
- switch(optname) {
- case IPV6_PKTINFO:
- if (len == 0) { /* just remove the option */
- ip6_clearpktopts(opt, 1, IPV6_PKTINFO);
- break;
- }
-
- if (len != sizeof(struct in6_pktinfo))
- return EINVAL;
- pktinfo = (struct in6_pktinfo *)buf;
-
- /*
- * An application can clear any sticky IPV6_PKTINFO option by
- * doing a "regular" setsockopt with ipi6_addr being
- * in6addr_any and ipi6_ifindex being zero.
- * [rfc2292bis-01, Section 6]
- * XXX: Is this a good feature?? (jinmei@kame.net)
- */
- if (pktinfo->ipi6_ifindex == 0 &&
- IN6_IS_ADDR_UNSPECIFIED(&pktinfo->ipi6_addr)) {
- ip6_clearpktopts(opt, 1, IPV6_PKTINFO);
- break;
- }
-
- /* XXX: this overrides the original data space */
- if (pktinfo->ipi6_ifindex &&
- IN6_IS_ADDR_LINKLOCAL(&pktinfo->ipi6_addr))
- pktinfo->ipi6_addr.s6_addr16[1] =
- htons(pktinfo->ipi6_ifindex);
-
- if (pktinfo->ipi6_ifindex > if_index ||
- pktinfo->ipi6_ifindex < 0)
- return(ENXIO);
-
- /*
- * Check if the requested source address is indeed a unicast
- * address assigned to the node.
- */
- if (!IN6_IS_ADDR_UNSPECIFIED(&pktinfo->ipi6_addr)) {
- struct ifaddr *ia;
- struct sockaddr_in6 sin6;
-
- bzero(&sin6, sizeof(sin6));
- sin6.sin6_len = sizeof(sin6);
- sin6.sin6_family = AF_INET6;
- sin6.sin6_addr = pktinfo->ipi6_addr;
- ia = ifa_ifwithaddr(sin6tosa(&sin6));
- if (ia == NULL)
- return(EADDRNOTAVAIL);
- }
-
- if (opt->ip6po_pktinfo == NULL)
- opt->ip6po_pktinfo = _MALLOC(sizeof(struct in6_pktinfo),
- M_IP6OPT, M_WAITOK);
- bcopy(pktinfo, opt->ip6po_pktinfo, sizeof(*pktinfo));
-
- break;
- case IPV6_HOPLIMIT:
- {
- int *hlimp;
-
- if (len != sizeof(int))
- return(EINVAL);
- hlimp = (int *)buf;
- if (*hlimp < -1 || *hlimp > 255)
- return(EINVAL);
-
- opt->ip6po_hlim = *hlimp;
- break;
- }
- case IPV6_NEXTHOP:
- if (!priv)
- return(EPERM);
-
- if (len == 0) { /* just remove the option */
- ip6_clearpktopts(opt, 1, IPV6_NEXTHOP);
- break;
- }
-
- /* check if cmsg_len is large enough for sa_len */
- if (len < sizeof(u_char) ||
- len < *buf)
- return(EINVAL);
-
- /* turn off the previous option */
- ip6_clearpktopts(opt, 1, IPV6_NEXTHOP);
-
- opt->ip6po_nexthop = _MALLOC(*buf, M_IP6OPT, M_WAITOK);
- bcopy(buf, opt->ip6po_nexthop, *buf);
- break;
- case IPV6_HOPOPTS:
- {
- struct ip6_hbh *hbh;
- int hbhlen;
-
- /*
- * XXX: We don't allow a non-privileged user to set ANY HbH
- * options, since per-option restriction has too much
- * overhead.
- */
- if (!priv)
- return(EPERM);
-
- if (len == 0) {
- ip6_clearpktopts(opt, 1, IPV6_HOPOPTS);
- break; /* just remove the option */
- }
-
- if (len < sizeof(struct ip6_hbh))
- return(EINVAL);
- hbh = (struct ip6_hbh *)buf;
- hbhlen = (hbh->ip6h_len + 1) << 3;
- if (len != hbhlen)
- return(EINVAL);
-
- /* turn off the previous option */
- ip6_clearpktopts(opt, 1, IPV6_HOPOPTS);
-
- opt->ip6po_hbh = _MALLOC(hbhlen, M_IP6OPT, M_WAITOK);
- bcopy(buf, opt->ip6po_hbh, hbhlen);
-
- break;
- }
- case IPV6_DSTOPTS:
- case IPV6_RTHDRDSTOPTS:
- {
- struct ip6_dest *dest, *newdest;
- int destlen;
-
- if (!priv) /* XXX: see the comment for IPV6_HOPOPTS */
- return(EPERM);
-
- if (len == 0) {
- ip6_clearpktopts(opt, 1, optname);
- break; /* just remove the option */
- }
-
- if (len < sizeof(struct ip6_dest))
- return(EINVAL);
- dest = (struct ip6_dest *)buf;
- destlen = (dest->ip6d_len + 1) << 3;
- if (len != destlen)
- return(EINVAL);
-
- /* turn off the previous option */
- ip6_clearpktopts(opt, 1, optname);
-
- newdest = _MALLOC(destlen, M_IP6OPT, M_WAITOK);
- bcopy(buf, newdest, destlen);
-
- if (optname == IPV6_DSTOPTS)
- opt->ip6po_dest2 = newdest;
- else
- opt->ip6po_dest1 = newdest;
-
- break;
- }
- case IPV6_RTHDR:
- {
- struct ip6_rthdr *rth;
- int rthlen;
-
- if (len == 0) {
- ip6_clearpktopts(opt, 1, IPV6_RTHDR);
- break; /* just remove the option */
- }
-
- if (len < sizeof(struct ip6_rthdr))
- return(EINVAL);
- rth = (struct ip6_rthdr *)buf;
- rthlen = (rth->ip6r_len + 1) << 3;
- if (len != rthlen)
- return(EINVAL);
-
- switch(rth->ip6r_type) {
- case IPV6_RTHDR_TYPE_0:
- if (rth->ip6r_len == 0) /* must contain one addr */
- return(EINVAL);
- if (rth->ip6r_len % 2) /* length must be even */
- return(EINVAL);
- if (rth->ip6r_len / 2 != rth->ip6r_segleft)
- return(EINVAL);
- break;
- default:
- return(EINVAL); /* not supported */
- }
-
- /* turn off the previous option */
- ip6_clearpktopts(opt, 1, IPV6_RTHDR);
-
- opt->ip6po_rthdr = _MALLOC(rthlen, M_IP6OPT, M_WAITOK);
- bcopy(buf, opt->ip6po_rthdr, rthlen);
-
- break;
- }
- default:
- return(ENOPROTOOPT);
- } /* end of switch */
-
- return(0);
-}
-
-static int
-ip6_getpcbopt(pktopt, optname, datap, datalenp)
- struct ip6_pktopts *pktopt;
- int optname, *datalenp;
- void **datap;