Index | Thread | Search

From:
YASUOKA Masahiko <yasuoka@openbsd.org>
Subject:
Re: iked: RADIUS support
To:
tobhe@openbsd.org, tech@openbsd.org
Cc:
markus@openbsd.org, uwe@werler.is, bilias@edu.physics.uoc.gr, stu@spacehopper.org
Date:
Mon, 29 Jan 2024 09:43:55 +0900

Download raw body.

Thread
  • YASUOKA Masahiko:

    iked: RADIUS support

  • 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
    
    
    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	29 Jan 2024 00:33:50 -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	29 Jan 2024 00:33:50 -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);
     }
     
    @@ -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)
    +				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);
    +		}
    +	}
    +
     	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,
    +	    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);
     }
    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	29 Jan 2024 00:33:50 -0000
    @@ -578,9 +578,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 -r1.6 eap.h
    --- sbin/iked/eap.h	16 Sep 2020 21:37:35 -0000	1.6
    +++ sbin/iked/eap.h	29 Jan 2024 00:33:50 -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	29 Jan 2024 00:33:50 -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	29 Jan 2024 00:33:50 -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 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.
     .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	29 Jan 2024 00:33:51 -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,16 @@ 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 *);
     
     /* 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	29 Jan 2024 00:33: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);
    +		iked_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 iked_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);
    +		iked_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);
    +	iked_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	29 Jan 2024 00:33:51 -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	29 Jan 2024 00:33:51 -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	29 Jan 2024 00:33:51 -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			 accounting;
     		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.accounting>		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;
    @@ -1336,6 +1451,7 @@ lookup(char *s)
     {
     	/* this has to be sorted always */
     	static const struct keywords keywords[] = {
    +		{ "accounting",		ACCOUNTING },
     		{ "active",		ACTIVE },
     		{ "ah",			AH },
     		{ "any",		ANY },
    @@ -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	29 Jan 2024 00:33:51 -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	29 Jan 2024 00:33: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	29 Jan 2024 00:33:52 -0000
    @@ -0,0 +1,779 @@
    +/*	$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_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;
    +	}
    +
    +	/* 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);
    +}
    +
    +/* version of radius_get_eap_msk(3) not limited to len0 == len1 == 16 */
    +static int
    +iked_radius_get_eap_msk(const RADIUS_PACKET * packet, void *buf, size_t * len,
    +    const char *secret)
    +{
    +	uint8_t  buf0[256];
    +	uint8_t  buf1[256];
    +	size_t   len0, len1;
    +
    +	len0 = sizeof(buf0);
    +	len1 = sizeof(buf1);
    +	if (radius_get_mppe_recv_key_attr(packet, buf0, &len0, secret) == 0 &&
    +	    radius_get_mppe_send_key_attr(packet, buf1, &len1, secret) == 0) {
    +		/* addition cannot overflow since len0/len1 are limited to 256 */
    +		if (*len < len0 + len1)
    +			return (-1);
    +		memcpy(buf, buf0, len0);
    +		memcpy(((char *)buf) + len0, buf1, len1);
    +		if (*len != len0 + len1) {
    +			memset(((char *)buf) + len0 + len1, 0, *len - len0 - len1);
    +			*len = len0 + len1;
    +		}
    +		return (0);
    +	}
    +
    +	return (-1);
    +}
    +
    +
    +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;
    +	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 retribute the EAP MSK from the RADIUS response */
    +		if (iked_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];
    +				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 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
    +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);
    +
    +	/* 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
    +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)
    +{
    +	iked_radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_ON);
    +}
    +
    +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;
    +	}
    +
    +	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);
    +		}
    +	}
    +#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 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);
    +		/* Identifier */
    +		radius_put_string_attr(pkt, RADIUS_TYPE_NAS_IDENTIFIER,
    +		    "OpenIKED");
    +		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));
    +		break;
    +	}
    +	req->rr_reqpkt = pkt;
    +	req->rr_ntry = 0;
    +	iked_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	29 Jan 2024 00:33: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
    
    
    
  • YASUOKA Masahiko:

    iked: RADIUS support