1 -module (lwes_net_udp
).
3 -include_lib ("lwes_internal.hrl").
5 % This modules attempts to separate out the low level network details from the
6 % rest of the system. It is by no means necessary to use this as a transport
7 % but the base system assumes UDP or UDP/Multicast, so those forms are
8 % attempted to be encapsulated here. A certain set of options is set by
17 -define (DEFAULT_TTL
, 25).
18 -define (DEFAULT_RECBUF
, 16777216).
20 -record(lwes_net_udp
, { ip
:: inet:ip_address(),
21 port
:: inet:port_number(),
22 is_multicast
= false
:: boolean(),
23 ttl
= ?DEFAULT_TTL
:: non_neg_integer(),
24 recbuf
= ?DEFAULT_RECBUF
:: non_neg_integer(),
27 new (listener
, Config
) ->
28 InitialStruct
= check_config (Config
),
30 #lwes_net_udp
{ ip
= Ip
, is_multicast
= true
, ttl
= TTL
,
31 recbuf
= Recbuf
, options
= ExtraOptions
} ->
32 InitialStruct#lwes_net_udp
{
33 options
= merge_options ([ { ip
, Ip
},
34 { multicast_ttl
, TTL
},
35 { multicast_loop
, false
},
36 { add_membership
, {Ip
, {0,0,0,0}}},
43 #lwes_net_udp
{ is_multicast
= false
,
44 recbuf
= Recbuf
, options
= ExtraOptions
} ->
45 InitialStruct#lwes_net_udp
{
46 options
= merge_options ([ { reuseaddr
, true
},
53 new (emitter
, Config
) ->
54 InitialStruct
= check_config (Config
),
55 InitialOptions
= InitialStruct#lwes_net_udp
.options
,
56 TTL
= InitialStruct#lwes_net_udp
.ttl
,
58 % emitters don't care about most options, but including ttl
59 % means we can use the same emitter for multicast as well as
61 InitialStruct#lwes_net_udp
{
62 options
= merge_options ([ { multicast_ttl
, TTL
},
68 open (listener
, #lwes_net_udp
{ port
= Port
, options
= Options
}) ->
69 gen_udp:open (Port
, Options
);
70 open (emitter
, #lwes_net_udp
{ options
= Options
}) ->
71 gen_udp:open (0, Options
).
73 % allow some of the other forms of config, so the config structure can
74 % just be reused when sending in most cases
75 send (Socket
, {Ip
, Port
, _
}, Packet
) ->
76 gen_udp:send (Socket
, Ip
, Port
, Packet
);
77 send (Socket
, {Ip
, Port
}, Packet
) ->
78 gen_udp:send (Socket
, Ip
, Port
, Packet
);
79 send (Socket
, #lwes_net_udp
{ ip
= Ip
, port
= Port
}, Packet
) ->
80 gen_udp:send (Socket
, Ip
, Port
, Packet
).
83 gen_udp:close (Socket
).
85 address (#lwes_net_udp
{ ip
= Ip
, port
= Port
}) ->
88 %%====================================================================
90 %%====================================================================
92 % this will merge in any passed in Overrides options on top of any defaults
93 % listed. it's currently assumed there are no raw defaults
94 merge_options (Defaults
, Overrides
) ->
95 % first split into options and raw options
96 {OverrideOpts
, OverrideRaw
} = partition (Overrides
),
97 % then merge, preferring overrides and add back the raw at the end
98 lists:ukeymerge(1, lists:sort(OverrideOpts
), lists:sort(Defaults
))
101 % gen_udp has a long list of mostly consistent options, the few outliers are
102 % binary | list - these can also be specified as {mode, binary | list}
103 % {raw,_,_,_} - these do not have a 2-tuple form
104 % this function will normalize on {mode, binary | list} and separate out
105 % {raw,_,_,_} options, resulting in two lists
106 partition (Options
) ->
107 lists:foldl( fun (list, {A
,O
}) -> {[{mode
,list} | A
],O
};
108 (binary, {A
,O
}) -> {[{mode
,binary} | A
],O
};
109 (R
= {raw
,_
,_
,_
},{A
,O
}) -> {A
,[R
|O
]};
110 (Other
, {A
,O
}) -> {[Other
|A
],O
}
115 parse_options ([], AccumlatedOptions
) ->
117 % some options are simply passed through
118 parse_options ([{recbuf
, Recbuf
} | Rest
], N
) ->
119 parse_options (Rest
, N#lwes_net_udp
{ recbuf
= Recbuf
});
120 % others have slightly different naming, so are changed
121 parse_options ([{ttl
, TTL
} | Rest
], N
) ->
122 parse_options (Rest
, N#lwes_net_udp
{ ttl
= TTL
});
123 % check_config options which get converted to raw options
124 parse_options ([reuseport
| Rest
],
125 N
= #lwes_net_udp
{ options
= OptionsIn
}) ->
126 parse_options (Rest
, N#lwes_net_udp
{ options
= reuseport() ++ OptionsIn
});
127 % finally options which we assume are correct for now
128 parse_options ([O
| Rest
],
129 N
= #lwes_net_udp
{ options
= OptionsIn
}) ->
130 parse_options (Rest
, N#lwes_net_udp
{ options
= [ O
| OptionsIn
]}).
132 is_multicast ({N1
, _
, _
, _
}) when N1
>= 224, N1
=< 239 ->
140 [ {raw
, 1, 15, <<1:32/native
>>} ];
141 {unix
, OS
} when OS
=:= darwin
;
145 [ {raw
, 16#ffff
, 16#
0200, <<1:32/native
>>} ];
149 check_ip (Ip
) when ?
is_ip_addr (Ip
) ->
151 check_ip (IpList
) when is_list (IpList
) ->
152 case inet_parse:address (IpList
) of
158 % essentially turns function_clause error into badarg
159 erlang:error (badarg
).
161 check_port(Port
) when ?
is_uint16 (Port
) ->
164 % essentially turns function_clause error into badarg
165 erlang:error (badarg
).
167 check_config ({Ip
, Port
}) ->
168 CheckedIp
= check_ip (Ip
),
169 #lwes_net_udp
{ ip
= CheckedIp
,
170 port
= check_port(Port
),
171 is_multicast
= is_multicast(CheckedIp
) };
172 check_config ({Ip
, Port
, Options
}) when is_list (Options
) ->
173 parse_options (Options
, check_config ({Ip
,Port
}));
175 % essentially turns function_clause error into badarg
176 erlang:error (badarg
).
179 %%--------------------------------------------------------------------
181 %%--------------------------------------------------------------------
183 -include_lib ("eunit/include/eunit.hrl").
186 EmitterConfig
= new (emitter
, Config
),
187 ListenerConfig
= new (listener
, Config
),
188 {ok
, Emitter
} = lwes_net_udp:open(emitter
, EmitterConfig
),
189 {ok
, Listener
}= lwes_net_udp:open(listener
, ListenerConfig
),
191 lwes_net_udp:send(Emitter
, EmitterConfig
, Input
),
192 Output
= receive {udp
,_
,_
,_
,P
} -> P
end,
193 lwes_net_udp:close(Listener
),
194 lwes_net_udp:close(Emitter
),
195 ?
assertEqual (Input
, Output
).
197 % test emission and receipt with various options
198 lwes_net_udp_test_ () ->
201 fun () -> test_one (C
) end
205 {"127.0.0.1",12321,[{ttl
,3}]},
206 {"127.0.0.1",12321,[{ttl
,12},{recbuf
, 65535}]},
207 {"224.1.1.111",12321,[{multicast_loop
, true
}]}
212 % test address functionality
213 lwes_net_udp_address_test_ () ->
215 ?
_assertEqual (Expected
, address(Given
))
218 {{{127,0,0,1},9191}, new(emitter
, {"127.0.0.1",9191})}
222 % test various error cases
223 lwes_net_udp_error_test_ () ->
225 ?
_assertError (badarg
, new(emitter
,{foo
,bar
})),
226 ?
_assertError (badarg
, new(emitter
,{"256.0.0.0",99})),
227 ?
_assertError (badarg
, new(emitter
,{"127.0.0.1",99999}))
230 % test options merging
231 lwes_net_udp_options_test_ () ->
233 ?
_assertEqual(Expected
, merge_options (Defaults
, Overrides
))
234 || {Expected
, Defaults
, Overrides
}
236 { [{mode
,list}], [{mode
,binary}], [list] },
237 { [{mode
,binary}], [{mode
,binary}], [binary] },
238 { [{mode
,list},{raw
,a
,b
,c
}], [{mode
,binary}], [list,{raw
,a
,b
,c
}] },
239 { [{add_membership
,{{127,0,0,1},{0,0,0,0}}},
242 {multicast_loop
,true
},
248 {multicast_loop
, false
},
249 {add_membership
, {{127,0,0,1}, {0,0,0,0}}},
254 [{multicast_loop
, true
}]
260 check_config_test_ () ->
262 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
263 is_multicast
= false
,
264 ttl
= 3, recbuf
= 65535},
265 check_config ({{127,0,0,1},9191,[{ttl
,3},{recbuf
,65535}]})),
266 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
267 is_multicast
= false
,
268 ttl
= 3, recbuf
= 65535},
269 check_config ({"127.0.0.1",9191,[{ttl
,3},{recbuf
,65535}]})),
270 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
271 is_multicast
= false
,
272 ttl
= 3, recbuf
= 65535,
273 options
= reuseport()},
274 check_config ({"127.0.0.1",9191,
275 [{ttl
,3}, {recbuf
,65535}, reuseport
]})),
276 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
277 is_multicast
= false
,
278 ttl
= 3, recbuf
= ?DEFAULT_RECBUF
},
279 check_config ({"127.0.0.1",9191,[{ttl
,3}]})),
280 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
281 is_multicast
= false
,
282 ttl
= 3, recbuf
= ?DEFAULT_RECBUF
},
283 check_config ({{127,0,0,1},9191,[{ttl
,3}]})),
284 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
285 is_multicast
= false
,
286 ttl
= ?DEFAULT_TTL
, recbuf
= ?DEFAULT_RECBUF
},
287 check_config ({"127.0.0.1",9191})),
288 ?
_assertEqual (#lwes_net_udp
{ip
= {127,0,0,1}, port
= 9191,
289 is_multicast
= false
,
290 ttl
= ?DEFAULT_TTL
, recbuf
= ?DEFAULT_RECBUF
},
291 check_config ({{127,0,0,1},9191})),
292 ?
_assertError (badarg
, check_config ({"655.0.0.1",9191,3})),
293 ?
_assertError (badarg
, check_config ({"655.0.0.1",9191,65})),
294 ?
_assertError (badarg
, check_config ({"655.0.0.1",9191})),
295 ?
_assertError (badarg
, check_config ({{655,0,0,1},9191})),
296 ?
_assertError (badarg
, check_config ({{127,0,0,1},91919}))