commit 7932431fa14478648bce14249b6b110741731daf
Author: Alexander Zubkov <green@qrator.net>
Date:   Sat Jun 22 19:32:48 2024 +0200

    BFD: add option to allow zero checksum for rx IPv6 UDP packets
    
    Some vendors do not set the checksum for IPv6 UDP packets.
    For interopertability with such implementations one can set UDP_NO_CHECK6_RX
    socket option on Linux, in order to allow a zero checksum.

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 8035ec18..50bfe9bc 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -2499,6 +2499,7 @@ milliseconds.
 <code>
 protocol bfd [&lt;name&gt;] {
 	accept [ipv4|ipv6] [direct|multihop];
+	allow udp6 zero checksum rx &lt;switch&gt;;
 	interface &lt;interface pattern&gt; {
 		interval &lt;time&gt;;
 		min rx interval &lt;time&gt;;
@@ -2541,6 +2542,12 @@ protocol bfd [&lt;name&gt;] {
 	to configure separate BFD protocol instances for IPv4 and for IPv6
 	sessions.
 
+	<tag><label id="bfd-allow-udp6-zero-checksum-rx">allow udp6 zero checksum rx <m/switch/</tag>
+	When enabled, this option configures the rx socket to allow an unset (zero)
+	IPv6 UDP checksum on the systems, which support such socket option
+	(ex. <cf>UDP_NO_CHECK6_RX</cf>). It is needed for interoperability with some
+	vendors that do not set the checksum. Default: disabled.
+
 	<tag><label id="bfd-strict-bind">strict bind <m/switch/</tag>
 	Specify whether each BFD interface should use a separate listening
 	socket bound to its local address, or just use a shared listening socket
diff --git a/lib/socket.h b/lib/socket.h
index 231c10d8..b3ffadad 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -131,6 +131,8 @@ extern int sk_priority_control;		/* Suggested priority for control traffic, shou
 #define SKF_HDRINCL	0x400	/* Used internally */
 #define SKF_PKTINFO	0x800	/* Used internally */
 
+#define SKF_UDP6_NO_CSUM_RX	0x1000	/* Allow zero checksum for incoming UDP6 */
+
 /*
  *	Socket types		     SA SP DA DP IF  TTL SendTo	(?=may, -=must not, *=must)
  */
diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c
index 4f8499ba..cd78884a 100644
--- a/proto/bfd/bfd.c
+++ b/proto/bfd/bfd.c
@@ -1149,7 +1149,8 @@ bfd_reconfigure(struct proto *P, struct proto_config *c)
       (new->accept_ipv6 != old->accept_ipv6) ||
       (new->accept_direct != old->accept_direct) ||
       (new->accept_multihop != old->accept_multihop) ||
-      (new->strict_bind != old->strict_bind))
+      (new->strict_bind != old->strict_bind) ||
+      (new->udp6_no_checksum_rx != old->udp6_no_checksum_rx))
     return 0;
 
   birdloop_mask_wakeups(p->loop);
diff --git a/proto/bfd/bfd.h b/proto/bfd/bfd.h
index 847c6b14..2792ba92 100644
--- a/proto/bfd/bfd.h
+++ b/proto/bfd/bfd.h
@@ -48,6 +48,7 @@ struct bfd_config
   u8 accept_direct;
   u8 accept_multihop;
   u8 strict_bind;
+  u8 udp6_no_checksum_rx;
 };
 
 struct bfd_iface_config
diff --git a/proto/bfd/config.Y b/proto/bfd/config.Y
index 39e7577f..c6d50cf8 100644
--- a/proto/bfd/config.Y
+++ b/proto/bfd/config.Y
@@ -24,7 +24,7 @@ CF_DECLS
 CF_KEYWORDS(BFD, MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE,
 	INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL, AUTHENTICATION,
 	NONE, SIMPLE, METICULOUS, KEYED, MD5, SHA1, IPV4, IPV6, DIRECT,
-	STRICT, BIND)
+	STRICT, BIND, UDP6, CHECKSUM, ZERO)
 
 %type <iface> bfd_neigh_iface
 %type <a> bfd_neigh_local
@@ -51,6 +51,7 @@ bfd_proto_item:
  | MULTIHOP bfd_multihop
  | NEIGHBOR bfd_neighbor
  | STRICT BIND bool { BFD_CFG->strict_bind = $3; }
+ | ALLOW UDP6 ZERO CHECKSUM RX bool { BFD_CFG->udp6_no_checksum_rx = $6; }
  ;
 
 bfd_proto_opts:
diff --git a/proto/bfd/packets.c b/proto/bfd/packets.c
index aec91ca6..0853e140 100644
--- a/proto/bfd/packets.c
+++ b/proto/bfd/packets.c
@@ -416,6 +416,8 @@ bfd_err_hook(sock *sk, int err)
 sock *
 bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af)
 {
+  struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
+
   sock *sk = sk_new(p->tpool);
   sk->type = SK_UDP;
   sk->subtype = af;
@@ -431,6 +433,8 @@ bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af)
   sk->tos = IP_PREC_INTERNET_CONTROL;
   sk->priority = sk_priority_control;
   sk->flags = SKF_THREAD | SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0);
+  if (cf->udp6_no_checksum_rx)
+    sk->flags |= SKF_UDP6_NO_CSUM_RX;
 
   if (sk_open(sk) < 0)
     goto err;
@@ -447,6 +451,8 @@ err:
 sock *
 bfd_open_rx_sk_bound(struct bfd_proto *p, ip_addr local, struct iface *ifa)
 {
+  struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
+
   sock *sk = sk_new(p->tpool);
   sk->type = SK_UDP;
   sk->saddr = local;
@@ -463,6 +469,8 @@ bfd_open_rx_sk_bound(struct bfd_proto *p, ip_addr local, struct iface *ifa)
   sk->tos = IP_PREC_INTERNET_CONTROL;
   sk->priority = sk_priority_control;
   sk->flags = SKF_THREAD | SKF_BIND | (ifa ? SKF_TTL_RX : 0);
+  if (cf->udp6_no_checksum_rx)
+    sk->flags |= SKF_UDP6_NO_CSUM_RX;
 
   if (sk_open(sk) < 0)
     goto err;
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 9b499020..92cb736e 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -517,6 +517,21 @@ sk_set_high_port(sock *s UNUSED)
   return 0;
 }
 
+static inline int
+sk_set_udp6_no_csum_rx(sock *s)
+{
+#ifdef UDP_NO_CHECK6_RX
+  int y = 1;
+
+  if (setsockopt(s->fd, SOL_UDP, UDP_NO_CHECK6_RX, &y, sizeof(y)) < 0)
+    ERR("UDP_NO_CHECK6_RX");
+
+  return 0;
+#else
+  ERR("UDP_NO_CHECK6_RX");
+#endif
+}
+
 static inline byte *
 sk_skip_ip_header(byte *pkt, int *len)
 {
@@ -1475,6 +1490,10 @@ sk_open(sock *s)
       if (sk_set_freebind(s) < 0)
         log(L_WARN "Socket error: %s%#m", s->err);
 
+    if (s->flags & SKF_UDP6_NO_CSUM_RX && s->af == AF_INET6 && s->type == SK_UDP)
+      if (sk_set_udp6_no_csum_rx(s) < 0)
+        log(L_WARN "Socket error: %s%#m", s->err);
+
     sockaddr_fill(&sa, s->af, bind_addr, s->iface, bind_port);
     if (bind(fd, &sa.sa, SA_LEN(sa)) < 0)
       ERR2("bind");
