+/* Fragment or send ICMP Fragmentation Needed */
+static void netlink_maybe_fragment(struct netlink *st,
+ struct netlink_client *sender,
+ netlink_deliver_fn *deliver,
+ void *deliver_dst,
+ const char *delivery_name,
+ int32_t mtu,
+ uint32_t source, uint32_t dest,
+ struct buffer_if *buf)
+{
+ struct iphdr *iph=(struct iphdr*)buf->start;
+ long hl = iph->ihl*4;
+ const char *ssource = ipaddr_to_string(source);
+
+ if (buf->size <= mtu) {
+ deliver(deliver_dst, buf);
+ return;
+ }
+
+ MDEBUG("%s: fragmenting %s->%s org.size=%"PRId32"\n",
+ st->name, ssource, delivery_name, buf->size);
+
+#define BADFRAG(m, ...) \
+ Message(M_WARNING, \
+ "%s: fragmenting packet from source %s" \
+ " for transmission via %s: " m "\n", \
+ st->name, ssource, delivery_name, \
+ ## __VA_ARGS__);
+
+ unsigned orig_frag = ntohs(iph->frag);
+
+ if (orig_frag&IPHDR_FRAG_DONT) {
+ union icmpinfofield info =
+ { .fragneeded = { .unused = 0, .mtu = htons(mtu) } };
+ netlink_icmp_simple(st,sender,buf,
+ ICMP_TYPE_UNREACHABLE,
+ ICMP_CODE_FRAGMENTATION_REQUIRED,
+ info);
+ BUF_FREE(buf);
+ return;
+ }
+ if (mtu < hl + 8) {
+ BADFRAG("mtu %"PRId32" too small", mtu);
+ BUF_FREE(buf);
+ return;
+ }
+
+ /* we (ab)use the icmp buffer to stash the original packet */
+ struct buffer_if *orig = &st->icmp;
+ BUF_ALLOC(orig,"netlink_client_deliver fragment orig");
+ buffer_copy(orig,buf);
+ BUF_FREE(buf);
+
+ const uint8_t *startindata = orig->start + hl;
+ const uint8_t *indata = startindata;
+ const uint8_t *endindata = orig->start + orig->size;
+ _Bool filtered = 0;
+
+ for (;;) {
+ /* compute our fragment offset */
+ long dataoffset = indata - startindata
+ + (orig_frag & IPHDR_FRAG_OFF)*8;
+ assert(!(dataoffset & 7));
+ if (dataoffset > IPHDR_FRAG_OFF*8) {
+ BADFRAG("ultimate fragment offset out of range");
+ break;
+ }
+
+ BUF_ALLOC(buf,"netlink_client_deliver fragment frag");
+ buffer_init(buf,calculate_max_start_pad());
+
+ /* copy header (possibly filtered); will adjust in a bit */
+ struct iphdr *fragh = buf_append(buf, hl);
+ memcpy(fragh, orig->start, hl);
+
+ /* decide how much payload to copy and copy it */
+ long avail = mtu - hl;
+ long remain = endindata - indata;
+ long use = avail < remain ? (avail & ~(long)7) : remain;
+ BUF_ADD_BYTES(append, buf, indata, use);
+ indata += use;
+
+ _Bool last_frag = indata >= endindata;
+
+ /* adjust the header */
+ fragh->tot_len = htons(buf->size);
+ fragh->frag =
+ htons((orig_frag & ~IPHDR_FRAG_OFF) |
+ (last_frag ? 0 : IPHDR_FRAG_MORE) |
+ (dataoffset >> 3));
+ fragh->check = 0;
+ fragh->check = ip_fast_csum((const void*)fragh, fragh->ihl);
+
+ /* actually send it */
+ deliver(deliver_dst, buf);
+ if (last_frag)
+ break;
+
+ /* after copying the header for the first frag,
+ * we filter the header for the remaining frags */
+ if (!filtered++) {
+ const char *bad = fragment_filter_header(orig->start, &hl);
+ if (bad) { BADFRAG("%s", bad); break; }
+ }
+ }
+
+ BUF_FREE(orig);
+
+#undef BADFRAG
+}
+
+/* Deliver a packet _to_ client; used after we have decided
+ * what to do with it (and just to check that the client has
+ * actually registered a delivery function with us). */
+static void netlink_client_deliver(struct netlink *st,
+ struct netlink_client *client,
+ uint32_t source, uint32_t dest,
+ struct buffer_if *buf)
+{
+ if (!client->deliver) {
+ string_t s,d;
+ s=ipaddr_to_string(source);
+ d=ipaddr_to_string(dest);
+ Message(M_ERR,"%s: dropping %s->%s, client not registered\n",
+ st->name,s,d);
+ BUF_FREE(buf);
+ return;
+ }
+ netlink_maybe_fragment(st,NULL, client->deliver,client->dst,client->name,
+ client->mtu, source,dest,buf);
+ client->outcount++;
+}
+
+/* Deliver a packet to the host; used after we have decided that that
+ * is what to do with it. */
+static void netlink_host_deliver(struct netlink *st,
+ struct netlink_client *sender,
+ uint32_t source, uint32_t dest,
+ struct buffer_if *buf)
+{
+ netlink_maybe_fragment(st,sender, st->deliver_to_host,st->dst,"(host)",
+ st->mtu, source,dest,buf);
+ st->outcount++;
+}
+
+/* Deliver a packet. "sender"==NULL for packets from the host and packets