diff --git a/doc/bird.sgml b/doc/bird.sgml
index 017251287a23017dd3a302286ed8d6e7099a78d0..8235740e9ef41ae95e44ecd3bb8f8b42611e36b7 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -828,8 +828,8 @@ agreement").
 	<tag><label id="proto-pass-algorithm">algorithm ( keyed md5 | keyed sha1 | hmac sha1 | hmac sha256 | hmac sha384 | hmac sha512 | blake2s128 | blake2s256 | blake2b256 | blake2b512 )</tag>
 	The message authentication algorithm for the password when cryptographic
 	authentication is enabled. The default value depends on the protocol.
-	For RIP and OSPFv2 it is Keyed-MD5 (for compatibility), for OSPFv3
-	protocol it is HMAC-SHA-256.
+	For RIP and OSPFv2 it is Keyed-MD5 (for compatibility), for OSPFv3 and
+	Babel it is HMAC-SHA-256.
 
 </descrip>
 
@@ -1817,6 +1817,19 @@ protocol babel [<name>] {
 		check link <switch>;
 		next hop ipv4 <address>;
 		next hop ipv6 <address>;
+		authentication none|mac [permissive];
+		password "&lt;text&gt;";
+		password "&lt;text&gt;" {
+			id &lt;num&gt;;
+			generate from "&lt;date&gt;";
+			generate to "&lt;date&gt;";
+			accept from "&lt;date&gt;";
+			accept to "&lt;date&gt;";
+			from "&lt;date&gt;";
+			to "&lt;date&gt;";
+			algorithm ( hmac sha1 | hmac sha256 | hmac sha384 | hmac
+	sha512 | blake2s | blake2b );
+		};
 	};
 }
 </code>
@@ -1907,6 +1920,25 @@ protocol babel [<name>] {
       interface. If not set, the same link-local address that is used as the
       source for Babel packets will be used. In normal operation, it should not
       be necessary to set this option.
+
+      <tag><label id="babel-authentication">authentication none|mac [permissive]</tag>
+      Selects authentication method to be used. <cf/none/ means that packets
+      are not authenticated at all, <cf/mac/ means MAC authentication is
+      performed as described in <rfc id="8967">. If MAC authentication is
+      selected, the <cf/permissive/ suffix can be used to select an operation
+      mode where outgoing packets are signed, but incoming packets will be
+      accepted even if they fail authentication. This can be useful for
+      incremental deployment of MAC authentication across a network. If MAC
+      authentication is selected, a key must be specified with the
+      <cf/password/ configuration option. Default: none.
+
+      <tag><label id="babel-password">password "<m/text/"</tag> Specifies a
+      password used for authentication. See the <ref id="proto-pass"
+      name="password"> common option for a detailed description. The Babel
+      protocol will only accept HMAC-based algorithms or one of the Blake
+      algorithms, and the length of the supplied password string must match the
+      key size used by the selected algorithm.
+
 </descrip>
 
 <sect1>Attributes
diff --git a/proto/babel/Makefile b/proto/babel/Makefile
index a5b4a13b2a8bd1d3a94ea536010b688fce88f5bd..06b58e95b9555d4e459f2456953c3b652e3ea2a0 100644
--- a/proto/babel/Makefile
+++ b/proto/babel/Makefile
@@ -3,4 +3,4 @@ obj := $(src-o-files)
 $(all-daemon)
 $(cf-local)
 
-tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index fdebc35265e984182b6973cd6ee067ea8740619e..82ba7da17ed1a52dc5a0493073da5e8782344aad 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -38,6 +38,8 @@
 #include <stdlib.h>
 #include "babel.h"
 
+#define LOG_PKT_AUTH(msg, args...) \
+  log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args)
 
 /*
  * Is one number greater or equal than another mod 2^16? This is based on the
@@ -55,6 +57,7 @@ static void babel_send_seqno_request(struct babel_proto *p, struct babel_entry *
 static void babel_update_cost(struct babel_neighbor *n);
 static inline void babel_kick_timer(struct babel_proto *p);
 static inline void babel_iface_kick_timer(struct babel_iface *ifa);
+static void babel_auth_init_neighbor(struct babel_neighbor *n);
 
 /*
  *	Functions to maintain data structures
@@ -428,6 +431,7 @@ babel_get_neighbor(struct babel_iface *ifa, ip_addr addr)
   init_list(&nbr->routes);
   init_list(&nbr->requests);
   add_tail(&ifa->neigh_list, NODE nbr);
+  babel_auth_init_neighbor(nbr);
 
   return nbr;
 }
@@ -509,11 +513,13 @@ babel_expire_neighbors(struct babel_proto *p)
 
       if (nbr->hello_expiry && nbr->hello_expiry <= now_)
         babel_expire_hello(p, nbr, now_);
+
+      if (nbr->auth_expiry && nbr->auth_expiry <= now_)
+        babel_flush_neighbor(p, nbr);
     }
   }
 }
 
-
 /*
  *	Best route selection
  */
@@ -1388,6 +1394,127 @@ babel_handle_seqno_request(union babel_msg *m, struct babel_iface *ifa)
   }
 }
 
