Skip to content

Commit

Permalink
Add option to keep attributes as integers (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
tverlaan committed Jun 14, 2023
1 parent 37ccd9e commit fdb3329
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 20 deletions.
63 changes: 45 additions & 18 deletions lib/radius/packet.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule Radius.Packet do
@moduledoc """
This module defines a struct and provides the main functions to encode and decode requests and replies.
"""
require Logger

alias __MODULE__
require Radius.Dict
alias Radius.Dict
alias Radius.Dict.EntryNotFoundError

Expand All @@ -26,10 +30,17 @@ defmodule Radius.Packet do

@doc """
Decode radius packet
Options:
* `attributes` - leave attributes as `:integers`, defaults to `:strings`. `:integers` can be
pattern matched with the macro's provided by `Radius.Dict`
"""
def decode(data, secret) do
def decode(data, secret, opts \\ []) do
attributes_as = Keyword.get(opts, :attributes, :strings)

pkt =
%{raw: data, secret: secret, attrs: nil}
%{raw: data, secret: secret, attrs: nil, attributes_as: attributes_as}
|> decode_header
|> decode_payload

Expand Down Expand Up @@ -68,7 +79,8 @@ defmodule Radius.Packet do
defp decode_code(x), do: x

defp decode_payload(ctx) do
decode_tlv(ctx.rest)
ctx.rest
|> decode_tlv()
|> resolve_tlv(ctx)
end

Expand All @@ -94,7 +106,13 @@ defmodule Radius.Packet do

# VSA Entry
defp resolve_tlv({26, value}, ctx, nil) do
type = "Vendor-Specific"
type =
if ctx.attributes_as == :integers do
Dict.attr_Vendor_Specific()
else
"Vendor-Specific"
end

<<vid::size(32), rest::binary>> = value

try do
Expand Down Expand Up @@ -123,6 +141,13 @@ defmodule Radius.Packet do
attr = vendor_attribute_by_id(vendor, type)
has_tag = Keyword.has_key?(attr.opts, :has_tag)

attr_name =
if ctx.attributes_as == :integers do
type
else
attr.name
end

{tag, value} =
case value do
<<0, rest::binary>> when has_tag == true ->
Expand All @@ -142,9 +167,9 @@ defmodule Radius.Packet do
|> decrypt_value(Keyword.get(attr.opts, :encrypt), ctx.auth, ctx.secret)

if tag do
{attr.name, {tag, value}}
{attr_name, {tag, value}}
else
{attr.name, value}
{attr_name, value}
end
rescue
_e in EntryNotFoundError ->
Expand Down Expand Up @@ -199,17 +224,17 @@ defmodule Radius.Packet do
end

@doc """
Return an iolist of encoded packet
Return an iolist of encoded packet
for request packets, leave packet.auth == nil, then I will generate one from random bytes.
for reply packets, set packet.auth = request.auth, I will calc the reply hash with it.
for request packets, leave packet.auth == nil, then I will generate one from random bytes.
for reply packets, set packet.auth = request.auth, I will calc the reply hash with it.
packet.attrs :: [attr]
attr :: {type,value}
type :: String.t | integer | {"Vendor-Specific", vendor}
value :: integer | String.t | ipaddr
vendor :: String.t | integer
ipaddr :: {a,b,c,d} | {a,b,c,d,e,f,g,h}
packet.attrs :: [attr]
attr :: {type,value}
type :: String.t | integer | {"Vendor-Specific", vendor}
value :: integer | String.t | ipaddr
vendor :: String.t | integer
ipaddr :: {a,b,c,d} | {a,b,c,d,e,f,g,h}
"""
@deprecated "Use encode_request/1-2 or encode_reply/1-2 instead"
Expand Down Expand Up @@ -239,6 +264,10 @@ defmodule Radius.Packet do
@doc """
Encode the reply packet into an iolist and put the result in the `:raw` key. The `:auth` key needs
to be filled with the authenticator of the request packet.
Options:
* `sign` - `true` if you want to sign the request, `false` by default
"""
@spec encode_reply(
packet :: Packet.t(),
Expand All @@ -261,7 +290,7 @@ defmodule Radius.Packet do

packet =
if sign? do
attrs = [{"Message-Authenticator", <<0::size(128)>>} | packet.attrs]
attrs = [Dict.attr_Message_Authenticator(<<0::size(128)>>) | packet.attrs]

%{packet | attrs: attrs}
else
Expand Down Expand Up @@ -440,8 +469,6 @@ defmodule Radius.Packet do
do: encode_vsa(Dict.vendor_by_id(vid), vsa, ctx)

defp encode_vsa(vendor, vsa, ctx) do
IO.inspect(vendor)

val =
Enum.map(vsa, fn x ->
x |> resolve_attr(ctx, vendor) |> encode_attr(vendor.format)
Expand Down
64 changes: 62 additions & 2 deletions test/radius_packet_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ defmodule Radius.PacketTest do
@sample_rep %Radius.Packet{
code: "Access-Accept",
id: 118,
length: 173,
auth: nil,
length: 155,
auth: <<25, 149, 189, 198, 178, 14, 197, 28, 131, 240, 157, 146, 150, 38, 53, 105>>,
attrs: [
{"NAS-IP-Address", {10, 62, 1, 238}},
{"NAS-Port", 50001},
Expand Down Expand Up @@ -110,6 +110,28 @@ defmodule Radius.PacketTest do
assert packet.auth == @sample_req.auth
end

test "decode reply" do
packet = Radius.Packet.decode(@sample_binary_rep, @secret)

assert {"NAS-Port", 50001} = Enum.find(packet.attrs, fn {k, _v} -> k == "NAS-Port" end)
assert packet.code == @sample_rep.code
assert packet.id == @sample_rep.id
assert packet.length == @sample_rep.length
assert packet.auth == @sample_rep.auth
end

test "decode reply - attributes as integer" do
packet = Radius.Packet.decode(@sample_binary_rep, @secret, attributes: :integers)

assert {attr_NAS_Port(), 50001} =
Enum.find(packet.attrs, fn {k, _v} -> k == attr_NAS_Port() end)

assert packet.code == @sample_rep.code
assert packet.id == @sample_rep.id
assert packet.length == @sample_rep.length
assert packet.auth == @sample_rep.auth
end

test "encode request - deprecated" do
# cut authenticator as it will be generated on each encoding
<<before::size(32), _random::size(128), rest::binary>> =
Expand Down Expand Up @@ -153,6 +175,44 @@ defmodule Radius.PacketTest do
assert <<before::size(32), rest::binary>> == <<sample_before::size(32), sample_rest::binary>>
end

test "encode request with vsa" do
secret = "112233"

attrs = [
attr_User_Password("1234"),
# tagged attribute (rfc2868)
attr_Tunnel_Type(val_Tunnel_Type_PPTP()),
# equals
{attr_Tunnel_Type(), {0, "PPTP"}},
attr_Tunnel_Type({10, "PPTP"}),
{attr_Service_Type(), "Login-User"},
# tag & value can be integer
{6, 1},
# ipaddr
{attr_NAS_IP_Address(), {1, 2, 3, 4}},
{attr_NAS_IP_Address(), 0x12345678},
# ipv6addr
{attr_Login_IPv6_Host(), {2003, 0xEFFF, 0, 0, 0, 0, 0, 4}},
# VSA
{{attr_Vendor_Specific(), 9},
[
{"Cisco-Disconnect-Cause", 10},
{195, "Unknown"}
]},
# empty VSA?
{{attr_Vendor_Specific(), "Microsoft"}, []},
# some unknown attribute
{255, "123456"}
]

# for request packets, authenticator will generate with random bytes
p = %Radius.Packet{code: "Access-Request", id: 12, secret: secret, attrs: attrs}
# will return an iolist
data = Radius.Packet.encode_request(p) |> Map.get(:raw) |> IO.iodata_to_binary()

assert is_binary(data)
end

test "encode reply" do
reply =
@sample_rep
Expand Down

0 comments on commit fdb3329

Please sign in to comment.