Index | Thread | Search

From:
Tobias Heider <tobias.heider@stusta.de>
Subject:
Re: iked: RADIUS support
To:
YASUOKA Masahiko <yasuoka@openbsd.org>
Cc:
tech@openbsd.org, markus@openbsd.org, uwe@werler.is, bilias@edu.physics.uoc.gr, stu@spacehopper.org
Date:
Sat, 13 Jul 2024 14:16:42 +0200

Download raw body.

Thread
On Fri, Jul 12, 2024 at 05:42:43PM +0200, YASUOKA Masahiko wrote:
> Hello,
> 
> On Mon, 26 Feb 2024 22:48:15 +0900 (JST)
> YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> > On Mon, 29 Jan 2024 09:43:55 +0900 (JST)
> > YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> >> On Thu, 25 Jan 2024 18:50:38 +0900 (JST)
> >> YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> >>> 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?
> >> 
> >> Let me update the diff.  Now I think it works with EAP methods other
> >> than MSCHAP-V2.
> >> 
> >> - feedbacks from markus
> >>   - support MSK which legnth != 16
> >>   - give "iked_" for the functions in radiusd
> >> - pass EAP messages which type isn't support eap.c
> > 
> > Let me share the latest updated diff.  There is no big difference.
> > The followings are about the changes from the previous diff.
> > 
> > - Fix wording in comments.  Found by stu.
> > - Check vendor-id and attribute-type in the config more strictly and
> >   accept hex.
> > - Use imsg_get_fd().
> > - Use radius_get_eap_msk() in libradius (it's fixed now).
> > - Use RADIUS_TYPE_FRAMED_IPV6_ADDRESS in <radius.h> (it's defined now).
> > - Include NAS-Identifier in all RADIUS requests.
> > - Send an Accounting-On message when RADIUS accouting is configured
> >   for the first time.
> 
> Let me update the diff.  Changes from the previous.
> 
> - Add Dynamic Authorization Extensions (DAE) for RADIUS server
>   feature.  (radiusd_ipcp(8) recently support DAE)
> - When EAP is used without Identifier, overwrite eapid by
>   authenticated username.  This is needed for accouting.
> - Rearrange how to add the RADIUS attributes.
> 
> The diff is getting bigger.  How about committing the diff and
> continue to work on the tree?

I think you are right. let's get it in and continue working on it in-tree.

ok tobhe@

> 
> 
> Index: sbin/iked/Makefile
> ===================================================================
> RCS file: /cvs/src/sbin/iked/Makefile,v
> diff -u -p -u -p -r1.22 Makefile
> --- sbin/iked/Makefile	28 May 2021 18:01:39 -0000	1.22
> +++ sbin/iked/Makefile	12 Jul 2024 15:22:40 -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 -u -p -r1.97 config.c
> --- sbin/iked/config.c	15 Feb 2024 19:11:00 -0000	1.97
> +++ sbin/iked/config.c	12 Jul 2024 15:22:41 -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);
>  
> +	iked_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);
> +	iked_radius_request_free(env, sa->sa_radreq);
> +
>  	free(sa);
>  }
>  
> @@ -591,6 +597,48 @@ 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;
> +		struct iked_raddae		*dae, *daet;
> +		struct iked_radclient		*client, *clientt;
> +
> +		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)
> +				iked_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)
> +				iked_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);
> +		}
> +		TAILQ_FOREACH_SAFE(dae, &env->sc_raddaes, rd_entry, daet) {
> +			close(dae->rd_sock);
> +			event_del(&dae->rd_ev);
> +			TAILQ_REMOVE(&env->sc_raddaes, dae, rd_entry);
> +			free(dae);
> +		}
> +		TAILQ_FOREACH_SAFE(client, &env->sc_raddaeclients, rc_entry,
> +		    clientt) {
> +			TAILQ_REMOVE(&env->sc_raddaeclients, client, rc_entry);
> +			free(client);
> +		}
> +	}
> +
>  	return (0);
>  }
>  
> @@ -1089,6 +1137,285 @@ 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_get_fd(imsg);
> +	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,
> +	    iked_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);
> +}
> +
> +int
> +config_setraddae(struct iked *env, struct sockaddr *sa, socklen_t salen)
> +{
> +	int			 sock, on;
> +	struct iked_raddae	 dae;
> +
> +	if (env->sc_opts & IKED_OPT_NOACTION)
> +		return (0);
> +	memset(&dae, 0, sizeof(dae));
> +	memcpy(&dae.rd_sockaddr, sa, salen);
> +	if ((sock = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
> +		log_warn("%s: socket() failed", __func__);
> +		goto error;
> +	}
> +	on = 1;
> +	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
> +		log_warn("%s: setsockopt(,,SO_REUSEADDR) failed", __func__);
> +	/* REUSEPORT is needed because the old sockets may not be closed yet */
> +	on = 1;
> +	if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) == -1)
> +		log_warn("%s: setsockopt(,,SO_REUSEPORT) failed", __func__);
> +	if (bind(sock, sa, salen) == -1) {
> +		log_warn("%s: bind() failed", __func__);
> +		goto error;
> +	}
> +
> +	proc_compose_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADDAE, -1,
> +	    sock, &dae, sizeof(dae));
> +
> +	return (0);
> + error:
> +	if (sock >= 0)
> +		close(sock);
> +	return (-1);
> +}
> +
> +int
> +config_getraddae(struct iked *env, struct imsg *imsg)
> +{
> +	struct iked_raddae	*dae;
> +
> +	if (IMSG_DATA_SIZE(imsg) < sizeof(*dae))
> +		fatalx("%s: invalid IMSG_CFG_RADDAE message", __func__);
> +
> +	if ((dae = calloc(1, sizeof(*dae))) == NULL) {
> +		log_warn("%s: calloc() failed", __func__);
> +		return (-1);
> +	}
> +	memcpy(dae, imsg->data, sizeof(*dae));
> +	dae->rd_sock = imsg_get_fd(imsg);
> +	dae->rd_env = env;
> +
> +	event_set(&dae->rd_ev, dae->rd_sock, EV_READ | EV_PERSIST,
> +	    iked_radius_dae_on_event, dae);
> +	event_add(&dae->rd_ev, NULL);
> +
> +	TAILQ_INSERT_TAIL(&env->sc_raddaes, dae, rd_entry);
> +
> +	return (0);
> +}
> +
> +int
> +config_setradclient(struct iked *env, struct sockaddr *sa, socklen_t salen,
> +    char *secret)
> +{
> +	struct iovec		 iov[2];
> +	struct iked_radclient	 client;
> +
> +	if (salen > sizeof(client.rc_sockaddr))
> +		fatal("%s: invalid salen", __func__);
> +
> +	memcpy(&client.rc_sockaddr, sa, salen);
> +
> +	iov[0].iov_base = &client;
> +	iov[0].iov_len = offsetof(struct iked_radclient, rc_secret[0]);
> +	iov[1].iov_base = secret;
> +	iov[1].iov_len = strlen(secret);
> +
> +	proc_composev_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADDAECLIENT,
> +	    -1, -1, iov, 2);
> +
> +	return (0);
> +}
> +
> +int
> +config_getradclient(struct iked *env, struct imsg *imsg)
> +{
> +	struct iked_radclient	*client;
> +	u_int			 len;
> +
> +	len = IMSG_DATA_SIZE(imsg);
> +
> +	if (len < sizeof(*client))
> +		fatalx("%s: invalid IMSG_CFG_RADDAE message", __func__);
> +
> +	if ((client = calloc(1, len + 1)) == NULL) {
> +		log_warn("%s: calloc() failed", __func__);
> +		return (-1);
> +	}
> +	memcpy(client, imsg->data, len);
> +
> +	TAILQ_INSERT_TAIL(&env->sc_raddaeclients, client, rc_entry);
>  
>  	return (0);
>  }
> Index: sbin/iked/eap.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/eap.c,v
> diff -u -p -u -p -r1.26 eap.c
> --- sbin/iked/eap.c	24 Mar 2024 00:05:01 -0000	1.26
> +++ sbin/iked/eap.c	12 Jul 2024 15:22:41 -0000
> @@ -583,9 +583,12 @@ eap_parse(struct iked *env, const struct
>  
>  		return (eap_mschap(env, sa, msg, eap));
>  	default:
> -		log_debug("%s: unsupported EAP type %s", __func__,
> -		    print_map(eap->eap_type, eap_type_map));
> -		return (-1);
> +		if (sa->sa_policy->pol_auth.auth_eap != EAP_TYPE_RADIUS) {
> +			log_debug("%s: unsupported EAP type %s", __func__,
> +			    print_map(eap->eap_type, eap_type_map));
> +			return (-1);
> +		} /* else, when RADIUS, pass it to the client */
> +		break;
>  	}
>  
>  	return (0);
> Index: sbin/iked/eap.h
> ===================================================================
> RCS file: /cvs/src/sbin/iked/eap.h,v
> diff -u -p -u -p -r1.6 eap.h
> --- sbin/iked/eap.h	16 Sep 2020 21:37:35 -0000	1.6
> +++ sbin/iked/eap.h	12 Jul 2024 15:22:41 -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 -u -p -r1.70 iked.c
> --- sbin/iked/iked.c	15 Feb 2024 20:10:45 -0000	1.70
> +++ sbin/iked/iked.c	12 Jul 2024 15:22:42 -0000
> @@ -307,6 +307,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);
>  
> @@ -324,6 +326,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 -u -p -r1.96 iked.conf.5
> --- sbin/iked/iked.conf.5	13 Apr 2024 12:11:08 -0000	1.96
> +++ sbin/iked/iked.conf.5	12 Jul 2024 15:22:42 -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
> @@ -779,6 +786,118 @@ 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 disable 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 accounting server,
> +specify optional
> +.Ic accounting
> +keyword.
> +Optionally specify the port number,
> +otherwise the default port number,
> +1812 for authentication or
> +1813 for accounting,
> +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 accounting requests,
> +specify optional
> +.Ic accounting
> +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 accounting requests,
> +specify optional
> +.Ic accounting
> +keyword.
> +The default value is 0.
> +.It Ic radius dae listen on Ar address Oo port Ar number Oc
> +Specify the local
> +.Ar address
> +.Xr iked 8
> +should listen on for the Dynamic Authorization Extensions
> +.Po DAE, RFC 5176 Pc requests,
> +Optionally specify a port
> +.Ar number,
> +the default port number is 3799.
> +.It Ic radius dae client Ar address Ic secret Ar secret
> +Specify
> +.Ar address
> +for a DAE client and
> +.Ar secret .
>  .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 -u -p -r1.230 iked.h
> --- sbin/iked/iked.h	2 Mar 2024 16:16:07 -0000	1.230
> +++ sbin/iked/iked.h	12 Jul 2024 15:22:42 -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>
> @@ -217,8 +218,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];
>  };
>  
> @@ -403,6 +404,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 */
> @@ -485,6 +495,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 */
> @@ -533,6 +544,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);
> @@ -648,6 +664,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;
> @@ -702,6 +719,72 @@ 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_raddae {
> +	int				 rd_sock;
> +	struct event			 rd_ev;
> +	struct iked			*rd_env;
> +	struct sockaddr_storage		 rd_sockaddr;
> +	TAILQ_ENTRY(iked_raddae)	 rd_entry;
> +};
> +TAILQ_HEAD(iked_raddaes, iked_raddae);
> +
> +struct iked_radclient {
> +	struct iked			*rc_env;
> +	struct sockaddr_storage		 rc_sockaddr;
> +	TAILQ_ENTRY(iked_radclient)	 rc_entry;
> +	char				 rc_secret[];
> +};
> +TAILQ_HEAD(iked_radclients , iked_radclient);
> +
> +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];
>  };
> @@ -810,6 +893,14 @@ 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;
> +	int				 sc_radaccton;
> +	struct iked_radservers		 sc_radauthservers;
> +	struct iked_radservers		 sc_radacctservers;
> +	struct iked_radcfgmaps		 sc_radcfgmaps;
> +	struct iked_raddaes		 sc_raddaes;
> +	struct iked_radclients		 sc_raddaeclients;
>  
>  	struct iked_stats		 sc_stats;
>  
> @@ -941,6 +1032,20 @@ 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 *);
> +int	 config_setraddae(struct iked *, struct sockaddr *, socklen_t);
> +int	 config_getraddae(struct iked *, struct imsg *);
> +int	 config_setradclient(struct iked *, struct sockaddr *, socklen_t,
> +	    char *);
> +int	 config_getradclient(struct iked *, struct imsg *);
>  
>  /* policy.c */
>  void	 policy_init(struct iked *);
> @@ -1156,6 +1261,17 @@ 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	 iked_radius_request(struct iked *, struct iked_sa *,
> +	    struct iked_message *);
> +void	 iked_radius_request_free(struct iked *, struct iked_radserver_req *);
> +void	 iked_radius_on_event(int, short, void *);
> +void	 iked_radius_acct_on(struct iked *);
> +void	 iked_radius_acct_off(struct iked *);
> +void	 iked_radius_acct_start(struct iked *, struct iked_sa *);
> +void	 iked_radius_acct_stop(struct iked *, struct iked_sa *);
> +void	 iked_radius_dae_on_event(int, short, void *);
>  
>  /* 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 -u -p -r1.386 ikev2.c
> --- sbin/iked/ikev2.c	21 Mar 2024 22:08:49 -0000	1.386
> +++ sbin/iked/ikev2.c	12 Jul 2024 15:22:44 -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>
> @@ -284,6 +285,7 @@ ikev2_dispatch_parent(int fd, struct pri
>  			timer_add(env, &env->sc_inittmr,
>  			    IKED_INITIATOR_INITIAL);
>  		}
> +		iked_radius_acct_on(env);
>  		return (0);
>  	case IMSG_UDP_SOCKET:
>  		return (config_getsocket(env, imsg, ikev2_msg_cb));
> @@ -295,6 +297,18 @@ 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_CFG_RADDAE:
> +		return (config_getraddae(env, imsg));
> +	case IMSG_CFG_RADDAECLIENT:
> +		return (config_getradclient(env, imsg));
>  	case IMSG_COMPILE:
>  		return (config_getcompile(env));
>  	case IMSG_CTL_STATIC:
> @@ -1782,6 +1796,7 @@ ikev2_init_done(struct iked *env, struct
>  		ret = ikev2_childsa_enable(env, sa);
>  	if (ret == 0) {
>  		sa_state(env, sa, IKEV2_STATE_ESTABLISHED);
> +		iked_radius_acct_start(env, sa);
>  		/* Delete exchange timeout. */
>  		timer_del(env, &sa->sa_timer);
>  		ikev2_enable_timer(env, sa);
> @@ -2456,7 +2471,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;
> @@ -2479,8 +2494,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 */
> @@ -3857,6 +3879,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 iked_radius_request(env, sa, msg);
>  	}
>  	return -1;
>  }
> @@ -4012,6 +4036,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);
> +		iked_radius_acct_start(env, sa);
>  		/* Delete exchange timeout. */
>  		timer_del(env, &sa->sa_timer);
>  		ikev2_enable_timer(env, sa);
> @@ -4746,10 +4771,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) -
> @@ -4760,6 +4785,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);
> +	iked_radius_acct_start(env, nsa);
>  	ikev2_enable_timer(env, nsa);
>  
>  	ikestat_inc(env, ikes_sa_rekeyed);
> @@ -7028,6 +7055,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 +7073,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 +7105,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 +7134,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 +7211,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 +7231,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 +7252,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 +7338,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 +7686,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 -u -p -r1.101 ikev2_msg.c
> --- sbin/iked/ikev2_msg.c	2 Mar 2024 16:16:07 -0000	1.101
> +++ sbin/iked/ikev2_msg.c	12 Jul 2024 15:22:45 -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 -u -p -r1.135 ikev2_pld.c
> --- sbin/iked/ikev2_pld.c	2 Apr 2024 19:58:28 -0000	1.135
> +++ sbin/iked/ikev2_pld.c	12 Jul 2024 15:22:45 -0000
> @@ -2104,6 +2104,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, 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 -u -p -r1.146 parse.y
> --- sbin/iked/parse.y	25 Apr 2024 14:24:54 -0000	1.146
> +++ sbin/iked/parse.y	12 Jul 2024 15:22:45 -0000
> @@ -38,9 +38,12 @@
>  #include <errno.h>
>  #include <fcntl.h>
>  #include <ifaddrs.h>
> +#include <inttypes.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 +110,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 +399,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 +414,7 @@ typedef struct {
>  		uint8_t			 ikemode;
>  		uint8_t			 dir;
>  		uint8_t			 satype;
> +		uint8_t			 accounting;
>  		char			*string;
>  		uint16_t		 port;
>  		struct ipsec_hosts	*hosts;
> @@ -427,6 +435,10 @@ typedef struct {
>  		struct ipsec_transforms	*transforms;
>  		struct ipsec_filters	*filters;
>  		struct ipsec_mode	*mode;
> +		struct {
> +			uint32_t	 vendorid;
> +			uint8_t		 attrtype;
> +		} radattr;
>  	} v;
>  	int lineno;
>  } YYSTYPE;
> @@ -446,6 +458,8 @@ typedef struct {
>  %token	TOLERATE MAXAGE DYNAMIC
>  %token	CERTPARTIALCHAIN
>  %token	REQUEST IFACE
> +%token	RADIUS ACCOUNTING SERVER SECRET MAX_TRIES MAX_FAILOVERS
> +%token	CLIENT DAE LISTEN ON
>  %token	<v.string>		STRING
>  %token	<v.number>		NUMBER
>  %type	<v.string>		string
> @@ -453,7 +467,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 hexdecnumber
>  %type	<v.peers>		peers
>  %type	<v.anyhost>		anyhost
>  %type	<v.host>		host host_spec
> @@ -470,6 +484,8 @@ typedef struct {
>  %type	<v.string>		name iface
>  %type	<v.cfg>			cfg ikecfg ikecfgvals
>  %type	<v.string>		transform_esn
> +%type	<v.accounting>		accounting
> +%type	<v.radattr>		radattr
>  %%
>  
>  grammar		: /* empty */
> @@ -478,6 +494,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 +1056,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 +1068,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 +1080,6 @@ ikeauth		: /* empty */			{
>  			free($2);
>  
>  			$$.auth_method = IKEV2_AUTH_SIG_ANY;
> -			$$.auth_eap = EAP_TYPE_MSCHAP_V2;
>  			$$.auth_length = 0;
>  		}
>  		| STRING			{
> @@ -1245,6 +1270,202 @@ 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 radattr {
> +			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;
> +			}
> +			free($4);
> +			config_setradcfgmap(env, cfgtype, $5.vendorid,
> +			    $5.attrtype);
> +		}
> +		| RADIUS DAE LISTEN ON STRING port {
> +			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($5, NULL, &hints, &ai))
> +			    != 0) {
> +				yyerror("could not parse the address: %s: %s",
> +				    $5, gai_strerror(gai_err));
> +				free($5);
> +				YYERROR;
> +			}
> +			port = $6;
> +			if (port == 0)
> +				port = htons(RADIUS_DAE_DEFAULT_PORT);
> +			socket_af(ai->ai_addr, port);
> +			if ((ret = config_setraddae(env, ai->ai_addr,
> +			    ai->ai_addrlen)) != 0) {
> +				yyerror("could not set radius server");
> +				free($5);
> +				YYERROR;
> +			}
> +			freeaddrinfo(ai);
> +			free($5);
> +		}
> +		| RADIUS DAE CLIENT STRING SECRET STRING {
> +			int		 gai_err;
> +			struct addrinfo	 hints, *ai;
> +
> +			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($6, strlen($6));
> +				free($6);
> +				YYERROR;
> +			}
> +			config_setradclient(env, ai->ai_addr, ai->ai_addrlen,
> +			    $6);
> +			free($4);
> +			explicit_bzero($6, strlen($6));
> +			free($6);
> +			freeaddrinfo(ai);
> +		}
> +		;
> +
> +radattr		: hexdecnumber hexdecnumber {
> +			if ($1 < 0 || 0xffffffL < $1) {
> +				yyerror("vendor-id must be in 0-0xffffff");
> +				YYERROR;
> +			}
> +			if ($2 < 0 || 256 <= $2) {
> +				yyerror("attribute type must be in 0-255");
> +				YYERROR;
> +			}
> +			$$.vendorid = $1;
> +			$$.attrtype = $2;
> +		}
> +		| hexdecnumber {
> +			if ($1 < 0 || 256 <= $1) {
> +				yyerror("attribute type must be in 0-255");
> +				YYERROR;
> +			}
> +			$$.vendorid = 0;
> +			$$.attrtype = $1;
> +		}
> +
> +hexdecnumber	: STRING {
> +			const char	*errstr;
> +			char		*ep;
> +			uintmax_t	 ul;
> +
> +			if ($1[0] == '0' && $1[1] == 'x' && isxdigit($1[2])) {
> +				ul = strtoumax($1 + 2, &ep, 16);
> +				if (*ep != '\0') {
> +					yyerror("`%s' is not a number", $1);
> +					free($1);
> +					YYERROR;
> +				}
> +				if (ul == UINTMAX_MAX || ul > UINT64_MAX) {
> +					yyerror("`%s' is out-of-range", $1);
> +					free($1);
> +					YYERROR;
> +				}
> +				$$ = ul;
> +			} else {
> +				$$ = strtonum($1, 0, UINT64_MAX, &errstr);
> +				if (errstr != NULL) {
> +					yyerror("`%s' is %s", $1, errstr);
> +					free($1);
> +					YYERROR;
> +				}
> +			}
> +			free($1);
> +		}
> +		| NUMBER
> +		;
> +
> +accounting	: {
> +			$$ = 0;
> +		}
> +		| ACCOUNTING {
> +			$$ = 1;
> +		}
> +		;
> +
>  varset		: STRING '=' string
>  		{
>  			char *s = $1;
> @@ -1336,6 +1557,7 @@ lookup(char *s)
>  {
>  	/* this has to be sorted always */
>  	static const struct keywords keywords[] = {
> +		{ "accounting",		ACCOUNTING },
>  		{ "active",		ACTIVE },
>  		{ "ah",			AH },
>  		{ "any",		ANY },
> @@ -1343,8 +1565,10 @@ lookup(char *s)
>  		{ "bytes",		BYTES },
>  		{ "cert_partial_chain",	CERTPARTIALCHAIN },
>  		{ "childsa",		CHILDSA },
> +		{ "client",		CLIENT },
>  		{ "config",		CONFIG },
>  		{ "couple",		COUPLE },
> +		{ "dae",		DAE },
>  		{ "decouple",		DECOUPLE },
>  		{ "default",		DEFAULT },
>  		{ "dpd_check_interval",	DPD_CHECK_INTERVAL },
> @@ -1370,7 +1594,10 @@ lookup(char *s)
>  		{ "inet6",		INET6 },
>  		{ "ipcomp",		IPCOMP },
>  		{ "lifetime",		LIFETIME },
> +		{ "listen",		LISTEN },
>  		{ "local",		LOCAL },
> +		{ "max-failovers",	MAX_FAILOVERS},
> +		{ "max-tries",		MAX_TRIES },
>  		{ "maxage",		MAXAGE },
>  		{ "mobike",		MOBIKE },
>  		{ "name",		NAME },
> @@ -1381,6 +1608,7 @@ lookup(char *s)
>  		{ "nostickyaddress",	NOSTICKYADDRESS },
>  		{ "novendorid",		NOVENDORID },
>  		{ "ocsp",		OCSP },
> +		{ "on",			ON },
>  		{ "passive",		PASSIVE },
>  		{ "peer",		PEER },
>  		{ "port",		PORT },
> @@ -1388,9 +1616,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 +2023,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 +2047,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 -u -p -r1.84 pfkey.c
> --- sbin/iked/pfkey.c	14 Aug 2023 12:02:02 -0000	1.84
> +++ sbin/iked/pfkey.c	12 Jul 2024 15:22:46 -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 -u -p -r1.98 policy.c
> --- sbin/iked/policy.c	3 Feb 2024 00:54:14 -0000	1.98
> +++ sbin/iked/policy.c	12 Jul 2024 15:22:46 -0000
> @@ -60,6 +60,11 @@ 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);
> +	TAILQ_INIT(&env->sc_raddaes);
> +	TAILQ_INIT(&env->sc_raddaeclients);
>  	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	12 Jul 2024 15:22:46 -0000
> @@ -0,0 +1,937 @@
> +/*	$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	 iked_radius_request_send(struct iked *, void *);
> +void	 iked_radius_fill_attributes(struct iked_sa *, RADIUS_PACKET *);
> +void	 iked_radius_config(struct iked_radserver_req *, const RADIUS_PACKET *,
> +	    int, uint32_t, uint8_t);
> +void	 iked_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
> +iked_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,
> +		    iked_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;
> +	}
> +
> +	iked_radius_fill_attributes(sa, pkt);
> +
> +	/* 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;
> +
> +	iked_radius_request_send(env, sa->sa_radreq);
> +
> +	return 0;
> +}
> +
> +void
> +iked_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
> +iked_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;
> +	char				 username[256];
> +	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) {
> +		/* accounting */
> +		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:
> +		log_info("%s: received Access-Accept for %s",
> +		    SPI_SA(req->rr_sa, __func__), req->rr_user);
> +		/* Try to retrieve 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 retrieve the EAP MSK from the "
> +			    "RADIUS message");
> +
> +		free(req->rr_sa->sa_eapid);
> +		/* The EAP identity might be protected (RFC 3748 7.3) */
> +		if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME,
> +		    username, sizeof(username)) == 0 &&
> +		    strcmp(username, req->rr_user) != 0) {
> +			/*
> +			 * The Access-Accept might have a User-Name.  It
> +			 * should be used for Accouting (RFC 2865 5.1).
> +			 */
> +			free(req->rr_user);
> +			req->rr_sa->sa_eapid = strdup(username);
> +		} else
> +			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];
> +				iked_radius_config(req, pkt, cfgmap->cfg_type,
> +				    cfgmap->vendor_id, cfgmap->attr_type);
> +			}
> +		} else {
> +			TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry)
> +				iked_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:
> +		log_info("%s: received Access-Reject for %s",
> +		    SPI_SA(req->rr_sa, __func__), req->rr_user);
> +		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 retrieve 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 retrieve 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
> +iked_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);
> +	/* Identifier */
> +	radius_put_string_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IDENTIFIER,
> +	    IKED_NAS_ID);
> +
> +	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
> +iked_radius_fill_attributes(struct iked_sa *sa, RADIUS_PACKET *pkt)
> +{
> +	/* NAS Port Type = Virtual */
> +	radius_put_uint32_attr(pkt,
> +	    RADIUS_TYPE_NAS_PORT_TYPE, RADIUS_NAS_PORT_TYPE_VIRTUAL);
> +	/* Service Type =  Framed */
> +	radius_put_uint32_attr(pkt, RADIUS_TYPE_SERVICE_TYPE,
> +	    RADIUS_SERVICE_TYPE_FRAMED);
> +	/* Tunnel Type = EAP */
> +	radius_put_uint32_attr(pkt, RADIUS_TYPE_TUNNEL_TYPE,
> +	    RADIUS_TUNNEL_TYPE_ESP);
> +
> +	radius_put_string_attr(pkt, RADIUS_TYPE_CALLED_STATION_ID,
> +	    print_addr(&sa->sa_local.addr));
> +	radius_put_string_attr(pkt, RADIUS_TYPE_CALLING_STATION_ID,
> +	    print_addr(&sa->sa_peer.addr));
> +}
> +
> +void
> +iked_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
> +iked_radius_acct_on(struct iked *env)
> +{
> +	if (TAILQ_EMPTY(&env->sc_radacctservers))
> +		return;
> +	if (env->sc_radaccton == 0) {	/* trigger once */
> +		iked_radius_acct_request(env, NULL,
> +		    RADIUS_ACCT_STATUS_TYPE_ACCT_ON);
> +		env->sc_radaccton = 1;
> +	}
> +}
> +
> +void
> +iked_radius_acct_off(struct iked *env)
> +{
> +	iked_radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_OFF);
> +}
> +
> +void
> +iked_radius_acct_start(struct iked *env, struct iked_sa *sa)
> +{
> +	iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_START);
> +}
> +
> +void
> +iked_radius_acct_stop(struct iked *env, struct iked_sa *sa)
> +{
> +	iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_STOP);
> +}
> +
> +void
> +iked_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				 sa_id[IKED_ID_SIZE];
> +	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) {
> +		/* fallback to IKEID for accounting */
> +		if (ikev2_print_id(IKESA_DSTID(sa), sa_id, sizeof(sa_id)) != -1)
> +			sa->sa_eapid = strdup(sa_id);
> +		if (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, iked_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;
> +		iked_radius_request_send(env, req);
> +		return;
> +	}
> +
> +	iked_radius_fill_attributes(sa, pkt);
> +
> +	radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME, sa->sa_eapid);
> +
> +	/* RFC 2866  5.5. Acct-Session-Id */
> +	snprintf(sid, sizeof(sid), "%016llx",
> +	    (unsigned long long)sa->sa_hdr.sh_ispi);
> +	radius_put_string_attr(pkt, RADIUS_TYPE_ACCT_SESSION_ID, sid);
> +
> +	/* Accounting 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);
> +		}
> +	}
> +	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;
> +			} else if (strcmp(sa->sa_reason,
> +			    "disconnect requested") == 0) {
> +				cause = RADIUS_TERMNATE_CAUSE_ADMIN_RESET;
> +			}
> +		}
> +		radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE,
> +		    cause);
> +		/* I/O statistics {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);
> +		break;
> +	}
> +	req->rr_reqpkt = pkt;
> +	req->rr_ntry = 0;
> +	iked_radius_request_send(env, req);
> +}
> +
> +void
> +iked_radius_dae_on_event(int fd, short ev, void *ctx)
> +{
> +	struct iked_raddae	*dae = ctx;
> +	struct iked		*env = dae->rd_env;
> +	RADIUS_PACKET		*req = NULL, *res = NULL;
> +	struct sockaddr_storage	 ss;
> +	socklen_t		 sslen;
> +	struct iked_radclient	*client;
> +	struct iked_sa		*sa = NULL;
> +	char			 attr[256], username[256];
> +	char			*endp, *reason, *nakcause = NULL;
> +	int			 code, n = 0;
> +	uint64_t		 ispi = 0;
> +	uint32_t		 u32, cause = 0;
> +	struct iked_addr	*addr4 = NULL;
> +
> +	reason = "disconnect requested";
> +
> +	sslen = sizeof(ss);
> +	req = radius_recvfrom(dae->rd_sock, 0, (struct sockaddr *)&ss, &sslen);
> +	if (req == NULL) {
> +		log_warn("%s: receiving a RADIUS message failed: %s", __func__,
> +		    strerror(errno));
> +		return;
> +	}
> +	TAILQ_FOREACH(client, &env->sc_raddaeclients, rc_entry) {
> +		if (sockaddr_cmp((struct sockaddr *)&client->rc_sockaddr,
> +		    (struct sockaddr *)&ss, -1) == 0)
> +			break;
> +	}
> +	if (client == NULL) {
> +		log_warnx("%s: received RADIUS message from %s: "
> +		    "unknown client", __func__, print_addr(&ss));
> +		goto out;
> +	}
> +
> +	if (radius_check_accounting_request_authenticator(req,
> +	    client->rc_secret) != 0) {
> +		log_warnx("%s: received an invalid RADIUS message from %s: bad "
> +		    "response authenticator", __func__, print_addr(&ss));
> +		goto out;
> +	}
> +
> +	if ((code = radius_get_code(req)) != RADIUS_CODE_DISCONNECT_REQUEST) {
> +		/* Code other than Disconnect-Request is not supported */
> +		if (code == RADIUS_CODE_COA_REQUEST) {
> +			code = RADIUS_CODE_COA_NAK;
> +			cause = RADIUS_ERROR_CAUSE_ADMINISTRATIVELY_PROHIBITED;
> +			nakcause = "Coa-Request is not supprted";
> +			goto send;
> +		}
> +		log_warnx("%s: received an invalid RADIUS message "
> +		    "from %s: unknown code %d", __func__,
> +		    print_addr(&ss), code);
> +		goto out;
> +	}
> +
> +	log_info("received Disconnect-Request from %s", print_addr(&ss));
> +
> +	if (radius_get_string_attr(req, RADIUS_TYPE_NAS_IDENTIFIER, attr,
> +	    sizeof(attr)) == 0 && strcmp(attr, IKED_NAS_ID) != 0) {
> +		cause = RADIUS_ERROR_CAUSE_NAS_IDENTIFICATION_MISMATCH;
> +		nakcause = "NAS-Identifier is not matched";
> +		goto search_done;
> +	}
> +
> +	/* prepare User-Name attribute */
> +	memset(username, 0, sizeof(username));
> +	radius_get_string_attr(req, RADIUS_TYPE_USER_NAME, username,
> +	    sizeof(username));
> +
> +	if (radius_get_string_attr(req, RADIUS_TYPE_ACCT_SESSION_ID, attr,
> +	    sizeof(attr)) == 0) {
> +		/* the client is to disconnect a session */
> +		ispi = strtoull(attr, &endp, 16);
> +		if (attr[0] == '\0' || *endp != '\0' || errno == ERANGE ||
> +		    ispi == ULLONG_MAX) {
> +			cause = RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE;
> +			nakcause = "Session-Id is wrong";
> +			goto search_done;
> +
> +		}
> +		RB_FOREACH(sa, iked_sas, &env->sc_sas) {
> +			if (sa->sa_hdr.sh_ispi == ispi)
> +				break;
> +		}
> +		if (sa == NULL)
> +			goto search_done;
> +		if (username[0] != '\0' && (sa->sa_eapid == NULL ||
> +		    strcmp(username, sa->sa_eapid) != 0)) {
> +			/* specified User-Name attribute is mismatched */
> +			cause = RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE;
> +			nakcause = "User-Name is not matched";
> +			goto search_done;
> +		}
> +		ikev2_ike_sa_setreason(sa, reason);
> +		ikev2_ike_sa_delete(env, sa);
> +		n++;
> +	} else if (username[0] != '\0') {
> +		RB_FOREACH(sa, iked_sas, &env->sc_sas) {
> +			if (sa->sa_eapid != NULL &&
> +			    strcmp(sa->sa_eapid, username) == 0) {
> +				ikev2_ike_sa_setreason(sa, reason);
> +				ikev2_ike_sa_delete(env, sa);
> +				n++;
> +			}
> +		}
> +	} else if (radius_get_uint32_attr(req, RADIUS_TYPE_FRAMED_IP_ADDRESS,
> +	    &u32) == 0) {
> +		addr4 = sa->sa_addrpool;
> +		if (addr4 != NULL) {
> +			RB_FOREACH(sa, iked_sas, &env->sc_sas) {
> +				if (u32 == ((struct sockaddr_in *)&addr4->addr)
> +				    ->sin_addr.s_addr) {
> +					ikev2_ike_sa_setreason(sa, reason);
> +					ikev2_ike_sa_delete(env, sa);
> +					n++;
> +				}
> +			}
> +		}
> +	}
> + search_done:
> +	if (n > 0)
> +		code = RADIUS_CODE_DISCONNECT_ACK;
> +	else {
> +		if (nakcause == NULL)
> +			nakcause = "session not found";
> +		if (cause == 0)
> +			cause = RADIUS_ERROR_CAUSE_SESSION_NOT_FOUND;
> +		code = RADIUS_CODE_DISCONNECT_NAK;
> +	}
> + send:
> +	res = radius_new_response_packet(code, req);
> +	if (res == NULL) {
> +		log_warn("%s: radius_new_response_packet", __func__);
> +		goto out;
> +	}
> +	if (cause != 0)
> +		radius_put_uint32_attr(res, RADIUS_TYPE_ERROR_CAUSE, cause);
> +	radius_set_response_authenticator(res, client->rc_secret);
> +	if (radius_sendto(dae->rd_sock, res, 0, (struct sockaddr *)&ss, sslen)
> +	    == -1)
> +		log_warn("%s: sendto", __func__);
> +	log_info("send %s for %s%s%s",
> +	    (code == RADIUS_CODE_DISCONNECT_ACK)? "Disconnect-ACK" :
> +	    (code == RADIUS_CODE_DISCONNECT_NAK)? "Disconnect-NAK" : "CoA-NAK",
> +	    print_addr(&ss), (nakcause)? ": " : "", (nakcause)? nakcause : "");
> + out:
> +	radius_delete_packet(req);
> +	if (res != NULL)
> +		radius_delete_packet(res);
> +}
> Index: sbin/iked/types.h
> ===================================================================
> RCS file: /cvs/src/sbin/iked/types.h,v
> diff -u -p -u -p -r1.54 types.h
> --- sbin/iked/types.h	15 Feb 2024 20:10:45 -0000	1.54
> +++ sbin/iked/types.h	12 Jul 2024 15:22:46 -0000
> @@ -42,6 +42,7 @@
>  #define IKED_PUBKEY		"local.pub"
>  
>  #define IKED_VENDOR_ID		"OpenIKED-"
> +#define IKED_NAS_ID		"OpenIKED"
>  
>  #define IKED_OCSP_RESPCERT	"ocsp/responder.crt"
>  
> @@ -112,6 +113,12 @@ 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_CFG_RADDAE,
> +	IMSG_CFG_RADDAECLIENT,
>  	IMSG_CERTREQ,
>  	IMSG_CERT,
>  	IMSG_CERTVALID,
> @@ -150,6 +157,7 @@ enum flushmode {
>  	RESET_POLICY,
>  	RESET_SA,
>  	RESET_USER,
> +	RESET_RADIUS,
>  };
>  
>  #ifndef nitems
>