+/*
+ *      Authentication functions
+ */
+
+/**
+ * babel_auth_reset_index - Reset authentication index on interface
+ * @ifa: Interface to reset
+ *
+ * This function resets the authentication index and packet counter for an
+ * interface, and should be called on interface configuration, or when the
+ * packet counter overflows.
+ */
+void
+babel_auth_reset_index(struct babel_iface *ifa)
+{
+  random_bytes(ifa->auth_index, BABEL_AUTH_INDEX_LEN);
+  ifa->auth_pc = 1;
+}
+
+/**
+ * babel_auth_init_neighbor - Initialise authentication data for neighbor
+ * @n: Neighbor to initialise
+ *
+ * This function initialises the authentication-related state for a new neighbor
+ * that has just been created.
+ */
+void
+babel_auth_init_neighbor(struct babel_neighbor *n)
+{
+  if (n->ifa->cf->auth_type != BABEL_AUTH_NONE)
+    n->auth_expiry = current_time() + BABEL_AUTH_NEIGHBOR_TIMEOUT;
+}
+
+static void
+babel_auth_send_challenge(struct babel_iface *ifa, struct babel_neighbor *n)
+{
+  struct babel_proto *p = ifa->proto;
+  union babel_msg msg = {};
+
+  TRACE(D_PACKETS, "Sending AUTH challenge to %I on %s",
+	n->addr, ifa->ifname);
+
+  random_bytes(n->auth_nonce, BABEL_AUTH_NONCE_LEN);
+  n->auth_nonce_expiry = current_time() + BABEL_AUTH_CHALLENGE_TIMEOUT;
+  n->auth_next_challenge = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL;
+
+  msg.type = BABEL_TLV_CHALLENGE_REQ;
+  msg.challenge.nonce_len = BABEL_AUTH_NONCE_LEN;
+  msg.challenge.nonce = n->auth_nonce;
+
+  babel_send_unicast(&msg, ifa, n->addr);
+}
+
+int
+babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg)
+{
+  struct babel_proto *p = ifa->proto;
+  struct babel_neighbor *n;
+
+  TRACE(D_PACKETS, "Handling MAC check from %I on %s",
+        msg->sender,  ifa->ifname);
+
+  /* We create the neighbour entry at this point because it makes it easier to
+   * rate limit challenge replies; this is explicitly allowed by the spec (see
+   *  Section 4.3).
+   */
+  n = babel_get_neighbor(ifa, msg->sender);
+
+  if (msg->challenge_seen && n->auth_next_challenge_reply <= current_time())
+  {
+    union babel_msg resp = {};
+    TRACE(D_PACKETS, "Sending MAC challenge response to %I", msg->sender);
+    resp.type = BABEL_TLV_CHALLENGE_REPLY;
+    resp.challenge.nonce_len = msg->challenge_len;
+    resp.challenge.nonce = msg->challenge;
+    n->auth_next_challenge_reply = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL;
+    babel_send_unicast(&resp, ifa, msg->sender);
+  }
+
+  if (msg->index_len > BABEL_AUTH_INDEX_LEN || !msg->pc_seen)
+  {
+    LOG_PKT_AUTH("Invalid index or no PC from %I on %s",
+                 msg->sender, ifa->ifname);
+    return 1;
+  }
+
+  /* On successful challenge, update PC and index to current values */
+  if (msg->challenge_reply_seen &&
+      n->auth_nonce_expiry &&
+      n->auth_nonce_expiry >= current_time() &&
+      !memcmp(msg->challenge_reply, n->auth_nonce, BABEL_AUTH_NONCE_LEN))
+  {
+    n->auth_index_len = msg->index_len;
+    memcpy(n->auth_index, msg->index, msg->index_len);
+    n->auth_pc = msg->pc;
+  }
+
+  /* If index differs, send challenge */
+  if ((n->auth_index_len != msg->index_len ||
+      memcmp(n->auth_index, msg->index, msg->index_len)) &&
+      n->auth_next_challenge <= current_time())
+  {
+    LOG_PKT_AUTH("Index mismatch from %I on %s; sending challenge",
+                 msg->sender, ifa->ifname);
+    babel_auth_send_challenge(ifa, n);
+    return 1;
+  }
+
+  /* Index matches; only accept if PC is greater than last */
+  if (n->auth_pc >= msg->pc)
+  {
+    LOG_PKT_AUTH("Packet counter too low from %I on %s",
+                 msg->sender, ifa->ifname);
+    return 1;
+  }
+
+  n->auth_pc = msg->pc;
+  n->auth_expiry = current_time() + BABEL_AUTH_NEIGHBOR_TIMEOUT;
+  n->auth_passed = 1;
+  return 0;
+}
 
 /*
  *	Babel interfaces
@@ -1556,6 +1683,8 @@ babel_iface_update_buffers(struct babel_iface *ifa)
   sk_set_tbsize(ifa->sk, tbsize);
 
   ifa->tx_length = tbsize - BABEL_OVERHEAD;
+
+  babel_auth_set_tx_overhead(ifa);
 }
 
 static struct babel_iface*
@@ -1615,6 +1744,9 @@ babel_add_iface(struct babel_proto *p, struct iface *new, struct babel_iface_con
   init_list(&ifa->neigh_list);
   ifa->hello_seqno = 1;
 
+  if (ic->auth_type != BABEL_AUTH_NONE)
+    babel_auth_reset_index(ifa);
+
   ifa->timer = tm_new_init(ifa->pool, babel_iface_timer, ifa, 0, 0);
 
   init_list(&ifa->msg_queue);
@@ -1722,6 +1854,9 @@ babel_reconfigure_iface(struct babel_proto *p, struct babel_iface *ifa, struct b
   ifa->next_hop_ip4 = ipa_nonzero(new->next_hop_ip4) ? new->next_hop_ip4 : addr4;
   ifa->next_hop_ip6 = ipa_nonzero(new->next_hop_ip6) ? new->next_hop_ip6 : ifa->addr;
 
+  if (new->auth_type != BABEL_AUTH_NONE && old->auth_type != new->auth_type)
+    babel_auth_reset_index(ifa);
+
   if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
     log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
 
@@ -1910,8 +2045,8 @@ babel_show_interfaces(struct proto *P, const char *iff)
   }
 
   cli_msg(-1023, "%s:", p->p.name);
-  cli_msg(-1023, "%-10s %-6s %7s %6s %7s %-15s %s",
-	  "Interface", "State", "RX cost", "Nbrs", "Timer",
+  cli_msg(-1023, "%-10s %-6s %-5s %7s %6s %7s %-15s %s",
+	  "Interface", "State", "Auth", "RX cost", "Nbrs", "Timer",
 	  "Next hop (v4)", "Next hop (v6)");
 
   WALK_LIST(ifa, p->interfaces)
@@ -1924,8 +2059,10 @@ babel_show_interfaces(struct proto *P, const char *iff)
 	nbrs++;
 
     btime timer = MIN(ifa->next_regular, ifa->next_hello) - current_time();
-    cli_msg(-1023, "%-10s %-6s %7u %6u %7t %-15I %I",
+    cli_msg(-1023, "%-10s %-6s %-5s %7u %6u %7t %-15I %I",
 	    ifa->iface->name, (ifa->up ? "Up" : "Down"),
+            (ifa->cf->auth_type == BABEL_AUTH_MAC ?
+             (ifa->cf->auth_permissive ? "Perm" : "Yes") : "No"),
 	    ifa->cf->rxcost, nbrs, MAX(timer, 0),
 	    ifa->next_hop_ip4, ifa->next_hop_ip6);
   }
@@ -1946,8 +2083,8 @@ babel_show_neighbors(struct proto *P, const char *iff)
   }
 
   cli_msg(-1024, "%s:", p->p.name);
-  cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s",
-	  "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires");
+  cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s",
+	  "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth");
 
   WALK_LIST(ifa, p->interfaces)
   {
@@ -1961,9 +2098,10 @@ babel_show_neighbors(struct proto *P, const char *iff)
         rts++;
 
       uint hellos = u32_popcount(n->hello_map);
-      btime timer = n->hello_expiry - current_time();
-      cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t",
-	      n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0));
+      btime timer = (n->hello_expiry ?: n->auth_expiry) - current_time();
+      cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s",
+	      n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0),
+              n->auth_passed ? "Yes" : "No");
     }
   }
 }
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index 09bf530c2b35643364a37eb49f26d7c1a569eae8..ef5b4a5d0514f49ea1047c323df4488b4150f88d 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -19,6 +19,7 @@
 #include "nest/route.h"
 #include "nest/protocol.h"
 #include "nest/locks.h"
+#include "nest/password.h"
 #include "lib/resource.h"
 #include "lib/lists.h"
 #include "lib/socket.h"
@@ -60,6 +61,14 @@
 #define BABEL_OVERHEAD		(IP6_HEADER_LENGTH+UDP_HEADER_LENGTH)
 #define BABEL_MIN_MTU		(512 + BABEL_OVERHEAD)
 
+#define BABEL_AUTH_NONE		0
+#define BABEL_AUTH_MAC			1
+#define BABEL_AUTH_NONCE_LEN		10	/* we send 80 bit nonces */
+#define BABEL_AUTH_MAX_NONCE_LEN	192	/* max allowed by spec */
+#define BABEL_AUTH_INDEX_LEN		32	/* max size in spec */
+#define BABEL_AUTH_NEIGHBOR_TIMEOUT	(300 S_)
+#define BABEL_AUTH_CHALLENGE_TIMEOUT	(30 S_)
+#define BABEL_AUTH_CHALLENGE_INTERVAL	(300 MS_) /* used for both challenges and replies */
 
 enum babel_tlv_type {
   BABEL_TLV_PAD1		= 0,
@@ -73,13 +82,10 @@ enum babel_tlv_type {
   BABEL_TLV_UPDATE		= 8,
   BABEL_TLV_ROUTE_REQUEST	= 9,
   BABEL_TLV_SEQNO_REQUEST	= 10,
-  /* extensions - not implemented
-  BABEL_TLV_TS_PC		= 11,
-  BABEL_TLV_HMAC		= 12,
-  BABEL_TLV_SS_UPDATE		= 13,
-  BABEL_TLV_SS_REQUEST		= 14,
-  BABEL_TLV_SS_SEQNO_REQUEST	= 15,
-  */
+  BABEL_TLV_MAC		= 16,
+  BABEL_TLV_PC			= 17,
+  BABEL_TLV_CHALLENGE_REQ	= 18,
+  BABEL_TLV_CHALLENGE_REPLY	= 19,
   BABEL_TLV_MAX
 };
 
@@ -137,6 +143,12 @@ struct babel_iface_config {
 
   ip_addr next_hop_ip4;
   ip_addr next_hop_ip6;
+
+  u8 auth_type;			/* Authentication type (BABEL_AUTH_*) */
+  u8 auth_permissive;			/* Don't drop packets failing auth check */
+  uint mac_num_keys;			/* Number of configured HMAC keys */
+  uint mac_total_len;			/* Total digest length for all configured keys */
+  list *passwords;			/* Passwords for authentication */
 };
 
 struct babel_proto {
@@ -184,6 +196,10 @@ struct babel_iface {
 
   u16 hello_seqno;			/* To be increased on each hello */
 
+  u32 auth_pc;
+  int auth_tx_overhead;
+  u8 auth_index[BABEL_AUTH_INDEX_LEN];
+
   btime next_hello;
   btime next_regular;
   btime next_triggered;
@@ -206,9 +222,20 @@ struct babel_neighbor {
   u16 hello_map;
   u16 next_hello_seqno;
   uint last_hello_int;
+
+  u32 auth_pc;
+  u8 auth_passed;
+  u8 auth_index_len;
+  u8 auth_index[BABEL_AUTH_INDEX_LEN];
+  u8 auth_nonce[BABEL_AUTH_NONCE_LEN];
+  btime auth_nonce_expiry;
+  btime auth_next_challenge;
+  btime auth_next_challenge_reply;
+
   /* expiry timers */
   btime hello_expiry;
   btime ihu_expiry;
+  btime auth_expiry;
 
   list routes;				/* Routes this neighbour has sent us (struct babel_route) */
   list requests;			/* Seqno requests bound to this neighbor */
@@ -340,6 +367,12 @@ struct babel_msg_seqno_request {
   ip_addr sender;
 };
 
+struct babel_msg_challenge {
+  u8 type;
+  u8 nonce_len;
+  u8 *nonce;
+};
+
 union babel_msg {
   u8 type;
   struct babel_msg_ack_req ack_req;
@@ -349,6 +382,7 @@ union babel_msg {
   struct babel_msg_update update;
   struct babel_msg_route_request route_request;
   struct babel_msg_seqno_request seqno_request;
+  struct babel_msg_challenge challenge;
 };
 
 struct babel_msg_node {
@@ -356,6 +390,20 @@ struct babel_msg_node {
   union babel_msg msg;
 };
 
+/* only used for auth checking, so not a part of union above */
+struct babel_msg_auth {
+  ip_addr sender;
+  u32 pc;
+  u8 pc_seen;
+  u8 index_len;
+  u8 *index;
+  u8 challenge_reply_seen;
+  u8 challenge_reply[BABEL_AUTH_NONCE_LEN];
+  u8 challenge_seen;
+  u8 challenge_len;
+  u8 challenge[BABEL_AUTH_MAX_NONCE_LEN];
+};
+
 static inline int babel_sadr_enabled(struct babel_proto *p)
 { return p->ip6_rtable.addr_type == NET_IP6_SADR; }
 
@@ -374,11 +422,15 @@ void babel_show_neighbors(struct proto *P, const char *iff);
 void babel_show_entries(struct proto *P);
 void babel_show_routes(struct proto *P);
 
+void babel_auth_reset_index(struct babel_iface *ifa);
+int babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg);
+
 /* packets.c */
 void babel_enqueue(union babel_msg *msg, struct babel_iface *ifa);
 void babel_send_unicast(union babel_msg *msg, struct babel_iface *ifa, ip_addr dest);
 int babel_open_socket(struct babel_iface *ifa);
 void babel_send_queue(void *arg);
+void babel_auth_set_tx_overhead(struct babel_iface *ifa);
 
 
 #endif
diff --git a/proto/babel/config.Y b/proto/babel/config.Y
index 2f3b637b4f6cc7a21d736ac135a28620edcb8ce7..5e0710b5b7594ba924819e8c09698664d82ad00a 100644
--- a/proto/babel/config.Y
+++ b/proto/babel/config.Y
@@ -25,7 +25,7 @@ CF_DECLS
 CF_KEYWORDS(BABEL, INTERFACE, METRIC, RXCOST, HELLO, UPDATE, INTERVAL, PORT,
 	TYPE, WIRED, WIRELESS, RX, TX, BUFFER, PRIORITY, LENGTH, CHECK, LINK,
 	NEXT, HOP, IPV4, IPV6, BABEL_METRIC, SHOW, INTERFACES, NEIGHBORS,
-	ENTRIES, RANDOMIZE, ROUTER, ID)
+	ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE)
 
 CF_GRAMMAR
 
@@ -59,6 +59,8 @@ babel_iface_start:
   this_ipatt = cfg_allocz(sizeof(struct babel_iface_config));
   add_tail(&BABEL_CFG->iface_list, NODE this_ipatt);
   init_list(&this_ipatt->ipn_list);
+  reset_passwords();
+
   BABEL_IFACE->port = BABEL_PORT;
   BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRED;
   BABEL_IFACE->limit = BABEL_HELLO_LIMIT;
@@ -91,6 +93,40 @@ babel_iface_finish:
   BABEL_IFACE->ihu_interval = MIN_(BABEL_IFACE->hello_interval*BABEL_IHU_INTERVAL_FACTOR, BABEL_MAX_INTERVAL);
 
   BABEL_CFG->hold_time = MAX_(BABEL_CFG->hold_time, BABEL_IFACE->update_interval*BABEL_HOLD_TIME_FACTOR);
+
+  BABEL_IFACE->passwords = get_passwords();
+
+  if (!BABEL_IFACE->auth_type != !BABEL_IFACE->passwords)
+    cf_error("Authentication and password options should be used together");
+
+  if (BABEL_IFACE->passwords)
+  {
+    struct password_item *pass;
+    uint len = 0, i = 0;
+    WALK_LIST(pass, *BABEL_IFACE->passwords)
+    {
+      /* Set default crypto algorithm (HMAC-SHA256) */
+      if (!pass->alg)
+	pass->alg = ALG_HMAC_SHA256;
+
+      if (pass->alg & ALG_HMAC) {
+        if (pass->length < mac_type_length(pass->alg) ||
+            pass->length > mac_type_block_size(pass->alg))
+          cf_error("key length %d is not between output size %d and block size %d for algorithm %s",
+                   pass->length, mac_type_length(pass->alg),
+                   mac_type_block_size(pass->alg), mac_type_name(pass->alg));
+      } else if (!(pass->alg == ALG_BLAKE2S_128 || pass->alg == ALG_BLAKE2S_256 ||
+                   pass->alg == ALG_BLAKE2B_256 || pass->alg == ALG_BLAKE2B_512)) {
+	cf_error("Only HMAC and Blake algorithms are supported");
+      }
+
+      len += mac_type_length(pass->alg);
+      i++;
+    }
+    BABEL_IFACE->mac_num_keys = i;
+    BABEL_IFACE->mac_total_len = len;
+  }
+
 };
 
 
@@ -109,6 +145,10 @@ babel_iface_item:
  | CHECK LINK bool { BABEL_IFACE->check_link = $3; }
  | NEXT HOP IPV4 ipa { BABEL_IFACE->next_hop_ip4 = $4; if (!ipa_is_ip4($4)) cf_error("Must be an IPv4 address"); }
  | NEXT HOP IPV6 ipa { BABEL_IFACE->next_hop_ip6 = $4; if (!ipa_is_ip6($4)) cf_error("Must be an IPv6 address"); }
+ | AUTHENTICATION NONE { BABEL_IFACE->auth_type = BABEL_AUTH_NONE; }
+ | AUTHENTICATION MAC { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; }
+ | AUTHENTICATION MAC PERMISSIVE { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 1; }
+ | password_list	{ }
  ;
 
 babel_iface_opts:
diff --git a/proto/babel/packets.c b/proto/babel/packets.c
index 1d2f5f5bdf3c64a3d88a15ad8ab52276e28b16b5..703d4026c9a42a24039e4dd1eace433a45f46a46 100644
--- a/proto/babel/packets.c
+++ b/proto/babel/packets.c
@@ -11,7 +11,7 @@
  */
 
 #include "babel.h"
-
+#include "lib/mac.h"
 
 struct babel_pkt_header {
   u8 magic;
@@ -112,6 +112,31 @@ struct babel_subtlv_source_prefix {
   u8 addr[0];
 } PACKED;
 
+struct babel_tlv_pc {
+  u8 type;
+  u8 length;
+  u32 pc;
+  u8 index[0];
+} PACKED;
+
+struct babel_tlv_mac {
+  u8 type;
+  u8 length;
+  u8 mac[0];
+} PACKED;
+
+struct babel_tlv_challenge {
+  u8 type;
+  u8 length;
+  u8 nonce[0];
+} PACKED;
+
+struct babel_mac_pseudohdr {
+  u8 src_addr[16];
+  u16 src_port;
+  u8 dst_addr[16];
+  u16 dst_port;
+} PACKED;
 
 /* Hello flags */
 #define BABEL_HF_UNICAST	0x8000
@@ -146,6 +171,9 @@ struct babel_parse_state {
   u8 def_ip4_prefix_seen;	/* def_ip4_prefix is valid */
   u8 current_tlv_endpos;	/* End of self-terminating TLVs (offset from start) */
   u8 sadr_enabled;
+  u8 is_unicast;
+
+  struct babel_msg_auth auth;
 };
 
 enum parse_result {
@@ -168,6 +196,10 @@ struct babel_write_state {
 #define DROP1(DSC) do { err_dsc = DSC; goto drop; } while(0)
 #define LOG_PKT(msg, args...) \
   log_rl(&p->log_pkt_tbf, L_REMOTE "%s: " msg, p->p.name, args)
+#define LOG_WARN(msg, args...) \
+  log_rl(&p->log_pkt_tbf, L_WARN "%s: " msg, p->p.name, args)
+#define LOG_PKT_AUTH(msg, args...) \
+  log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args)
 
 #define FIRST_TLV(p) ((struct babel_tlv *) (((struct babel_pkt_header *) p) + 1))
 #define NEXT_TLV(t) ((struct babel_tlv *) (((byte *) t) + TLV_LENGTH(t)))
@@ -274,6 +306,17 @@ put_ip6_ll(void *p, ip6_addr addr)
   put_u32(p+4, _I3(addr));
 }
 
+/*
+ *      Authentication-related functions
+ */
+uint babel_auth_write_challenge(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
+int babel_auth_add_tlvs(struct babel_iface *ifa, struct babel_tlv *tlv, int max_len);
+int babel_auth_sign(struct babel_iface *ifa, ip_addr dest);
+int babel_auth_check(struct babel_iface *ifa,
+                     ip_addr saddr, u16 sport,
+                     ip_addr daddr, u16 dport,
+                     struct babel_pkt_header *pkt,
+                     byte *start, uint len);
 
 /*
  *	TLV read/write functions
@@ -352,6 +395,16 @@ static const struct babel_tlv_data tlv_data[BABEL_TLV_MAX] = {
     babel_write_seqno_request,
     babel_handle_seqno_request
   },
+  [BABEL_TLV_CHALLENGE_REQ] = {
+    sizeof(struct babel_tlv),
+    NULL,
+    babel_auth_write_challenge,
+  },
+  [BABEL_TLV_CHALLENGE_REPLY] = {
+    sizeof(struct babel_tlv),
+    NULL,
+    babel_auth_write_challenge,
+  },
 };
 
 static const struct babel_tlv_data *get_packet_tlv_data(u8 type)
@@ -1225,7 +1278,6 @@ babel_write_tlv(struct babel_tlv *hdr,
   return tlv_data[msg->type].write_tlv(hdr, msg, state, max_len);
 }
 
-
 /*
  *	Packet RX/TX functions
  */
@@ -1237,6 +1289,8 @@ babel_send_to(struct babel_iface *ifa, ip_addr dest)
   struct babel_pkt_header *hdr = (void *) sk->tbuf;
   int len = get_u16(&hdr->length) + sizeof(struct babel_pkt_header);
 
+  len += babel_auth_sign(ifa, dest);
+
   DBG("Babel: Sending %d bytes to %I\n", len, dest);
   return sk_send_to(sk, len, dest, 0);
 }
@@ -1289,6 +1343,8 @@ babel_write_queue(struct babel_iface *ifa, list *queue)
     sl_free(p->msg_slab, msg);
   }
 
+  pos += babel_auth_add_tlvs(ifa, (struct babel_tlv *) pos, end-pos);
+
   uint plen = pos - (byte *) pkt;
   put_u16(&pkt->length, plen - sizeof(struct babel_pkt_header));
 
@@ -1367,10 +1423,13 @@ babel_enqueue(union babel_msg *msg, struct babel_iface *ifa)
 
 /**
  * babel_process_packet - process incoming data packet
+ * @ifa: Interface packet was received on.
  * @pkt: Pointer to the packet data
  * @len: Length of received packet
  * @saddr: Address of packet sender
- * @ifa: Interface packet was received on.
+ * @sport: Packet source port
+ * @daddr: Destination address of packet
+ * @dport: Packet destination port
  *
  * This function is the main processing hook of incoming Babel packets. It
  * checks that the packet header is well-formed, then processes the TLVs
@@ -1382,8 +1441,10 @@ babel_enqueue(union babel_msg *msg, struct babel_iface *ifa)
  * order.
  */
 static void
-babel_process_packet(struct babel_pkt_header *pkt, int len,
-                     ip_addr saddr, struct babel_iface *ifa)
+babel_process_packet(struct babel_iface *ifa,
+		     struct babel_pkt_header *pkt, int len,
+                     ip_addr saddr, u16 sport,
+		     ip_addr daddr, u16 dport)
 {
   u8 frame_err UNUSED = 0;
   struct babel_proto *p = ifa->proto;
@@ -1422,6 +1483,9 @@ babel_process_packet(struct babel_pkt_header *pkt, int len,
   TRACE(D_PACKETS, "Packet received from %I via %s",
         saddr, ifa->iface->name);
 
+  if (babel_auth_check(ifa, saddr, sport, daddr, dport, pkt, end, len-plen))
+    return;
+
   init_list(&msgs);
 
   /* First pass through the packet TLV by TLV, parsing each into internal data
@@ -1512,7 +1576,10 @@ babel_rx_hook(sock *sk, uint len)
   if (sk->flags & SKF_TRUNCATED)
     DROP("truncated", len);
 
-  babel_process_packet((struct babel_pkt_header *) sk->rbuf, len, sk->faddr, ifa);
+  babel_process_packet(ifa,
+		       (struct babel_pkt_header *) sk->rbuf, len,
+		       sk->faddr, sk->fport,
+		       sk->laddr, sk->dport);
   return 1;
 
 drop:
@@ -1562,3 +1629,420 @@ err:
   rfree(sk);
   return 0;
 }
+
+
+/* Authentication checks */
+static int
+babel_read_pc(struct babel_tlv *hdr, union babel_msg *m UNUSED,
+              struct babel_parse_state *state)
+{
+  struct babel_tlv_pc *tlv = (void *) hdr;
+
+  if (!state->auth.pc_seen)
+  {
+    state->auth.pc_seen = 1;
+    state->auth.pc = get_u32(&tlv->pc);
+    state->auth.index_len = TLV_OPT_LENGTH(tlv);
+    state->auth.index = tlv->index;
+  }
+
+  return PARSE_IGNORE;
+}
+
+static const struct babel_tlv_data pc_tlv_data = {
+  .min_length = sizeof(struct babel_tlv_pc),
+  .read_tlv = &babel_read_pc
+};
+
+static int
+babel_read_challenge_req(struct babel_tlv *hdr, union babel_msg *m UNUSED,
+			 struct babel_parse_state *state)
+{
+  struct babel_tlv_challenge *tlv = (void *) hdr;
+
+  if (!state->is_unicast)
+  {
+    DBG("Ignoring non-unicast challenge request from %I\n", state->saddr);
+    return PARSE_IGNORE;
+  }
+
+  if (tlv->length > BABEL_AUTH_MAX_NONCE_LEN)
+    return PARSE_IGNORE;
+
+  state->auth.challenge_len = tlv->length;
+  if (state->auth.challenge_len)
+    memcpy(state->auth.challenge, tlv->nonce, state->auth.challenge_len);
+  state->auth.challenge_seen = 1;
+
+  return PARSE_IGNORE;
+}
+
+static const struct babel_tlv_data challenge_req_tlv_data = {
+  .min_length = sizeof(struct babel_tlv_challenge),
+  .read_tlv = &babel_read_challenge_req,
+};
+
+static int
+babel_read_challenge_reply(struct babel_tlv *hdr, union babel_msg *m UNUSED,
+                           struct babel_parse_state *state)
+{
+  struct babel_tlv_challenge *tlv = (void *) hdr;
+
+  if (tlv->length != BABEL_AUTH_NONCE_LEN || state->auth.challenge_reply_seen)
+    return PARSE_IGNORE;
+
+  state->auth.challenge_reply_seen = 1;
+  memcpy(state->auth.challenge_reply, tlv->nonce, BABEL_AUTH_NONCE_LEN);
+
+  return PARSE_IGNORE;
+}
+
+static const struct babel_tlv_data challenge_reply_tlv_data = {
+  .min_length = sizeof(struct babel_tlv_challenge),
+  .read_tlv = &babel_read_challenge_reply,
+};
+
+static const struct babel_tlv_data *
+get_auth_tlv_data(u8 type)
+{
+  switch(type)
+  {
+  case BABEL_TLV_PC:
+    return &pc_tlv_data;
+  case BABEL_TLV_CHALLENGE_REQ:
+    return &challenge_req_tlv_data;
+  case BABEL_TLV_CHALLENGE_REPLY:
+    return &challenge_reply_tlv_data;
+  default:
+    return NULL;
+  }
+}
+
+uint
+babel_auth_write_challenge(struct babel_tlv *hdr, union babel_msg *m,
+                           struct babel_write_state *state UNUSED,uint max_len)
+{
+  struct babel_tlv_challenge *tlv = (void *) hdr;
+  struct babel_msg_challenge *msg = &m->challenge;
+
+  uint len = sizeof(struct babel_tlv_challenge) + msg->nonce_len;
+
+  if (len > max_len)
+    return 0;
+
+  TLV_HDR(tlv, msg->type, len);
+  memcpy(tlv->nonce, msg->nonce, msg->nonce_len);
+
+  return len;
+}
+
+static int
+babel_mac_hash(struct password_item *pass,
+               struct babel_mac_pseudohdr *phdr,
+               byte *pkt, uint pkt_len,
+               byte *buf, uint *buf_len)
+{
+  struct mac_context ctx;
+
+  if (mac_type_length(pass->alg) > *buf_len)
+    return 1;
+
+  mac_init(&ctx, pass->alg, pass->password, pass->length);
+  mac_update(&ctx, (byte *)phdr, sizeof(*phdr));
+  mac_update(&ctx, (byte *)pkt, pkt_len);
+
+  *buf_len = mac_get_length(&ctx);
+  memcpy(buf, mac_final(&ctx), *buf_len);
+
+  mac_cleanup(&ctx);
+
+  return 0;
+}
+
+static void
+babel_mac_build_phdr(struct babel_mac_pseudohdr *phdr,
+                     ip_addr saddr, u16 sport,
+                     ip_addr daddr, u16 dport)
+{
+  memset(phdr, 0, sizeof(*phdr));
+  put_ip6(phdr->src_addr, saddr);
+  put_u16(&phdr->src_port, sport);
+  put_ip6(phdr->dst_addr, daddr);
+  put_u16(&phdr->dst_port, dport);
+  DBG("MAC pseudo-header: %I %d %I %d\n", saddr, sport, daddr, dport);
+}
+
+static int
+babel_auth_check_mac(struct babel_iface *ifa, byte *pkt,
+                     byte *trailer, uint trailer_len,
+                     ip_addr saddr, u16 sport,
+                     ip_addr daddr, u16 dport)
+{
+  uint hash_len = (uint)(trailer - pkt);
+  struct babel_proto *p = ifa->proto;
+  byte *end = trailer + trailer_len;
+  btime now_ = current_real_time();
+  struct babel_mac_pseudohdr phdr;
+  struct password_item *pass;
+  struct babel_tlv *tlv;
+
+  if (trailer_len < sizeof(*tlv))
+  {
+    LOG_PKT_AUTH("No MAC signature on packet from %I on %s",
+                 saddr, ifa->ifname);
+    return 1;
+  }
+
+  babel_mac_build_phdr(&phdr, saddr, sport, daddr, dport);
+
+  WALK_LIST(pass, *ifa->cf->passwords)
+  {
+    byte mac_res[MAX_HASH_SIZE];
+    uint mac_len = MAX_HASH_SIZE;
+    u8 frame_err = 0;
+
+    if (pass->accfrom > now_ || pass->accto < now_)
+      continue;
+
+    if (babel_mac_hash(pass, &phdr,
+                       pkt, hash_len,
+                       mac_res, &mac_len))
+      continue;
+
+    WALK_TLVS((void *)trailer, end, tlv, frame_err, saddr, ifa->ifname)
+    {
+      struct babel_tlv_mac *mac = (void *)tlv;
+
+      if (tlv->type != BABEL_TLV_MAC)
+	continue;
+
+      if (tlv->length == mac_len && !memcmp(mac->mac, mac_res, mac_len))
+        return 0;
+
+      DBG("MAC mismatch key id %d pos %d len %d/%d\n",
+	  pass->id, (byte *)tlv - (byte *)pkt, mac_len, tlv->length);
+    }
+    WALK_TLVS_END;
+
+    if (frame_err) {
+      DBG("MAC trailer TLV framing error\n");
+      return 1;
+    }
+  }
+
+  LOG_PKT_AUTH("No MAC key matching packet from %I found on %s",
+               saddr, ifa->ifname);
+  return 1;
+}
+
+/**
+ * babel_auth_check - Check authentication for a packet
+ * @ifa: Interface holding the transmission buffer
+ * @saddr: Source address the packet was received from
+ * @sport: Source port the packet was received from
+ * @daddr: Destination address the packet was sent to
+ * @dport: Destination port the packet was sent to
+ * @pkt: Pointer to start of the packet data
+ * @trailer: Pointer to the packet trailer
+ * @trailer_len: Length of the packet trailer
+ *
+ * This function performs any necessary authentication checks on a packet and
+ * returns 0 if the packet should be accepted (either because it has been
+ * successfully authenticated or because authentication is disabled or
+ * configured in permissive mode), or 1 if the packet should be dropped without
+ * further processing.
+ */
+int
+babel_auth_check(struct babel_iface *ifa,
+                 ip_addr saddr, u16 sport,
+                 ip_addr daddr, u16 dport,
+                 struct babel_pkt_header *pkt,
+                 byte *trailer, uint trailer_len)
+{
+  u8 frame_err UNUSED = 0;
+  struct babel_proto *p = ifa->proto;
+  struct babel_tlv *tlv;
+
+  struct babel_parse_state state = {
+    .get_tlv_data = &get_auth_tlv_data,
+    .proto        = p,
+    .ifa          = ifa,
+    .saddr        = saddr,
+    .is_unicast     = !(ipa_classify(daddr) & IADDR_MULTICAST),
+    .auth = {
+      .sender = saddr,
+    },
+  };
+
+  if (ifa->cf->auth_type == BABEL_AUTH_NONE)
+    return 0;
+
+  TRACE(D_PACKETS, "Checking packet authentication signature");
+
+  if (babel_auth_check_mac(ifa, (byte *)pkt,
+                           trailer, trailer_len,
+                           saddr, sport,
+                           daddr, dport))
+    goto fail;
+
+  /* MAC verified; parse packet to check packet counter and challenge */
+  WALK_TLVS(FIRST_TLV(pkt), trailer, tlv, frame_err, saddr, ifa->iface->name)
+  {
+    union babel_msg msg;
+    enum parse_result res;
+
+    res = babel_read_tlv(tlv, &msg, &state);
+    if (res == PARSE_ERROR)
+    {
+      LOG_PKT_AUTH("Bad TLV from %I via %s type %d pos %d - parse error",
+                   saddr, ifa->iface->name, tlv->type, (byte *)tlv - (byte *)pkt);
+      goto fail;
+    }
+  }
+  WALK_TLVS_END;
+
+  if (babel_auth_check_pc(ifa, &state.auth))
+    goto fail;
+
+  TRACE(D_PACKETS, "Packet from %I via %s authenticated successfully",
+        saddr, ifa->ifname);
+  return 0;
+
+fail:
+  LOG_PKT_AUTH("Packet from %I via %s failed authentication%s",
+               saddr, ifa->ifname,
+               ifa->cf->auth_permissive ? " but accepted in permissive mode" : "");
+
+  return !ifa->cf->auth_permissive;
+}
+
+/**
+ * babel_auth_add_tlvs - Add authentication-related TLVs to a packet
+ * @ifa: Interface holding the transmission buffer
+ * @tlv: Pointer to the place where any new TLVs should be added
+ * @max_len: Maximum length available for adding new TLVs
+ *
+ * This function adds any new TLVs required by the authentication mode to a
+ * packet before it is shipped out. For MAC authentication, this is the packet
+ * counter TLV that must be included in every packet.
+ */
+int
+babel_auth_add_tlvs(struct babel_iface *ifa, struct babel_tlv *tlv, int max_len)
+{
+  struct babel_proto *p = ifa->proto;
+  struct babel_tlv_pc *msg;
+  int len;
+
+  if (ifa->cf->auth_type == BABEL_AUTH_NONE)
+    return 0;
+
+  msg = (void *)tlv;
+  len = sizeof(*msg) + BABEL_AUTH_INDEX_LEN;
+  max_len += ifa->auth_tx_overhead;
+
+  if (len > max_len)
+  {
+    LOG_WARN("Insufficient space to add MAC seqno TLV on iface %s: %d < %d",
+             ifa->ifname, max_len, len);
+    return 0;
+  }
+
+  msg->type = BABEL_TLV_PC;
+  msg->length = len - sizeof(struct babel_tlv);
+  put_u32(&msg->pc, ifa->auth_pc++);
+  memcpy(msg->index, ifa->auth_index, BABEL_AUTH_INDEX_LEN);
+
+  /* Reset index on overflow to 0 */
+  if (!ifa->auth_pc)
+    babel_auth_reset_index(ifa);
+
+  return len;
+}
+
+/**
+ * babel_auth_sign - Sign an outgoing packet before transmission
+ * @ifa: Interface holding the transmission buffer
+ * @dest: Destination address of the packet
+ *
+ * This function adds authentication signature(s) to the packet trailer for each
+ * of the configured authentication keys on the interface.
+ */
+int
+babel_auth_sign(struct babel_iface *ifa, ip_addr dest)
+{
+  struct babel_proto *p = ifa->proto;
+  struct babel_mac_pseudohdr phdr;
+  struct babel_pkt_header *hdr;
+  struct password_item *pass;
+  int tot_len = 0, i = 0;
+  struct babel_tlv *tlv;
+  sock *sk = ifa->sk;
+  byte *pos, *end;
+  btime now_;
+  int len;
+
+  if (ifa->cf->auth_type == BABEL_AUTH_NONE)
+    return 0;
+
+  hdr = (void *) sk->tbuf;
+  len = get_u16(&hdr->length) + sizeof(struct babel_pkt_header);
+
+  pos = (byte *)hdr + len;
+  end = (byte *)hdr + ifa->tx_length + ifa->auth_tx_overhead;
+  tlv = (void *)pos;
+  now_ = current_real_time();
+
+  babel_mac_build_phdr(&phdr, sk->saddr, sk->fport, dest, sk->dport);
+
+  WALK_LIST(pass, *ifa->cf->passwords)
+  {
+    struct babel_tlv_mac *msg = (void *)tlv;
+    uint buf_len = (uint) (end - (byte *)msg - sizeof(*msg));
+
+    if (pass->genfrom > now_ || pass->gento < now_)
+      continue;
+
+    if (babel_mac_hash(pass, &phdr,
+                       (byte *)hdr, len,
+                       msg->mac, &buf_len))
+    {
+      LOG_WARN("Insufficient space for MAC signatures on iface %s dest %I",
+               ifa->ifname, dest);
+      break;
+    }
+
+    msg->type = BABEL_TLV_MAC;
+    msg->length = buf_len;
+
+    tlv = NEXT_TLV(tlv);
+    tot_len += buf_len + sizeof(*msg);
+    i++;
+  }
+
+  DBG("Added %d MAC signatures (%d bytes) on ifa %s for dest %I\n",
+      i, tot_len, ifa->ifname, dest);
+
+  return tot_len;
+}
+
+/**
+ * babel_auth_set_tx_overhead - Set interface TX overhead for authentication
+ * @ifa: Interface to configure
+ *
+ * This function sets the TX overhead for an interface based on its
+ * authentication configuration.
+ */
+void
+babel_auth_set_tx_overhead(struct babel_iface *ifa)
+{
+  if (ifa->cf->auth_type == BABEL_AUTH_NONE)
+  {
+    ifa->auth_tx_overhead = 0;
+    return;
+  }
+
+  ifa->auth_tx_overhead = (sizeof(struct babel_tlv_pc) +
+                           sizeof(struct babel_tlv_mac) * ifa->cf->mac_num_keys +
+                           ifa->cf->mac_total_len);
+  ifa->tx_length -= ifa->auth_tx_overhead;
+}