Index | Thread | Search

From:
YASUOKA Masahiko <yasuoka@openbsd.org>
Subject:
iked: RADIUS support
To:
tobhe@openbsd.org, tech@openbsd.org
Cc:
markus@openbsd.org
Date:
Thu, 25 Jan 2024 18:50:38 +0900

Download raw body.

Thread
Hello,

The diff adds RADIUS support for iked(8).

  ---
  ikev2 RAS passive esp \
    from 0.0.0.0/0 to 0.0.0.0  \
    local any peer any \
    srcid (FQDN) \
    eap radius \
    config address 192.168.0.0/24
    
  radius server 192.168.0.4 secret testing123
  # radius accounting server 192.168.0.4 secret testing123
  ---

We can ask EAP for a RADIUS server which supports EAP.  Unfortunetely
radiusd(8) has no config which terminates EAP yet, so freeradius,
Windows AD, or other is needed for test.

Also

 - Use RADIUS attriubutes for configurations
 - RADIUS accouting is also supported

comments? test? ok?

Index: sbin/iked/Makefile
===================================================================
RCS file: /cvs/src/sbin/iked/Makefile,v
diff -u -p -r1.22 Makefile
--- sbin/iked/Makefile	28 May 2021 18:01:39 -0000	1.22
+++ sbin/iked/Makefile	25 Jan 2024 09:30:49 -0000
@@ -4,15 +4,15 @@ PROG=		iked
 SRCS=		ca.c chap_ms.c config.c control.c crypto.c dh.c \
 		eap.c iked.c ikev2.c ikev2_msg.c ikev2_pld.c \
 		log.c ocsp.c pfkey.c policy.c print.c proc.c timer.c util.c \
-		imsg_util.c smult_curve25519_ref.c vroute.c
+		imsg_util.c radius.c smult_curve25519_ref.c vroute.c
 SRCS+=		eap_map.c ikev2_map.c
 SRCS+=		crypto_hash.c sntrup761.c
 SRCS+=		parse.y
 MAN=		iked.conf.5 iked.8
 #NOMAN=		yes
 
-LDADD=		-lutil -levent -lcrypto
-DPADD=		${LIBUTIL} ${LIBEVENT} ${LIBCRYPTO}
+LDADD=		-lutil -levent -lcrypto -lradius
+DPADD=		${LIBUTIL} ${LIBEVENT} ${LIBCRYPTO} ${LIBRADIUS}
 CFLAGS+=	-Wall -I${.CURDIR}
 CFLAGS+=	-Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+=	-Wmissing-declarations
Index: sbin/iked/config.c
===================================================================
RCS file: /cvs/src/sbin/iked/config.c,v
diff -u -p -r1.95 config.c
--- sbin/iked/config.c	17 Jan 2024 08:25:02 -0000	1.95
+++ sbin/iked/config.c	25 Jan 2024 09:30:49 -0000
@@ -123,6 +123,8 @@ config_free_sa(struct iked *env, struct 
 	sa_configure_iface(env, sa, 0);
 	sa_free_flows(env, &sa->sa_flows);
 
+	radius_acct_stop(env, sa);
+
 	if (sa->sa_addrpool) {
 		(void)RB_REMOVE(iked_addrpool, &env->sc_addrpool, sa);
 		free(sa->sa_addrpool);
@@ -187,6 +189,10 @@ config_free_sa(struct iked *env, struct 
 		ikestat_dec(env, ikes_sa_established_current);
 	ikestat_inc(env, ikes_sa_removed);
 
+	free(sa->sa_rad_addr);
+	free(sa->sa_rad_addr6);
+	radius_request_free(env, sa->sa_radreq);
+
 	free(sa);
 }
 
@@ -585,6 +591,35 @@ config_doreset(struct iked *env, unsigne
 		}
 	}
 
+	if (mode == RESET_ALL || mode == RESET_RADIUS) {
+		struct iked_radserver_req	*req;
+		struct iked_radserver		*rad, *radt;
+		struct iked_radcfgmap		*cfg, *cfgt;
+
+		TAILQ_FOREACH_SAFE(rad, &env->sc_radauthservers, rs_entry,
+		    radt) {
+			close(rad->rs_sock);
+			event_del(&rad->rs_ev);
+			TAILQ_REMOVE(&env->sc_radauthservers, rad, rs_entry);
+			while ((req = TAILQ_FIRST(&rad->rs_reqs)) != NULL)
+				radius_request_free(env, req);
+			freezero(rad, sizeof(*rad));
+		}
+		TAILQ_FOREACH_SAFE(rad, &env->sc_radacctservers, rs_entry,
+		    radt) {
+			close(rad->rs_sock);
+			event_del(&rad->rs_ev);
+			TAILQ_REMOVE(&env->sc_radacctservers, rad, rs_entry);
+			while ((req = TAILQ_FIRST(&rad->rs_reqs)) != NULL)
+				radius_request_free(env, req);
+			freezero(rad, sizeof(*rad));
+		}
+		TAILQ_FOREACH_SAFE(cfg, &env->sc_radcfgmaps, entry, cfgt) {
+			TAILQ_REMOVE(&env->sc_radcfgmaps, cfg, entry);
+			free(cfg);
+		}
+	}
+
 	return (0);
 }
 
@@ -1071,5 +1106,178 @@ config_getkey(struct iked *env, struct i
 	explicit_bzero(imsg->data, len);
 	ca_getkey(&env->sc_ps, &id, imsg->hdr.type);
 
+	return (0);
+}
+
+int
+config_setradauth(struct iked *env)
+{
+	proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CFG_RADAUTH,
+	    &env->sc_radauth, sizeof(env->sc_radauth));
+	return (0);
+}
+
+int
+config_getradauth(struct iked *env, struct imsg *imsg)
+{
+	if (IMSG_DATA_SIZE(imsg) < sizeof(struct iked_radopts))
+		fatalx("%s: invalid radauth message", __func__);
+
+	memcpy(&env->sc_radauth, imsg->data, sizeof(struct iked_radopts));
+
+	return (0);
+}
+
+int
+config_setradacct(struct iked *env)
+{
+	proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CFG_RADACCT,
+	    &env->sc_radacct, sizeof(env->sc_radacct));
+	return (0);
+}
+
+int
+config_getradacct(struct iked *env, struct imsg *imsg)
+{
+	if (IMSG_DATA_SIZE(imsg) < sizeof(struct iked_radopts))
+		fatalx("%s: invalid radacct message", __func__);
+
+	memcpy(&env->sc_radacct, imsg->data, sizeof(struct iked_radopts));
+
+	return (0);
+}
+
+int
+config_setradserver(struct iked *env, struct sockaddr *sa, socklen_t salen,
+    char *secret, int isaccounting)
+{
+	int			 sock = -1;
+	struct iovec		 iov[2];
+	struct iked_radserver	 server;
+
+	if (env->sc_opts & IKED_OPT_NOACTION)
+		return (0);
+	memset(&server, 0, sizeof(server));
+	memcpy(&server.rs_sockaddr, sa, salen);
+	server.rs_accounting = isaccounting;
+	if ((sock = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+		log_warn("%s: socket() failed", __func__);
+		goto error;
+	}
+	if (connect(sock, sa, salen) == -1) {
+		log_warn("%s: connect() failed", __func__);
+		goto error;
+	}
+	iov[0].iov_base = &server;
+	iov[0].iov_len = offsetof(struct iked_radserver, rs_secret[0]);
+	iov[1].iov_base = secret;
+	iov[1].iov_len = strlen(secret) + 1;
+
+	proc_composev_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADSERVER, -1,
+	    sock, iov, 2);
+
+	return (0);
+ error:
+	if (sock >= 0)
+		close(sock);
+	return (-1);
+}
+
+int
+config_getradserver(struct iked *env, struct imsg *imsg)
+{
+	size_t			 len;
+	struct iked_radserver	*server;
+
+	len = IMSG_DATA_SIZE(imsg);
+	if (len <= sizeof(*server))
+		fatalx("%s: invalid IMSG_CFG_RADSERVER message", __func__);
+
+	if ((server = calloc(1, len)) == NULL) {
+		log_warn("%s: calloc() failed", __func__);
+		return (-1);
+	}
+	memcpy(server, imsg->data, len);
+	explicit_bzero(imsg->data, len);
+	TAILQ_INIT(&server->rs_reqs);
+	server->rs_sock = imsg->fd;
+	server->rs_env = env;
+
+	if (!server->rs_accounting)
+		TAILQ_INSERT_TAIL(&env->sc_radauthservers, server, rs_entry);
+	else
+		TAILQ_INSERT_TAIL(&env->sc_radacctservers, server, rs_entry);
+	event_set(&server->rs_ev, server->rs_sock, EV_READ | EV_PERSIST,
+	    radius_on_event, server);
+	event_add(&server->rs_ev, NULL);
+
+	return (0);
+}
+
+int
+config_setradcfgmap(struct iked *env, int cfg_type, uint32_t vendor_id,
+    uint8_t attr_type)
+{
+	struct iked_radcfgmap cfgmap;
+
+	if (env->sc_opts & IKED_OPT_NOACTION)
+		return (0);
+	memset(&cfgmap, 0, sizeof(cfgmap));
+	cfgmap.cfg_type = cfg_type;
+	cfgmap.vendor_id = vendor_id;
+	cfgmap.attr_type = attr_type;
+
+	proc_compose_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADCFGMAP, -1,
+	    -1, &cfgmap, sizeof(cfgmap));
+
+	return (0);
+}
+
+int
+config_getradcfgmap(struct iked *env, struct imsg *imsg)
+{
+	int			 i;
+	size_t			 len;
+	struct iked_radcfgmap	*cfgmap, *cfgmap0;
+	struct iked_radcfgmaps	 cfgmaps = TAILQ_HEAD_INITIALIZER(cfgmaps);
+
+	len = IMSG_DATA_SIZE(imsg);
+	if (len < sizeof(*cfgmap))
+		fatalx("%s: invalid IMSG_CFG_RADCFGMAP message", __func__);
+
+	if (TAILQ_EMPTY(&env->sc_radcfgmaps)) {
+		/* no customized config map yet */
+		for (i = 0; radius_cfgmaps[i].cfg_type != 0; i++) {
+			if ((cfgmap = calloc(1, len)) == NULL) {
+				while ((cfgmap = TAILQ_FIRST(&cfgmaps))
+				    != NULL) {
+					TAILQ_REMOVE(&cfgmaps, cfgmap, entry);
+					free(cfgmap);
+				}
+				return (-1);
+			}
+			*cfgmap = radius_cfgmaps[i];
+			TAILQ_INSERT_TAIL(&cfgmaps, cfgmap, entry);
+		}
+		TAILQ_CONCAT(&env->sc_radcfgmaps, &cfgmaps, entry);
+	}
+
+	cfgmap0 = (struct iked_radcfgmap *)imsg->data;
+	TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry) {
+		if (cfgmap->vendor_id == cfgmap0->vendor_id &&
+		    cfgmap->attr_type == cfgmap0->attr_type) {
+			/* override existing config map */
+			cfgmap->cfg_type = cfgmap0->cfg_type;
+			break;
+		}
+	}
+	if (cfgmap == NULL) {
+		if ((cfgmap = calloc(1, len)) == NULL) {
+			log_warn("%s: calloc() failed", __func__);
+			return (-1);
+		}
+		memcpy(cfgmap, imsg->data, len);
+		TAILQ_INSERT_TAIL(&env->sc_radcfgmaps, cfgmap, entry);
+	}
 	return (0);
 }
