authenticator_misc.c
Go to the documentation of this file.
1 /**
2  * @file authenticator_misc.c
3  * @brief Helper functions for 802.1X authenticator
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2022-2026 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneEAP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.6.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL AUTHENTICATOR_TRACE_LEVEL
33 
34 //Dependencies
39 #include "radius/radius.h"
41 #include "radius/radius_debug.h"
42 #include "eap/eap_debug.h"
43 #include "debug.h"
44 
45 //Check EAP library configuration
46 #if (AUTHENTICATOR_SUPPORT == ENABLED)
47 
48 //PAE group address (refer to IEEE Std 802.1X-2010, section 11.1.1)
49 static const MacAddr PAE_GROUP_ADDR = {{{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}}};
50 
51 
52 /**
53  * @brief Handle periodic operations
54  * @param[in] context Pointer to the 802.1X authenticator context
55  **/
56 
58 {
59  uint_t i;
60  bool_t macOpState;
62 
63  //Loop through the ports
64  for(i = 0; i < context->numPorts; i++)
65  {
66  //Point to the current port
67  port = &context->ports[i];
68 
69  //Poll link state
70  macOpState = authenticatorGetLinkState(port);
71 
72  //Link state change detected?
73  if(macOpState && !port->portEnabled)
74  {
75  //Session statistics for a port can be retained by the system until a
76  //new session begins on that port
77  port->sessionStats.sessionOctetsRx = 0;
78  port->sessionStats.sessionOctetsTx = 0;
79  port->sessionStats.sessionFramesRx = 0;
80  port->sessionStats.sessionFramesTx = 0;
81  port->sessionStats.sessionTime = 0;
82 
83  //The port is up
84  port->sessionStats.sessionTerminateCause =
86  }
87  else if(!macOpState && port->portEnabled)
88  {
89  //The port is down
90  port->sessionStats.sessionTerminateCause =
92  }
93  else if(macOpState)
94  {
95  //Duration of the session in seconds
96  port->sessionStats.sessionTime++;
97  }
98  else
99  {
100  //No link state change
101  }
102 
103  //The portEnabled variable is externally controlled. Its value reflects
104  //the operational state of the MAC service supporting the port
105  port->portEnabled = macOpState;
106 
107  //Timers are decremented once per second
109  authenticatorDecrementTimer(&port->quietWhile);
110  authenticatorDecrementTimer(&port->reAuthWhen);
111  authenticatorDecrementTimer(&port->retransWhile);
112  authenticatorDecrementTimer(&port->aaaRetransTimer);
113  }
114 
115  //Update authenticator state machines
116  authenticatorFsm(context);
117 
118  //Any registered callback?
119  if(context->tickCallback != NULL)
120  {
121  //Invoke user callback function
122  context->tickCallback(context);
123  }
124 }
125 
126 
127 /**
128  * @brief Port's MAC address generation
129  * @param[in] port Pointer to the port context
130  **/
131 
133 {
134  int_t i;
135  uint8_t c;
136  MacAddr *macAddr;
137  AuthenticatorContext *context;
138 
139  //Point to the 802.1X authenticator context
140  context = port->context;
141 
142  //Get the MAC address of the underlying network interface
143  macAddr = &context->interface->macAddr;
144 
145  //Retrieve port index
146  c = port->portIndex;
147 
148  //Generate a unique MAC address for the port
149  for(i = 5; i >= 0; i--)
150  {
151  //Generate current byte
152  port->macAddr.b[i] = macAddr->b[i] + c;
153 
154  //Propagate the carry if necessary
155  if(port->macAddr.b[i] < macAddr->b[i])
156  {
157  c = 1;
158  }
159  else
160  {
161  c = 0;
162  }
163  }
164 }
165 
166 
167 /**
168  * @brief Get link state
169  * @param[in] port Pointer to the port context
170  * @return Error code
171  **/
172 
174 {
175  bool_t linkState;
176  NetInterface *interface;
177  AuthenticatorContext *context;
178 
179  //Point to the 802.1X authenticator context
180  context = port->context;
181  //Point to the underlying network interface
182  interface = context->interface;
183 
184  //Valid switch driver?
185  if(interface->switchDriver != NULL &&
186  interface->switchDriver->getLinkState != NULL)
187  {
188  //Get exclusive access
189  netLock(context->netContext);
190 
191  //Retrieve the link state of the specified port
192  linkState = interface->switchDriver->getLinkState(interface,
193  port->portIndex);
194 
195  //Release exclusive access
196  netUnlock(context->netContext);
197  }
198  else
199  {
200  //Retrieve the link state of the network interface
201  linkState = interface->linkState;
202  }
203 
204  //Return link state
205  return linkState;
206 }
207 
208 
209 /**
210  * @brief Add the PAE group address to the static MAC table
211  * @param[in] context Pointer to the 802.1X authenticator context
212  * @return Error code
213  **/
214 
216 {
217  error_t error;
218  SwitchFdbEntry entry;
219  NetInterface *interface;
220 
221  //Initialize status code
222  error = NO_ERROR;
223 
224  //Point to the underlying network interface
225  interface = context->interface;
226 
227  //Get exclusive access
228  netLock(context->netContext);
229 
230  //Valid switch driver?
231  if(interface->switchDriver != NULL &&
232  interface->switchDriver->addStaticFdbEntry != NULL)
233  {
234  //Format forwarding database entry
235  entry.macAddr = PAE_GROUP_ADDR;
236  entry.srcPort = 0;
238  entry.override = TRUE;
239 
240  //Update the static MAC table of the switch
241  error = interface->switchDriver->addStaticFdbEntry(interface, &entry);
242  }
243 
244  //Check status code
245  if(!error)
246  {
247  //Add the PAE group address to the MAC filter table
248  error = ethAcceptMacAddr(interface, &PAE_GROUP_ADDR);
249  }
250 
251  //Release exclusive access
252  netUnlock(context->netContext);
253 
254  //Return status code
255  return error;
256 }
257 
258 
259 /**
260  * @brief Remove the PAE group address from the static MAC table
261  * @param[in] context Pointer to the 802.1X authenticator context
262  * @return Error code
263  **/
264 
266 {
267  error_t error;
268  SwitchFdbEntry entry;
269  NetInterface *interface;
270 
271  //Initialize status code
272  error = NO_ERROR;
273 
274  //Point to the underlying network interface
275  interface = context->interface;
276 
277  //Get exclusive access
278  netLock(context->netContext);
279 
280  //Valid switch driver?
281  if(interface->switchDriver != NULL &&
282  interface->switchDriver->deleteStaticFdbEntry != NULL)
283  {
284  //Format forwarding database entry
285  entry.macAddr = PAE_GROUP_ADDR;
286  entry.srcPort = 0;
287  entry.destPorts = 0;
288  entry.override = FALSE;
289 
290  //Update the static MAC table of the switch
291  error = interface->switchDriver->deleteStaticFdbEntry(interface, &entry);
292  }
293 
294  //Check status code
295  if(!error)
296  {
297  //Remove the PAE group address to the MAC filter table
298  ethDropMacAddr(interface, &PAE_GROUP_ADDR);
299  }
300 
301  //Release exclusive access
302  netUnlock(context->netContext);
303 
304  //Return status code
305  return error;
306 }
307 
308 
309 /**
310  * @brief Send EAPOL PDU
311  * @param[in] port Pointer to the port context
312  * @param[in] pdu Pointer to the PDU to be transmitted
313  * @param[in] length Length of the PDU, in bytes
314  * @return Error code
315  **/
316 
318  size_t length)
319 {
320  SocketMsg msg;
321 
322  //Point to the PDU to be transmitted
323  msg = SOCKET_DEFAULT_MSG;
324  msg.data = (uint8_t *) pdu;
325  msg.length = length;
326 
327  //The PAE group address is assigned specifically for use by EAPOL clients
328  //designed to maximize plug-and-play interoperability, and should be the
329  //default for those clients (refer to IEEE Std 802.1X-2010, section 11.1.1)
330  msg.destMacAddr = PAE_GROUP_ADDR;
331 
332  //The source address for each MAC service request used to transmit an EAPOL
333  //MPDU shall be an individual address associated with the service access
334  //point at which the request is made (refer to IEEE Std 802.1X-2010,
335  //section 11.1.2)
336  msg.srcMacAddr = port->macAddr;
337 
338  //All EAPOL MPDUs shall be identified using the PAE EtherType (refer to
339  //IEEE Std 802.1X-2010, section 11.1.4)
340  msg.ethType = ETH_TYPE_EAPOL;
341 
342 #if (ETH_PORT_TAGGING_SUPPORT == ENABLED)
343  //Specify the egress port
344  msg.switchPort = port->portIndex;
345 #endif
346 
347  //Number of EAPOL frames of any type that have been transmitted
348  port->stats.eapolFramesTx++;
349 
350  //Send EAPOL MPDU
351  return socketSendMsg(port->context->peerSocket, &msg, 0);
352 }
353 
354 
355 /**
356  * @brief Process incoming EAPOL PDU
357  * @param[in] context Pointer to the 802.1X authenticator context
358  **/
359 
361 {
362  error_t error;
363  size_t length;
364  uint_t portIndex;
365  SocketMsg msg;
366  EapolPdu *pdu;
368 
369  //Point to the receive buffer
370  msg = SOCKET_DEFAULT_MSG;
371  msg.data = context->rxBuffer;
373 
374  //Receive EAPOL MPDU
375  error = socketReceiveMsg(context->peerSocket, &msg, 0);
376  //Failed to receive packet
377  if(error)
378  return;
379 
380 #if (ETH_PORT_TAGGING_SUPPORT == ENABLED)
381  //Save the port number on which the EAPOL PDU was received
382  portIndex = MAX(msg.switchPort, 1);
383 #else
384  //The station has a single port
385  portIndex = 1;
386 #endif
387 
388  //The destination MAC address field must contain the PAE group address
389  if(!macCompAddr(&msg.destMacAddr, &PAE_GROUP_ADDR))
390  return;
391 
392  //The received MPDU must contain the PAE EtherType
393  if(msg.ethType != ETH_TYPE_EAPOL)
394  return;
395 
396  //Malformed EAPOL packet?
397  if(msg.length < sizeof(EapolPdu))
398  return;
399 
400  //Point to the EAPOL packet
401  pdu = (EapolPdu *) context->rxBuffer;
402 
403  //Debug message
404  TRACE_INFO("Port %" PRIu8 ": EAPOL packet received (%" PRIuSIZE " bytes)...\r\n",
405  portIndex, msg.length);
406 
407  //Dump EAPOL header contents for debugging purpose
409 
410  //Sanity check
411  if(portIndex > context->numPorts)
412  return;
413 
414  //Point to the port that matches the specified port index
415  port = &context->ports[portIndex - 1];
416 
417  //Any octets following the Packet Body field in the frame conveying the
418  //EAPOL PDU shall be ignored (refer to IEEE Std 802.1X-2004, section 11.4)
419  length = ntohs(pdu->packetBodyLen);
420 
421  //Malformed EAPOL packet?
422  if(msg.length < (sizeof(EapolPdu) + length))
423  {
424  //Number of EAPOL frames that have been received by this authenticator
425  //in which the Packet Body Length field is invalid
426  port->stats.eapLengthErrorFramesRx++;
427 
428  //Exit immediately
429  return;
430  }
431 
432  //Number of valid EAPOL frames of any type that have been received
433  port->stats.eapolFramesRx++;
434  //Protocol version number carried in the most recently received EAPOL frame
435  port->stats.lastEapolFrameVersion = pdu->protocolVersion;
436 
437  //Save the MAC address of the supplicant
438  port->supplicantMacAddr = msg.srcMacAddr;
439 
440  //Check packet type
441  if(pdu->packetType == EAPOL_TYPE_EAP)
442  {
443  //Process incoming EAP packet
445  length);
446  }
447  else if(pdu->packetType == EAPOL_TYPE_START)
448  {
449  //Number of EAPOL Start frames that have been received
450  port->stats.eapolStartFramesRx++;
451 
452  //The eapolStart variable is set TRUE if an EAPOL PDU carrying a packet
453  //type of EAPOL-Start is received
454  port->eapolStart = TRUE;
455  }
456  else if(pdu->packetType == EAPOL_TYPE_LOGOFF)
457  {
458  //Number of EAPOL Logoff frames that have been received
459  port->stats.eapolLogoffFramesRx++;
460 
461  //The Logoff variable is set TRUE if an EAPOL PDU carrying a packet type
462  //of EAPOL-Logoff is received
463  port->eapolLogoff = TRUE;
464  }
465  else
466  {
467  //Number of EAPOL frames that have been received by this authenticator
468  //in which the frame type is not recognized
469  port->stats.invalidEapolFramesRx++;
470  }
471 }
472 
473 
474 /**
475  * @brief Process incoming EAP packet
476  * @param[in] port Pointer to the port context
477  * @param[in] packet Pointer to the received EAP packet
478  * @param[in] length Length of the packet, in bytes
479  **/
480 
482  const EapPacket *packet, size_t length)
483 {
484  //Malformed EAP packet?
485  if(length < sizeof(EapPacket))
486  return;
487 
488  //Debug message
489  TRACE_DEBUG("Port %" PRIu8 ": EAP packet received (%" PRIuSIZE " bytes)...\r\n",
490  port->portIndex, length);
491 
492  //Dump EAP header contents for debugging purpose
493  eapDumpHeader(packet);
494 
495  //The Length field is two octets and indicates the length, in octets, of the
496  //EAP packet including the Code, Identifier, Length, and Data fields
497  if(ntohs(packet->length) < sizeof(EapPacket))
498  return;
499 
500  //A message with the Length field set to a value larger than the number of
501  //received octets must be silently discarded (refer to RFC 3748, section 4.0)
502  if(ntohs(packet->length) > length)
503  return;
504 
505  //Octets outside the range of the Length field should be treated as data
506  //link layer padding and must be ignored upon reception
507  length = ntohs(packet->length);
508 
509  //Based on the Code field, the EAP layer demultiplexes incoming EAP packets
510  //to the EAP peer and authenticator layers
511  if(packet->code == EAP_CODE_RESPONSE)
512  {
513  //Point to the EAP response
514  port->eapRespData = (uint8_t *) packet;
515  port->eapRespDataLen = length;
516 
517  //The eapolEap variable is set TRUE by an external entity if an EAPOL
518  //PDU carrying a Packet Type of EAP-Packet is received
519  port->eapolEap = TRUE;
520 
521  //Invoke EAP to perform whatever processing is needed
522  authenticatorFsm(port->context);
523  }
524  else
525  {
526  //Unless a host implements an EAP peer layer, EAP Request, Success and
527  //Failure packets will be silently discarded (refer to RFC 3748,
528  //section 2.3)
529  }
530 }
531 
532 
533 /**
534  * @brief Build RADIUS Access-Request packet
535  * @param[in] port Pointer to the port context
536  **/
537 
539 {
540  error_t error;
541  size_t i;
542  size_t n;
543  IpAddr ipAddr;
544  MacAddr macAddr;
545  RadiusPacket *packet;
546  AuthenticatorContext *context;
547  uint8_t buffer[32];
548 
549  //Point to the 802.1X authenticator context
550  context = port->context;
551 
552  //Total length of the RADIUS packet
553  port->aaaReqDataLen = 0;
554 
555  //The Request Authenticator value must be changed each time a new
556  //Identifier is used (refer to RFC 2865, section 4.1)
557  error = context->prngAlgo->generate(context->prngContext,
558  port->reqAuthenticator, 16);
559  //Any error to report?
560  if(error)
561  return error;
562 
563  //Generate a new RADIUS packet identifier
564  port->aaaReqId = authenticatorGetNextRadiusId(context);
565 
566  //Point to the buffer where to format the RADIUS packet
567  packet = (RadiusPacket *) port->aaaReqData;
568 
569  //The Length field indicates the length of the packet including the Code,
570  //Identifier, Length, Authenticator and Attribute fields
571  n = sizeof(RadiusPacket);
572 
573  //Format RADIUS packet
574  packet->code = RADIUS_CODE_ACCESS_REQUEST;
575  packet->identifier = port->aaaReqId;
576  packet->length = htons(n);
577 
578  //The Authenticator field is 16 octets. This value is used to authenticate
579  //the reply from the RADIUS server (refer to RFC 2865, section 3)
580  osMemcpy(packet->authenticator, port->reqAuthenticator, 16);
581 
582  //The NAS must include the Type-Data field of the EAP-Response/Identity
583  //in the User-Name attribute in every subsequent Access-Request (refer to
584  //RFC 3579, section 2.1)
585  radiusAddAttribute(packet, RADIUS_ATTR_USER_NAME, port->aaaIdentity,
586  osStrlen(port->aaaIdentity));
587 
588  //The Service-Type attribute indicates the type of service the user has
589  //requested, or the type of service to be provided (refer to RFC 2865,
590  //section 5.6)
592 
593  //Add Service-Type attribute
595  sizeof(uint32_t));
596 
597  //The Framed-MTU attribute indicates the Maximum Transmission Unit to be
598  //configured for the user (refer to RFC 2865, section 5.12)
599  STORE32BE(EAP_MAX_FRAG_SIZE, buffer);
600 
601  //Add Framed-MTU attribute
603  sizeof(uint32_t));
604 
605  //Get exclusive access
606  netLock(context->netContext);
607 
608  //Retrieve the IP address of the NAS
609  error = ipSelectSourceAddr(context->netContext, &context->serverInterface,
610  &context->serverIpAddr, &ipAddr);
611 
612  //Release exclusive access
613  netUnlock(context->netContext);
614 
615  //Any error to report?
616  if(error)
617  return error;
618 
619  //Either NAS-Identifier, NAS-IP-Address or NAS-IPv6-Address attributes
620  //must be included (refer to RFC 3579, section 3)
621  if(ipAddr.length == sizeof(Ipv4Addr))
622  {
624  ipAddr.length);
625  }
626  else if(ipAddr.length == sizeof(Ipv6Addr))
627  {
629  ipAddr.length);
630  }
631  else
632  {
633  return ERROR_INVALID_ADDRESS;
634  }
635 
636  //The NAS-Port attribute indicates the physical port number of the NAS which
637  //is authenticating the user (refer to RFC 2865, section 5.5)
638  STORE32BE(port->portIndex, buffer);
639 
640  //Add NAS-Port attribute
641  radiusAddAttribute(packet, RADIUS_ATTR_NAS_PORT, buffer, sizeof(uint32_t));
642 
643  //The NAS-Port-Type attribute indicates the type of the physical port of
644  //the NAS which is authenticating the user. It can be used instead of or in
645  //addition to the NAS-Port attribute (refer to RFC 2865, section 5.41)
647 
648  //Add NAS-Port-Type attribute
650  sizeof(uint32_t));
651 
652  //The NAS-Port-Id attribute contains a text string which identifies the
653  //port of the NAS which is authenticating the user (refer to RFC 2869,
654  //section 5.17)
655  osSprintf((char_t *) buffer, "%s_%" PRIu8, context->interface->name,
656  port->portIndex);
657 
659  osStrlen((char_t *) buffer));
660 
661  //Retrieve the MAC address of the bridge
662  netGetMacAddr(context->serverInterface, &macAddr);
663  macAddrToString(&macAddr, (char_t *) buffer);
664 
665  //The Called-Station-Id attribute is used to store the bridge or access
666  //point MAC address in ASCII format (refer to RFC 3580, section 3.20)
668  osStrlen((char_t *) buffer));
669 
670  //Retrieve the MAC address of the supplicant
671  macAddrToString(&port->supplicantMacAddr, (char_t *) buffer);
672 
673  //The Calling-Station-Id attribute is used to store the supplicant MAC
674  //address in ASCII format (refer to RFC 3580, section 3.21)
676  osStrlen((char_t *) buffer));
677 
678  //Any State attribute received from previous Access-Challenge?
679  if(port->serverStateLen > 0)
680  {
681  //The NAS must include the State attribute unchanged in that
682  //Access-Request (refer to RFC 2865, section 5.24)
683  radiusAddAttribute(packet, RADIUS_ATTR_STATE, port->serverState,
684  port->serverStateLen);
685  }
686 
687  //The NAS places EAP messages received from the authenticating peer into
688  //one or more EAP-Message attributes and forwards them to the RADIUS server
689  //within an Access-Request message (refer to RFC 3579, section 3.1)
690  for(i = 0; i < port->eapRespDataLen; i += n)
691  {
692  //Each attribute can contain up to 253 octets of binary data
693  n = MIN(port->eapRespDataLen - i, RADIUS_MAX_ATTR_VALUE_LEN);
694 
695  //Make sure the buffer is large enough to hold the EAP-Message attribute
696  if((htons(packet->length) + sizeof(RadiusAttribute) + n) >
698  {
699  return ERROR_BUFFER_OVERFLOW;
700  }
701 
702  //If multiple EAP-Message attributes are contained within an Access-
703  //Request, they must be in order and they must be consecutive attributes
705  port->eapRespData + i, n);
706  }
707 
708  //When the checksum is calculated the signature string should be considered
709  //to be sixteen octets of zero (refer to RFC 2869, section 5.14)
710  osMemset(buffer, 0, MD5_DIGEST_SIZE);
711 
712  //Make sure the buffer is large enough to hold the Message-Authenticator
713  //attribute
714  if((htons(packet->length) + sizeof(RadiusAttribute) + MD5_DIGEST_SIZE) >
716  {
717  return ERROR_BUFFER_OVERFLOW;
718  }
719 
720  //Add Message-Authenticator attribute
723 
724  //Retrieve the total length of the RADIUS packet
725  n = htons(packet->length);
726 
727  //Transactions between the client and RADIUS server are authenticated through
728  //the use of a shared secret (refer to RFC 2865, section 1)
729  error = hmacInit(&context->hmacContext, MD5_HASH_ALGO, context->serverKey,
730  context->serverKeyLen);
731  //Any error to report?
732  if(error)
733  return error;
734 
735  //When present in an Access-Request packet, Message-Authenticator is an
736  //HMAC-MD5 hash of the entire Access-Request packet, including Type, ID,
737  //Length and Authenticator, using the shared secret as the key (refer to
738  //RFC 3579, section 3.2)
739  hmacUpdate(&context->hmacContext, port->aaaReqData, n);
740  hmacFinal(&context->hmacContext, buffer);
741 
742  //Copy the resulting HMAC-MD5 hash
743  osMemcpy(port->aaaReqData + n - MD5_DIGEST_SIZE, buffer, MD5_DIGEST_SIZE);
744 
745  //Save the total length of the RADIUS packet
746  port->aaaReqDataLen = n;
747  //Initialize retransmission counter
748  port->aaaRetransCount = 0;
749 
750  //Sucessful processing
751  return NO_ERROR;
752 }
753 
754 
755 /**
756  * @brief Send RADIUS Access-Request packet
757  * @param[in] port Pointer to the port context
758  **/
759 
761 {
762  error_t error;
763  SocketMsg msg;
764  AuthenticatorContext *context;
765 
766  //Initialize status code
767  error = NO_ERROR;
768 
769  //Point to the 802.1X authenticator context
770  context = port->context;
771 
772  //Valid RADIUS packet?
773  if(port->aaaReqDataLen > 0)
774  {
775  //Exactly one RADIUS packet is encapsulated in the UDP data field,
776  //where the UDP destination Port field indicates 1812 (refer to
777  //RFC 2865, section 3)
778  msg = SOCKET_DEFAULT_MSG;
779  msg.data = port->aaaReqData;
780  msg.length = port->aaaReqDataLen;
781  msg.destIpAddr = context->serverIpAddr;
782  msg.destPort = context->serverPort;
783 
784 #if (ETH_PORT_TAGGING_SUPPORT == ENABLED)
785  //Specify the egress port
786  msg.switchPort = context->serverPortIndex;
787 #endif
788 
789  //Debug message
790  TRACE_INFO("Sending RADIUS packet (%" PRIuSIZE " bytes)...\r\n",
791  port->aaaReqDataLen);
792 
793  //Dump RADIUS header contents for debugging purpose
794  radiusDumpPacket((RadiusPacket *) port->aaaReqData, port->aaaReqDataLen);
795 
796  //Send UDP datagram
797  error = socketSendMsg(context->serverSocket, &msg, 0);
798 
799  //Increment retransmission counter
800  port->aaaRetransCount++;
801  //Set retransmission timeout
802  port->aaaRetransTimer = AUTHENTICATOR_RADIUS_TIMEOUT;
803  }
804 
805  //Return status code
806  return error;
807 }
808 
809 
810 /**
811  * @brief Process incoming RADIUS packet
812  * @param[in] context Pointer to the 802.1X authenticator context
813  **/
814 
816 {
817  error_t error;
818  uint_t i;
819  size_t n;
820  size_t length;
821  SocketMsg msg;
823  EapPacket *eapPacket;
824  const RadiusPacket *packet;
825  const RadiusAttribute *attribute;
826  Md5Context *md5Context;
827  HmacContext *hmacContext;
828  uint8_t digest[MD5_DIGEST_SIZE];
829 
830  //Point to the receive buffer
831  msg = SOCKET_DEFAULT_MSG;
832  msg.data = context->rxBuffer;
834 
835  //Receive EAPOL MPDU
836  error = socketReceiveMsg(context->serverSocket, &msg, 0);
837  //Failed to receive packet
838  if(error)
839  return;
840 
841  //Debug message
842  TRACE_INFO("RADIUS packet received (%" PRIuSIZE " bytes)...\r\n",
843  msg.length);
844 
845 #if (ETH_PORT_TAGGING_SUPPORT == ENABLED)
846  //Check the port number on which the EAPOL PDU was received
847  if(msg.switchPort != context->serverPortIndex && context->serverPortIndex != 0)
848  return;
849 #endif
850 
851  //Ensure the source IP address matches the RADIUS server's IP address
852  if(!ipCompAddr(&msg.srcIpAddr, &context->serverIpAddr))
853  return;
854 
855  //The officially assigned port number for RADIUS is 1812 (refer to RFC 2865,
856  //section 3)
857  if(msg.srcPort != context->serverPort)
858  return;
859 
860  //Malformed RADIUS packet?
861  if(msg.length < sizeof(RadiusPacket))
862  return;
863 
864  //Point to the RADIUS packet
865  packet = (RadiusPacket *) context->rxBuffer;
866 
867  //Octets outside the range of the Length field must be treated as padding
868  //and ignored on reception (refer to RFC 2865, section 3)
869  length = ntohs(packet->length);
870 
871  //If the packet is shorter than the Length field indicates, it must be
872  //silently discarded
873  if(msg.length < length)
874  return;
875 
876  //The minimum length is 20 and maximum length is 4096
878  return;
879 
880  //Dump RADIUS header contents for debugging purpose
881  radiusDumpPacket(packet, length);
882 
883  //Calculate the length of the RADIUS attributes
884  length -= sizeof(RadiusPacket);
885 
886  //The RADIUS packet type is determined by the Code field
887  if(packet->code != RADIUS_CODE_ACCESS_ACCEPT &&
888  packet->code != RADIUS_CODE_ACCESS_REJECT &&
889  packet->code != RADIUS_CODE_ACCESS_CHALLENGE)
890  {
891  return;
892  }
893 
894  //The Identifier field aids in matching requests and replies
895  for(i = 0; i < context->numPorts; i++)
896  {
897  //Point to the current port
898  port = &context->ports[i];
899 
900  //The Identifier field is matched with a pending Access-Request
901  if(port->eapFullAuthState == EAP_FULL_AUTH_STATE_AAA_IDLE &&
902  !port->aaaEapResp)
903  {
904  //Matching identifier?
905  if(port->aaaReqId == packet->identifier)
906  {
907  break;
908  }
909  }
910  }
911 
912  //No matching request found?
913  if(i >= context->numPorts)
914  return;
915 
916  //Point to the MD5 context
917  md5Context = &context->hmacContext.hashContext.md5Context;
918  //Initialize MD5 calculation
919  md5Init(md5Context);
920 
921  //The Response Authenticator contains a one-way MD5 hash calculated over the
922  //RADIUS packet, beginning with the Code field, including the Identifier, the
923  //Length, the Request Authenticator field from the Access-Request packet, and
924  //the response Attributes, followed by the shared secret (refer to RFC 2865,
925  //section 3)
926  md5Update(md5Context, packet, 4);
927  md5Update(md5Context, port->reqAuthenticator, 16);
928  md5Update(md5Context, packet->attributes, length);
929  md5Update(md5Context, context->serverKey, context->serverKeyLen);
930  md5Final(md5Context, digest);
931 
932  //Debug message
933  TRACE_DEBUG("Calculated Response Authenticator:\r\n");
934  TRACE_DEBUG_ARRAY(" ", digest, MD5_DIGEST_SIZE);
935 
936  //The Response Authenticator field must contain the correct response for the
937  //pending Access-Request. Invalid packets are silently discarded
938  if(osMemcmp(digest, packet->authenticator, MD5_DIGEST_SIZE) != 0)
939  {
940  //Debug message
941  TRACE_WARNING("Invalid Response Authenticator value!\r\n");
942  //Exit immediately
943  return;
944  }
945 
946  //The Message-Authenticator attribute must be used to protect all
947  //Access-Request, Access-Challenge, Access-Accept, and Access-Reject
948  //packets containing an EAP-Message attribute (refer to RFC 3579,
949  //section 3.2)
951 
952  //Access-Challenge, Access-Accept, or Access-Reject packets including
953  //EAP-Message attribute(s) without a Message-Authenticator attribute should
954  //be silently discarded by the NAS (refer to RFC 3579, section 3.1)
955  if(attribute == NULL)
956  return;
957 
958  //Malformed Message-Authenticator attribute?
959  if(attribute->length != (sizeof(RadiusAttribute) + MD5_DIGEST_SIZE))
960  return;
961 
962  //Save the offset to the Message-Authenticator value
963  n = attribute->value - packet->attributes;
964 
965  //When the checksum is calculated the signature string should be considered
966  //to be sixteen octets of zero (refer to RFC 2869, section 5.14)
967  osMemset(digest, 0, MD5_DIGEST_SIZE);
968 
969  //Point to the HMAC context
970  hmacContext = &context->hmacContext;
971 
972  //Initialize HMAC-MD5 calculation
973  error = hmacInit(hmacContext, MD5_HASH_ALGO, context->serverKey,
974  context->serverKeyLen);
975  //Any error to report?
976  if(error)
977  return;
978 
979  //For Access-Challenge, Access-Accept, and Access-Reject packets, the
980  //Message-Authenticator is calculated as follows, using the Request-
981  //Authenticator from the Access-Request this packet is in reply to (refer
982  //to RFC 3579, section 3.2)
983  hmacUpdate(hmacContext, packet, 4);
984  hmacUpdate(hmacContext, port->reqAuthenticator, 16);
985  hmacUpdate(hmacContext, packet->attributes, n);
986  hmacUpdate(hmacContext, digest, 16);
987  hmacUpdate(hmacContext, packet->attributes + n + 16, length - n - 16);
988  hmacFinal(hmacContext, digest);
989 
990  //Debug message
991  TRACE_DEBUG("Calculated Message Authenticator:\r\n");
992  TRACE_DEBUG_ARRAY(" ", digest, MD5_DIGEST_SIZE);
993 
994  //A NAS supporting the EAP-Message attribute must calculate the correct
995  //value of the Message-Authenticator and must silently discard the packet
996  //if it does not match the value sent (refer to RFC 3579, section 3.1)
997  if(osMemcmp(digest, attribute->value, MD5_DIGEST_SIZE) != 0)
998  {
999  //Debug message
1000  TRACE_WARNING("Invalid Message Authenticator value!\r\n");
1001  //Exit immediately
1002  return;
1003  }
1004 
1005  //Search the RADIUS packet for the State attribute
1006  attribute = radiusGetAttribute(packet, RADIUS_ATTR_STATE, 0);
1007 
1008  //State attribute found?
1009  if(attribute != NULL)
1010  {
1011  //Retrieve the length of the attribute value
1012  n = attribute->length - sizeof(RadiusAttribute);
1013 
1014  //Check the length of the attribute
1015  if(n >= 1 && n <= AUTHENTICATOR_MAX_STATE_SIZE)
1016  {
1017  //The actual format of the information is site or application
1018  //specific, and a robust implementation should support the field
1019  //as undistinguished octets (refer to RFC 2865, section 5.24)
1020  osMemcpy(port->serverState, attribute->value, n);
1021  port->serverStateLen = n;
1022  }
1023  }
1024 
1025  //EAP-Message attribute(s) encapsulate a single EAP packet which the NAS
1026  //decapsulates and passes on to the authenticating peer
1027  port->aaaEapReqDataLen = 0;
1028 
1029  //Decapsulate the EAP packet
1030  for(i = 0; ; i++)
1031  {
1032  //Point to the next EAP-Message attribute
1033  attribute = radiusGetAttribute(packet, RADIUS_ATTR_EAP_MESSAGE, i);
1034 
1035  //EAP-Message attribute found?
1036  if(attribute != NULL)
1037  {
1038  //Retrieve the length of the fragment
1039  n = attribute->length - sizeof(RadiusAttribute);
1040 
1041  //Make sure the buffer is large enough to hold the reconstructed EAP
1042  //packet
1043  if((port->aaaEapReqDataLen + n) <= AUTHENTICATOR_TX_BUFFER_SIZE)
1044  {
1045  //Copy the current fragment
1046  osMemcpy(context->txBuffer + port->aaaEapReqDataLen,
1047  attribute->value, n);
1048 
1049  //Adjust the length of the reconstructed EAP packet
1050  port->aaaEapReqDataLen += n;
1051  }
1052  else
1053  {
1054  //The reassembly process failed
1055  port->aaaEapReqDataLen = 0;
1056  break;
1057  }
1058  }
1059  else
1060  {
1061  //The reassembly process is now complete
1062  break;
1063  }
1064  }
1065 
1066  //Malformed EAP packet?
1067  if(port->aaaEapReqDataLen < sizeof(EapPacket))
1068  return;
1069 
1070  //Point to the EAP packet
1071  eapPacket = (EapPacket *) context->txBuffer;
1072 
1073  //Check Code field
1074  if(eapPacket->code == EAP_CODE_REQUEST ||
1075  eapPacket->code == EAP_CODE_SUCCESS ||
1076  eapPacket->code == EAP_CODE_FAILURE)
1077  {
1078  //The corresponding request (or success/failure) packet is stored in
1079  //aaaEapReqData
1080  osMemcpy(port->aaaEapReqData, context->txBuffer, port->aaaEapReqDataLen);
1081 
1082  //Debug message
1083  TRACE_DEBUG("Port %" PRIu8 ": Sending EAP packet (%" PRIuSIZE " bytes)...\r\n",
1084  port->portIndex, port->aaaEapReqDataLen);
1085 
1086  //Dump EAP header contents for debugging purpose
1087  eapDumpHeader(eapPacket);
1088 
1089  //When the authenticator has finished processing the message, it sets one
1090  //of the signals aaaEapReq, aaaSuccess, and aaaFail
1091  if(eapPacket->code == EAP_CODE_REQUEST)
1092  {
1093  port->aaaEapReq = TRUE;
1094  }
1095  else if(eapPacket->code == EAP_CODE_SUCCESS)
1096  {
1097  port->aaaSuccess = TRUE;
1098  }
1099  else
1100  {
1101  port->aaaFail = TRUE;
1102  }
1103 
1104  }
1105  else
1106  {
1107  //The aaaEapNoReq flag indicates that the most recent response has been
1108  //processed, but that there is no new request to send
1109  port->aaaEapNoReq = TRUE;
1110  }
1111 
1112  //Invoke EAP to perform whatever processing is needed
1113  authenticatorFsm(port->context);
1114 }
1115 
1116 
1117 /**
1118  * @brief Generate a new RADIUS packet identifier
1119  * @param[in] context Pointer to the 802.1X authenticator context
1120  **/
1121 
1123 {
1124  uint_t i;
1125  bool_t acceptable;
1127 
1128  //Generate a new RADIUS packet identifier
1129  do
1130  {
1131  //Increment identifier value
1132  context->radiusId++;
1133 
1134  //Loop through the ports
1135  for(acceptable = TRUE, i = 0; i < context->numPorts; i++)
1136  {
1137  //Point to the current port
1138  port = &context->ports[i];
1139 
1140  //Pending Access-Request?
1141  if(port->eapFullAuthState == EAP_FULL_AUTH_STATE_AAA_IDLE &&
1142  !port->aaaEapResp)
1143  {
1144  //Check whether the identifier is a duplicate
1145  if(port->aaaReqId == context->radiusId)
1146  {
1147  acceptable = FALSE;
1148  }
1149  }
1150  }
1151 
1152  //Repeat as necessary until a unique identifier value is generated
1153  } while(!acceptable);
1154 
1155  //Return the identifier value
1156  return context->radiusId;
1157 }
1158 
1159 #endif
error_t ethAcceptMacAddr(NetInterface *interface, const MacAddr *macAddr)
Add a unicast/multicast address to the MAC filter table.
Definition: ethernet.c:601
void radiusDumpPacket(const RadiusPacket *packet, size_t length)
Dump RADIUS packet for debugging purpose.
Definition: radius_debug.c:259
#define htons(value)
Definition: cpu_endian.h:413
void netUnlock(NetContext *context)
Release exclusive access to the core of the TCP/IP stack.
Definition: net.c:319
int bool_t
Definition: compiler_port.h:63
HMAC algorithm context.
Definition: hmac.h:59
void eapDumpHeader(const EapPacket *header)
Dump EAP header for debugging purpose.
Definition: eap_debug.c:105
@ AUTHENTICATOR_TERMINATE_CAUSE_PORT_FAILURE
@ RADIUS_SERVICE_TYPE_FRAMED
Framed.
uint32_t destPorts
Definition: nic.h:152
@ RADIUS_ATTR_MESSAGE_AUTHENTICATOR
Message-Authenticator.
@ EAP_CODE_RESPONSE
Response.
Definition: eap.h:153
void authenticatorGeneratePortAddr(AuthenticatorPort *port)
Port's MAC address generation.
signed int int_t
Definition: compiler_port.h:56
IP network address.
Definition: ip.h:94
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:143
RadiusPacket
Definition: radius.h:91
error_t authenticatorSendRadiusRequest(AuthenticatorPort *port)
Send RADIUS Access-Request packet.
@ RADIUS_CODE_ACCESS_REQUEST
Access-Request.
Definition: radius.h:61
void authenticatorProcessRadiusPacket(AuthenticatorContext *context)
Process incoming RADIUS packet.
EapolPdu
Definition: eap.h:211
@ RADIUS_ATTR_STATE
State.
#define TRUE
Definition: os_port.h:50
Message and ancillary data.
Definition: socket.h:241
@ EAP_CODE_FAILURE
Failure.
Definition: eap.h:155
void md5Final(Md5Context *context, uint8_t *digest)
Finish the MD5 message digest.
Ipv6Addr
Definition: ipv6.h:282
@ RADIUS_ATTR_NAS_PORT
NAS-Port.
void * data
Pointer to the payload.
Definition: socket.h:242
#define osMemcmp(p1, p2, length)
Definition: os_port.h:159
error_t ipSelectSourceAddr(NetContext *context, NetInterface **interface, const IpAddr *destAddr, IpAddr *srcAddr)
IP source address selection.
Definition: ip.c:120
error_t authenticatorDropPaeGroupAddr(AuthenticatorContext *context)
Remove the PAE group address from the static MAC table.
@ RADIUS_ATTR_NAS_IPV6_ADDR
NAS-IPv6-Address.
void authenticatorProcessEapPacket(AuthenticatorPort *port, const EapPacket *packet, size_t length)
Process incoming EAP packet.
#define osStrlen(s)
Definition: os_port.h:171
uint8_t authenticatorGetNextRadiusId(AuthenticatorContext *context)
Generate a new RADIUS packet identifier.
uint32_t Ipv4Addr
IPv4 network address.
Definition: ipv4.h:324
Data logging functions for debugging purpose (RADIUS)
@ RADIUS_CODE_ACCESS_REJECT
Access-Reject.
Definition: radius.h:63
error_t ethDropMacAddr(NetInterface *interface, const MacAddr *macAddr)
Remove a unicast/multicast address from the MAC filter table.
Definition: ethernet.c:673
uint16_t ethType
Ethernet type field.
Definition: socket.h:256
void md5Init(Md5Context *context)
Initialize MD5 message digest context.
uint16_t destPort
Destination port.
Definition: socket.h:252
Helper functions for 802.1X authenticator.
Authenticator state machine procedures.
@ RADIUS_PORT_TYPE_ETHERNET
Ethernet.
#define AUTHENTICATOR_MAX_STATE_SIZE
#define RADIUS_MAX_ATTR_VALUE_LEN
error_t socketSendMsg(Socket *socket, const SocketMsg *message, uint_t flags)
Send a message to a connectionless socket.
Definition: socket.c:1664
Formatting and parsing of RADIUS attributes.
#define FALSE
Definition: os_port.h:46
const SocketMsg SOCKET_DEFAULT_MSG
Definition: socket.c:49
@ RADIUS_ATTR_EAP_MESSAGE
EAP-Message.
size_t length
Actual length of the payload, in bytes.
Definition: socket.h:244
@ RADIUS_ATTR_FRAMED_MTU
Framed-MTU.
@ RADIUS_ATTR_CALLING_STATION_ID
Calling-Station-Id.
void eapolDumpHeader(const EapolPdu *header)
Dump EAPOL header for debugging purpose.
Definition: eap_debug.c:85
#define osMemcpy(dest, src, length)
Definition: os_port.h:147
#define RADIUS_MAX_PACKET_SIZE
Definition: radius.h:47
802.1X authenticator
error_t
Error codes.
Definition: error.h:43
bool_t ipCompAddr(const IpAddr *ipAddr1, const IpAddr *ipAddr2)
Compare IP addresses.
Definition: ip.c:318
#define osSprintf(dest,...)
Definition: os_port.h:237
#define AUTHENTICATOR_RADIUS_TIMEOUT
uint8_t pdu[]
#define EAP_MAX_FRAG_SIZE
Definition: eap.h:98
const RadiusAttribute * radiusGetAttribute(const RadiusPacket *packet, uint8_t type, uint_t index)
Search a RADIUS packet for a given attribute.
@ ERROR_INVALID_ADDRESS
Definition: error.h:103
char_t * macAddrToString(const MacAddr *macAddr, char_t *str)
Convert a MAC address to a dash delimited string.
Definition: ethernet.c:926
@ EAPOL_TYPE_EAP
EAPOL-EAP.
Definition: eap.h:134
#define MD5_HASH_ALGO
Definition: md5.h:49
#define NetInterface
Definition: net.h:40
void authenticatorDecrementTimer(uint_t *x)
Decrement timer value.
@ EAP_CODE_SUCCESS
Success.
Definition: eap.h:154
IpAddr srcIpAddr
Source IP address.
Definition: socket.h:249
uint8_t switchPort
Switch port identifier.
Definition: socket.h:259
error_t socketReceiveMsg(Socket *socket, SocketMsg *message, uint_t flags)
Receive a message from a connectionless socket.
Definition: socket.c:1889
error_t netGetMacAddr(NetInterface *interface, MacAddr *macAddr)
Retrieve MAC address.
Definition: net.c:605
MD5 algorithm context.
Definition: md5.h:62
error_t authenticatorBuildRadiusRequest(AuthenticatorPort *port)
Build RADIUS Access-Request packet.
RADIUS (Remote Authentication Dial In User Service)
#define TRACE_INFO(...)
Definition: debug.h:105
@ ETH_TYPE_EAPOL
Definition: ethernet.h:171
uint8_t length
Definition: tcp.h:375
@ RADIUS_ATTR_NAS_PORT_TYPE
NAS-Port-Type.
#define MIN(a, b)
Definition: os_port.h:63
@ RADIUS_CODE_ACCESS_ACCEPT
Access-Accept.
Definition: radius.h:62
Authenticator state machine.
@ EAP_CODE_REQUEST
Request.
Definition: eap.h:152
#define MD5_DIGEST_SIZE
Definition: md5.h:45
@ RADIUS_CODE_ACCESS_CHALLENGE
Access-Challenge.
Definition: radius.h:66
error_t authenticatorSendEapolPdu(AuthenticatorPort *port, const uint8_t *pdu, size_t length)
Send EAPOL PDU.
MacAddr
Definition: ethernet.h:197
MacAddr srcMacAddr
Source MAC address.
Definition: socket.h:254
error_t authenticatorAcceptPaeGroupAddr(AuthenticatorContext *context)
Add the PAE group address to the static MAC table.
RadiusAttribute
@ RADIUS_ATTR_USER_NAME
User-Name.
uint16_t port
Definition: dns_common.h:272
#define ntohs(value)
Definition: cpu_endian.h:421
__weak_func void hmacUpdate(HmacContext *context, const void *data, size_t length)
Update the HMAC context with a portion of the message being hashed.
Definition: hmac.c:201
IpAddr destIpAddr
Destination IP address.
Definition: socket.h:251
#define TRACE_WARNING(...)
Definition: debug.h:93
#define TRACE_DEBUG(...)
Definition: debug.h:119
#define MAX(a, b)
Definition: os_port.h:67
MacAddr destMacAddr
Destination MAC address.
Definition: socket.h:255
char char_t
Definition: compiler_port.h:55
#define AuthenticatorContext
Definition: authenticator.h:36
Data logging functions for debugging purpose (EAP)
@ EAP_FULL_AUTH_STATE_AAA_IDLE
#define TRACE_DEBUG_ARRAY(p, a, n)
Definition: debug.h:120
uint8_t n
void authenticatorTick(AuthenticatorContext *context)
Handle periodic operations.
__weak_func void hmacFinal(HmacContext *context, uint8_t *digest)
Finish the HMAC calculation.
Definition: hmac.c:218
EapPacket
Definition: eap.h:224
MacAddr macAddr
Definition: nic.h:150
@ RADIUS_ATTR_NAS_IP_ADDR
NAS-IP-Address.
uint8_t srcPort
Definition: nic.h:151
@ AUTHENTICATOR_TERMINATE_CAUSE_NOT_TERMINATED_YET
@ RADIUS_ATTR_CALLED_STATION_ID
Called-Station-Id.
@ RADIUS_ATTR_SERVICE_TYPE
Service-Type.
#define macCompAddr(macAddr1, macAddr2)
Definition: ethernet.h:130
size_t size
Size of the payload, in bytes.
Definition: socket.h:243
void authenticatorFsm(AuthenticatorContext *context)
Authenticator state machine implementation.
@ RADIUS_ATTR_NAS_PORT_ID
NAS-Port-Id.
void netLock(NetContext *context)
Get exclusive access to the core of the TCP/IP stack.
Definition: net.c:307
Ipv4Addr ipAddr
Definition: ipcp.h:105
bool_t authenticatorGetLinkState(AuthenticatorPort *port)
Get link state.
void authenticatorProcessEapolPdu(AuthenticatorContext *context)
Process incoming EAPOL PDU.
@ EAPOL_TYPE_LOGOFF
EAPOL-Logoff.
Definition: eap.h:136
#define SWITCH_CPU_PORT_MASK
Definition: nic.h:60
@ EAPOL_TYPE_START
EAPOL-Start.
Definition: eap.h:135
#define PRIuSIZE
unsigned int uint_t
Definition: compiler_port.h:57
#define osMemset(p, value, length)
Definition: os_port.h:141
#define AuthenticatorPort
Definition: authenticator.h:40
__weak_func error_t hmacInit(HmacContext *context, const HashAlgo *hash, const void *key, size_t keyLen)
Initialize HMAC calculation.
Definition: hmac.c:140
uint16_t srcPort
Source port.
Definition: socket.h:250
#define STORE32BE(a, p)
Definition: cpu_endian.h:286
#define AUTHENTICATOR_RX_BUFFER_SIZE
Definition: authenticator.h:85
void md5Update(Md5Context *context, const void *data, size_t length)
Update the MD5 context with a portion of the message being hashed.
@ NO_ERROR
Success.
Definition: error.h:44
uint8_t c
Definition: ndp.h:514
bool_t override
Definition: nic.h:153
#define AUTHENTICATOR_TX_BUFFER_SIZE
Definition: authenticator.h:78
Debugging facilities.
Forwarding database entry.
Definition: nic.h:149
void radiusAddAttribute(RadiusPacket *packet, uint8_t type, const void *value, size_t length)
Append an attribute to a RADIUS packet.