Skip to content

Commit fdb3329

Browse files
authored
Add option to keep attributes as integers (#7)
1 parent 37ccd9e commit fdb3329

2 files changed

Lines changed: 107 additions & 20 deletions

File tree

lib/radius/packet.ex

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
defmodule Radius.Packet do
2+
@moduledoc """
3+
This module defines a struct and provides the main functions to encode and decode requests and replies.
4+
"""
25
require Logger
36

47
alias __MODULE__
8+
require Radius.Dict
59
alias Radius.Dict
610
alias Radius.Dict.EntryNotFoundError
711

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

2731
@doc """
2832
Decode radius packet
33+
34+
Options:
35+
36+
* `attributes` - leave attributes as `:integers`, defaults to `:strings`. `:integers` can be
37+
pattern matched with the macro's provided by `Radius.Dict`
2938
"""
30-
def decode(data, secret) do
39+
def decode(data, secret, opts \\ []) do
40+
attributes_as = Keyword.get(opts, :attributes, :strings)
41+
3142
pkt =
32-
%{raw: data, secret: secret, attrs: nil}
43+
%{raw: data, secret: secret, attrs: nil, attributes_as: attributes_as}
3344
|> decode_header
3445
|> decode_payload
3546

@@ -68,7 +79,8 @@ defmodule Radius.Packet do
6879
defp decode_code(x), do: x
6980

7081
defp decode_payload(ctx) do
71-
decode_tlv(ctx.rest)
82+
ctx.rest
83+
|> decode_tlv()
7284
|> resolve_tlv(ctx)
7385
end
7486

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

95107
# VSA Entry
96108
defp resolve_tlv({26, value}, ctx, nil) do
97-
type = "Vendor-Specific"
109+
type =
110+
if ctx.attributes_as == :integers do
111+
Dict.attr_Vendor_Specific()
112+
else
113+
"Vendor-Specific"
114+
end
115+
98116
<<vid::size(32), rest::binary>> = value
99117

100118
try do
@@ -123,6 +141,13 @@ defmodule Radius.Packet do
123141
attr = vendor_attribute_by_id(vendor, type)
124142
has_tag = Keyword.has_key?(attr.opts, :has_tag)
125143

144+
attr_name =
145+
if ctx.attributes_as == :integers do
146+
type
147+
else
148+
attr.name
149+
end
150+
126151
{tag, value} =
127152
case value do
128153
<<0, rest::binary>> when has_tag == true ->
@@ -142,9 +167,9 @@ defmodule Radius.Packet do
142167
|> decrypt_value(Keyword.get(attr.opts, :encrypt), ctx.auth, ctx.secret)
143168

144169
if tag do
145-
{attr.name, {tag, value}}
170+
{attr_name, {tag, value}}
146171
else
147-
{attr.name, value}
172+
{attr_name, value}
148173
end
149174
rescue
150175
_e in EntryNotFoundError ->
@@ -199,17 +224,17 @@ defmodule Radius.Packet do
199224
end
200225

201226
@doc """
202-
Return an iolist of encoded packet
227+
Return an iolist of encoded packet
203228
204-
for request packets, leave packet.auth == nil, then I will generate one from random bytes.
205-
for reply packets, set packet.auth = request.auth, I will calc the reply hash with it.
229+
for request packets, leave packet.auth == nil, then I will generate one from random bytes.
230+
for reply packets, set packet.auth = request.auth, I will calc the reply hash with it.
206231
207-
packet.attrs :: [attr]
208-
attr :: {type,value}
209-
type :: String.t | integer | {"Vendor-Specific", vendor}
210-
value :: integer | String.t | ipaddr
211-
vendor :: String.t | integer
212-
ipaddr :: {a,b,c,d} | {a,b,c,d,e,f,g,h}
232+
packet.attrs :: [attr]
233+
attr :: {type,value}
234+
type :: String.t | integer | {"Vendor-Specific", vendor}
235+
value :: integer | String.t | ipaddr
236+
vendor :: String.t | integer
237+
ipaddr :: {a,b,c,d} | {a,b,c,d,e,f,g,h}
213238
214239
"""
215240
@deprecated "Use encode_request/1-2 or encode_reply/1-2 instead"
@@ -239,6 +264,10 @@ defmodule Radius.Packet do
239264
@doc """
240265
Encode the reply packet into an iolist and put the result in the `:raw` key. The `:auth` key needs
241266
to be filled with the authenticator of the request packet.
267+
268+
Options:
269+
270+
* `sign` - `true` if you want to sign the request, `false` by default
242271
"""
243272
@spec encode_reply(
244273
packet :: Packet.t(),
@@ -261,7 +290,7 @@ defmodule Radius.Packet do
261290

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

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

442471
defp encode_vsa(vendor, vsa, ctx) do
443-
IO.inspect(vendor)
444-
445472
val =
446473
Enum.map(vsa, fn x ->
447474
x |> resolve_attr(ctx, vendor) |> encode_attr(vendor.format)

test/radius_packet_test.exs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ defmodule Radius.PacketTest do
6262
@sample_rep %Radius.Packet{
6363
code: "Access-Accept",
6464
id: 118,
65-
length: 173,
66-
auth: nil,
65+
length: 155,
66+
auth: <<25, 149, 189, 198, 178, 14, 197, 28, 131, 240, 157, 146, 150, 38, 53, 105>>,
6767
attrs: [
6868
{"NAS-IP-Address", {10, 62, 1, 238}},
6969
{"NAS-Port", 50001},
@@ -110,6 +110,28 @@ defmodule Radius.PacketTest do
110110
assert packet.auth == @sample_req.auth
111111
end
112112

113+
test "decode reply" do
114+
packet = Radius.Packet.decode(@sample_binary_rep, @secret)
115+
116+
assert {"NAS-Port", 50001} = Enum.find(packet.attrs, fn {k, _v} -> k == "NAS-Port" end)
117+
assert packet.code == @sample_rep.code
118+
assert packet.id == @sample_rep.id
119+
assert packet.length == @sample_rep.length
120+
assert packet.auth == @sample_rep.auth
121+
end
122+
123+
test "decode reply - attributes as integer" do
124+
packet = Radius.Packet.decode(@sample_binary_rep, @secret, attributes: :integers)
125+
126+
assert {attr_NAS_Port(), 50001} =
127+
Enum.find(packet.attrs, fn {k, _v} -> k == attr_NAS_Port() end)
128+
129+
assert packet.code == @sample_rep.code
130+
assert packet.id == @sample_rep.id
131+
assert packet.length == @sample_rep.length
132+
assert packet.auth == @sample_rep.auth
133+
end
134+
113135
test "encode request - deprecated" do
114136
# cut authenticator as it will be generated on each encoding
115137
<<before::size(32), _random::size(128), rest::binary>> =
@@ -153,6 +175,44 @@ defmodule Radius.PacketTest do
153175
assert <<before::size(32), rest::binary>> == <<sample_before::size(32), sample_rest::binary>>
154176
end
155177

178+
test "encode request with vsa" do
179+
secret = "112233"
180+
181+
attrs = [
182+
attr_User_Password("1234"),
183+
# tagged attribute (rfc2868)
184+
attr_Tunnel_Type(val_Tunnel_Type_PPTP()),
185+
# equals
186+
{attr_Tunnel_Type(), {0, "PPTP"}},
187+
attr_Tunnel_Type({10, "PPTP"}),
188+
{attr_Service_Type(), "Login-User"},
189+
# tag & value can be integer
190+
{6, 1},
191+
# ipaddr
192+
{attr_NAS_IP_Address(), {1, 2, 3, 4}},
193+
{attr_NAS_IP_Address(), 0x12345678},
194+
# ipv6addr
195+
{attr_Login_IPv6_Host(), {2003, 0xEFFF, 0, 0, 0, 0, 0, 4}},
196+
# VSA
197+
{{attr_Vendor_Specific(), 9},
198+
[
199+
{"Cisco-Disconnect-Cause", 10},
200+
{195, "Unknown"}
201+
]},
202+
# empty VSA?
203+
{{attr_Vendor_Specific(), "Microsoft"}, []},
204+
# some unknown attribute
205+
{255, "123456"}
206+
]
207+
208+
# for request packets, authenticator will generate with random bytes
209+
p = %Radius.Packet{code: "Access-Request", id: 12, secret: secret, attrs: attrs}
210+
# will return an iolist
211+
data = Radius.Packet.encode_request(p) |> Map.get(:raw) |> IO.iodata_to_binary()
212+
213+
assert is_binary(data)
214+
end
215+
156216
test "encode reply" do
157217
reply =
158218
@sample_rep

0 commit comments

Comments
 (0)