Index: sbin/iked/eap.c
===================================================================
RCS file: /cvs/src/sbin/iked/eap.c,v
diff -u -p -r1.25 eap.c
--- sbin/iked/eap.c	18 Jul 2023 15:07:41 -0000	1.25
+++ sbin/iked/eap.c	25 Jan 2024 09:30:49 -0000
@@ -21,6 +21,7 @@
 #include <sys/uio.h>
 
 #include <netinet/in.h>
+#include <netinet/ip_ipsp.h>
 #include <arpa/inet.h>
 
 #include <stdlib.h>
Index: sbin/iked/eap.h
===================================================================
RCS file: /cvs/src/sbin/iked/eap.h,v
diff -u -p -r1.6 eap.h
--- sbin/iked/eap.h	16 Sep 2020 21:37:35 -0000	1.6
+++ sbin/iked/eap.h	25 Jan 2024 09:30:49 -0000
@@ -93,6 +93,7 @@ extern struct iked_constmap eap_code_map
 #define EAP_TYPE_PWD		52	/* RFC-harkins-emu-eap-pwd-12.txt */
 #define EAP_TYPE_EXPANDED_TYPE	254	/* RFC3748 */
 #define EAP_TYPE_EXPERIMENTAL	255	/* RFC3748 */
+#define EAP_TYPE_RADIUS		10001	/* internal use for EAP RADIUS */
 
 extern struct iked_constmap eap_type_map[];
 
Index: sbin/iked/iked.c
===================================================================
RCS file: /cvs/src/sbin/iked/iked.c,v
diff -u -p -r1.67 iked.c
--- sbin/iked/iked.c	15 Jan 2024 15:29:00 -0000	1.67
+++ sbin/iked/iked.c	25 Jan 2024 09:30:49 -0000
@@ -301,6 +301,8 @@ parent_configure(struct iked *env)
 	config_setstatic(env);
 	config_setcoupled(env, env->sc_decoupled ? 0 : 1);
 	config_setocsp(env);
+	config_setradauth(env);
+	config_setradacct(env);
 	/* Must be last */
 	config_setmode(env, env->sc_passive ? 1 : 0);
 
