1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2018 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
|
17 |
|
-module(mongoose_udp_listener). |
18 |
|
-author('konrad.zemek@erlang-solutions.com'). |
19 |
|
|
20 |
|
-include("mongoose.hrl"). |
21 |
|
|
22 |
|
-type udp_listen_option() :: gen_udp:option() |
23 |
|
| {ip, _} |
24 |
|
| {fd, pos_integer()} |
25 |
|
| {ifaddr, _} |
26 |
|
| inet:address_family() |
27 |
|
| {port, inet:port_number()}. |
28 |
|
|
29 |
|
-export([start_link/6, init/6]). |
30 |
|
|
31 |
|
%% Internal |
32 |
|
-export([recv_loop/3]). |
33 |
|
|
34 |
|
-ignore_xref([start_link/6, init/6]). |
35 |
|
|
36 |
|
%%-------------------------------------------------------------------- |
37 |
|
%% API |
38 |
|
%%-------------------------------------------------------------------- |
39 |
|
|
40 |
|
-spec start_link(Id :: mongoose_listener_config:listener_id(), |
41 |
|
Module :: atom(), |
42 |
|
Opts :: [any(), ...], |
43 |
|
SockOpts :: [udp_listen_option()], |
44 |
|
Port :: inet:port_number(), |
45 |
|
IPS :: [any()]) -> any(). |
46 |
|
start_link(Id, Module, Opts, SockOpts, Port, IPS) -> |
47 |
:-( |
proc_lib:start_link(?MODULE, init, [Id, Module, Opts, SockOpts, Port, IPS]). |
48 |
|
|
49 |
|
-spec init(Id :: mongoose_listener_config:listener_id(), |
50 |
|
Module :: atom(), |
51 |
|
Opts :: [any(), ...], |
52 |
|
SockOpts :: [udp_listen_option()], |
53 |
|
Port :: inet:port_number(), |
54 |
|
IPS :: [any()]) -> no_return(). |
55 |
|
init(Id, Module, Opts, SockOpts, Port, IPS) -> |
56 |
:-( |
case gen_udp:open(Port, [binary, {active, false}, {reuseaddr, true} |
57 |
|
| SockOpts]) of |
58 |
|
{ok, Socket} -> |
59 |
|
%% Inform my parent that this port was opened succesfully |
60 |
:-( |
proc_lib:init_ack({ok, self()}), |
61 |
:-( |
recv_loop(Socket, Module, Opts); |
62 |
|
{error, Reason} -> |
63 |
:-( |
ejabberd_listener:socket_error(Reason, Id, Module, SockOpts, Port, IPS) |
64 |
|
end. |
65 |
|
|
66 |
|
%%-------------------------------------------------------------------- |
67 |
|
%% Helpers |
68 |
|
%%-------------------------------------------------------------------- |
69 |
|
|
70 |
|
-spec recv_loop(Socket :: port(), |
71 |
|
Module :: atom(), |
72 |
|
Opts :: [any(), ...]) -> no_return(). |
73 |
|
recv_loop(Socket, Module, Opts) -> |
74 |
:-( |
case gen_udp:recv(Socket, 0) of |
75 |
|
{ok, {Addr, Port, Packet}} -> |
76 |
:-( |
try Module:udp_recv(Socket, Addr, Port, Packet, Opts) |
77 |
|
catch Class:Reason:Stacktrace -> |
78 |
:-( |
?LOG_ERROR(#{what => udp_listener_recv_failed, |
79 |
|
text => <<"Failed to process UDP packet">>, |
80 |
|
socket => Socket, handler_module => Module, |
81 |
|
ip => Addr, port => Port, |
82 |
|
class => Class, reason => Reason, |
83 |
:-( |
stacktrace => Stacktrace, udp_packet => Packet}) |
84 |
|
end, |
85 |
:-( |
?MODULE:recv_loop(Socket, Module, Opts); |
86 |
|
{error, Reason} -> |
87 |
:-( |
?LOG_ERROR(#{what => udp_listener_recv_failed, |
88 |
|
text => <<"Unexpected UDP error">>, |
89 |
|
socket => Socket, handler_module => Module, |
90 |
:-( |
reason => ejabberd_listener:format_error(Reason)}), |
91 |
:-( |
exit({error, Reason}) |
92 |
|
end. |