+ return digest[0] + random() + pf_tcp_iss_off;
+}
+
+/*
+ * This routine is called to perform address family translation on the
+ * inner IP header (that may come as payload) of an ICMP(v4addr/6) error
+ * response.
+ */
+static int
+pf_change_icmp_af(pbuf_t *pbuf, int off,
+ struct pf_pdesc *pd, struct pf_pdesc *pd2, struct pf_addr *src,
+ struct pf_addr *dst, sa_family_t af, sa_family_t naf)
+{
+ struct ip *ip4 = NULL;
+ struct ip6_hdr *ip6 = NULL;
+ void *hdr;
+ int hlen, olen;
+
+ if (af == naf || (af != AF_INET && af != AF_INET6) ||
+ (naf != AF_INET && naf != AF_INET6)) {
+ return -1;
+ }
+
+ /* old header */
+ olen = pd2->off - off;
+ /* new header */
+ hlen = naf == AF_INET ? sizeof(*ip4) : sizeof(*ip6);
+
+ /* Modify the pbuf to accommodate the new header */
+ hdr = pbuf_resize_segment(pbuf, off, olen, hlen);
+ if (hdr == NULL) {
+ return -1;
+ }
+
+ /* translate inner ip/ip6 header */
+ switch (naf) {
+ case AF_INET:
+ ip4 = hdr;
+ bzero(ip4, sizeof(*ip4));
+ ip4->ip_v = IPVERSION;
+ ip4->ip_hl = sizeof(*ip4) >> 2;
+ ip4->ip_len = htons(sizeof(*ip4) + pd2->tot_len - olen);
+ ip4->ip_id = rfc6864 ? 0 : htons(ip_randomid());
+ ip4->ip_off = htons(IP_DF);
+ ip4->ip_ttl = pd2->ttl;
+ if (pd2->proto == IPPROTO_ICMPV6) {
+ ip4->ip_p = IPPROTO_ICMP;
+ } else {
+ ip4->ip_p = pd2->proto;
+ }
+ ip4->ip_src = src->v4addr;
+ ip4->ip_dst = dst->v4addr;
+ ip4->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, ip4->ip_hl << 2);
+ break;
+ case AF_INET6:
+ ip6 = hdr;
+ bzero(ip6, sizeof(*ip6));
+ ip6->ip6_vfc = IPV6_VERSION;
+ ip6->ip6_plen = htons(pd2->tot_len - olen);
+ if (pd2->proto == IPPROTO_ICMP) {
+ ip6->ip6_nxt = IPPROTO_ICMPV6;
+ } else {
+ ip6->ip6_nxt = pd2->proto;
+ }
+ if (!pd2->ttl || pd2->ttl > IPV6_DEFHLIM) {
+ ip6->ip6_hlim = IPV6_DEFHLIM;
+ } else {
+ ip6->ip6_hlim = pd2->ttl;
+ }
+ ip6->ip6_src = src->v6addr;
+ ip6->ip6_dst = dst->v6addr;
+ break;
+ }
+
+ /* adjust payload offset and total packet length */
+ pd2->off += hlen - olen;
+ pd->tot_len += hlen - olen;
+
+ return 0;
+}
+
+#define PTR_IP(field) ((int32_t)offsetof(struct ip, field))
+#define PTR_IP6(field) ((int32_t)offsetof(struct ip6_hdr, field))
+
+static int
+pf_translate_icmp_af(int af, void *arg)
+{
+ struct icmp *icmp4;
+ struct icmp6_hdr *icmp6;
+ u_int32_t mtu;
+ int32_t ptr = -1;
+ u_int8_t type;
+ u_int8_t code;
+
+ switch (af) {
+ case AF_INET:
+ icmp6 = arg;
+ type = icmp6->icmp6_type;
+ code = icmp6->icmp6_code;
+ mtu = ntohl(icmp6->icmp6_mtu);
+
+ switch (type) {
+ case ICMP6_ECHO_REQUEST:
+ type = ICMP_ECHO;
+ break;
+ case ICMP6_ECHO_REPLY:
+ type = ICMP_ECHOREPLY;
+ break;
+ case ICMP6_DST_UNREACH:
+ type = ICMP_UNREACH;
+ switch (code) {
+ case ICMP6_DST_UNREACH_NOROUTE:
+ case ICMP6_DST_UNREACH_BEYONDSCOPE:
+ case ICMP6_DST_UNREACH_ADDR:
+ code = ICMP_UNREACH_HOST;
+ break;
+ case ICMP6_DST_UNREACH_ADMIN:
+ code = ICMP_UNREACH_HOST_PROHIB;
+ break;
+ case ICMP6_DST_UNREACH_NOPORT:
+ code = ICMP_UNREACH_PORT;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case ICMP6_PACKET_TOO_BIG:
+ type = ICMP_UNREACH;
+ code = ICMP_UNREACH_NEEDFRAG;
+ mtu -= 20;
+ break;
+ case ICMP6_TIME_EXCEEDED:
+ type = ICMP_TIMXCEED;
+ break;
+ case ICMP6_PARAM_PROB:
+ switch (code) {
+ case ICMP6_PARAMPROB_HEADER:
+ type = ICMP_PARAMPROB;
+ code = ICMP_PARAMPROB_ERRATPTR;
+ ptr = ntohl(icmp6->icmp6_pptr);
+
+ if (ptr == PTR_IP6(ip6_vfc)) {
+ ; /* preserve */
+ } else if (ptr == PTR_IP6(ip6_vfc) + 1) {
+ ptr = PTR_IP(ip_tos);
+ } else if (ptr == PTR_IP6(ip6_plen) ||
+ ptr == PTR_IP6(ip6_plen) + 1) {
+ ptr = PTR_IP(ip_len);
+ } else if (ptr == PTR_IP6(ip6_nxt)) {
+ ptr = PTR_IP(ip_p);
+ } else if (ptr == PTR_IP6(ip6_hlim)) {
+ ptr = PTR_IP(ip_ttl);
+ } else if (ptr >= PTR_IP6(ip6_src) &&
+ ptr < PTR_IP6(ip6_dst)) {
+ ptr = PTR_IP(ip_src);
+ } else if (ptr >= PTR_IP6(ip6_dst) &&
+ ptr < (int32_t)sizeof(struct ip6_hdr)) {
+ ptr = PTR_IP(ip_dst);
+ } else {
+ return -1;
+ }
+ break;
+ case ICMP6_PARAMPROB_NEXTHEADER:
+ type = ICMP_UNREACH;
+ code = ICMP_UNREACH_PROTOCOL;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ default:
+ return -1;
+ }
+ icmp6->icmp6_type = type;
+ icmp6->icmp6_code = code;
+ /* aligns well with a icmpv4 nextmtu */
+ icmp6->icmp6_mtu = htonl(mtu);
+ /* icmpv4 pptr is a one most significant byte */
+ if (ptr >= 0) {
+ icmp6->icmp6_pptr = htonl(ptr << 24);
+ }
+ break;
+
+ case AF_INET6:
+ icmp4 = arg;
+ type = icmp4->icmp_type;
+ code = icmp4->icmp_code;
+ mtu = ntohs(icmp4->icmp_nextmtu);
+
+ switch (type) {
+ case ICMP_ECHO:
+ type = ICMP6_ECHO_REQUEST;
+ break;
+ case ICMP_ECHOREPLY:
+ type = ICMP6_ECHO_REPLY;
+ break;
+ case ICMP_UNREACH:
+ type = ICMP6_DST_UNREACH;
+ switch (code) {
+ case ICMP_UNREACH_NET:
+ case ICMP_UNREACH_HOST:
+ case ICMP_UNREACH_NET_UNKNOWN:
+ case ICMP_UNREACH_HOST_UNKNOWN:
+ case ICMP_UNREACH_ISOLATED:
+ case ICMP_UNREACH_TOSNET:
+ case ICMP_UNREACH_TOSHOST:
+ code = ICMP6_DST_UNREACH_NOROUTE;
+ break;
+ case ICMP_UNREACH_PORT:
+ code = ICMP6_DST_UNREACH_NOPORT;
+ break;
+ case ICMP_UNREACH_NET_PROHIB:
+ case ICMP_UNREACH_HOST_PROHIB:
+ case ICMP_UNREACH_FILTER_PROHIB:
+ case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+ code = ICMP6_DST_UNREACH_ADMIN;
+ break;
+ case ICMP_UNREACH_PROTOCOL:
+ type = ICMP6_PARAM_PROB;
+ code = ICMP6_PARAMPROB_NEXTHEADER;
+ ptr = offsetof(struct ip6_hdr, ip6_nxt);
+ break;
+ case ICMP_UNREACH_NEEDFRAG:
+ type = ICMP6_PACKET_TOO_BIG;
+ code = 0;
+ mtu += 20;
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case ICMP_TIMXCEED:
+ type = ICMP6_TIME_EXCEEDED;
+ break;
+ case ICMP_PARAMPROB:
+ type = ICMP6_PARAM_PROB;
+ switch (code) {
+ case ICMP_PARAMPROB_ERRATPTR:
+ code = ICMP6_PARAMPROB_HEADER;
+ break;
+ case ICMP_PARAMPROB_LENGTH:
+ code = ICMP6_PARAMPROB_HEADER;
+ break;
+ default:
+ return -1;
+ }
+
+ ptr = icmp4->icmp_pptr;
+ if (ptr == 0 || ptr == PTR_IP(ip_tos)) {
+ ; /* preserve */
+ } else if (ptr == PTR_IP(ip_len) ||
+ ptr == PTR_IP(ip_len) + 1) {
+ ptr = PTR_IP6(ip6_plen);
+ } else if (ptr == PTR_IP(ip_ttl)) {
+ ptr = PTR_IP6(ip6_hlim);
+ } else if (ptr == PTR_IP(ip_p)) {
+ ptr = PTR_IP6(ip6_nxt);
+ } else if (ptr >= PTR_IP(ip_src) &&
+ ptr < PTR_IP(ip_dst)) {
+ ptr = PTR_IP6(ip6_src);
+ } else if (ptr >= PTR_IP(ip_dst) &&
+ ptr < (int32_t)sizeof(struct ip)) {
+ ptr = PTR_IP6(ip6_dst);
+ } else {
+ return -1;
+ }
+ break;
+ default:
+ return -1;
+ }
+ icmp4->icmp_type = type;
+ icmp4->icmp_code = code;
+ icmp4->icmp_nextmtu = htons(mtu);
+ if (ptr >= 0) {
+ icmp4->icmp_void = htonl(ptr);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* Note: frees pbuf if PF_NAT64 is returned */
+static int
+pf_nat64_ipv6(pbuf_t *pbuf, int off, struct pf_pdesc *pd)
+{
+ struct ip *ip4;
+ struct mbuf *m;
+
+ /*
+ * ip_input asserts for rcvif to be not NULL
+ * That may not be true for two corner cases
+ * 1. If for some reason a local app sends DNS
+ * AAAA query to local host
+ * 2. If IPv6 stack in kernel internally generates a
+ * message destined for a synthesized IPv6 end-point.
+ */
+ if (pbuf->pb_ifp == NULL) {
+ return PF_DROP;
+ }
+
+ ip4 = (struct ip *)pbuf_resize_segment(pbuf, 0, off, sizeof(*ip4));
+ if (ip4 == NULL) {
+ return PF_DROP;
+ }
+
+ ip4->ip_v = 4;
+ ip4->ip_hl = 5;
+ ip4->ip_tos = pd->tos & htonl(0x0ff00000);
+ ip4->ip_len = htons(sizeof(*ip4) + (pd->tot_len - off));
+ ip4->ip_id = 0;
+ ip4->ip_off = htons(IP_DF);
+ ip4->ip_ttl = pd->ttl;
+ ip4->ip_p = pd->proto;
+ ip4->ip_sum = 0;
+ ip4->ip_src = pd->naddr.v4addr;
+ ip4->ip_dst = pd->ndaddr.v4addr;
+ ip4->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, ip4->ip_hl << 2);
+
+ /* recalculate icmp checksums */
+ if (pd->proto == IPPROTO_ICMP) {
+ struct icmp *icmp;
+ int hlen = sizeof(*ip4);
+
+ icmp = (struct icmp *)pbuf_contig_segment(pbuf, hlen,
+ ICMP_MINLEN);
+ if (icmp == NULL) {
+ return PF_DROP;
+ }
+
+ icmp->icmp_cksum = 0;
+ icmp->icmp_cksum = pbuf_inet_cksum(pbuf, 0, hlen,
+ ntohs(ip4->ip_len) - hlen);
+ }
+
+ if ((m = pbuf_to_mbuf(pbuf, TRUE)) != NULL) {
+ ip_input(m);
+ }
+
+ return PF_NAT64;
+}
+
+static int
+pf_nat64_ipv4(pbuf_t *pbuf, int off, struct pf_pdesc *pd)
+{
+ struct ip6_hdr *ip6;
+ struct mbuf *m;
+
+ if (pbuf->pb_ifp == NULL) {
+ return PF_DROP;
+ }
+
+ ip6 = (struct ip6_hdr *)pbuf_resize_segment(pbuf, 0, off, sizeof(*ip6));
+ if (ip6 == NULL) {
+ return PF_DROP;
+ }
+
+ ip6->ip6_vfc = htonl((6 << 28) | (pd->tos << 20));
+ ip6->ip6_plen = htons(pd->tot_len - off);
+ ip6->ip6_nxt = pd->proto;
+ ip6->ip6_hlim = pd->ttl;
+ ip6->ip6_src = pd->naddr.v6addr;
+ ip6->ip6_dst = pd->ndaddr.v6addr;
+
+ /* recalculate icmp6 checksums */
+ if (pd->proto == IPPROTO_ICMPV6) {
+ struct icmp6_hdr *icmp6;
+ int hlen = sizeof(*ip6);
+
+ icmp6 = (struct icmp6_hdr *)pbuf_contig_segment(pbuf, hlen,
+ sizeof(*icmp6));
+ if (icmp6 == NULL) {
+ return PF_DROP;
+ }
+
+ icmp6->icmp6_cksum = 0;
+ icmp6->icmp6_cksum = pbuf_inet6_cksum(pbuf,
+ IPPROTO_ICMPV6, hlen,
+ ntohs(ip6->ip6_plen));
+ } else if (pd->proto == IPPROTO_UDP) {
+ struct udphdr *uh;
+ int hlen = sizeof(*ip6);
+
+ uh = (struct udphdr *)pbuf_contig_segment(pbuf, hlen,
+ sizeof(*uh));
+ if (uh == NULL) {
+ return PF_DROP;
+ }
+
+ if (uh->uh_sum == 0) {
+ uh->uh_sum = pbuf_inet6_cksum(pbuf, IPPROTO_UDP,
+ hlen, ntohs(ip6->ip6_plen));
+ }
+ }
+
+ if ((m = pbuf_to_mbuf(pbuf, TRUE)) != NULL) {
+ ip6_input(m);
+ }
+
+ return PF_NAT64;