@@ -318,6 +320,7 @@ parent_reload(struct iked *env, int rese
 
 	if (reset == RESET_RELOAD) {
 		config_setreset(env, RESET_POLICY, PROC_IKEV2);
+		config_setreset(env, RESET_RADIUS, PROC_IKEV2);
 		if (config_setkeys(env) == -1)
 			fatalx("%s: failed to send keys", __func__);
 		config_setreset(env, RESET_CA, PROC_CERT);
Index: sbin/iked/iked.conf.5
===================================================================
RCS file: /cvs/src/sbin/iked/iked.conf.5,v
diff -u -p -r1.95 iked.conf.5
--- sbin/iked/iked.conf.5	22 Jul 2022 20:31:39 -0000	1.95
+++ sbin/iked/iked.conf.5	25 Jan 2024 09:30:49 -0000
@@ -648,11 +648,18 @@ for more information.
 .Bl -tag -width $domain -compact -offset indent
 .It Ic eap Ar type
 Use EAP to authenticate the initiator.
-The only supported EAP
-.Ar type
-is currently
-.Ar MSCHAP-V2 .
+Currently
+.Ar MSCHAP-V2
+or
+.Ar RADIUS
+is supported for EAP
+.Ar type .
 The responder will use RSA public key authentication.
+To use RADIUS for EAP,
+at least one RADIUS server should be configured.
+See
+.Sx RADIUS
+section for the RADIUS support.
 .It Ic ecdsa256
 Use ECDSA with a 256-bit elliptic curve key and SHA2-256 for authentication.
 .It Ic ecdsa384
@@ -775,6 +782,102 @@ for filtering and monitoring.
 The traffic will be blocked if the specified
 .Ar interface
 does not exist.
+.El
+.Sh RADIUS CONFIGURATION
+.Pp
+The configuration options for RADIUS are as follows:
+.Bl -tag -width xxxx
+.It Ic radius config Oo Ar af Oc Ar option Oo Ar vendor Oc Ar attr
+When the RADIUS authentication succeeded,
+.Xr iked 8
+uses the RADIUS attributes contained the response from the RADIUS server to construct IKEv2 configuration payloads (CP).
+This configuration option defines a mapping from a RADIUS attribute to an IKE CP with the following parameters:
+.Pp
+.Bl -tag -width "vendor attr" -compact
+.It Op Ar af
+Specify either
+.Ar inet
+or
+.Ar inet6
+for the address family of the IKE CP option.
+.It Ar option
+Specify an IKE CP option.
+Choose from
+.Sx AUTOMATIC KEYING POLICIES
+config options
+.Po
+.Ic address ,
+.Ic netmask ,
+.Ic name-server ,
+.Ic netbios-server ,
+.Ic dhcp-server ,
+and
+.Ic access-server
+.Pc ,
+or use
+.Ic none
+to disables the existing or default mapping.
+.It Ar attr
+For a standard RADIUS attribute,
+specify its Attribute-Type for
+.Ar attr .
+.It Ar vendor Ar attr
+For a vendor specific RADIUS attribute,
+specify its Vendor-ID for
+.Ar vendor
+and the Attribute-Type for
+.Ar attr .
+.El
+.Pp
+By default,
+.Xr iked 8
+uses the following attributes for the options:
+.Bl -column "inet6 netbios-server" "Vendor" "Type" "MS-Secondary-NBNS-Server" \
+-offset "XX"
+.It Em "Option" Ta Em "Vendor" Ta Em "Type" Ta Em "Attribute Name"
+.It Li "inet address" Ta "" Ta "8" Ta "Framed-IP-Address"
+.It Li "inet netmask" Ta "" Ta "9" Ta "Framed-IP-Netmask"
+.It Li "inet name-server" Ta "0x137" Ta "28" Ta "MS-Primary-DNS-Server"
+.It Li "inet name-server" Ta "0x137" Ta "29" Ta "MS-Secondary-DNS-Server"
+.It Li "inet netbios-server" Ta "0x137" Ta "30" Ta "MS-Primary-NBNS-Server"
+.It Li "inet netbios-server" Ta "0x137" Ta "31" Ta "MS-Secondary-NBNS-Server"
+.El
+.It Ic radius Oo Ic accounting Oc Ic server Ar address Oo port Ar number Oc \
+secret Ar secret
+Specify the RADIUS server's IP address and the shared secret with the server.
+For a RADIUS accouting server,
+specify optional
+.Ic accounting
+keyword.
+Optionally specify the port number,
+otherwise the default port number,
+1812 for authentication or
+1813 for accouting,
+is used as the default.
+.It Ic radius Oo Ic accounting Oc Ic max-tries Ar number
+Specify the maximum number of retransmissions for a server.
+.Xr iked 8
+will retransmit 2, 6, 14, 22, 30 seconds after the first transmission
+and subsequent retransmissions will occur every 8 seconds.
+If the number of retransmissions per server reaches this value,
+the current server is marked as failed,
+and the next server is used for subsequent requests.
+For RADIUS accouting requests,
+specify optional
+.Ic accouting
+keyword.
+The default value is 3.
+.It Ic radius Oo Ic accounting Oc Ic max-failovers Ar number
+If a positive number is specified,
+.Xr iked 8
+will failover to the next server when the current server is marked
+.Dq fail .
+This key and value specifies the maximum number of failovers.
+For RADIUS accouting requests,
+specify optional
+.Ic accouting
+keyword.
+The default value is 0.
 .El
 .Sh PACKET FILTERING
 IPsec traffic appears unencrypted on the
Index: sbin/iked/iked.h
===================================================================
RCS file: /cvs/src/sbin/iked/iked.h,v
diff -u -p -r1.226 iked.h
--- sbin/iked/iked.h	24 Jan 2024 10:09:07 -0000	1.226
+++ sbin/iked/iked.h	25 Jan 2024 09:30:50 -0000
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/tree.h>
 #include <sys/queue.h>
+#include <netinet/in.h>
 #include <arpa/inet.h>
 #include <limits.h>
 #include <imsg.h>
@@ -220,8 +221,8 @@ struct iked_static_id {
 
 struct iked_auth {
 	uint8_t		auth_method;
-	uint8_t		auth_eap;			/* optional EAP */
 	uint8_t		auth_length;			/* zero if EAP */
+	uint16_t	auth_eap;			/* optional EAP */
 	uint8_t		auth_data[IKED_PSK_SIZE];
 };
 
@@ -406,6 +407,15 @@ struct iked_ipcomp {
 	uint8_t				 ic_transform;	/* transform */
 };
 
+struct iked_sastats {
+	uint64_t			 sas_ipackets;
+	uint64_t			 sas_opackets;
+	uint64_t			 sas_ibytes;
+	uint64_t			 sas_obytes;
+	uint64_t			 sas_idrops;
+	uint64_t			 sas_odrops;
+};
+
 struct iked_sa {
 	struct iked_sahdr		 sa_hdr;
 	uint32_t			 sa_msgid;	/* Last request rcvd */
@@ -488,6 +498,7 @@ struct iked_sa {
 	struct iked_proposals		 sa_proposals;	/* SA proposals */
 	struct iked_childsas		 sa_childsas;	/* IPsec Child SAs */
 	struct iked_saflows		 sa_flows;	/* IPsec flows */
+	struct iked_sastats		 sa_stats;
 
 	struct iked_sa			*sa_nexti;	/* initiated IKE SA */
 	struct iked_sa			*sa_previ;	/* matching back pointer */
@@ -536,6 +547,11 @@ struct iked_sa {
 	RB_ENTRY(iked_sa)		 sa_addrpool6_entry;	/* pool entries */
 	time_t				 sa_last_recvd;
 #define IKED_IKE_SA_LAST_RECVD_TIMEOUT	 300		/* 5 minutes */
+	struct timespec			 sa_starttime;
+
+	struct iked_radserver_req	*sa_radreq;
+	struct iked_addr		*sa_rad_addr;	/* requested address */
+	struct iked_addr		*sa_rad_addr6;	/* requested address */
 };
 RB_HEAD(iked_sas, iked_sa);
 RB_HEAD(iked_dstid_sas, iked_sa);
@@ -651,6 +667,7 @@ struct iked_message {
 	uint8_t			 msg_transform;
 	uint16_t		 msg_flags;
 	struct eap_msg		 msg_eap;
+	struct ibuf		*msg_eapmsg;
 	size_t			 msg_del_spisize;
 	size_t			 msg_del_cnt;
 	struct ibuf		*msg_del_buf;
@@ -704,6 +721,55 @@ struct iked_user {
 };
 RB_HEAD(iked_users, iked_user);
 
+struct iked_radserver_req;
+
+struct iked_radserver {
+	int				 rs_sock;
+	int				 rs_accounting;
+	struct event			 rs_ev;
+	struct iked			*rs_env;
+	struct sockaddr_storage		 rs_sockaddr;
+	TAILQ_ENTRY(iked_radserver)	 rs_entry;
+	struct in_addr			 rs_nas_ipv4;
+	struct in6_addr			 rs_nas_ipv6;
+	unsigned int			 rs_reqseq;
+	TAILQ_HEAD(, iked_radserver_req) rs_reqs;
+	char				 rs_secret[];
+};
+TAILQ_HEAD(iked_radservers, iked_radserver);
+
+struct iked_radopts {
+	int				 max_tries;
+	int				 max_failovers;
+};
+
+struct iked_radcfgmap {
+	uint16_t			 cfg_type;
+	uint32_t			 vendor_id;
+	uint8_t				 attr_type;
+	TAILQ_ENTRY(iked_radcfgmap)	 entry;
+};
+TAILQ_HEAD(iked_radcfgmaps, iked_radcfgmap);
+
+extern const struct iked_radcfgmap radius_cfgmaps[];
+
+struct iked_radserver_req {
+	struct iked_radserver		*rr_server;
+	struct iked_sa			*rr_sa;
+	struct iked_timer		 rr_timer;
+	int				 rr_reqid;
+	int				 rr_accounting;
+	struct timespec			 rr_accttime;
+	void				*rr_reqpkt;
+	struct ibuf			*rr_state;
+	char				*rr_user;
+	int				 rr_ntry;
+	int				 rr_nfailover;
+	struct iked_cfg			 rr_cfg[IKED_CFG_MAX];
+	unsigned int			 rr_ncfg;
+	TAILQ_ENTRY(iked_radserver_req)	 rr_entry;
+};
+
 struct privsep_pipes {
 	int				*pp_pipes[PROC_MAX];
 };
@@ -811,6 +877,11 @@ struct iked {
 	struct iked_activesas		 sc_activesas;
 	struct iked_flows		 sc_activeflows;
 	struct iked_users		 sc_users;
+	struct iked_radopts		 sc_radauth;
+	struct iked_radopts		 sc_radacct;
+	struct iked_radservers		 sc_radauthservers;
+	struct iked_radservers		 sc_radacctservers;
+	struct iked_radcfgmaps		 sc_radcfgmaps;
 
 	struct iked_stats		 sc_stats;
 
@@ -941,6 +1012,15 @@ int	 config_setkeys(struct iked *);
 int	 config_getkey(struct iked *, struct imsg *);
 int	 config_setstatic(struct iked *);
 int	 config_getstatic(struct iked *, struct imsg *);
+int	 config_setradauth(struct iked *);
+int	 config_getradauth(struct iked *, struct imsg *);
+int	 config_setradacct(struct iked *);
+int	 config_getradacct(struct iked *, struct imsg *);
+int	 config_setradserver(struct iked *, struct sockaddr *, socklen_t,
+	    char *, int);
+int	 config_getradserver(struct iked *, struct imsg *);
+int	 config_setradcfgmap(struct iked *, int, uint32_t, uint8_t);
+int	 config_getradcfgmap(struct iked *, struct imsg *);
 
 /* policy.c */
 void	 policy_init(struct iked *);
@@ -1154,6 +1234,15 @@ int	 eap_mschap_challenge(struct iked *,
 	    uint8_t *, size_t);
 int	 eap_mschap_success(struct iked *, struct iked_sa *, int);
 int	 eap_challenge_request(struct iked *, struct iked_sa *, int);
+
+/* radius.c */
+int	 radius_request(struct iked *, struct iked_sa *, struct iked_message *);
+void	 radius_request_free(struct iked *, struct iked_radserver_req *);
+void	 radius_on_event(int, short, void *);
+void	 radius_acct_on(struct iked *);
+void	 radius_acct_off(struct iked *);
+void	 radius_acct_start(struct iked *, struct iked_sa *);
+void	 radius_acct_stop(struct iked *, struct iked_sa *);
 
 /* pfkey.c */
 int	 pfkey_couple(struct iked *, struct iked_sas *, int);
Index: sbin/iked/ikev2.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2.c,v
diff -u -p -r1.383 ikev2.c
--- sbin/iked/ikev2.c	24 Jan 2024 10:09:07 -0000	1.383
+++ sbin/iked/ikev2.c	25 Jan 2024 09:30:51 -0000
@@ -36,6 +36,7 @@
 #include <errno.h>
 #include <err.h>
 #include <event.h>
+#include <time.h>
 
 #include <openssl/sha.h>
 #include <openssl/evp.h>
@@ -294,6 +295,14 @@ ikev2_dispatch_parent(int fd, struct pri
 		return (config_getflow(env, imsg));
 	case IMSG_CFG_USER:
 		return (config_getuser(env, imsg));
+	case IMSG_CFG_RADAUTH:
+		return (config_getradauth(env, imsg));
+	case IMSG_CFG_RADACCT:
+		return (config_getradacct(env, imsg));
+	case IMSG_CFG_RADSERVER:
+		return (config_getradserver(env, imsg));
+	case IMSG_CFG_RADCFGMAP:
+		return (config_getradcfgmap(env, imsg));
 	case IMSG_COMPILE:
 		return (config_getcompile(env));
 	case IMSG_CTL_STATIC:
@@ -1781,6 +1790,7 @@ ikev2_init_done(struct iked *env, struct
 		ret = ikev2_childsa_enable(env, sa);
 	if (ret == 0) {
 		sa_state(env, sa, IKEV2_STATE_ESTABLISHED);
+		radius_acct_start(env, sa);
 		/* Delete exchange timeout. */
 		timer_del(env, &sa->sa_timer);
 		ikev2_enable_timer(env, sa);
@@ -2455,7 +2465,7 @@ ikev2_add_cp(struct iked *env, struct ik
 	struct ikev2_cp		*cp;
 	struct ikev2_cfg	*cfg;
 	struct iked_cfg		*ikecfg;
-	unsigned int		 i;
+	unsigned int		 i, rad_ncfg = 0;
 	uint32_t		 mask4;
 	size_t			 len;
 	struct sockaddr_in	*in4;
@@ -2478,8 +2488,15 @@ ikev2_add_cp(struct iked *env, struct ik
 		return (-1);
 	}
 
-	for (i = 0; i < pol->pol_ncfg; i++) {
-		ikecfg = &pol->pol_cfg[i];
+	if (sa->sa_radreq != NULL)
+		rad_ncfg = sa->sa_radreq->rr_ncfg;
+
+	for (i = 0; i < pol->pol_ncfg + rad_ncfg; i++) {
+		if (i < pol->pol_ncfg)
+			ikecfg = &pol->pol_cfg[i];
+		else
+			ikecfg = &sa->sa_radreq->rr_cfg[i - pol->pol_ncfg];
+
 		if (ikecfg->cfg_action != cp->cp_type)
 			continue;
 		/* only return one address in case of multiple pools */
@@ -3856,6 +3873,8 @@ ikev2_resp_ike_eap(struct iked *env, str
 	switch (sa->sa_policy->pol_auth.auth_eap) {
 	case EAP_TYPE_MSCHAP_V2:
 		return ikev2_resp_ike_eap_mschap(env, sa, msg);
+	case EAP_TYPE_RADIUS:
+		return radius_request(env, sa, msg);
 	}
 	return -1;
 }
@@ -4011,6 +4030,7 @@ ikev2_resp_ike_auth(struct iked *env, st
 		ret = ikev2_childsa_enable(env, sa);
 	if (ret == 0) {
 		sa_state(env, sa, IKEV2_STATE_ESTABLISHED);
+		radius_acct_start(env, sa);
 		/* Delete exchange timeout. */
 		timer_del(env, &sa->sa_timer);
 		ikev2_enable_timer(env, sa);
@@ -4745,10 +4765,10 @@ ikev2_ikesa_enable(struct iked *env, str
 		nsa->sa_tag = sa->sa_tag;
 		sa->sa_tag = NULL;
 	}
-	if (sa->sa_eapid) {
-		nsa->sa_eapid = sa->sa_eapid;
-		sa->sa_eapid = NULL;
-	}
+	/* sa_eapid needs to be set on both for radius accounting */
+	if (sa->sa_eapid)
+		nsa->sa_eapid = strdup(sa->sa_eapid);
+
 	log_info("%srekeyed as new IKESA %s (enc %s%s%s group %s prf %s)",
 	    SPI_SA(sa, NULL), print_spi(nsa->sa_hdr.sh_ispi, 8),
 	    print_xf(nsa->sa_encr->encr_id, cipher_keylength(nsa->sa_encr) -
@@ -4759,6 +4779,8 @@ ikev2_ikesa_enable(struct iked *env, str
 	    print_xf(nsa->sa_dhgroup->id, 0, groupxfs),
 	    print_xf(nsa->sa_prf->hash_id, hash_keylength(sa->sa_prf), prfxfs));
 	sa_state(env, nsa, IKEV2_STATE_ESTABLISHED);
+	clock_gettime(CLOCK_MONOTONIC, &nsa->sa_starttime);
+	radius_acct_start(env, nsa);
 	ikev2_enable_timer(env, nsa);
 
 	ikestat_inc(env, ikes_sa_rekeyed);
@@ -7028,6 +7050,7 @@ ikev2_cp_setaddr(struct iked *env, struc
 	const char		*errstr = NULL;
 	int			 ret, pass, passes;
 	size_t			 i;
+	struct sockaddr_in	*in4;
 
 	switch (family) {
 	case AF_INET:
@@ -7045,8 +7068,23 @@ ikev2_cp_setaddr(struct iked *env, struc
 		return (0);
 	/* default if no pool configured */
 	ret = 0;
+
+	/* handle the special addresses from RADIUS */
+	if (sa->sa_rad_addr != NULL) {
+		in4 = (struct sockaddr_in *)&sa->sa_rad_addr->addr;
+		/* 0xFFFFFFFF allows the user to select an address (RFC 2865) */
+		if (in4->sin_addr.s_addr == htonl(0xFFFFFFFF))
+			;/* this is  default behavior if the user selects */
+		/* 0xFFFFFFFE indicated the NAS should select (RFC 2865) */
+		else if (in4->sin_addr.s_addr == htonl(0xFFFFFFFE)) {
+			free(sa->sa_cp_addr);
+			sa->sa_cp_addr = NULL;
+		}
+	}
+
 	/* two passes if client requests from specific pool */
-	passes = (sa->sa_cp_addr != NULL || sa->sa_cp_addr6 != NULL) ? 2 : 1;
+	passes = (sa->sa_cp_addr != NULL || sa->sa_cp_addr6 != NULL ||
+	    sa->sa_rad_addr != NULL || sa->sa_rad_addr6 != NULL) ? 2 : 1;
 	for (pass = 0; pass < passes; pass++) {
 		/* loop over all address pool configs (addr_net) */
 		for (i = 0; i < pol->pol_ncfg; i++) {
@@ -7062,13 +7100,16 @@ ikev2_cp_setaddr(struct iked *env, struc
 					return (0);
 			}
 		}
-		if (sa->sa_cp_addr != NULL) {
+		if (family == AF_INET) {
 			free(sa->sa_cp_addr);
 			sa->sa_cp_addr = NULL;
-		}
-		if (sa->sa_cp_addr6 != NULL) {
+			free(sa->sa_rad_addr);
+			sa->sa_rad_addr = NULL;
+		} else {
 			free(sa->sa_cp_addr6);
 			sa->sa_cp_addr6 = NULL;
+			free(sa->sa_rad_addr6);
+			sa->sa_rad_addr6 = NULL;
 		}
 	}
 
@@ -7088,7 +7129,7 @@ ikev2_cp_setaddr_pool(struct iked *env, 
 	char			 idstr[IKED_ID_SIZE];
 	struct iked_addr	 addr;
 	uint32_t		 mask, host, lower, upper, start, nhost;
-	int			 requested = 0;
+	int			 requested = 0, rad_requested = 0;
 
 	/*
 	 * failure: pool configured, but not requested.
@@ -7165,8 +7206,14 @@ ikev2_cp_setaddr_pool(struct iked *env, 
 	case AF_INET:
 		cfg4 = (struct sockaddr_in *)&ikecfg->cfg.address.addr;
 		mask = prefixlen2mask(ikecfg->cfg.address.addr_mask);
-		if (sa->sa_cp_addr != NULL) {
-			memcpy(&addr, sa->sa_cp_addr, sizeof(addr));
+		if (sa->sa_cp_addr != NULL || sa->sa_rad_addr != NULL) {
+			if (sa->sa_rad_addr != NULL) {
+				rad_requested = 1;
+				memcpy(&addr, sa->sa_rad_addr, sizeof(addr));
+			} else {
+				requested = 1;
+				memcpy(&addr, sa->sa_cp_addr, sizeof(addr));
+			}
 			key.sa_addrpool = &addr;
 			in4 = (struct sockaddr_in *)&addr.addr;
 			if ((in4->sin_addr.s_addr & mask) !=
@@ -7179,10 +7226,16 @@ ikev2_cp_setaddr_pool(struct iked *env, 
 				*errstr = "requested addr in use";
 				return (-1);
 			}
-			sa->sa_addrpool = sa->sa_cp_addr;
-			sa->sa_cp_addr = NULL;
+			if (sa->sa_rad_addr != NULL) {
+				sa->sa_addrpool = sa->sa_rad_addr;
+				sa->sa_rad_addr = NULL;
+			} else {
+				sa->sa_addrpool = sa->sa_cp_addr;
+				sa->sa_cp_addr = NULL;
+			}
+			free(sa->sa_cp_addr);
+			free(sa->sa_rad_addr);
 			RB_INSERT(iked_addrpool, &env->sc_addrpool, sa);
-			requested = 1;
 			goto done;
 		}
 		in4 = (struct sockaddr_in *)&addr.addr;
@@ -7194,7 +7247,7 @@ ikev2_cp_setaddr_pool(struct iked *env, 
 	case AF_INET6:
 		cfg6 = (struct sockaddr_in6 *)&ikecfg->cfg.address.addr;
 		in6 = (struct sockaddr_in6 *)&addr.addr;
-		if (sa->sa_cp_addr6 != NULL) {
+		if (sa->sa_cp_addr6 != NULL || sa->sa_rad_addr6 != NULL) {
 			/* XXX not yet supported */
 		}
 		in6->sin6_family = AF_INET6;
@@ -7280,9 +7333,10 @@ ikev2_cp_setaddr_pool(struct iked *env, 
  done:
 	if (ikev2_print_id(IKESA_DSTID(sa), idstr, sizeof(idstr)) == -1)
 		bzero(idstr, sizeof(idstr));
-	log_info("%sassigned address %s to %s%s", SPI_SA(sa, NULL),
+	log_info("%sassigned address %s to %s%s%s", SPI_SA(sa, NULL),
 	    print_addr(&addr.addr),
-	    idstr, requested ? " (requested by peer)" : "");
+	    idstr, requested ? " (requested by peer)" : "",
+	    rad_requested? "(requested by RADIUS)" : "");
 	return (0);
 }
 
@@ -7627,6 +7681,8 @@ void
 ikev2_log_established(struct iked_sa *sa)
 {
 	char dstid[IKED_ID_SIZE], srcid[IKED_ID_SIZE];
+
+	clock_gettime(CLOCK_MONOTONIC, &sa->sa_starttime);
 
 	if (ikev2_print_id(IKESA_DSTID(sa), dstid, sizeof(dstid)) == -1)
 		bzero(dstid, sizeof(dstid));
Index: sbin/iked/ikev2_msg.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2_msg.c,v
diff -u -p -r1.100 ikev2_msg.c
--- sbin/iked/ikev2_msg.c	4 Aug 2023 19:06:25 -0000	1.100
+++ sbin/iked/ikev2_msg.c	25 Jan 2024 09:30:52 -0000
@@ -203,6 +203,7 @@ ikev2_msg_cleanup(struct iked *env, stru
 		ibuf_free(msg->msg_cookie);
 		ibuf_free(msg->msg_cookie2);
 		ibuf_free(msg->msg_del_buf);
+		ibuf_free(msg->msg_eapmsg);
 		free(msg->msg_eap.eam_user);
 		free(msg->msg_cp_addr);
 		free(msg->msg_cp_addr6);
@@ -219,6 +220,7 @@ ikev2_msg_cleanup(struct iked *env, stru
 		msg->msg_cookie = NULL;
 		msg->msg_cookie2 = NULL;
 		msg->msg_del_buf = NULL;
+		msg->msg_eapmsg = NULL;
 		msg->msg_eap.eam_user = NULL;
 		msg->msg_cp_addr = NULL;
 		msg->msg_cp_addr6 = NULL;
Index: sbin/iked/ikev2_pld.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2_pld.c,v
diff -u -p -r1.133 ikev2_pld.c
--- sbin/iked/ikev2_pld.c	2 Sep 2023 18:36:30 -0000	1.133
+++ sbin/iked/ikev2_pld.c	25 Jan 2024 09:30:52 -0000
@@ -2098,6 +2098,15 @@ ikev2_pld_eap(struct iked *env, struct i
 
 		if (eap_parse(env, sa, msg, eap, msg->msg_response) == -1)
 			return (-1);
+		if (msg->msg_parent->msg_eapmsg != NULL) {
+			log_info("%s: duplicate EAP in payload", __func__);
+			return (-1);
+		}
+		if ((msg->msg_parent->msg_eapmsg = ibuf_new(eap, len)) == NULL)
+		    {
+			log_debug("%s: failed to save eap", __func__);
+			return (-1);
+		}
 		msg->msg_parent->msg_eap.eam_found = 1;
 	}
 
Index: sbin/iked/parse.y
===================================================================
RCS file: /cvs/src/sbin/iked/parse.y,v
diff -u -p -r1.144 parse.y
--- sbin/iked/parse.y	11 Aug 2023 11:24:55 -0000	1.144
+++ sbin/iked/parse.y	25 Jan 2024 09:30:52 -0000
@@ -40,7 +40,9 @@
 #include <ifaddrs.h>
 #include <limits.h>
 #include <netdb.h>
+#include <radius.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -107,6 +109,8 @@ static char		*ocsp_url = NULL;
 static long		 ocsp_tolerate = 0;
 static long		 ocsp_maxage = -1;
 static int		 cert_partial_chain = 0;
+static struct iked_radopts
+			 radauth, radacct;
 
 struct iked_transform ikev2_default_ike_transforms[] = {
 	{ IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 256 },
@@ -394,6 +398,8 @@ static int		 expand_flows(struct iked_po
 			    struct ipsec_addr_wrap *);
 static struct ipsec_addr_wrap *
 			 expand_keyword(struct ipsec_addr_wrap *);
+struct iked_radserver *
+			 create_radserver(const char *, u_short, const char *);
 
 struct ipsec_transforms *ipsec_transforms;
 struct ipsec_filters *ipsec_filters;
@@ -407,6 +413,7 @@ typedef struct {
 		uint8_t			 ikemode;
 		uint8_t			 dir;
 		uint8_t			 satype;
+		uint8_t			 accouting;
 		char			*string;
 		uint16_t		 port;
 		struct ipsec_hosts	*hosts;
@@ -446,6 +453,7 @@ typedef struct {
 %token	TOLERATE MAXAGE DYNAMIC
 %token	CERTPARTIALCHAIN
 %token	REQUEST IFACE
+%token	RADIUS ACCOUNTING SERVER SECRET MAX_TRIES MAX_FAILOVERS
 %token	<v.string>		STRING
 %token	<v.number>		NUMBER
 %type	<v.string>		string
@@ -453,7 +461,7 @@ typedef struct {
 %type	<v.proto>		proto proto_list protoval
 %type	<v.hosts>		hosts hosts_list
 %type	<v.port>		port
-%type	<v.number>		portval af rdomain
+%type	<v.number>		portval af rdomain number
 %type	<v.peers>		peers
 %type	<v.anyhost>		anyhost
 %type	<v.host>		host host_spec
@@ -470,6 +478,7 @@ typedef struct {
 %type	<v.string>		name iface
 %type	<v.cfg>			cfg ikecfg ikecfgvals
 %type	<v.string>		transform_esn
+%type	<v.accouting>		accounting
 %%
 
 grammar		: /* empty */
@@ -478,6 +487,7 @@ grammar		: /* empty */
 		| grammar set '\n'
 		| grammar user '\n'
 		| grammar ikev2rule '\n'
+		| grammar radius '\n'
 		| grammar varset '\n'
 		| grammar otherrule skipline '\n'
 		| grammar error '\n'		{ file->errors++; }
@@ -1039,6 +1049,11 @@ ikeauth		: /* empty */			{
 			$$.auth_eap = 0;
 			explicit_bzero(&$2, sizeof($2));
 		}
+		| EAP RADIUS			{
+			$$.auth_method = IKEV2_AUTH_SIG_ANY;
+			$$.auth_eap = EAP_TYPE_RADIUS;
+			$$.auth_length = 0;
+		}
 		| EAP STRING			{
 			unsigned int i;
 
@@ -1046,7 +1061,11 @@ ikeauth		: /* empty */			{
 				if ($2[i] == '-')
 					$2[i] = '_';
 
-			if (strcasecmp("mschap_v2", $2) != 0) {
+			if (strcasecmp("mschap_v2", $2) == 0)
+				$$.auth_eap = EAP_TYPE_MSCHAP_V2;
+			else if (strcasecmp("radius", $2) == 0)
+				$$.auth_eap = EAP_TYPE_RADIUS;
+			else {
 				yyerror("unsupported EAP method: %s", $2);
 				free($2);
 				YYERROR;
@@ -1054,7 +1073,6 @@ ikeauth		: /* empty */			{
 			free($2);
 
 			$$.auth_method = IKEV2_AUTH_SIG_ANY;
-			$$.auth_eap = EAP_TYPE_MSCHAP_V2;
 			$$.auth_length = 0;
 		}
 		| STRING			{
@@ -1245,6 +1263,103 @@ string		: string STRING
 		| STRING
 		;
 
+radius		: RADIUS accounting SERVER STRING port SECRET STRING
+		{
+			int		 ret, gai_err;
+			struct addrinfo	 hints, *ai;
+			u_short		 port;
+
+			memset(&hints, 0, sizeof(hints));
+			hints.ai_family = PF_UNSPEC;
+			hints.ai_socktype = SOCK_DGRAM;
+			hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+			if ((gai_err = getaddrinfo($4, NULL, &hints, &ai))
+			    != 0) {
+				yyerror("could not parse the address: %s: %s",
+				    $4, gai_strerror(gai_err));
+				free($4);
+				explicit_bzero($7, strlen($7));
+				free($7);
+				YYERROR;
+			}
+			port = $5;
+			if (port == 0)
+				port = htons((!$2)? RADIUS_DEFAULT_PORT :
+				    RADIUS_ACCT_DEFAULT_PORT);
+			socket_af(ai->ai_addr, port);
+			if ((ret = config_setradserver(env, ai->ai_addr,
+			    ai->ai_addrlen, $7, $2)) != 0) {
+				yyerror("could not set radius server");
+				free($4);
+				explicit_bzero($7, strlen($7));
+				free($7);
+				YYERROR;
+			}
+			explicit_bzero($7, strlen($7));
+			freeaddrinfo(ai);
+			free($4);
+			free($7);
+		}
+		| RADIUS accounting MAX_TRIES NUMBER {
+			if ($4 <= 0) {
+				yyerror("max-tries must a positive value");
+				YYERROR;
+			}
+			if ($2)
+				radacct.max_tries = $4;
+			else
+				radauth.max_tries = $4;
+		}
+		| RADIUS accounting MAX_FAILOVERS NUMBER {
+			if ($4 < 0) {
+				yyerror("max-failovers must be 0 or a "
+				    "positive value");
+				YYERROR;
+			}
+			if ($2)
+				radacct.max_failovers = $4;
+			else
+				radauth.max_failovers = $4;
+		}
+		| RADIUS CONFIG af STRING NUMBER number {
+			const struct ipsec_xf	*xf;
+			int			 af, cfgtype;
+
+			af = $3;
+			if (af == AF_UNSPEC)
+				af = AF_INET;
+			if (strcmp($4, "none") == 0)
+				cfgtype = 0;
+			else {
+				if ((xf = parse_xf($4, af, cpxfs)) == NULL ||
+				    xf->id == IKEV2_CFG_INTERNAL_IP4_SUBNET ||
+				    xf->id == IKEV2_CFG_INTERNAL_IP6_SUBNET) {
+					yyerror("not a valid ikecfg option");
+					free($4);
+					YYERROR;
+				}
+				cfgtype = xf->id;
+			}
+			if ($6 != -1)
+				config_setradcfgmap(env, cfgtype, $5, $6);
+			else
+				config_setradcfgmap(env, cfgtype, 0, $5);
+			free($4);
+		}
+		;
+
+number		: { $$ = -1; }
+		| NUMBER { $$ = $1; }
+		;
+
+accounting	: {
+			$$ = 0;
+		}
+		| ACCOUNTING {
+			$$ = 1;
+		}
+		;
+
 varset		: STRING '=' string
 		{
 			char *s = $1;
@@ -1338,6 +1453,7 @@ lookup(char *s)
 	static const struct keywords keywords[] = {
 		{ "active",		ACTIVE },
 		{ "ah",			AH },
+		{ "accounting",		ACCOUNTING },
 		{ "any",		ANY },
 		{ "auth",		AUTHXF },
 		{ "bytes",		BYTES },
@@ -1371,6 +1487,8 @@ lookup(char *s)
 		{ "ipcomp",		IPCOMP },
 		{ "lifetime",		LIFETIME },
 		{ "local",		LOCAL },
+		{ "max-failovers",	MAX_FAILOVERS},
+		{ "max-tries",		MAX_TRIES },
 		{ "maxage",		MAXAGE },
 		{ "mobike",		MOBIKE },
 		{ "name",		NAME },
@@ -1388,9 +1506,12 @@ lookup(char *s)
 		{ "proto",		PROTO },
 		{ "psk",		PSK },
 		{ "quick",		QUICK },
+		{ "radius",		RADIUS },
 		{ "rdomain",		RDOMAIN },
 		{ "request",		REQUEST },
 		{ "sa",			SA },
+		{ "secret",		SECRET },
+		{ "server",		SERVER },
 		{ "set",		SET },
 		{ "skip",		SKIP },
 		{ "srcid",		SRCID },
@@ -1792,6 +1913,10 @@ parse_config(const char *filename, struc
 	dpd_interval = IKED_IKE_SA_ALIVE_TIMEOUT;
 	decouple = passive = 0;
 	ocsp_url = NULL;
+	radauth.max_tries = 3;
+	radauth.max_failovers = 0;
+	radacct.max_tries = 3;
+	radacct.max_failovers = 0;
 
 	if (env->sc_opts & IKED_OPT_PASSIVE)
 		passive = 1;
@@ -1812,6 +1937,8 @@ parse_config(const char *filename, struc
 	env->sc_ocsp_maxage = ocsp_maxage;
 	env->sc_cert_partial_chain = cert_partial_chain;
 	env->sc_vendorid = vendorid;
+	env->sc_radauth = radauth;
+	env->sc_radacct = radacct;
 
 	if (!rules)
 		log_warnx("%s: no valid configuration rules found",
Index: sbin/iked/pfkey.c
===================================================================
RCS file: /cvs/src/sbin/iked/pfkey.c,v
diff -u -p -r1.84 pfkey.c
--- sbin/iked/pfkey.c	14 Aug 2023 12:02:02 -0000	1.84
+++ sbin/iked/pfkey.c	25 Jan 2024 09:30:52 -0000
@@ -111,8 +111,11 @@ int	pfkey_write(struct iked *, struct sa
 	    uint8_t **, ssize_t *);
 int	pfkey_reply(int, uint8_t **, ssize_t *);
 void	pfkey_dispatch(int, short, void *);
-int	pfkey_sa_lookup(struct iked *, struct iked_childsa *, uint64_t *);
+int	pfkey_sa_lookup(struct iked *, struct iked_childsa *, uint64_t *,
+	    struct iked_sastats *);
 int	pfkey_sa_check_exists(struct iked *, struct iked_childsa *);
+int	pfkey_sa_sastats(struct iked *, struct iked_childsa *,
+	    struct iked_sastats *);
 
 struct sadb_ident *
 	pfkey_id2ident(struct iked_id *, unsigned int);
@@ -872,7 +875,8 @@ pfkey_sa(struct iked *env, uint8_t satyp
 }
 
 int
-pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
+pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used,
+    struct iked_sastats *stats)
 {
 	struct iked_policy	*pol = sa->csa_ikesa->sa_policy;
 	struct sadb_msg		*msg, smsg;
@@ -880,6 +884,7 @@ pfkey_sa_lookup(struct iked *env, struct
 	struct sadb_sa		 sadb;
 	struct sadb_x_rdomain	 sa_rdomain;
 	struct sadb_lifetime	*sa_life;
+	struct sadb_x_counter	*sa_counter;
 	struct sockaddr_storage	 ssrc, sdst;
 	struct iovec		 iov[IOV_CNT];
 	uint64_t		 pad = 0;
@@ -1012,6 +1017,20 @@ pfkey_sa_lookup(struct iked *env, struct
 		*last_used = sa_life->sadb_lifetime_usetime;
 		log_debug("%s: last_used %llu", __func__, *last_used);
 	}
+	if (stats) {
+		if ((sa_counter = pfkey_find_ext(data, n,
+		    SADB_X_EXT_COUNTER)) == NULL) {
+			/* has never been used */
+			ret = -1;
+			goto done;
+		}
+		stats->sas_ibytes = sa_counter->sadb_x_counter_ibytes;
+		stats->sas_obytes = sa_counter->sadb_x_counter_obytes;
+		stats->sas_ipackets = sa_counter->sadb_x_counter_ipackets;
+		stats->sas_opackets = sa_counter->sadb_x_counter_opackets;
+		stats->sas_idrops = sa_counter->sadb_x_counter_idrops;
+		stats->sas_odrops = sa_counter->sadb_x_counter_odrops;
+	}
 
 #undef PAD
 done:
@@ -1022,13 +1041,20 @@ done:
 int
 pfkey_sa_last_used(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
 {
-	return pfkey_sa_lookup(env, sa, last_used);
+	return pfkey_sa_lookup(env, sa, last_used, NULL);
 }
 
 int
 pfkey_sa_check_exists(struct iked *env, struct iked_childsa *sa)
 {
-	return pfkey_sa_lookup(env, sa, NULL);
+	return pfkey_sa_lookup(env, sa, NULL, NULL);
+}
+
+int
+pfkey_sa_sastats(struct iked *env, struct iked_childsa *sa,
+    struct iked_sastats *stats)
+{
+	return pfkey_sa_lookup(env, sa, NULL, stats);
 }
 
 int
@@ -1582,7 +1608,8 @@ pfkey_sa_update_addresses(struct iked *e
 int
 pfkey_sa_delete(struct iked *env, struct iked_childsa *sa)
 {
-	uint8_t		satype;
+	uint8_t			satype;
+	struct iked_sastats	sas;
 
 	if (!sa->csa_loaded || sa->csa_spi.spi == 0)
 		return (0);
@@ -1590,11 +1617,23 @@ pfkey_sa_delete(struct iked *env, struct
 	if (pfkey_map(pfkey_satype, sa->csa_saproto, &satype) == -1)
 		return (-1);
 
+	/* preserve the statistics */
+	memset(&sas, 0, sizeof(sas));
+	pfkey_sa_sastats(env, sa, &sas);
+
 	if (pfkey_sa(env, satype, SADB_DELETE, sa) == -1 &&
 	    pfkey_sa_check_exists(env, sa) == 0)
 		return (-1);
 
 	sa->csa_loaded = 0;
+
+	sa->csa_ikesa->sa_stats.sas_ipackets += sas.sas_ipackets;
+	sa->csa_ikesa->sa_stats.sas_opackets += sas.sas_opackets;
+	sa->csa_ikesa->sa_stats.sas_ibytes += sas.sas_ibytes;
+	sa->csa_ikesa->sa_stats.sas_obytes += sas.sas_obytes;
+	sa->csa_ikesa->sa_stats.sas_idrops += sas.sas_idrops;
+	sa->csa_ikesa->sa_stats.sas_odrops += sas.sas_odrops;
+
 	return (0);
 }
 
Index: sbin/iked/policy.c
===================================================================
RCS file: /cvs/src/sbin/iked/policy.c,v
diff -u -p -r1.97 policy.c
--- sbin/iked/policy.c	10 Nov 2023 08:03:02 -0000	1.97
+++ sbin/iked/policy.c	25 Jan 2024 09:30:52 -0000
@@ -60,6 +60,9 @@ policy_init(struct iked *env)
 {
 	TAILQ_INIT(&env->sc_policies);
 	TAILQ_INIT(&env->sc_ocsp);
+	TAILQ_INIT(&env->sc_radauthservers);
+	TAILQ_INIT(&env->sc_radacctservers);
+	TAILQ_INIT(&env->sc_radcfgmaps);
 	RB_INIT(&env->sc_users);
 	RB_INIT(&env->sc_sas);
 	RB_INIT(&env->sc_dstid_sas);
Index: sbin/iked/radius.c
===================================================================
RCS file: sbin/iked/radius.c
diff -N sbin/iked/radius.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sbin/iked/radius.c	25 Jan 2024 09:30:52 -0000
@@ -0,0 +1,741 @@
+/*	$OpenBSD$	*/
+
+/*
+ * Copyright (c) 2024 Internet Initiative Japan Inc.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <netinet/ip_ipsp.h>
+
+#include <endian.h>
+#include <event.h>
+#include <errno.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <radius.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+
+#include "iked.h"
+#include "eap.h"
+#include "ikev2.h"
+#include "types.h"
+
+void	 radius_reset_timeout(struct iked_sa *);
+void	 radius_request_send(struct iked *, void *);
+void	 radius_config(struct iked_radserver_req *, const RADIUS_PACKET *, int,
+	    uint32_t, uint8_t);
+void	 radius_acct_request(struct iked *, struct iked_sa *, uint8_t);
+
+const struct iked_radcfgmap radius_cfgmaps[] = {
+    { IKEV2_CFG_INTERNAL_IP4_ADDRESS, 0, RADIUS_TYPE_FRAMED_IP_ADDRESS },
+    { IKEV2_CFG_INTERNAL_IP4_NETMASK, 0, RADIUS_TYPE_FRAMED_IP_NETMASK },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+	RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+	RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+	RADIUS_VTYPE_MS_PRIMARY_NBNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+	RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER },
+    { 0 }
+};
+
+int
+radius_request(struct iked *env, struct iked_sa *sa, struct iked_message *msg)
+{
+	struct eap_message		*eap;
+	RADIUS_PACKET			*pkt;
+	size_t				 len;
+
+	eap = ibuf_data(msg->msg_eapmsg);
+	len = betoh16(eap->eap_length);
+	if (eap->eap_code != EAP_CODE_RESPONSE) {
+		log_debug("%s: eap_code is not response %u", __func__,
+		    (unsigned)eap->eap_code);
+		return -1;
+	}
+
+	if (eap->eap_type == EAP_TYPE_IDENTITY) {
+		if ((sa->sa_radreq = calloc(1,
+		    sizeof(struct iked_radserver_req))) == NULL) {
+			log_debug(
+			    "%s: calloc failed for iked_radserver_req: %s",
+			    __func__, strerror(errno));
+			return (-1);
+		}
+		timer_set(env, &sa->sa_radreq->rr_timer, radius_request_send,
+		    sa->sa_radreq);
+		sa->sa_radreq->rr_user = strdup(msg->msg_eap.eam_identity);
+	}
+
+	if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST))
+	    == NULL) {
+		log_debug("%s: radius_new_request_packet failed %s", __func__,
+		    strerror(errno));
+		return -1;
+	}
+
+	radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+	    sa->sa_radreq->rr_user);
+	if (sa->sa_radreq->rr_state != NULL)
+		radius_put_raw_attr(pkt, RADIUS_TYPE_STATE,
+		    ibuf_data(sa->sa_radreq->rr_state),
+		    ibuf_size(sa->sa_radreq->rr_state));
+
+	if (radius_put_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+	    (uint8_t *)eap, len) == -1) {
+		log_debug("%s: radius_put_raw_attr_cat failed %s", __func__,
+		    strerror(errno));
+		return -1;
+	}
+
+	/* save the request, it'll be needed for message authentication */
+	if (sa->sa_radreq->rr_reqpkt != NULL)
+		radius_delete_packet(sa->sa_radreq->rr_reqpkt);
+	sa->sa_radreq->rr_reqpkt = pkt;
+	sa->sa_radreq->rr_sa = sa;
+	sa->sa_radreq->rr_ntry = 0;
+
+	radius_request_send(env, sa->sa_radreq);
+
+	return 0;
+}
+
+void
+radius_request_free(struct iked *env, struct iked_radserver_req *req)
+{
+	if (req == NULL)
+		return;
+	timer_del(env, &req->rr_timer);
+	free(req->rr_user);
+	ibuf_free(req->rr_state);
+	if (req->rr_reqpkt)
+		radius_delete_packet(req->rr_reqpkt);
+	if (req->rr_sa)
+		req->rr_sa->sa_radreq = NULL;
+	if (req->rr_server)
+		TAILQ_REMOVE(&req->rr_server->rs_reqs, req, rr_entry);
+	free(req);
+}
+
+void
+radius_on_event(int fd, short ev, void *ctx)
+{
+	struct iked			*env;
+	struct iked_radserver		*server = ctx;
+	struct iked_radserver_req	*req;
+	const struct iked_radcfgmap	*cfgmap;
+	RADIUS_PACKET			*pkt;
+	int				 i, resid;
+	struct ibuf			*e;
+	const void			*attrval;
+	size_t				 attrlen;
+	uint8_t				 code;
+	u_char				 eapmsk[128];
+	/* RFC 3748 defines the MSK minimum size is 64 bytes */
+	size_t				 eapmsksiz = sizeof(eapmsk);
+
+	env = server->rs_env;
+	pkt = radius_recv(server->rs_sock, 0);
+	if (pkt == NULL) {
+		log_info("%s: receiving a RADIUS message failed: %s", __func__,
+		    strerror(errno));
+		return;
+	}
+	resid = radius_get_id(pkt);
+
+	TAILQ_FOREACH(req, &server->rs_reqs, rr_entry) {
+		if (req->rr_reqid == resid)
+			break;
+	}
+	if (req == NULL) {
+		log_debug("%s: received an unknown RADIUS message: id=%u",
+		    __func__, (unsigned)resid);
+		return;
+	}
+
+	radius_set_request_packet(pkt, req->rr_reqpkt);
+	if (radius_check_response_authenticator(pkt, server->rs_secret) != 0) {
+		log_info("%s: received an invalid RADIUS message: bad "
+		    "response authenticator", __func__);
+		return;
+	}
+	if (req->rr_accounting) {
+		/* accouting */
+		code = radius_get_code(pkt);
+		switch (code) {
+		case RADIUS_CODE_ACCOUNTING_RESPONSE: /* Expected */
+			break;
+		default:
+			log_info("%s: received an invalid RADIUS message: "
+			    "code %u", __func__, (unsigned)code);
+		}
+		timer_del(env, &req->rr_timer);
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+		req->rr_server = NULL;
+		free(req);
+		return;
+	}
+
+	/* authentication */
+	if (radius_check_message_authenticator(pkt, server->rs_secret) != 0) {
+		log_info("%s: received an invalid RADIUS message: bad "
+		    "message authenticator", __func__);
+		return;
+	}
+
+	timer_del(env, &req->rr_timer);
+	req->rr_ntry = 0;
+
+	if (req->rr_sa == NULL)
+		goto fail;
+
+	code = radius_get_code(pkt);
+	switch (code) {
+	case RADIUS_CODE_ACCESS_CHALLENGE:
+		if (radius_get_raw_attr_ptr(pkt, RADIUS_TYPE_STATE, &attrval,
+		    &attrlen) != 0) {
+			log_info("%s: received an invalid RADIUS message: no "
+			    "state attribute", __func__);
+			goto fail;
+		}
+		if ((req->rr_state != NULL &&
+		    ibuf_set(req->rr_state, 0, attrval, attrlen) != 0) ||
+		    (req->rr_state = ibuf_new(attrval, attrlen)) == NULL) {
+			log_info("%s: ibuf_new() failed: %s", __func__,
+			    strerror(errno));
+			goto fail;
+		}
+		break;
+	case RADIUS_CODE_ACCESS_ACCEPT:
+		/* Try to retribute the EAP MSK from the RADIUS response */
+		if (radius_get_eap_msk(pkt, eapmsk, &eapmsksiz,
+		    server->rs_secret) == 0) {
+			ibuf_free(req->rr_sa->sa_eapmsk);
+			if ((req->rr_sa->sa_eapmsk = ibuf_new(eapmsk,
+			    eapmsksiz)) == NULL) {
+				log_info("%s: ibuf_new() failed: %s", __func__,
+				    strerror(errno));
+				goto fail;
+			}
+		} else
+			log_debug("Could not retribute the EAP MSK from the "
+			    "RADIUS message");
+		free(req->rr_sa->sa_eapid);
+		req->rr_sa->sa_eapid = req->rr_user;
+		req->rr_user = NULL;
+		sa_state(env, req->rr_sa, IKEV2_STATE_AUTH_SUCCESS);
+
+		/* Map RADIUS attributes to cp */
+		if (TAILQ_EMPTY(&env->sc_radcfgmaps)) {
+			for (i = 0; radius_cfgmaps[i].cfg_type != 0; i++) {
+				cfgmap = &radius_cfgmaps[i];
+				radius_config(req, pkt, cfgmap->cfg_type,
+				    cfgmap->vendor_id, cfgmap->attr_type);
+			}
+		} else {
+			TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry)
+				radius_config(req, pkt, cfgmap->cfg_type,
+				    cfgmap->vendor_id, cfgmap->attr_type);
+		}
+
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+		req->rr_server = NULL;
+		break;
+	case RADIUS_CODE_ACCESS_REJECT:
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+		req->rr_server = NULL;
+		break;
+	default:
+		log_debug("%s: received an invalid RADIUS message: code %u",
+		    __func__, (unsigned)code);
+		break;
+	}
+
+	/* get the length first */
+	if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, NULL,
+	    &attrlen) != 0) {
+		log_info("%s: failed to retribute the EAP message", __func__);
+		goto fail;
+	}
+	/* allocate a buffer */
+	if ((e = ibuf_new(NULL, attrlen)) == NULL) {
+		log_info("%s: ibuf_new() failed: %s", __func__,
+		    strerror(errno));
+		goto fail;
+	}
+	/* copy the message to the buffer */
+	if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+	    ibuf_data(e), &attrlen) != 0) {
+		ibuf_free(e);
+		log_info("%s: failed to retribute the EAP message", __func__);
+		goto fail;
+	}
+	ikev2_send_ike_e(env, req->rr_sa, e, IKEV2_PAYLOAD_EAP,
+	    IKEV2_EXCHANGE_IKE_AUTH, 1);
+	return;
+ fail:
+	if (req->rr_server != NULL)
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+	req->rr_server = NULL;
+	if (req->rr_sa != NULL) {
+		ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+		sa_free(env, req->rr_sa);
+	}
+}
+
+void
+radius_request_send(struct iked *env, void *ctx)
+{
+	struct iked_radserver_req	*req = ctx, *req0;
+	struct iked_radserver		*server = req->rr_server;
+	const int			 timeouts[] = { 2, 4, 8 };
+	uint8_t				 seq;
+	int				 i, max_tries, max_failovers;
+	struct sockaddr_storage		 ss;
+	socklen_t			 sslen;
+	struct iked_radservers		*radservers;
+	struct timespec			 now;
+
+	if (!req->rr_accounting) {
+		max_tries = env->sc_radauth.max_tries;
+		max_failovers = env->sc_radauth.max_failovers;
+		radservers = &env->sc_radauthservers;
+	} else {
+		max_tries = env->sc_radacct.max_tries;
+		max_failovers = env->sc_radacct.max_failovers;
+		radservers = &env->sc_radacctservers;
+	}
+
+	if (req->rr_ntry > max_tries) {
+		req->rr_ntry = 0;
+		log_info("%s: RADIUS server %s failed", __func__,
+		    print_addr(&server->rs_sockaddr));
+ next_server:
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+		req->rr_server = NULL;
+		if (req->rr_nfailover >= max_failovers ||
+		    TAILQ_NEXT(server, rs_entry) == NULL) {
+			log_info("%s: No more RADIUS server", __func__);
+			goto fail;
+		} else if (req->rr_state != NULL) {
+			log_info("%s: Can't change RADIUS server: "
+			    "client has a state already", __func__);
+			goto fail;
+		} else {
+			TAILQ_REMOVE(radservers, server, rs_entry);
+			TAILQ_INSERT_TAIL(radservers, server, rs_entry);
+			server = TAILQ_FIRST(radservers);
+			log_info("%s: RADIUS server %s is active",
+			    __func__, print_addr(&server->rs_sockaddr));
+		}
+		req->rr_nfailover++;
+	}
+
+	if (req->rr_server != NULL &&
+	    req->rr_server != TAILQ_FIRST(radservers)) {
+		/* Current server is marked fail */
+		if (req->rr_state != NULL || req->rr_nfailover >= max_failovers)
+			goto fail; /* can't fail over */
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+		req->rr_server = NULL;
+		req->rr_nfailover++;
+	}
+
+	if (req->rr_server == NULL) {
+		/* Select a new server */
+		server = TAILQ_FIRST(radservers);
+		if (server == NULL) {
+			log_info("%s: No RADIUS server is configured",
+			    __func__);
+			goto fail;
+		}
+		TAILQ_INSERT_TAIL(&server->rs_reqs, req, rr_entry);
+		req->rr_server = server;
+
+		/* Prepare NAS-IP-Address */
+		if (server->rs_nas_ipv4.s_addr == INADDR_ANY &&
+		    IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6)) {
+			sslen = sizeof(ss);
+			if (getsockname(server->rs_sock, (struct sockaddr *)&ss,
+			    &sslen) == 0) {
+				if (ss.ss_family == AF_INET)
+					server->rs_nas_ipv4 =
+					    ((struct sockaddr_in *)&ss)
+					    ->sin_addr;
+				else
+					server->rs_nas_ipv6 =
+					    ((struct sockaddr_in6 *)&ss)
+					    ->sin6_addr;
+			}
+		}
+	}
+	if (req->rr_ntry == 0) {
+		/* decide the ID */
+		seq = ++server->rs_reqseq;
+		for (i = 0; i < UCHAR_MAX; i++) {
+			TAILQ_FOREACH(req0, &server->rs_reqs, rr_entry) {
+				if (req0->rr_reqid == seq)
+					break;
+			}
+			if (req0 == NULL)
+				break;
+			seq++;
+		}
+		if (i >= UCHAR_MAX) {
+			log_info("%s: RADIUS server %s failed.  Too many "
+			    "pending requests", __func__,
+			    print_addr(&server->rs_sockaddr));
+			if (TAILQ_NEXT(server, rs_entry) != NULL)
+				goto next_server;
+			goto fail;
+		}
+		req->rr_reqid = seq;
+		radius_set_id(req->rr_reqpkt, req->rr_reqid);
+	}
+
+	if (server->rs_nas_ipv4.s_addr != INADDR_ANY)
+		radius_put_ipv4_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
+		    server->rs_nas_ipv4);
+	else if (!IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6))
+		radius_put_ipv6_attr(req->rr_reqpkt,
+		    RADIUS_TYPE_NAS_IPV6_ADDRESS, &server->rs_nas_ipv6);
+
+	/* NAS Port Type = Virtual */
+	radius_put_uint32_attr(req->rr_reqpkt,
+	    RADIUS_TYPE_NAS_PORT_TYPE, RADIUS_NAS_PORT_TYPE_VIRTUAL);
+	/* Service Type =  Framed */
+	radius_put_uint32_attr(req->rr_reqpkt,
+	    RADIUS_TYPE_SERVICE_TYPE, RADIUS_SERVICE_TYPE_FRAMED);
+
+	if (req->rr_accounting) {
+		if (req->rr_ntry == 0 && req->rr_nfailover == 0)
+			radius_put_uint32_attr(req->rr_reqpkt,
+			    RADIUS_TYPE_ACCT_DELAY_TIME, 0);
+		else {
+			clock_gettime(CLOCK_MONOTONIC, &now);
+			timespecsub(&now, &req->rr_accttime, &now);
+			radius_put_uint32_attr(req->rr_reqpkt,
+			    RADIUS_TYPE_ACCT_DELAY_TIME, now.tv_sec);
+		}
+		radius_set_accounting_request_authenticator(req->rr_reqpkt,
+		    server->rs_secret);
+	} else {
+		radius_put_message_authenticator(req->rr_reqpkt,
+		    server->rs_secret);
+	}
+
+	if (radius_send(server->rs_sock, req->rr_reqpkt, 0) < 0)
+		log_info("%s: sending a RADIUS message failed: %s", __func__,
+		    strerror(errno));
+
+	if (req->rr_ntry >= (int)nitems(timeouts))
+		timer_add(env, &req->rr_timer, timeouts[nitems(timeouts) - 1]);
+	else
+		timer_add(env, &req->rr_timer, timeouts[req->rr_ntry]);
+	req->rr_ntry++;
+	return;
+ fail:
+	if (req->rr_server != NULL)
+		TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+	req->rr_server = NULL;
+	if (req->rr_sa != NULL) {
+		ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+		sa_free(env, req->rr_sa);
+	}
+}
+
+void
+radius_config(struct iked_radserver_req *req, const RADIUS_PACKET *pkt,
+    int cfg_type, uint32_t vendor_id, uint8_t attr_type)
+{
+	unsigned int		 i;
+	struct iked_sa		*sa = req->rr_sa;
+	struct in_addr		 ia4;
+	struct in6_addr		 ia6;
+	struct sockaddr_in	*sin4;
+	struct sockaddr_in6	*sin6;
+	struct iked_addr	*addr;
+	struct iked_cfg		*ikecfg;
+
+	for (i = 0; i < sa->sa_policy->pol_ncfg; i++) {
+		ikecfg = &sa->sa_policy->pol_cfg[i];
+		if (ikecfg->cfg_type == cfg_type &&
+		    ikecfg->cfg_type != IKEV2_CFG_INTERNAL_IP4_ADDRESS)
+			return;	/* use config rather than radius */
+	}
+	switch (cfg_type) {
+	case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+	case IKEV2_CFG_INTERNAL_IP4_NETMASK:
+	case IKEV2_CFG_INTERNAL_IP4_DNS:
+	case IKEV2_CFG_INTERNAL_IP4_NBNS:
+	case IKEV2_CFG_INTERNAL_IP4_DHCP:
+	case IKEV2_CFG_INTERNAL_IP4_SERVER:
+		if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+			radius_get_ipv4_attr(pkt, attr_type, &ia4);
+		else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+		    attr_type))
+			radius_get_vs_ipv4_attr(pkt, vendor_id, attr_type,
+			    &ia4);
+		else
+			break; /* no attribute contained */
+
+		if (cfg_type == IKEV2_CFG_INTERNAL_IP4_NETMASK) {
+			/*
+			 * This assumes IKEV2_CFG_INTERNAL_IP4_ADDRESS is
+			 * called before IKEV2_CFG_INTERNAL_IP4_NETMASK
+			 */
+			if (sa->sa_rad_addr == NULL) {
+				/*
+				 * RFC 7296, IKEV2_CFG_INTERNAL_IP4_NETMASK
+				 * must be used with
+				 * IKEV2_CFG_INTERNAL_IP4_ADDRESS
+				 */
+				break;
+			}
+			if (ia4.s_addr == 0) {
+				log_debug("%s: netmask is wrong", __func__);
+				break;
+			}
+			if (ia4.s_addr == htonl(0))
+				sa->sa_rad_addr->addr_mask = 0;
+			else
+				sa->sa_rad_addr->addr_mask =
+				    33 - ffs(ntohl(ia4.s_addr));
+			if (sa->sa_rad_addr->addr_mask < 32)
+				sa->sa_rad_addr->addr_net = 1;
+		}
+		if (cfg_type == IKEV2_CFG_INTERNAL_IP4_ADDRESS) {
+			if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+				log_warn("%s: calloc", __func__);
+				return;
+			}
+			sa->sa_rad_addr = addr;
+		} else {
+			req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+			req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+			addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+			req->rr_ncfg++;
+		}
+		addr->addr_af = AF_INET;
+		sin4 = (struct sockaddr_in *)&addr->addr;
+		sin4->sin_family = AF_INET;
+		sin4->sin_len = sizeof(struct sockaddr_in);
+		sin4->sin_addr = ia4;
+		break;
+	case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+	case IKEV2_CFG_INTERNAL_IP6_DNS:
+	case IKEV2_CFG_INTERNAL_IP6_NBNS:
+	case IKEV2_CFG_INTERNAL_IP6_DHCP:
+	case IKEV2_CFG_INTERNAL_IP6_SERVER:
+		if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+			radius_get_ipv6_attr(pkt, attr_type, &ia6);
+		else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+		    attr_type))
+			radius_get_vs_ipv6_attr(pkt, vendor_id, attr_type,
+			    &ia6);
+		else
+			break; /* no attribute contained */
+
+		if (cfg_type == IKEV2_CFG_INTERNAL_IP6_ADDRESS) {
+			if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+				log_warn("%s: calloc", __func__);
+				return;
+			}
+			sa->sa_rad_addr = addr;
+		} else {
+			req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+			req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+			addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+			req->rr_ncfg++;
+		}
+		addr->addr_af = AF_INET;
+		sin6 = (struct sockaddr_in6 *)&addr->addr;
+		sin6->sin6_family = AF_INET6;
+		sin6->sin6_len = sizeof(struct sockaddr_in6);
+		sin6->sin6_addr = ia6;
+		break;
+	}
+	return;
+}
+
+void
+radius_acct_on(struct iked *env)
+{
+	radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_ON);
+}
+
+void
+radius_acct_off(struct iked *env)
+{
+	radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_OFF);
+}
+
+void
+radius_acct_start(struct iked *env, struct iked_sa *sa)
+{
+	radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_START);
+}
+
+void
+radius_acct_stop(struct iked *env, struct iked_sa *sa)
+{
+	radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_STOP);
+}
+
+void
+radius_acct_request(struct iked *env, struct iked_sa *sa, uint8_t stype)
+{
+	struct iked_radserver_req	*req;
+	RADIUS_PACKET			*pkt;
+	struct iked_addr		*addr4 = NULL;
+	struct iked_addr		*addr6 = NULL;
+	struct in_addr			 mask4;
+	char				 sid[16 + 1];
+	struct timespec			 now;
+	int				 cause;
+
+	if (TAILQ_EMPTY(&env->sc_radacctservers))
+		return;
+	/*
+	 * In RFC2866 5.6, "Users who are delivered service without
+	 * being authenticated SHOULD NOT generate Accounting records
+	 */
+	if (sa != NULL && sa->sa_eapid == NULL)
+		return;
+
+	if ((req = calloc(1, sizeof(struct iked_radserver_req))) == NULL) {
+		log_debug("%s: calloc faile for iked_radserver_req: %s",
+		    __func__, strerror(errno));
+		return;
+	}
+	req->rr_accounting = 1;
+	clock_gettime(CLOCK_MONOTONIC, &now);
+	req->rr_accttime = now;
+	timer_set(env, &req->rr_timer, radius_request_send, req);
+
+	if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCOUNTING_REQUEST))
+	    == NULL) {
+		log_debug("%s: radius_new_request_packet failed %s", __func__,
+		    strerror(errno));
+		return;
+	}
+
+	/* RFC 2866  5.1. Acct-Status-Type */
+	radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE, stype);
+
+	if (sa == NULL) {
+		/* ASSERT(stype == RADIUS_ACCT_STATUS_TYPE_ACCT_ON ||
+		    stype == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) */
+		req->rr_reqpkt = pkt;
+		req->rr_ntry = 0;
+		radius_request_send(env, req);
+		return;
+	}
+
+	radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME, sa->sa_eapid);
+
+	/* RFC 2866  5.5. Acct-Session-Id */
+	snprintf(sid, sizeof(sid), "%16llx",
+	    (unsigned long long)sa->sa_hdr.sh_ispi);
+	radius_put_string_attr(pkt, RADIUS_TYPE_ACCT_SESSION_ID, sid);
+
+	/* Accouting Request must have Framed-IP-Address */
+	addr4 = sa->sa_addrpool;
+	if (addr4 != NULL) {
+		radius_put_ipv4_attr(pkt, RADIUS_TYPE_FRAMED_IP_ADDRESS,
+		    ((struct sockaddr_in *)&addr4->addr)->sin_addr);
+		if (addr4->addr_mask != 0) {
+			mask4.s_addr = htonl(
+			    0xFFFFFFFFUL << (32 - addr4->addr_mask));
+			radius_put_ipv4_attr(pkt,
+			    RADIUS_TYPE_FRAMED_IP_NETMASK, mask4);
+		}
+	}
+#ifndef RADIUS_TYPE_FRAMED_IPV6_ADDRESS
+#define RADIUS_TYPE_FRAMED_IPV6_ADDRESS 168
+#endif
+	addr6 = sa->sa_addrpool6;
+	if (addr6 != NULL)
+		radius_put_ipv6_attr(pkt, RADIUS_TYPE_FRAMED_IPV6_ADDRESS,
+		    &((struct sockaddr_in6 *)&addr6->addr)->sin6_addr);
+
+
+	/* RFC2866 5.6 Acct-Authentic */
+	radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_AUTHENTIC,
+	    (sa->sa_radreq != NULL)? RADIUS_ACCT_AUTHENTIC_RADIUS :
+	    RADIUS_ACCT_AUTHENTIC_LOCAL);
+
+	switch (stype) {
+	case RADIUS_ACCT_STATUS_TYPE_START:
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE,
+		    RADIUS_ACCT_STATUS_TYPE_START);
+		break;
+	case RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE:
+	case RADIUS_ACCT_STATUS_TYPE_STOP:
+		/* RFC 2866 5.7.  Acct-Session-Time */
+		timespecsub(&now, &sa->sa_starttime, &now);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_SESSION_TIME,
+		    now.tv_sec);
+		/* RFC 2866 5.10 Acct-Terminate-Cause */
+		cause = RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL;
+		if (sa->sa_reason) {
+			if (strcmp(sa->sa_reason, "received delete") == 0) {
+				cause = RADIUS_TERMNATE_CAUSE_USER_REQUEST;
+			} else if (strcmp(sa->sa_reason, "SA rekeyed") == 0) {
+				cause = RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT;
+			} else if (strncmp(sa->sa_reason, "retransmit",
+			    strlen("retransmit")) == 0) {
+				cause = RADIUS_TERMNATE_CAUSE_LOST_SERVICE;
+			}
+		}
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE,
+		    cause);
+		/* I/O statistsics {Input,Output}-{Packets,Octets,Gigawords} */
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_PACKETS,
+		    sa->sa_stats.sas_ipackets);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS,
+		    sa->sa_stats.sas_opackets);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_OCTETS,
+		    sa->sa_stats.sas_ibytes & 0xffffffffUL);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_OCTETS,
+		    sa->sa_stats.sas_obytes & 0xffffffffUL);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_GIGAWORDS,
+		    sa->sa_stats.sas_ibytes >> 32);
+		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS,
+		    sa->sa_stats.sas_obytes >> 32);
+		/* Identifier */
+		radius_put_string_attr(pkt, RADIUS_TYPE_NAS_IDENTIFIER,
+		    "OpenIKED");
+		radius_put_string_attr(pkt, RADIUS_TYPE_CALLED_STATION_ID,
+		    print_addr((struct sockaddr *)&sa->sa_local.addr));
+		radius_put_string_attr(pkt, RADIUS_TYPE_CALLING_STATION_ID,
+		    print_addr((struct sockaddr *)&sa->sa_peer.addr));
+		break;
+	}
+	req->rr_reqpkt = pkt;
+	req->rr_ntry = 0;
+	radius_request_send(env, req);
+}
Index: sbin/iked/types.h
===================================================================
RCS file: /cvs/src/sbin/iked/types.h,v
diff -u -p -r1.53 types.h
--- sbin/iked/types.h	15 Jan 2024 15:29:00 -0000	1.53
+++ sbin/iked/types.h	25 Jan 2024 09:30:52 -0000
@@ -112,6 +112,10 @@ enum imsg_type {
 	IMSG_CFG_POLICY,
 	IMSG_CFG_FLOW,
 	IMSG_CFG_USER,
+	IMSG_CFG_RADAUTH,
+	IMSG_CFG_RADACCT,
+	IMSG_CFG_RADSERVER,
+	IMSG_CFG_RADCFGMAP,
 	IMSG_CERTREQ,
 	IMSG_CERT,
 	IMSG_CERTVALID,
@@ -149,6 +153,7 @@ enum flushmode {
 	RESET_POLICY,
 	RESET_SA,
 	RESET_USER,
+	RESET_RADIUS,
 };
 
 #ifndef nitems