1 %%%-------------------------------------------------------------------
2 %%% File : etorrent_bad_peer_mgr.erl
3 %%% Author : Jesper Louis Andersen <jlouis@ogre.home>
4 %%% Description : Peer management server
6 %%% Created : 19 Jul 2008 by Jesper Louis Andersen <jlouis@ogre.home>
7 %%%-------------------------------------------------------------------
9 %%% TODO: Monitor peers and retry them. In general, we need peer management here.
10 -module(etorrent_peer_mgr
).
12 -include("etorrent_mnesia_table.hrl").
13 -include("etorrent_bad_peer.hrl").
15 -behaviour(gen_server
).
18 -export([start_link
/1, enter_bad_peer
/3, bad_peer_list
/0, add_peers
/2,
21 %% gen_server callbacks
22 -export([init
/1, handle_call
/3, handle_cast
/2, handle_info
/2,
23 terminate
/2, code_change
/3]).
25 -record(state
, { our_peer_id
,
26 available_peers
= []}).
28 -define(SERVER
, ?MODULE
).
29 -define(DEFAULT_BAD_COUNT
, 2).
30 -define(GRACE_TIME
, 900).
31 -define(CHECK_TIME
, timer:seconds(300)).
33 %%====================================================================
35 %%====================================================================
36 %%--------------------------------------------------------------------
37 %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
38 %% Description: Starts the server
39 %%--------------------------------------------------------------------
40 start_link(OurPeerId
) ->
41 gen_server:start_link({local
, ?SERVER
}, ?MODULE
, [OurPeerId
], []).
43 enter_bad_peer(IP
, Port
, PeerId
) ->
44 gen_server:cast(?SERVER
, {enter_bad_peer
, IP
, Port
, PeerId
}).
46 add_peers(TorrentId
, IPList
) ->
47 gen_server:cast(?SERVER
, {add_peers
,
48 [{TorrentId
, {IP
, Port
}} || {IP
, Port
} <- IPList
]}).
50 bad_peer(IP
, Port
, TorrentId
) ->
51 case ets:lookup(etorrent_bad_peer
, {IP
, Port
}) of
53 etorrent_peer:connected(IP
, Port
, TorrentId
);
55 P#bad_peer
.offenses
> ?DEFAULT_BAD_COUNT
59 ets:match(etorrent_bad_peer
, '_').
61 %%====================================================================
62 %% gen_server callbacks
63 %%====================================================================
65 %%--------------------------------------------------------------------
66 %% Function: init(Args) -> {ok, State} |
67 %% {ok, State, Timeout} |
70 %% Description: Initiates the server
71 %%--------------------------------------------------------------------
73 process_flag(trap_exit
, true
),
74 _Tref
= timer:send_interval(?CHECK_TIME
, self(), cleanup_table
),
75 _Tid
= ets:new(etorrent_bad_peer
, [set
, protected
, named_table
,
76 {keypos
, #bad_peer
.ipport
}]),
77 {ok
, #state
{ our_peer_id
= OurPeerId
}}.
79 %%--------------------------------------------------------------------
80 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
81 %% {reply, Reply, State, Timeout} |
83 %% {noreply, State, Timeout} |
84 %% {stop, Reason, Reply, State} |
85 %% {stop, Reason, State}
86 %% Description: Handling call messages
87 %%--------------------------------------------------------------------
88 %% A peer is bad if it has offended us or if it is already connected.
89 handle_call({is_bad_peer
, IP
, Port
, TorrentId
}, _From
, S
) ->
90 Reply
= bad_peer(IP
, Port
, TorrentId
),
92 handle_call(_Request
, _From
, State
) ->
94 {reply
, Reply
, State
}.
96 %%--------------------------------------------------------------------
97 %% Function: handle_cast(Msg, State) -> {noreply, State} |
98 %% {noreply, State, Timeout} |
99 %% {stop, Reason, State}
100 %% Description: Handling cast messages
101 %%--------------------------------------------------------------------
102 handle_cast({add_peers
, IPList
}, S
) ->
103 NS
= start_new_peers(IPList
, S
),
105 handle_cast({enter_bad_peer
, IP
, Port
, PeerId
}, S
) ->
106 case ets:lookup(etorrent_bad_peer
, {IP
, Port
}) of
108 ets:insert(etorrent_bad_peer
,
109 #bad_peer
{ ipport
= {IP
, Port
},
112 last_offense
= now() });
114 ets:insert(etorrent_bad_peer
,
115 P#bad_peer
{ offenses
= P#bad_peer
.offenses
+ 1,
117 last_offense
= now() })
120 handle_cast(_Msg
, State
) ->
123 %%--------------------------------------------------------------------
124 %% Function: handle_info(Info, State) -> {noreply, State} |
125 %% {noreply, State, Timeout} |
126 %% {stop, Reason, State}
127 %% Description: Handling all non call/cast messages
128 %%--------------------------------------------------------------------
129 handle_info(cleanup_table
, S
) ->
130 Bound
= etorrent_time:now_subtract_seconds(now(), ?GRACE_TIME
),
131 _N
= ets:select_delete(etorrent_bad_peer
,
132 [{#bad_peer
{ last_offense
= '$1', _
='_'},
133 [{'<','$1',{Bound
}}],
136 handle_info(_Info
, State
) ->
139 %%--------------------------------------------------------------------
140 %% Function: terminate(Reason, State) -> void()
141 %% Description: This function is called by a gen_server when it is about to
142 %% terminate. It should be the opposite of Module:init/1 and do any necessary
143 %% cleaning up. When it returns, the gen_server terminates with Reason.
144 %% The return value is ignored.
145 %%--------------------------------------------------------------------
146 terminate(_Reason
, _State
) ->
147 ets:delete(etorrent_bad_peer
),
150 %%--------------------------------------------------------------------
151 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
152 %% Description: Convert process state when code is changed
153 %%--------------------------------------------------------------------
154 code_change(_OldVsn
, State
, _Extra
) ->
157 %%--------------------------------------------------------------------
158 %%% Internal functions
159 %%--------------------------------------------------------------------
162 start_new_peers(IPList
, State
) ->
163 %% Update the PeerList with the new incoming peers
164 PeerList
= lists:usort(IPList
++ State#state
.available_peers
),
165 S
= State#state
{ available_peers
= PeerList
},
167 %% Replenish the connected peers.
170 %%% NOTE: fill_peers/2 and spawn_new_peer/5 tail calls each other.
172 case S#state
.available_peers
of
174 % No peers available, just stop trying to fill peers
176 [{TorrentId
, {IP
, Port
}} | R
] ->
177 % Possible peer. Check it.
178 case bad_peer(IP
, Port
, TorrentId
) of
180 fill_peers(S#state
{available_peers
= R
});
182 error_logger:info_report([spawning
, {ip
, IP
},
183 {port
, Port
}, {tid
, TorrentId
}]),
184 spawn_new_peer(IP
, Port
, TorrentId
,
185 S#state
{available_peers
= R
})
189 %%--------------------------------------------------------------------
190 %% Function: spawn_new_peer(IP, Port, S) -> S
191 %% Args: IP ::= ip_address()
192 %% Port ::= integer() (16-bit)
194 %% Description: Attempt to spawn the peer at IP/Port. Returns modified state.
195 %%--------------------------------------------------------------------
196 spawn_new_peer(IP
, Port
, TorrentId
, S
) ->
197 case etorrent_peer:connected(IP
, Port
, TorrentId
) of
201 {atomic
, [TM
]} = etorrent_tracking_map:select(TorrentId
),
202 case etorrent_counters:obtain_peer_slot() of
205 {ok
, Pid
} = etorrent_t_sup:add_peer(
206 TM#tracking_map
.supervisor_pid
,
208 TM#tracking_map
.info_hash
,
211 ok
= etorrent_t_peer_recv:connect(Pid
, IP
, Port
)
213 throw:_
-> etorrent_counters:release_peer_slot();
214 exit:_
-> etorrent_counters:release_peer_slot()