+ /* How do we spot broadcast destination addresses? */
+ if (ntohs(iph->frag)&IPHDR_FRAG_OFF) return False;
+ source=ntohl(iph->saddr);
+ if (source==0) return False;
+ if ((source&0xff000000)==0x7f000000) return False;
+ /* How do we spot broadcast source addresses? */
+ if ((source&0xf0000000)==0xe0000000) return False; /* Multicast */
+ if ((source&0xf0000000)==0xf0000000) return False; /* Class E */
+ return True;
+}
+
+/* How much of the original IP packet do we include in its ICMP
+ response? The header plus up to 64 bits. */
+
+/* XXX TODO RFC1812:
+4.3.2.3 Original Message Header
+
+ Historically, every ICMP error message has included the Internet
+ header and at least the first 8 data bytes of the datagram that
+ triggered the error. This is no longer adequate, due to the use of
+ IP-in-IP tunneling and other technologies. Therefore, the ICMP
+ datagram SHOULD contain as much of the original datagram as possible
+ without the length of the ICMP datagram exceeding 576 bytes. The
+ returned IP header (and user data) MUST be identical to that which
+ was received, except that the router is not required to undo any
+ modifications to the IP header that are normally performed in
+ forwarding that were performed before the error was detected (e.g.,
+ decrementing the TTL, or updating options). Note that the
+ requirements of Section [4.3.3.5] supersede this requirement in some
+ cases (i.e., for a Parameter Problem message, if the problem is in a
+ modified field, the router must undo the modification). See Section
+ [4.3.3.5]).
+ */
+static uint16_t netlink_icmp_reply_len(struct buffer_if *buf)
+{
+ if (buf->size < (int)sizeof(struct iphdr)) return 0;
+ struct iphdr *iph=(struct iphdr *)buf->start;
+ uint16_t hlen,plen;
+
+ hlen=iph->ihl*4;
+ /* We include the first 8 bytes of the packet data, provided they exist */
+ hlen+=8;
+ plen=ntohs(iph->tot_len);
+ return (hlen>plen?plen:hlen);
+}
+
+/* client indicates where the packet we're constructing a response to
+ comes from. NULL indicates the host. */
+static void netlink_icmp_simple(struct netlink *st, struct buffer_if *buf,
+ uint8_t type, uint8_t code,
+ union icmpinfofield info)
+{
+ struct icmphdr *h;
+ uint16_t len;
+
+ if (netlink_icmp_may_reply(buf)) {
+ struct iphdr *iph=(struct iphdr *)buf->start;
+ len=netlink_icmp_reply_len(buf);
+ h=netlink_icmp_tmpl(st,ntohl(iph->saddr),len);
+ h->type=type; h->code=code; h->d=info;
+ memcpy(buf_append(&st->icmp,len),buf->start,len);
+ netlink_icmp_csum(h);
+ netlink_packet_deliver(st,NULL,&st->icmp);
+ BUF_ASSERT_FREE(&st->icmp);
+ }
+}
+
+/*
+ * RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the
+ * checksum.
+ * RFC1812: 4.2.2.5 MUST discard messages containing invalid checksums.
+ *
+ * Is the datagram acceptable?
+ *
+ * 1. Length at least the size of an ip header
+ * 2. Version of 4
+ * 3. Checksums correctly.
+ * 4. Doesn't have a bogus length
+ */
+static bool_t netlink_check(struct netlink *st, struct buffer_if *buf,
+ char *errmsgbuf, int errmsgbuflen)
+{
+#define BAD(...) do{ \
+ snprintf(errmsgbuf,errmsgbuflen,__VA_ARGS__); \
+ return False; \
+ }while(0)
+
+ if (buf->size < (int)sizeof(struct iphdr)) BAD("len %"PRIu32"",buf->size);
+ struct iphdr *iph=(struct iphdr *)buf->start;
+ int32_t len;
+
+ if (iph->ihl < 5) BAD("ihl %u",iph->ihl);
+ if (iph->version != 4) BAD("version %u",iph->version);
+ if (buf->size < iph->ihl*4) BAD("size %"PRId32"<%u*4",buf->size,iph->ihl);
+ if (ip_fast_csum((uint8_t *)iph, iph->ihl)!=0) BAD("csum");
+ len=ntohs(iph->tot_len);
+ /* There should be no padding */
+ if (buf->size!=len) BAD("len %"PRId32"!=%"PRId32,buf->size,len);
+ if (len<(iph->ihl<<2)) BAD("len %"PRId32"<(%u<<2)",len,iph->ihl);
+ /* XXX check that there's no source route specified */
+ return True;
+
+#undef BAD
+}
+
+static const char *fragment_filter_header(uint8_t *base, long *hlp)
+{
+ const int fixedhl = sizeof(struct iphdr);
+ long hl = *hlp;
+ const uint8_t *ipend = base + hl;
+ uint8_t *op = base + fixedhl;
+ const uint8_t *ip = op;
+
+ while (ip < ipend) {
+ uint8_t opt = ip[0];
+ int remain = ipend - ip;
+ if (opt == 0x00) /* End of Options List */ break;
+ if (opt == 0x01) /* No Operation */ continue;
+ if (remain < 2) return "IPv4 options truncated at length";
+ int optlen = ip[1];
+ if (remain < optlen) return "IPv4 options truncated in option";
+ if (opt & 0x80) /* copy */ {
+ memmove(op, ip, optlen);
+ op += optlen;