Handle HAVE_ALL and HAVE_NONE. Cleanup the BITFIELD message.
[etorrent.git] / lib / etorrent-1.0 / src / etorrent_peer_mgr.erl
blobd9c0f152cacff7f3928396f643869791e6d9c2e3
1 %%%-------------------------------------------------------------------
2 %%% File : etorrent_bad_peer_mgr.erl
3 %%% Author : Jesper Louis Andersen <jlouis@ogre.home>
4 %%% Description : Peer management server
5 %%%
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).
17 %% API
18 -export([start_link/1, enter_bad_peer/3, bad_peer_list/0, add_peers/2,
19 bad_peer/3]).
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 %%====================================================================
34 %% API
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
52 [] ->
53 etorrent_peer:connected(IP, Port, TorrentId);
54 [P] ->
55 P#bad_peer.offenses > ?DEFAULT_BAD_COUNT
56 end.
58 bad_peer_list() ->
59 ets:match(etorrent_bad_peer, '_').
61 %%====================================================================
62 %% gen_server callbacks
63 %%====================================================================
65 %%--------------------------------------------------------------------
66 %% Function: init(Args) -> {ok, State} |
67 %% {ok, State, Timeout} |
68 %% ignore |
69 %% {stop, Reason}
70 %% Description: Initiates the server
71 %%--------------------------------------------------------------------
72 init([OurPeerId]) ->
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} |
82 %% {noreply, State} |
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),
91 {reply, Reply, S};
92 handle_call(_Request, _From, State) ->
93 Reply = ok,
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),
104 {noreply, NS};
105 handle_cast({enter_bad_peer, IP, Port, PeerId}, S) ->
106 case ets:lookup(etorrent_bad_peer, {IP, Port}) of
107 [] ->
108 ets:insert(etorrent_bad_peer,
109 #bad_peer { ipport = {IP, Port},
110 peerid = PeerId,
111 offenses = 1,
112 last_offense = now() });
113 [P] ->
114 ets:insert(etorrent_bad_peer,
115 P#bad_peer { offenses = P#bad_peer.offenses + 1,
116 peerid = PeerId,
117 last_offense = now() })
118 end,
119 {noreply, S};
120 handle_cast(_Msg, State) ->
121 {noreply, 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}}],
134 [true]}]),
135 {noreply, S};
136 handle_info(_Info, State) ->
137 {noreply, 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) ->
155 {ok, State}.
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.
168 fill_peers(S).
170 %%% NOTE: fill_peers/2 and spawn_new_peer/5 tail calls each other.
171 fill_peers(S) ->
172 case S#state.available_peers of
173 [] ->
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
179 true ->
180 fill_peers(S#state{available_peers = R});
181 false ->
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})
187 end.
189 %%--------------------------------------------------------------------
190 %% Function: spawn_new_peer(IP, Port, S) -> S
191 %% Args: IP ::= ip_address()
192 %% Port ::= integer() (16-bit)
193 %% S :: #state()
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
198 true ->
199 fill_peers(S);
200 false ->
201 {atomic, [TM]} = etorrent_tracking_map:select(TorrentId),
202 case etorrent_counters:obtain_peer_slot() of
203 ok ->
205 {ok, Pid} = etorrent_t_sup:add_peer(
206 TM#tracking_map.supervisor_pid,
207 S#state.our_peer_id,
208 TM#tracking_map.info_hash,
209 TorrentId,
210 {IP, Port}),
211 ok = etorrent_t_peer_recv:connect(Pid, IP, Port)
212 catch
213 throw:_ -> etorrent_counters:release_peer_slot();
214 exit:_ -> etorrent_counters:release_peer_slot()
215 end,
216 fill_peers(S);
217 full ->
220 end.