/// <summary> /// Get the raw packet bytes /// </summary> /// <returns></returns> public Byte[] GetBytes(RadiusDictionary dictionary) { var packetBytes = new List <Byte> { (Byte)Code, Identifier }; packetBytes.AddRange(new Byte[18]); // Placeholder for length and authenticator var messageAuthenticatorPosition = 0; foreach (var attribute in Attributes) { // todo add logic to check attribute object type matches type in dictionary? foreach (var value in attribute.Value) { var contentBytes = GetAttributeValueBytes(value); var headerBytes = new Byte[2]; dictionary.AttributeNames.TryGetValue(attribute.Key, out var attributeType); switch (attributeType) { case DictionaryVendorAttribute _attributeType: headerBytes = new Byte[8]; headerBytes[0] = 26; // VSA type var vendorId = BitConverter.GetBytes(_attributeType.VendorId); Array.Reverse(vendorId); Buffer.BlockCopy(vendorId, 0, headerBytes, 2, 4); headerBytes[6] = (Byte)_attributeType.VendorCode; headerBytes[7] = (Byte)(2 + contentBytes.Length); // length of the vsa part break; case DictionaryAttribute _attributeType: headerBytes[0] = attributeType.Code; // Encrypt password if this is a User-Password attribute if (_attributeType.Code == 2) { contentBytes = RadiusPassword.Encrypt(SharedSecret, Authenticator, contentBytes); } else if (_attributeType.Code == 80) // Remember the position of the message authenticator, because it has to be added after everything else { messageAuthenticatorPosition = packetBytes.Count; } break; default: throw new InvalidOperationException($"Unknown attribute {attribute.Key}, check spelling or dictionary"); } headerBytes[1] = (Byte)(headerBytes.Length + contentBytes.Length); packetBytes.AddRange(headerBytes); packetBytes.AddRange(contentBytes); } } // Note the order of the bytes... var packetLengthBytes = BitConverter.GetBytes(packetBytes.Count); packetBytes[2] = packetLengthBytes[1]; packetBytes[3] = packetLengthBytes[0]; var packetBytesArray = packetBytes.ToArray(); if (Code == PacketCode.AccountingRequest || Code == PacketCode.DisconnectRequest) { var authenticator = CalculateRequestAuthenticator(SharedSecret, packetBytesArray); Buffer.BlockCopy(authenticator, 0, packetBytesArray, 4, 16); } else { var authenticator = _requestAuthenticator != null?CalculateResponseAuthenticator(SharedSecret, _requestAuthenticator, packetBytesArray) : Authenticator; Buffer.BlockCopy(authenticator, 0, packetBytesArray, 4, 16); } if (messageAuthenticatorPosition != 0) { var temp = new Byte[16]; Buffer.BlockCopy(temp, 0, packetBytesArray, messageAuthenticatorPosition + 2, 16); var messageAuthenticatorBytes = CalculateMessageAuthenticator(packetBytesArray, SharedSecret); Buffer.BlockCopy(messageAuthenticatorBytes, 0, packetBytesArray, messageAuthenticatorPosition + 2, 16); } return(packetBytesArray); }
/// <summary> /// Parses packet bytes and returns an IRadiusPacket /// </summary> /// <param name="packetBytes"></param> /// <param name="dictionary"></param> /// <param name="sharedSecret"></param> public static IRadiusPacket Parse(Byte[] packetBytes, RadiusDictionary dictionary, Byte[] sharedSecret) { var packetLength = BitConverter.ToUInt16(packetBytes.Skip(2).Take(2).Reverse().ToArray(), 0); if (packetBytes.Length != packetLength) { throw new InvalidOperationException($"Packet length does not match, expected: {packetLength}, actual: {packetBytes.Length}"); } var packet = new RadiusPacket { SharedSecret = sharedSecret, Identifier = packetBytes[1], Code = (PacketCode)packetBytes[0], }; Buffer.BlockCopy(packetBytes, 4, packet.Authenticator, 0, 16); if (packet.Code == PacketCode.AccountingRequest || packet.Code == PacketCode.DisconnectRequest) { if (!packet.Authenticator.SequenceEqual(CalculateRequestAuthenticator(packet.SharedSecret, packetBytes))) { throw new InvalidOperationException($"Invalid request authenticator in packet {packet.Identifier}, check secret?"); } } // The rest are attribute value pairs var position = 20; var messageAuthenticatorPosition = 0; while (position < packetBytes.Length) { var typecode = packetBytes[position]; var length = packetBytes[position + 1]; if (position + length > packetLength) { throw new ArgumentOutOfRangeException("Go home roamserver, youre drunk"); } var contentBytes = new Byte[length - 2]; Buffer.BlockCopy(packetBytes, position + 2, contentBytes, 0, length - 2); try { if (typecode == 26) // VSA { var vsa = new VendorSpecificAttribute(contentBytes); var vendorAttributeDefinition = dictionary.VendorSpecificAttributes.FirstOrDefault(o => o.VendorId == vsa.VendorId && o.VendorCode == vsa.VendorCode); if (vendorAttributeDefinition == null) { _log.Info($"Unknown vsa: {vsa.VendorId}:{vsa.VendorCode}"); } else { try { var content = ParseContentBytes(vsa.Value, vendorAttributeDefinition.Type, typecode, packet.Authenticator, packet.SharedSecret); packet.AddAttributeObject(vendorAttributeDefinition.Name, content); } catch (Exception ex) { _log.Error($"Something went wrong with vsa {vendorAttributeDefinition.Name}", ex); } } } else { var attributeDefinition = dictionary.Attributes[typecode]; if (attributeDefinition.Code == 80) { messageAuthenticatorPosition = position; } try { var content = ParseContentBytes(contentBytes, attributeDefinition.Type, typecode, packet.Authenticator, packet.SharedSecret); packet.AddAttributeObject(attributeDefinition.Name, content); } catch (Exception ex) { _log.Error($"Something went wrong with {attributeDefinition.Name}", ex); _log.Debug($"Attribute bytes: {contentBytes.ToHexString()}"); } } } catch (KeyNotFoundException) { _log.Warn($"Attribute {typecode} not found in dictionary"); } catch (Exception ex) { _log.Error($"Something went wrong parsing attribute {typecode}", ex); } position += length; } if (messageAuthenticatorPosition != 0) { var messageAuthenticator = packet.GetAttribute <Byte[]>("Message-Authenticator"); var temp = new Byte[16]; Buffer.BlockCopy(temp, 0, packetBytes, messageAuthenticatorPosition + 2, 16); var calculatedMessageAuthenticator = CalculateMessageAuthenticator(packetBytes, sharedSecret); if (!calculatedMessageAuthenticator.SequenceEqual(messageAuthenticator)) { throw new InvalidOperationException($"Invalid Message-Authenticator in packet {packet.Identifier}"); } } return(packet); }
/// <summary> /// Tries to get a packet from the stream. Returns true if successful /// Returns false if no packet could be parsed or stream is empty ie closing /// </summary> /// <param name="stream"></param> /// <param name="packet"></param> /// <param name="dictionary"></param> /// <returns></returns> public static Boolean TryParsePacketFromStream(Stream stream, out IRadiusPacket packet, RadiusDictionary dictionary, Byte[] sharedSecret) { var packetHeaderBytes = new Byte[4]; var i = stream.Read(packetHeaderBytes, 0, 4); if (i != 0) { try { var packetLength = BitConverter.ToUInt16(packetHeaderBytes.Reverse().ToArray(), 0); var packetContentBytes = new Byte[packetLength - 4]; stream.Read(packetContentBytes, 0, packetContentBytes.Length); packet = Parse(packetHeaderBytes.Concat(packetContentBytes).ToArray(), dictionary, sharedSecret); return(true); } catch (Exception ex) { _log.Warn("Unable to parse packet from stream", ex); } } packet = null; return(false); }