p2p,node,client: Add Greylists

- add admin commands to ban and unban ips and peers
- add greylist_timeout option to configuration file (node)
- Add greylist modules + RPC
This commit is contained in:
Pietro 2018-02-22 15:35:50 +01:00 committed by Grégoire Henry
parent 496cbe566d
commit 02838a4cbc
30 changed files with 1441 additions and 139 deletions

View File

@ -161,6 +161,21 @@ test:proto_alpha:
script:
- jbuilder build @src/proto_alpha/lib_protocol/runtest
test:p2p:peerset:
<<: *test_definition
script:
- jbuilder build @src/lib_p2p/runtest_p2p_peerset
test:p2p:ipv6set:
<<: *test_definition
script:
- jbuilder build @src/lib_p2p/runtest_p2p_ipv6set
test:p2p:banned_peers:
<<: *test_definition
script:
- jbuilder build @src/lib_p2p/runtest_p2p_banned_peers
# test:client_alpha:transaction:
# <<: *test_definition
# script:

View File

@ -28,12 +28,15 @@ General operation
I/O Scheduling
~~~~~~~~~~~~~~
The P2P layer uses I/O scheduling in order to be able to control its
bandwidth usage as well as implementing different policies
(e.g. read/write quotas) to different peers. For now, each peer is
granted a fair share of the global allocated bandwidth, but it is
planned for the individual allocated bandwidth to each peer to be a
function of the peer's score.
The P2P layer uses a scheduling mechanism in order to be able to control its
bandwidth usage as well as implementing different policies (e.g. read/write
quotas) to different peers. For now, each peer is granted a fair share of the
global allocated bandwidth, but it is planned for the individual allocated
bandwidth to each peer to be a function of the peer's score. Each connection is
associated tp a read/write queues where data is copied at a rate of equivalent
to max_download_speed / num_connections (resp. max_upload_speed /
num_connections).
Encryption
~~~~~~~~~~
@ -83,6 +86,19 @@ layer. It basically runs the ``accept(2)`` syscall and call
that it is made aware of an incoming connection. From there, the pool
will decide how this new connection must be handled.
BlackLists, WhiteLists, GreyLists
--------------------------------
The welcome worker takes care of filtering all incoming connections using two
static lists of address provided by the admin (either via the ``tezos-admin``
client or directly in the configuration file) and an automatic (grey)list
handled automatically by the p2p layer. The node admin can block or whitelist
individual ip addresses, while the p2p layer can temporarly ban ip addresses and
peer's identities that misbihaved. The delay to remove an ip address from
the greylist table is defined by the varaible ``greylist_timeout``, while
identities are greylisted in a fixed size ring and periodically removed. The
node admin can also flush the greylist tables using the ``tezos-admin`` client.
Maintenance worker
------------------
@ -111,4 +127,5 @@ Given these bounds, the maintenance worker:
peers until it reaches at least ``min_target`` connections (and never
more than ``max_target`` connections).
The maintenance worker is also in charge to periodically run the greylists
GC functions to unban ip addresses from the greylist.

View File

@ -42,7 +42,6 @@ let get_commands_for_version ctxt block protocol =
let select_commands ctxt { block ; protocol } =
get_commands_for_version ctxt block protocol >>|? fun (_, commands_for_version) ->
Client_rpc_commands.commands @
Client_p2p_commands.commands () @
Client_keys_commands.commands () @
Client_helpers_commands.commands () @
commands_for_version

View File

@ -22,7 +22,7 @@ for client in "${client_instances[@]}"; do
echo "### $client p2p stat"
echo
$client bootstrapped
$client p2p stat
$admin_client network stat
echo
done

View File

@ -62,6 +62,7 @@ and shell = {
let default_p2p_limits : P2p.limits = {
connection_timeout = 10. ;
authentication_timeout = 5. ;
greylist_timeout = 86400. ; (* one day *)
min_connections = 10 ;
expected_connections = 50 ;
max_connections = 100 ;
@ -159,7 +160,7 @@ let default_config = {
let limit : P2p.limits Data_encoding.t =
let open Data_encoding in
conv
(fun { P2p.connection_timeout ; authentication_timeout ;
(fun { P2p.connection_timeout ; authentication_timeout ; greylist_timeout ;
min_connections ; expected_connections ; max_connections ;
backlog ; max_incoming_connections ;
max_download_speed ; max_upload_speed ;
@ -170,8 +171,8 @@ let limit : P2p.limits Data_encoding.t =
max_known_points ; max_known_peer_ids ;
swap_linger ; binary_chunks_size
} ->
(((( connection_timeout,
authentication_timeout, min_connections, expected_connections,
(((( connection_timeout, authentication_timeout,
min_connections, expected_connections,
max_connections, backlog, max_incoming_connections,
max_download_speed, max_upload_speed, swap_linger),
( binary_chunks_size, read_buffer_size, read_queue_size, write_queue_size,
@ -179,9 +180,9 @@ let limit : P2p.limits Data_encoding.t =
incoming_message_queue_size, outgoing_message_queue_size,
known_points_history_size, known_peer_ids_history_size,
max_known_points)),
max_known_peer_ids)))
(fun (((( connection_timeout,
authentication_timeout, min_connections, expected_connections,
( max_known_peer_ids, greylist_timeout))))
(fun (((( connection_timeout, authentication_timeout,
min_connections, expected_connections,
max_connections, backlog, max_incoming_connections,
max_download_speed, max_upload_speed, swap_linger),
( binary_chunks_size, read_buffer_size, read_queue_size, write_queue_size,
@ -189,8 +190,9 @@ let limit : P2p.limits Data_encoding.t =
incoming_message_queue_size, outgoing_message_queue_size,
known_points_history_size, known_peer_ids_history_size,
max_known_points)),
max_known_peer_ids)) ->
{ connection_timeout ; authentication_timeout ; min_connections ; expected_connections ;
( max_known_peer_ids, greylist_timeout))) ->
{ connection_timeout ; authentication_timeout ; greylist_timeout ;
min_connections ; expected_connections ;
max_connections ; backlog ; max_incoming_connections ;
max_download_speed ; max_upload_speed ;
read_buffer_size ; read_queue_size ; write_queue_size ;
@ -271,8 +273,14 @@ let limit : P2p.limits Data_encoding.t =
default_p2p_limits.known_points_history_size)
(opt "max_known_points" (tup2 uint16 uint16))
))
(obj1
(opt "max_known_peer_ids" (tup2 uint16 uint16))))
(obj2
(opt "max_known_peer_ids" (tup2 uint16 uint16))
(dft "greylist-timeout"
(Data_encoding.describe
~description: "GC delay for the greylists tables, in seconds."
float) default_p2p_limits.greylist_timeout)
))
let p2p =
let open Data_encoding in

View File

@ -26,3 +26,23 @@ let encoding =
end
type port = int
let pp ppf addr =
match Ipaddr.v4_of_v6 addr with
| Some addr ->
Format.fprintf ppf "%a" Ipaddr.V4.pp_hum addr
| None ->
Format.fprintf ppf "[%a]" Ipaddr.V6.pp_hum addr
let of_string_exn str =
match Ipaddr.of_string_exn str with
| V4 addr -> Ipaddr.v6_of_v4 addr
| V6 addr -> addr
let of_string str =
try Ok (of_string_exn str) with
| Invalid_argument s -> Error s
| Failure s -> Error s
| _ -> Error "P2p_addr.of_string"
let to_string saddr = Format.asprintf "%a" pp saddr

View File

@ -11,3 +11,10 @@ type t = Ipaddr.V6.t
type port = int
val encoding : t Data_encoding.t
val pp : Format.formatter -> t -> unit
val of_string : string -> (t, string) result
val of_string_exn : string -> t
val to_string : t -> string

View File

@ -10,6 +10,8 @@
type t
include Compare.S with type t := t
val hash : t -> int
val min_value : t
val epoch : t
val max_value : t

View File

@ -11,8 +11,24 @@ let group =
{ Clic.name = "p2p" ;
title = "Commands for monitoring and controlling p2p-layer state" }
let commands () = [
let port_arg () =
let open Clic in
default_arg
~long:"port"
~placeholder:"number"
~doc:"peer-to-peer port of the node"
~default: "9732"
(parameter
(fun _ x -> try
return (int_of_string x)
with Failure _ ->
failwith "Invalid peer-to-peer port"))
let commands () =
let open Clic in
let addr_parameter = parameter (fun _ x -> return (P2p_addr.of_string_exn x)) in
[
command ~group ~desc: "show global network status"
no_options
(prefixes ["p2p" ; "stat"] stop) begin fun () (cctxt : #Client_context.full) ->
@ -65,5 +81,109 @@ let commands () = [
(if pi.trusted then "" else " ")
end points >>= fun () ->
return ()
end
end ;
command ~group ~desc: "Connect to a new point."
(args1 (port_arg ()))
(prefixes [ "connect" ; "address" ]
@@ param ~name:"address" ~desc:"IPv4 or IPV6 address" addr_parameter
@@ stop)
(fun port address (cctxt : #Client_context.full) ->
P2p_services.connect cctxt ~timeout:10. (address,port) >>=? fun () ->
return ()
);
command ~group ~desc: "Remove an IP address from the blacklist and whitelist."
no_options
(prefixes [ "forget" ; "address" ]
@@ param ~name:"address" ~desc:"IPv4 or IPV6 address" addr_parameter
@@ stop)
(fun () address (cctxt : #Client_context.full) ->
P2p_services.Points.forget cctxt (address,0) >>=? fun () ->
return ()
);
command ~group ~desc: "Add a IP address to the blacklist."
no_options
(prefixes [ "ban" ; "address" ]
@@ param ~name:"address" ~desc:"IPv4 or IPV6 address" addr_parameter
@@ stop)
(fun () address (cctxt : #Client_context.full) ->
P2p_services.Points.ban cctxt (address,0) >>=? fun () ->
return ()
);
command ~group ~desc: "Add a IP address to the whitelist."
no_options
(prefixes [ "trust" ; "address" ]
@@ param ~name:"address" ~desc:"IPv4 or IPV6 address" addr_parameter
@@ stop)
(fun () address (cctxt : #Client_context.full) ->
P2p_services.Points.trust cctxt (address,0) >>=? fun () ->
return ()
);
command ~group ~desc: "Check if an IP address is banned."
no_options
(prefixes [ "is" ; "address" ; "banned" ]
@@ param ~name:"address" ~desc:"IPv4 or IPV6 address" addr_parameter
@@ stop)
(fun () address (cctxt : #Client_context.full) ->
P2p_services.Points.is_banned cctxt (address,0) >>=? fun answer ->
cctxt#message
"The given ip address is %s"
(if answer then "banned" else "not banned") >>= fun () ->
return ()
);
command ~group ~desc: "Remove a peer ID from the blacklist and whitelist."
no_options
(prefixes [ "forget" ; "peer" ]
@@ P2p_peer.Id.param ~name:"peer" ~desc:"peer network identity"
@@ stop)
(fun () peer (cctxt : #Client_context.full) ->
P2p_services.Peers.forget cctxt peer >>=? fun () ->
return ()
);
command ~group ~desc: "Add a peer ID to the blacklist."
no_options
(prefixes [ "ban" ; "peer" ]
@@ P2p_peer.Id.param ~name:"peer" ~desc:"peer network identity"
@@ stop)
(fun () peer (cctxt : #Client_context.full) ->
P2p_services.Peers.ban cctxt peer >>=? fun () ->
return ()
);
command ~group ~desc: "Add a peer ID to the whitelist."
no_options
(prefixes [ "trust" ; "peer" ]
@@ P2p_peer.Id.param ~name:"peer" ~desc:"peer network identity"
@@ stop)
(fun () peer (cctxt : #Client_context.full) ->
P2p_services.Peers.trust cctxt peer >>=? fun () ->
return ()
);
command ~group ~desc: "Check if a peer ID is banned."
no_options
(prefixes [ "is" ; "peer" ; "banned" ]
@@ P2p_peer.Id.param ~name:"peer" ~desc:"peer network identity"
@@ stop)
(fun () peer (cctxt : #Client_context.full) ->
P2p_services.Peers.is_banned cctxt peer >>=? fun answer ->
cctxt#message
"The given peer ID is %s"
(if answer then "banned" else "not banned") >>= fun () ->
return ()
);
command ~group ~desc: "Clear all greylist tables."
no_options
(prefixes [ "clear" ; "greylists" ] @@ stop)
(fun () (cctxt : #Client_context.full) ->
P2p_services.Greylist.clear cctxt () >>=? fun () ->
return ()
);
]

View File

@ -43,6 +43,7 @@ type limits = {
connection_timeout : float ;
authentication_timeout : float ;
greylist_timeout : float ;
min_connections : int ;
expected_connections : int ;
@ -97,6 +98,7 @@ let create_connection_pool config limits meta_cfg msg_cfg io_sched =
max_incoming_connections = limits.max_incoming_connections ;
connection_timeout = limits.connection_timeout ;
authentication_timeout = limits.authentication_timeout ;
greylist_timeout = limits.greylist_timeout ;
incoming_app_message_queue_size = limits.incoming_app_message_queue_size ;
incoming_message_queue_size = limits.incoming_message_queue_size ;
outgoing_message_queue_size = limits.outgoing_message_queue_size ;
@ -132,7 +134,7 @@ let create_maintenance_worker limits pool =
~expected:limits.expected_connections
~max:limits.max_connections
in
P2p_maintenance.run bounds pool
P2p_maintenance.run bounds ~greylist_timeout:limits.greylist_timeout pool
let may_create_welcome_worker config limits pool =
match config.listening_port with
@ -444,6 +446,16 @@ let fold_connections net = net.fold_connections
let iter_connections net = net.iter_connections
let on_new_connection net = net.on_new_connection
let temp_ban_peer net peer_id =
match net.pool with
|None -> ()
|Some pool -> P2p_pool.temp_ban_peer pool peer_id
let temp_ban_addr net addr =
match net.pool with
|None -> ()
|Some pool -> P2p_pool.temp_ban_addr pool addr
module Raw = struct
type 'a t = 'a P2p_pool.Message.t =
| Bootstrap
@ -536,6 +548,9 @@ let build_rpc_directory net =
match net.pool with
| None -> failwith "The node has disable the P2P layer."
| Some pool ->
fail_unless
(not(P2p_pool.Points.is_banned pool point))
(P2p_errors.Point_banned point) >>=? fun () ->
ignore (P2p_pool.connect ~timeout pool point : _ tzresult Lwt.t) ;
return ()
end in
@ -638,6 +653,37 @@ let build_rpc_directory net =
RPC_answer.return_stream { next ; shutdown }
end in
let dir =
RPC_directory.gen_register1 dir P2p_services.Peers.S.ban
begin fun peer_id () () ->
match net.pool with
| None -> RPC_answer.not_found
| Some pool ->
P2p_pool.Peers.unset_trusted pool peer_id;
P2p_pool.Peers.ban pool peer_id ;
RPC_answer.return ()
end in
let dir =
RPC_directory.gen_register1 dir P2p_services.Peers.S.trust
begin fun peer_id () () ->
match net.pool with
| None -> RPC_answer.not_found
| Some pool ->
P2p_pool.Peers.set_trusted pool peer_id ;
RPC_answer.return ()
end in
let dir =
RPC_directory.register1 dir P2p_services.Peers.S.is_banned
begin fun peer_id () () ->
match net.pool with
| None -> return false
| Some pool ->
if P2p_pool.Peers.get_trusted pool peer_id then return false
else return (P2p_pool.Peers.is_banned pool peer_id)
end in
(* Network : Point *)
let dir =
@ -700,4 +746,49 @@ let build_rpc_directory net =
RPC_answer.return_stream { next ; shutdown }
end in
let dir =
RPC_directory.gen_register1 dir P2p_services.Points.S.ban
begin fun point () () ->
match net.pool with
| None -> RPC_answer.not_found
| Some pool ->
P2p_pool.Points.unset_trusted pool point;
P2p_pool.Points.ban pool point;
RPC_answer.return ()
end in
let dir =
RPC_directory.gen_register1 dir P2p_services.Points.S.trust
begin fun point () () ->
match net.pool with
| None -> RPC_answer.not_found
| Some pool ->
P2p_pool.Points.set_trusted pool point ;
RPC_answer.return ()
end in
let dir =
RPC_directory.gen_register1 dir P2p_services.Points.S.is_banned
begin fun point () () ->
match net.pool with
| None -> RPC_answer.return false
| Some pool ->
if P2p_pool.Points.get_trusted pool point then
RPC_answer.return false
else
RPC_answer.return (P2p_pool.Points.is_banned pool point)
end in
(* Network : Greylist *)
let dir =
RPC_directory.register dir P2p_services.Greylist.S.clear
begin fun () () () ->
match net.pool with
| None -> return ()
| Some pool ->
P2p_pool.greylist_clear pool ;
return ()
end in
dir

View File

@ -73,6 +73,9 @@ type limits = {
authentication_timeout : float ;
(** Delay granted to a peer to perform authentication, in seconds. *)
greylist_timeout : float ;
(** GC delay for the grelists tables, in seconds. *)
min_connections : int ;
(** Strict minimum number of connections (triggers an urgent maintenance) *)
@ -210,6 +213,9 @@ val on_new_connection :
val build_rpc_directory : _ t -> unit RPC_directory.t
val temp_ban_addr : ('msg, 'meta) net -> P2p_addr.t -> unit
val temp_ban_peer : ('msg, 'meta) net -> P2p_peer.Id.t -> unit
(**/**)
module Raw : sig

223
src/lib_p2p/p2p_acl.ml Normal file
View File

@ -0,0 +1,223 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
module PeerRing = Ring.MakeTable(struct
include P2p_peer.Id
let hash = Hashtbl.hash
end)
module PatriciaTree(V:HashPtree.Value) = struct
module Size = struct
let size = 128
end
module Bits = HashPtree.Bits(Size)
module M = HashPtree.Make_BE_sized(V)(Size)
type t = M.t
let empty = M.empty
(* take into consideration the fact that the int64
* returned by Ipaddr.V6.to_int64 is signed *)
let z_of_bytes i =
let i = Z.of_int64 i in
Z.(if i < zero then i + of_int 2 ** 64 else i)
let z_of_ipv6 ip =
let hi_x, lo_x = Ipaddr.V6.to_int64 ip in
let hi = z_of_bytes hi_x in
let lo = z_of_bytes lo_x in
Z.((hi lsl 64) + lo)
let key_of_ipv6 ip =
Bits.of_z (z_of_ipv6 ip)
let z_mask_of_ipv6_prefix p =
let ip = Ipaddr.V6.Prefix.network p in
let len = Ipaddr.V6.Prefix.bits p in
z_of_ipv6 ip, Z.(lsl) Z.one (128 - len)
let key_mask_of_ipv6_prefix p =
let z, m = z_mask_of_ipv6_prefix p in
Bits.of_z z, Bits.of_z m
let z_to_ipv6 z =
(* assumes z is a 128 bit value *)
let hi_z = Z.(z asr 64) in
let hi =
if Z.(hi_z >= of_int 2 ** 63) then
(* If overflows int64, then returns the bit equivalent
representation (which is negative) *)
Int64.add 0x8000000000000000L
((Z.(to_int64 (hi_z - (of_int 2 ** 63)))))
else
Z.(to_int64 hi_z)
in
let lo = Z.(to_int64 (z mod (pow ~$2 64))) in
Ipaddr.V6.of_int64 (hi, lo)
let remove key t =
M.remove (key_of_ipv6 key) t
let remove_prefix prefix t =
let key, mask = key_mask_of_ipv6_prefix prefix in
M.remove_prefix key mask t
let add_prefix prefix value t =
let key, mask = key_mask_of_ipv6_prefix prefix in
M.add (fun _ v -> v) ~key ~value ~mask t
let add key value t =
let key = key_of_ipv6 key in
M.add (fun _ v -> v) ~key ~value t
let mem key t = M.mem (key_of_ipv6 key) t
let key_mask_to_prefix key mask =
let len =
if Bits.(equal mask zero) then 0
else 128 - (Z.trailing_zeros (Bits.to_z mask))
in
Ipaddr.V6.Prefix.make len (z_to_ipv6 (Bits.to_z key))
let fold f t acc =
let f key mask value acc =
let prefix = key_mask_to_prefix key mask in
f prefix value acc
in
M.fold f t acc
let pp ppf t =
let lst = fold (fun p _ l -> p :: l) t [] in
Format.fprintf ppf "@[<2>[%a]@]"
Format.(pp_print_list ~pp_sep:(fun ppf () -> fprintf ppf ";@ ")
Ipaddr.V6.Prefix.pp_hum)
lst
end
(* patricia trees using IpV6 addresses as keys *)
module IpSet = struct
include PatriciaTree(Time)
let gc t ~delay =
let timenow = Time.now() in
let module MI =
struct
type result = Time.t
let default = Time.now()
let map _t _key value = value
let reduce _t left right = Time.(max left right)
end
in
let module MR = M.Map_Reduce(MI) in
MR.filter (fun addtime ->
Time.(timenow < (add addtime (Int64.of_float delay)))
) t
end
module IpTable = Hashtbl.Make(struct
type t = Ipaddr.V6.t
let hash = Hashtbl.hash
let equal x y = Ipaddr.V6.compare x y = 0
end)
type raw = {
mutable greylist_ips : IpSet.t ;
greylist_peers : PeerRing.t ;
banned_ips : unit IpTable.t ;
banned_peers : unit P2p_peer.Table.t ;
}
type t = raw ref
let create size = ref {
greylist_ips = IpSet.empty;
greylist_peers = PeerRing.create size;
banned_ips = IpTable.create 53;
banned_peers = P2p_peer.Table.create 53;
}
(* check if an ip is banned. priority is for static blacklist, then
in the greylist *)
let is_banned_addr acl addr =
(IpTable.mem !acl.banned_ips addr) ||
(IpSet.mem addr !acl.greylist_ips)
(* Check is the peer_id is in the banned ring. It might be possible that
a peer ID that is not banned, but its ip address is. *)
let is_banned_peer acl peer_id =
(P2p_peer.Table.mem !acl.banned_peers peer_id) ||
(PeerRing.mem !acl.greylist_peers peer_id)
let greylist_clear acl =
!acl.greylist_ips <- IpSet.empty;
P2p_peer.Table.clear !acl.banned_peers;
IpTable.clear !acl.banned_ips;
PeerRing.clear !acl.greylist_peers
module IPGreylist = struct
(* Add the given ip address to the ip greylist *)
let add acl addr =
!acl.greylist_ips <- IpSet.add addr (Time.now()) !acl.greylist_ips
let mem acl addr = IpSet.mem addr !acl.greylist_ips
(* The GC operation works only on the address set. Peers are removed
from the ring in a round-robin fashion. If a address is removed
by the GC from the acl.greylist set, it could potentially
persist in the acl.peers set until more peers are banned. *)
let gc acl ~delay =
!acl.greylist_ips <- IpSet.gc !acl.greylist_ips ~delay
let encoding = Data_encoding.(list P2p_addr.encoding)
end
module IPBlacklist = struct
(* Add the given ip address to the ip blacklist *)
let add acl addr =
IpTable.add !acl.banned_ips addr ()
(* Remove the given ip address to the ip blacklist *)
let remove acl addr =
IpTable.remove !acl.banned_ips addr
let mem acl addr =
IpTable.mem !acl.banned_ips addr
end
module PeerBlacklist = struct
let add acl addr =
P2p_peer.Table.add !acl.banned_peers addr ()
let remove acl addr =
P2p_peer.Table.remove !acl.banned_peers addr
let mem acl addr =
P2p_peer.Table.mem !acl.banned_peers addr
end
module PeerGreylist = struct
(* Ban the given peer_id. It also add the given ip address to the blacklist. *)
let add acl peer_id =
PeerRing.add !acl.greylist_peers peer_id
let mem acl peer_id =
(PeerRing.mem !acl.greylist_peers peer_id)
end

100
src/lib_p2p/p2p_acl.mli Normal file
View File

@ -0,0 +1,100 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
(**
This module implements four tables
- ip grey lists used to automatically ban a given ip addr
- peer_id greylist used to automatically ban a given peer_id
- ip black lists used to manually ban a given ip addr
- peers black list used to manually trust a given peer_id
IP greylists use a time based GC to periodically remove entries from
the table, while peer_id grey lists are built using a ring structure,
where peers are removed from the table when removed from the fixed size
ring. Other tables are user defined and static.
*)
type t
(** Create a new ACL of given size *)
val create : int -> t
(** Check if an address is banned either temporally or permanently *)
val is_banned_addr : t -> P2p_addr.t -> bool
(** Check if a peer is banned either temporally or permanently *)
val is_banned_peer : t -> P2p_peer.Id.t -> bool
(** Reinitialize the Greylist tables *)
val greylist_clear : t -> unit
module IPGreylist : sig
(* Add the given ip address to the ip greylist *)
val add: t -> P2p_addr.t -> unit
(** [gc time] removes all banned peers older than the given time in seconds
The GC operation works only on the address set. Peers are removed
from the ring in a round-robin fashion. If a address is removed
by the GC from the greylist set, it could potentially
persist in the peers' blacklist set until more peers are banned. *)
val gc: t -> delay:float -> unit
val encoding: P2p_addr.t list Data_encoding.t
end
module IPBlacklist : sig
(* Add the given ip address to the ip blacklist *)
val add: t -> P2p_addr.t -> unit
(* Remove the given ip address to the ip blacklist *)
val remove: t -> P2p_addr.t -> unit
end
module PeerBlacklist : sig
(* Add the given ip address to the ip blacklist *)
val add: t -> P2p_peer.Id.t -> unit
(* Remove the given ip address to the ip blacklist *)
val remove: t -> P2p_peer.Id.t -> unit
end
module PeerGreylist : sig
(* Ban the given peer_id. It also add the given ip address to the blacklist. *)
val add: t -> P2p_peer.Id.t -> unit
end
(** / *)
module PeerRing : Ring.TABLE with type v = P2p_peer.Id.t
module IpSet : sig
type t
val empty: t
val add : Ipaddr.V6.t -> Time.t -> t -> t
val add_prefix : Ipaddr.V6.Prefix.t -> Time.t -> t -> t
val remove : Ipaddr.V6.t -> t -> t
val remove_prefix : Ipaddr.V6.Prefix.t -> t -> t
val mem : Ipaddr.V6.t -> t -> bool
val fold: (Ipaddr.V6.Prefix.t -> Time.t -> 'a -> 'a) -> t -> 'a -> 'a
val pp : Format.formatter -> t -> unit
val gc : t -> delay:float -> t
end
module IpTable : Hashtbl.S with type key = Ipaddr.V6.t

View File

@ -20,6 +20,7 @@ type 'meta pool = Pool : ('msg, 'meta) P2p_pool.t -> 'meta pool
type 'meta t = {
canceler: Lwt_canceler.t ;
greylist_timeout: float;
bounds: bounds ;
pool: 'meta pool ;
just_maintained: unit Lwt_condition.t ;
@ -29,8 +30,8 @@ type 'meta t = {
(** Select [expected] points amongst the disconnected known points.
It ignores points which are greylisted, or for which a connection
failed after [start_time]. It first selects points with the oldest
last tentative. *)
failed after [start_time] and the pointes that are banned. It
first selects points with the oldest last tentative. *)
let connectable st start_time expected =
let Pool pool = st.pool in
let now = Time.now () in
@ -52,6 +53,7 @@ let connectable st start_time expected =
match P2p_point_state.Info.last_miss pi with
| Some last when Time.(start_time < last)
|| P2p_point_state.Info.greylisted ~now pi -> ()
| _ when (P2p_pool.Points.is_banned pool point) -> ()
| last ->
Bounded_point_info.insert (last, point) acc
end
@ -87,10 +89,13 @@ let rec try_to_contact
(min_to_contact - established) (max_to_contact - established)
(** Do a maintenance step. It will terminate only when the number
of connections is between `min_threshold` and `max_threshold`. *)
of connections is between `min_threshold` and `max_threshold`.
Do a pass in the list of banned peers and remove all peers that
have been banned for more then xxx seconds *)
let rec maintain st =
let Pool pool = st.pool in
let n_connected = P2p_pool.active_connections pool in
P2p_pool.gc_greylist pool ~delay:st.greylist_timeout;
if n_connected < st.bounds.min_threshold then
too_few_connections st n_connected
else if st.bounds.max_threshold < n_connected then
@ -163,10 +168,11 @@ let rec worker_loop st =
| Error [ Canceled ] -> Lwt.return_unit
| Error _ -> Lwt.return_unit
let run bounds pool =
let run ~greylist_timeout bounds pool =
let canceler = Lwt_canceler.create () in
let st = {
canceler ;
greylist_timeout ;
bounds ;
pool = Pool pool ;
just_maintained = Lwt_condition.create () ;

View File

@ -37,9 +37,11 @@ type 'meta t
(** Type of a maintenance worker. *)
val run:
greylist_timeout:float ->
bounds -> ('msg, 'meta) P2p_pool.t -> 'meta t
(** [run bounds pool] is a maintenance worker for [pool] with
connection targets specified in [bounds]. *)
(** [run ~greylist_timeout bounds pool] is a maintenance worker for
[pool] with connection targets specified in [bounds] and greylist
GC frequency [greylist_timeout]. *)
val maintain: 'meta t -> unit Lwt.t
(** [maintain t] gives a hint to maintenance worker [t] that

View File

@ -176,6 +176,7 @@ type config = {
max_incoming_connections : int ;
connection_timeout : float ;
authentication_timeout : float ;
greylist_timeout : float ;
incoming_app_message_queue_size : int option ;
incoming_message_queue_size : int option ;
@ -218,6 +219,7 @@ type ('msg, 'meta) t = {
encoding : 'msg Message.t Data_encoding.t ;
events : events ;
watcher : P2p_connection.Pool_event.t Lwt_watcher.input ;
acl : P2p_acl.t;
mutable new_connection_hook :
(P2p_peer.Id.t -> ('msg, 'meta) connection -> unit) list ;
mutable latest_accepted_swap : Time.t ;
@ -396,12 +398,76 @@ let broadcast_bootstrap_msg pool =
(***************************************************************************)
(* this function duplicates bit of code from the modules below to avoid
creating mutually recurvive modules *)
let get_addr pool peer_id =
let info peer_id =
try Some (P2p_peer.Table.find pool.known_peer_ids peer_id)
with Not_found -> None
in
let find_by_peer_id peer_id =
Option.apply
(info peer_id)
~f:(fun p ->
match P2p_peer_state.get p with
| Running { data } -> Some data
| _ -> None)
in
match find_by_peer_id peer_id with
|None -> None
|Some ci ->
let info = P2p_socket.info ci.conn in
Some(info.id_point)
module Points = struct
type ('msg, 'meta) info = ('msg, 'meta) connection P2p_point_state.Info.t
let info { known_points } point =
try Some (P2p_point.Table.find known_points point)
with Not_found -> None
let get_trusted pool point =
try P2p_point_state.Info.trusted (P2p_point.Table.find pool.known_points point)
with Not_found -> false
let set_trusted pool point =
try
P2p_point_state.Info.set_trusted
(register_point pool pool.config.identity.peer_id point)
with Not_found -> ()
let unset_trusted pool point =
try P2p_point_state.Info.unset_trusted (P2p_point.Table.find pool.known_points point)
with Not_found -> ()
let fold_known pool ~init ~f =
P2p_point.Table.fold f pool.known_points init
let fold_connected pool ~init ~f =
P2p_point.Table.fold f pool.connected_points init
let is_banned pool point =
P2p_acl.is_banned_addr pool.acl (fst point)
let ban pool point =
P2p_acl.IPBlacklist.add pool.acl (fst point)
let trust pool point =
P2p_acl.IPBlacklist.remove pool.acl (fst point)
let forget pool point =
unset_trusted pool point; (* remove from whitelist *)
P2p_acl.IPBlacklist.remove pool.acl (fst point)
end
module Peers = struct
type ('msg, 'meta) info = (('msg, 'meta) connection, 'meta) P2p_peer_state.Info.t
let info { known_peer_ids } point =
try Some (P2p_peer.Table.find known_peer_ids point)
let info { known_peer_ids } peer_id =
try Some (P2p_peer.Table.find known_peer_ids peer_id)
with Not_found -> None
let get_metadata pool peer_id =
@ -432,35 +498,29 @@ module Peers = struct
let fold_connected pool ~init ~f =
P2p_peer.Table.fold f pool.connected_peer_ids init
end
let forget pool peer =
match get_addr pool peer with
|None -> ()
|Some point ->
unset_trusted pool peer; (* remove from whitelist *)
P2p_acl.PeerBlacklist.remove pool.acl peer;
P2p_acl.IPBlacklist.remove pool.acl (fst point)
module Points = struct
let ban pool peer =
match get_addr pool peer with
|None -> ()
|Some point ->
Points.ban pool point;
P2p_acl.PeerBlacklist.add pool.acl peer
type ('msg, 'meta) info = ('msg, 'meta) connection P2p_point_state.Info.t
let trust pool peer =
match get_addr pool peer with
|None -> ()
|Some point ->
Points.trust pool point
let info { known_points } point =
try Some (P2p_point.Table.find known_points point)
with Not_found -> None
let get_trusted pool point =
try P2p_point_state.Info.trusted (P2p_point.Table.find pool.known_points point)
with Not_found -> false
let set_trusted pool point =
try
P2p_point_state.Info.set_trusted
(register_point pool pool.config.identity.peer_id point)
with Not_found -> ()
let unset_trusted pool peer_id =
try P2p_point_state.Info.unset_trusted (P2p_point.Table.find pool.known_points peer_id)
with Not_found -> ()
let fold_known pool ~init ~f =
P2p_point.Table.fold f pool.known_points init
let fold_connected pool ~init ~f =
P2p_point.Table.fold f pool.connected_points init
let is_banned pool peer =
P2p_acl.is_banned_peer pool.acl peer
end
@ -532,10 +592,24 @@ module Connection = struct
end
let temp_ban_addr pool addr =
P2p_acl.IPGreylist.add pool.acl addr
let temp_ban_peer pool peer =
match get_addr pool peer with
|None -> ()
|Some point ->
temp_ban_addr pool (fst point);
P2p_acl.PeerGreylist.add pool.acl peer
let greylist_clear pool =
P2p_acl.greylist_clear pool.acl
let gc_greylist ~delay pool = P2p_acl.IPGreylist.gc ~delay pool.acl
let pool_stat { io_sched } =
P2p_io_scheduler.global_stat io_sched
(***************************************************************************)
let fail_unless_disconnected_point point_info =
@ -571,6 +645,9 @@ let compare_known_point_info p1 p2 =
| true, true -> compare_last_seen p2 p1
let rec connect ?timeout pool point =
fail_unless
(not(Points.is_banned pool point))
(P2p_errors.Point_banned point) >>=? fun () ->
let timeout =
Option.unopt ~default:pool.config.connection_timeout timeout in
fail_unless
@ -645,6 +722,9 @@ and authenticate pool ?point_info canceler fd point =
lwt_debug "authenticate: %a -> auth %a"
P2p_point.Id.pp point
P2p_connection.Info.pp info >>= fun () ->
fail_unless
(not(Peers.is_banned pool info.peer_id))
(P2p_errors.Peer_banned info.peer_id) >>=? fun () ->
let remote_point_info =
match info.id_point with
| addr, Some port
@ -901,7 +981,9 @@ and swap pool conn current_peer_id new_point =
let accept pool fd point =
log pool (Incoming_connection point) ;
if pool.config.max_incoming_connections <= P2p_point.Table.length pool.incoming
|| pool.config.max_connections <= active_connections pool then
|| pool.config.max_connections <= active_connections pool
(* silently ignore banned points *)
|| (P2p_acl.is_banned_addr pool.acl (fst point)) then
Lwt.async (fun () -> Lwt_utils_unix.safe_close fd)
else
let canceler = Lwt_canceler.create () in
@ -947,6 +1029,7 @@ let create config meta_config message_config io_sched =
encoding = Message.encoding message_config.encoding ;
events ;
watcher = Lwt_watcher.create_input () ;
acl = P2p_acl.create 1023;
new_connection_hook = [] ;
latest_accepted_swap = Time.epoch ;
latest_succesfull_swap = Time.epoch ;

View File

@ -81,6 +81,9 @@ type config = {
authentication_timeout : float ;
(** Delay granted to a peer to perform authentication, in seconds. *)
greylist_timeout : float ;
(** GC delay for the grelists tables, in seconds. *)
incoming_app_message_queue_size : int option ;
(** Size of the message queue for user messages (messages returned
by this module's [read] function. *)
@ -174,6 +177,7 @@ module Pool_event : sig
end
(** {1 Connections management} *)
type ('msg, 'meta) connection
@ -266,6 +270,11 @@ val broadcast_bootstrap_msg: ('msg, 'meta) pool -> unit
(** [write_all pool msg] is [P2P_connection.write_now conn Bootstrap]
for all member connections to [pool] in [Running] state. *)
val temp_ban_peer : ('msg, 'meta) pool -> P2p_peer.Id.t -> unit
val temp_ban_addr : ('msg, 'meta) pool -> P2p_addr.t -> unit
val gc_greylist: delay:float -> ('msg, 'meta) pool -> unit
val greylist_clear : ('msg, 'meta) pool -> unit
(** {1 Functions on [Peer_id]} *)
module Peers : sig
@ -295,6 +304,11 @@ module Peers : sig
f:(P2p_peer.Id.t -> ('msg, 'meta) info -> 'a -> 'a) ->
'a
val forget : ('msg, 'meta) pool -> P2p_peer.Id.t -> unit
val ban : ('msg, 'meta) pool -> P2p_peer.Id.t -> unit
val trust : ('msg, 'meta) pool -> P2p_peer.Id.t -> unit
val is_banned : ('msg, 'meta) pool -> P2p_peer.Id.t -> bool
end
(** {1 Functions on [Points]} *)
@ -322,6 +336,11 @@ module Points : sig
f:(P2p_point.Id.t -> ('msg, 'meta) info -> 'a -> 'a) ->
'a
val forget : ('msg, 'meta) pool -> P2p_point.Id.t -> unit
val ban : ('msg, 'meta) pool -> P2p_point.Id.t -> unit
val trust : ('msg, 'meta) pool -> P2p_point.Id.t -> unit
val is_banned : ('msg, 'meta) pool -> P2p_point.Id.t -> bool
end
val watch: ('msg, 'meta) pool -> P2p_connection.Pool_event.t Lwt_stream.t * Lwt_watcher.stopper

View File

@ -3,7 +3,11 @@
(executables
((names (test_p2p_socket
test_p2p_pool
test_p2p_io_scheduler))
test_p2p_io_scheduler
test_p2p_peerset
test_p2p_ipv6set
test_p2p_banned_peers
))
(libraries (tezos-base
tezos-stdlib-unix
tezos-shell-services
@ -21,7 +25,11 @@
((name buildtest)
(deps (test_p2p_socket.exe
test_p2p_pool.exe
test_p2p_io_scheduler.exe))))
test_p2p_io_scheduler.exe
test_p2p_peerset.exe
test_p2p_ipv6set.exe
test_p2p_banned_peers.exe
))))
(alias
((name runtest_p2p_socket)
@ -39,11 +47,27 @@
--max-download-speed 1048576 ;; 1 << 20 = 1MB
))))
(alias
((name runtest_p2p_ipv6set)
(action (run ${exe:test_p2p_ipv6set.exe} -v))))
(alias
((name runtest_p2p_peerset)
(action (run ${exe:test_p2p_peerset.exe} -v))))
(alias
((name runtest_p2p_banned_peers)
(action (run ${exe:test_p2p_banned_peers.exe} -v))))
(alias
((name runtest)
(deps ((alias runtest_p2p_socket)
(alias runtest_p2p_pool)
(alias runtest_p2p_io_scheduler)))))
(alias runtest_p2p_io_scheduler)
(alias runtest_p2p_peerset)
(alias runtest_p2p_ipv6set)
(alias runtest_p2p_banned_peers)
))))
(alias
((name runtest_indent)

View File

@ -0,0 +1,66 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
open Error_monad
include Logging.Make (struct let name = "test-p2p-banned_peers" end)
let assert_equal_bool ~msg a b =
if a <> b then Alcotest.fail msg
let a = fun (peer,addr) ->
(P2p_peer.Id.hash_string [peer], Ipaddr.V6.of_string_exn addr)
let foo = a ("foo","ffff::3")
let bar = a ("bar","ffff:00::ff")
let baz = a ("baz","a::2")
let peers = [foo;bar;baz]
let test_empty _ =
let empty = P2p_acl.create 10 in
List.iter (fun (_peer,addr) ->
assert_equal_bool ~msg:__LOC__ false (P2p_acl.is_banned_addr empty addr)
) peers ;
Lwt.return ()
;;
let test_ban _ =
let set = P2p_acl.create 10 in
List.iter (fun (_,addr) -> P2p_acl.IPGreylist.add set addr) peers;
List.iter (fun (_,addr) ->
assert_equal_bool ~msg:__LOC__ true (P2p_acl.is_banned_addr set addr)
) peers ;
Lwt.return ()
;;
let test_gc _ =
let set = P2p_acl.create 10 in
List.iter (fun (_,addr) -> P2p_acl.IPGreylist.add set addr) peers;
List.iter (fun (_peer,addr) ->
assert_equal_bool ~msg:__LOC__ true (P2p_acl.is_banned_addr set addr)
) peers ;
Lwt_unix.sleep 3. >>= fun _ ->
(* remove all peers after one second *)
P2p_acl.IPGreylist.gc set ~delay:1. ;
List.iter (fun (_peer,addr) ->
assert_equal_bool ~msg:__LOC__ false (P2p_acl.is_banned_addr set addr)
) peers ;
Lwt.return ()
let () =
let wrap (n, f) =
Alcotest_lwt.test_case n `Quick (fun _ () -> f ()) in
Alcotest.run ~argv:[|""|] "tezos-p2p" [
"p2p.peerset",
List.map wrap [
"empty", test_empty ;
"ban", test_ban;
"gc", test_gc;
]
]

View File

@ -0,0 +1,139 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
include Logging.Make (struct let name = "test-p2p-banned_ip" end)
let assert_equal ?(eq = (=)) ?prn ~msg a b =
let msg = match prn with
| None -> msg
| Some prn ->
Format.asprintf "@[<v 2>%s@,n(%a)@,<>@,(%a)@]" msg prn a prn b in
if not (eq a b) then Alcotest.fail msg
let assert_equal_bool = assert_equal
let a = Ipaddr.V6.of_string_exn
let p = Ipaddr.V6.Prefix.of_string_exn
let timenow = Time.now ()
let of_list l =
List.fold_left (fun acc k ->
P2p_acl.IpSet.add_prefix k timenow acc
) P2p_acl.IpSet.empty l
let test_empty _ =
let addrs = List.map a [ "::" ; "ffff::" ; "a::2" ; ] in
List.iter (fun addr ->
assert_equal_bool ~msg:__LOC__ false (P2p_acl.IpSet.mem addr P2p_acl.IpSet.empty)
) addrs
let test_inclusion _ =
let set = P2p_acl.IpSet.add_prefix (p "ffff::/16") timenow P2p_acl.IpSet.empty in
let included = List.map a [ "ffff::3" ; "ffff:ffff::" ; "ffff:00::ff" ; ] in
let not_included = List.map a [ "fffe::3" ; "ffee:ffff::" ; "::" ; ] in
List.iter (fun addr ->
assert_equal_bool ~msg:__LOC__ true (P2p_acl.IpSet.mem addr set)
) included ;
List.iter (fun addr ->
assert_equal_bool ~msg:__LOC__ false (P2p_acl.IpSet.mem addr set)
) not_included;
let set = P2p_acl.IpSet.add_prefix (p "f000::/4") timenow P2p_acl.IpSet.empty in
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "e000::") set) ;
(* Add one IP *)
let set = P2p_acl.IpSet.add_prefix (p "::/128") timenow P2p_acl.IpSet.empty in
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "1::") set) ;
let set = P2p_acl.IpSet.add_prefix (p "ffff:eeee::/32") timenow P2p_acl.IpSet.empty in
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "eeee:ffff::1") set) ;
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "ffff:eeee::1") set) ;
let set = P2p_acl.IpSet.add_prefix (p "::/17") timenow P2p_acl.IpSet.empty in
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "0000:0000::") set) ;
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "0000:7000::") set) ;
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "0000:8000::1") set) ;
let setlist = [p "e000::/4" ; p "a000::/4" ; p "ffff::/16"] in
let set = of_list setlist in
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "ffff::1") set) ;
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "a111:8000::1") set) ;
let set = of_list [p "e000::/4" ; p "a000::/4" ;
p "1234:5678::1/128"; p "ffff::/16"] in
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "1234:5678::1") set) ;
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem (a "a111:8000::1") set) ;
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "b111:8000::1") set) ;
assert_equal ~msg:__LOC__ false (P2p_acl.IpSet.mem (a "1234:5678::100") set)
let test_contiguous _ =
let set = of_list [p "::/1" ; p "8000::/1"] in
List.iter (fun addr ->
assert_equal ~msg:__LOC__ true (P2p_acl.IpSet.mem addr set)
) [a "00::" ; a "01::" ; a "ff::" ]
module PSet = Set.Make(Ipaddr.V6.Prefix)
let test_fold _ =
let addr_list = [p "::/1" ; p "8000::/1" ; p "ffff:ffff::/32" ; ] in
let pset = PSet.of_list addr_list in
let ipv6set =
P2p_acl.IpSet.fold (fun prefix _value s ->
PSet.add prefix s
) (of_list addr_list) PSet.empty ;
in
assert_equal ~eq:PSet.equal ~msg:__LOC__ ipv6set pset
let print_pset ppf pset =
PSet.iter (fun p ->
Format.fprintf ppf "%a " Ipaddr.V6.Prefix.pp_hum p
) pset
let print_list ppf l =
List.iter (fun p ->
Format.fprintf ppf "%a " Ipaddr.V6.Prefix.pp_hum p
) l
let test_to_list _ =
let to_list s = P2p_acl.IpSet.fold (fun k _v acc -> k::acc) s [] in
let list_eq = List.for_all2 (fun x y -> Ipaddr.V6.Prefix.compare x y = 0) in
let assert_equal_set ~msg a b =
let a = List.sort compare a in
let b = List.sort compare (to_list b) in
assert_equal ~prn:print_list ~eq:list_eq ~msg a b in
let set = P2p_acl.IpSet.add_prefix (p "::/0") timenow P2p_acl.IpSet.empty in
assert_equal ~eq:list_eq ~prn:print_list ~msg:__LOC__ [p "::/0"] (to_list set) ;
let set = of_list [p "::/1" ; p "8000::/1"] in
assert_equal ~eq:list_eq ~prn:print_list ~msg:__LOC__ [p "8000::/1"; p "::/1" ] (to_list set) ;
let setlist = [p "1234:5678::/32"] in
let set = of_list setlist in
assert_equal_set ~msg:__LOC__ setlist set ;
let setlist = [p "e000::/4" ; p "a000::/4" ;
p "ffff::/16" ;
p "1234:5678::/32" ;
] in
let set = of_list setlist in
assert_equal_set ~msg:__LOC__ setlist set
let () =
Alcotest.run ~argv:[|""|] "tezos-p2p" [
"p2p.ipv6set", [
"empty", `Quick, test_empty ;
"inclusion", `Quick, test_inclusion ;
"contiguous", `Quick, test_contiguous ;
"test_fold", `Quick, test_fold ;
"to_list", `Quick, test_to_list ;
]
]

View File

@ -0,0 +1,57 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
include Logging.Make (struct let name = "test-p2p-banned_peers" end)
let assert_equal_bool ~msg a b =
if a <> b then Alcotest.fail msg
let a = fun s -> P2p_peer.Id.hash_string [s]
let test_empty _ =
let peers = List.map a [ "foo"; "bar"; "baz" ; ] in
let empty = P2p_acl.PeerRing.create 10 in
List.iter (fun peer ->
assert_equal_bool ~msg:__LOC__ false (P2p_acl.PeerRing.mem empty peer)
) peers
let test_add _ =
let peers = List.map a [ "foo"; "bar"; "baz" ; ] in
let set = P2p_acl.PeerRing.create 10 in
List.iter (fun peer -> P2p_acl.PeerRing.add set peer) peers;
List.iter (fun peer ->
assert_equal_bool ~msg:__LOC__ true (P2p_acl.PeerRing.mem set peer)
) peers
let test_remove _ =
let peers = List.map a [ "foo"; "bar"; "baz" ; ] in
let set = P2p_acl.PeerRing.create 10 in
List.iter (fun peer -> P2p_acl.PeerRing.add set peer) peers;
assert_equal_bool ~msg:__LOC__ true (P2p_acl.PeerRing.mem set (a "bar"));
P2p_acl.PeerRing.remove set (a "bar");
assert_equal_bool ~msg:__LOC__ false (P2p_acl.PeerRing.mem set (a "bar"))
let test_overflow _ =
let peers = List.map a [ "foo"; "bar"; "baz" ; ] in
let set = P2p_acl.PeerRing.create 3 in
List.iter (fun peer -> P2p_acl.PeerRing.add set peer) peers;
assert_equal_bool ~msg:__LOC__ true (P2p_acl.PeerRing.mem set (a "baz"));
P2p_acl.PeerRing.add set (a "zor");
assert_equal_bool ~msg:__LOC__ true (P2p_acl.PeerRing.mem set (a "zor"));
assert_equal_bool ~msg:__LOC__ false (P2p_acl.PeerRing.mem set (a "baz"))
let () =
Alcotest.run ~argv:[|""|] "tezos-p2p" [
"p2p.peerset", [
"empty", `Quick, test_empty ;
"add", `Quick, test_add;
"overflow", `Quick, test_overflow;
"remove", `Quick, test_remove;
]
]

View File

@ -71,6 +71,7 @@ let detach_node f points n =
max_incoming_connections = nb_points ;
connection_timeout = 10. ;
authentication_timeout = 2. ;
greylist_timeout = 2. ;
incoming_app_message_queue_size = None ;
incoming_message_queue_size = None ;
outgoing_message_queue_size = None ;

View File

@ -484,8 +484,10 @@ module P2p_reader = struct
(State.Block.known_invalid chain_db.chain_state)
(Block_header.hash head :: hist) >>= fun known_invalid ->
if not known_invalid then
chain_db.callback.notify_branch state.gid locator ;
(* TODO Kickban *)
chain_db.callback.notify_branch state.gid locator
else
(* Kickban *)
P2p.temp_ban_peer global_db.p2p state.gid;
Lwt.return_unit
| Deactivate chain_id ->
@ -508,8 +510,10 @@ module P2p_reader = struct
let head = Block_header.hash header in
State.Block.known_invalid chain_db.chain_state head >>= fun known_invalid ->
if not known_invalid then
chain_db.callback.notify_head state.gid header mempool ;
(* TODO Kickban *)
chain_db.callback.notify_head state.gid header mempool
else
(* Kickban *)
P2p.temp_ban_peer global_db.p2p state.gid ;
Lwt.return_unit
| Get_block_headers hashes ->
@ -764,6 +768,9 @@ let get_chain { active_chains } chain_id =
try Some (Chain_id.Table.find active_chains chain_id)
with Not_found -> None
let temp_ban { global_db = { p2p } } peer_id =
Lwt.return (P2p.temp_ban_peer p2p peer_id)
let disconnect { global_db = { p2p } } peer_id =
match P2p.find_connection p2p peer_id with
| None -> Lwt.return_unit

View File

@ -54,6 +54,9 @@ val set_callback: chain_db -> callback -> unit
(** Kick a given peer. *)
val disconnect: chain_db -> P2p_peer.Id.t -> unit Lwt.t
(** Temporarily ban of a given peer. *)
val temp_ban: chain_db -> P2p_peer.Id.t -> unit Lwt.t
(** Various accessors. *)
val chain_state: chain_db -> State.Chain.t
val db: chain_db -> db

View File

@ -251,7 +251,7 @@ let on_error w r st errs =
((( Validation_errors.Unknown_ancestor
| Validation_errors.Invalid_locator _
| Block_validator_errors.Invalid_block _ ) :: _) as errors ) ->
(* TODO ban the peer_id... *)
Distributed_db.temp_ban pv.parameters.chain_db pv.peer_id >>= fun () ->
debug w
"Terminating the validation worker for peer %a (kickban)."
P2p_peer.Id.pp_short pv.peer_id ;
@ -267,7 +267,7 @@ let on_error w r st errs =
protocol >>= function
| Ok _ -> return ()
| Error _ ->
(* TODO penality... *)
(* TODO: punish *)
debug w
"Terminating the validation worker for peer %a \
(missing protocol %a)."

View File

@ -145,6 +145,8 @@ type error += Connection_refused
type error += Rejected of P2p_peer.Id.t
type error += Too_many_connections
type error += Closed_network
type error += Point_banned of P2p_point.Id.t
type error += Peer_banned of P2p_peer.Id.t
let () =
(* Pending connection *)
@ -207,4 +209,30 @@ let () =
~pp:(fun ppf () -> Format.fprintf ppf "Network is closed.")
Data_encoding.empty
(function Closed_network -> Some () | _ -> None)
(fun () -> Closed_network)
(fun () -> Closed_network) ;
(* Point Banned *)
register_error_kind
`Permanent
~id:"node.p2p_pool.point_banned"
~title:"Point Banned"
~description:"The addr you tried to connect is banned."
~pp:(fun ppf point ->
Format.fprintf ppf
"The addr you tried to connect (%a) is banned."
P2p_addr.pp (fst point))
Data_encoding.(obj1 (req "point" P2p_point.Id.encoding))
(function Point_banned point -> Some point | _ -> None)
(fun point -> Point_banned point) ;
(* Peer Banned *)
register_error_kind
`Permanent
~id:"node.p2p_pool.peer_banned"
~title:"Peer Banned"
~description:"The peer identity you tried to connect is banned."
~pp:(fun ppf peer_id ->
Format.fprintf ppf
"The peer identity you tried to connect (%a) is banned."
P2p_peer.Id.pp peer_id)
Data_encoding.(obj1 (req "peer" P2p_peer.Id.encoding))
(function Peer_banned peer_id -> Some peer_id | _ -> None)
(fun peer_id -> Peer_banned peer_id)

View File

@ -15,7 +15,7 @@ module S = struct
~query: RPC_query.empty
~input: Data_encoding.empty
~output: (Data_encoding.list P2p_version.encoding)
RPC_path.(root / "p2p" / "versions")
RPC_path.(root / "network" / "versions")
let stat =
RPC_service.post_service
@ -23,7 +23,7 @@ module S = struct
~query: RPC_query.empty
~input: Data_encoding.empty
~output: P2p_stat.encoding
RPC_path.(root / "p2p" / "stat")
RPC_path.(root / "network" / "stat")
let events =
RPC_service.post_service
@ -31,7 +31,7 @@ module S = struct
~query: RPC_query.empty
~input: Data_encoding.empty
~output: P2p_connection.Pool_event.encoding
RPC_path.(root / "p2p" / "log")
RPC_path.(root / "network" / "log")
let connect =
RPC_service.post_service
@ -39,7 +39,7 @@ module S = struct
~query: RPC_query.empty
~input: Data_encoding.(obj1 (dft "timeout" float 5.))
~output: Data_encoding.empty
RPC_path.(root / "p2p" / "connect" /: P2p_point.Id.rpc_arg)
RPC_path.(root / "network" / "connect" /: P2p_point.Id.rpc_arg)
end
@ -62,7 +62,7 @@ module Connections = struct
~query: RPC_query.empty
~input: Data_encoding.empty
~output: (Data_encoding.list P2p_connection.Info.encoding)
RPC_path.(root / "p2p" / "connections")
RPC_path.(root / "network" / "connections")
let info =
RPC_service.post_service
@ -70,7 +70,7 @@ module Connections = struct
~input: Data_encoding.empty
~output: P2p_connection.Info.encoding
~description:"Details about the current P2P connection to the given peer."
RPC_path.(root / "p2p" / "connections" /: P2p_peer.Id.rpc_arg)
RPC_path.(root / "network" / "connections" /: P2p_peer.Id.rpc_arg)
let kick =
RPC_service.post_service
@ -78,7 +78,7 @@ module Connections = struct
~input: Data_encoding.(obj1 (req "wait" bool))
~output: Data_encoding.empty
~description:"Forced close of the current P2P connection to the given peer."
RPC_path.(root / "p2p" / "connections" /: P2p_peer.Id.rpc_arg / "kick")
RPC_path.(root / "network" / "connections" /: P2p_peer.Id.rpc_arg / "kick")
end
@ -98,7 +98,7 @@ module Points = struct
~input: Data_encoding.empty
~output: P2p_point.Info.encoding
~description: "Details about a given `IP:addr`."
RPC_path.(root / "p2p" / "points" /: P2p_point.Id.rpc_arg)
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg)
let events =
RPC_service.post_service
@ -107,7 +107,7 @@ module Points = struct
~output: (Data_encoding.list
P2p_point.Pool_event.encoding)
~description: "Monitor network events related to an `IP:addr`."
RPC_path.(root / "p2p" / "points" /: P2p_point.Id.rpc_arg / "log")
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg / "log")
let list =
let filter =
@ -122,7 +122,41 @@ module Points = struct
P2p_point.Info.encoding))
~description:"List the pool of known `IP:port` \
used for establishing P2P connections."
RPC_path.(root / "networks" / "point")
RPC_path.(root / "network" / "points")
let forget =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Remove the given address from the whitelist/blacklist."
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg / "forget" )
let ban =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Ban the given address permanently."
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg / "ban" )
let trust =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Trust a given address permanently. \
Connections from this address can still be closed \
on authentication if the peer is banned or blocked."
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg / "trust" )
let is_banned =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.bool
~description:"Check is a given address is blocked, permanently or temporarily."
RPC_path.(root / "network" / "points" /: P2p_point.Id.rpc_arg / "isbanned" )
end
@ -131,6 +165,10 @@ module Points = struct
let events ctxt point =
make_streamed_call S.events ctxt ((), point) () true
let list ?(filter = []) ctxt = make_call S.list ctxt () () filter
let forget ctxt peer_id = make_call1 S.forget ctxt peer_id () ()
let ban ctxt peer_id = make_call1 S.ban ctxt peer_id () ()
let trust ctxt peer_id = make_call1 S.trust ctxt peer_id () ()
let is_banned ctxt peer_id = make_call1 S.is_banned ctxt peer_id () ()
end
@ -144,7 +182,7 @@ module Peers = struct
~input: Data_encoding.empty
~output: P2p_peer.Info.encoding
~description:"Details about a given peer."
RPC_path.(root / "p2p" / "peers" /: P2p_peer.Id.rpc_arg)
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg)
let events =
RPC_service.post_service
@ -153,7 +191,7 @@ module Peers = struct
~output: (Data_encoding.list
P2p_peer.Pool_event.encoding)
~description:"Monitor network events related to a given peer."
RPC_path.(root / "p2p" / "peers" /: P2p_peer.Id.rpc_arg / "log")
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg / "log")
let list =
let filter =
@ -167,7 +205,40 @@ module Peers = struct
P2p_peer.Id.encoding
P2p_peer.Info.encoding))
~description:"List the peers the node ever met."
RPC_path.(root / "p2p" / "peers")
RPC_path.(root / "network" / "peers")
let forget =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Remove the given peer from the whitelist/blacklist."
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg / "forget" )
let ban =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Ban the given peer permanently."
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg / "ban" )
let trust =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Trust a given peer permanently: \
the peer cannot be blocked (but its machine's IP still can)."
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg / "trust" )
let is_banned =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.bool
~description:"Check is a given peer is blocked, permanently or temporarily."
RPC_path.(root / "network" / "peers" /: P2p_peer.Id.rpc_arg / "isbanned" )
end
@ -175,5 +246,26 @@ module Peers = struct
let events ctxt point =
make_streamed_call S.events ctxt ((), point) () true
let list ?(filter = []) ctxt = make_call S.list ctxt () () filter
let forget ctxt point_id = make_call1 S.forget ctxt point_id () ()
let ban ctxt point_id = make_call1 S.ban ctxt point_id () ()
let trust ctxt point_id = make_call1 S.trust ctxt point_id () ()
let is_banned ctxt point_id = make_call1 S.is_banned ctxt point_id () ()
end
module Greylist = struct
module S = struct
let clear =
RPC_service.post_service
~query: RPC_query.empty
~input: Data_encoding.empty
~output: Data_encoding.empty
~description:"Clear all greylists tables."
RPC_path.(root / "network" / "greylist" / "clear" )
end
let clear ctxt = make_call S.clear ctxt () ()
end

View File

@ -87,6 +87,14 @@ module Points : sig
P2p_point.Id.t ->
(P2p_point.Pool_event.t list Lwt_stream.t * stopper) tzresult Lwt.t
val forget : #simple -> P2p_point.Id.t -> unit tzresult Lwt.t
val ban: #simple -> P2p_point.Id.t -> unit tzresult Lwt.t
val trust: #simple -> P2p_point.Id.t -> unit tzresult Lwt.t
val is_banned: #simple -> P2p_point.Id.t -> bool tzresult Lwt.t
module S : sig
val list :
@ -104,6 +112,27 @@ module Points : sig
unit * P2p_point.Id.t, unit, bool,
P2p_point.Pool_event.t list) RPC_service.t
val forget :
([ `POST ], unit,
unit * P2p_point.Id.t, unit, unit,
unit) RPC_service.t
val ban :
([ `POST ], unit,
unit * P2p_point.Id.t, unit, unit,
unit) RPC_service.t
val trust :
([ `POST ], unit,
unit * P2p_point.Id.t, unit, unit,
unit) RPC_service.t
val is_banned :
([ `POST ], unit,
unit * P2p_point.Id.t, unit, unit,
bool) RPC_service.t
end
end
@ -122,6 +151,14 @@ module Peers : sig
#streamed -> P2p_peer.Id.t ->
(P2p_peer.Pool_event.t list Lwt_stream.t * stopper) tzresult Lwt.t
val forget : #simple -> P2p_peer.Id.t -> unit tzresult Lwt.t
val ban: #simple -> P2p_peer.Id.t -> unit tzresult Lwt.t
val trust: #simple -> P2p_peer.Id.t -> unit tzresult Lwt.t
val is_banned: #simple -> P2p_peer.Id.t -> bool tzresult Lwt.t
module S : sig
val list :
@ -139,6 +176,41 @@ module Peers : sig
unit * P2p_peer.Id.t, unit, bool,
P2p_peer.Pool_event.t list) RPC_service.t
val forget :
([ `POST ], unit,
unit * P2p_peer.Id.t, unit, unit,
unit) RPC_service.t
val ban :
([ `POST ], unit,
unit * P2p_peer.Id.t, unit, unit,
unit) RPC_service.t
val trust :
([ `POST ], unit,
unit * P2p_peer.Id.t, unit, unit,
unit) RPC_service.t
val is_banned :
([ `POST ], unit,
unit * P2p_peer.Id.t, unit, unit,
bool) RPC_service.t
end
end
module Greylist : sig
val clear: #simple -> unit -> unit tzresult Lwt.t
module S : sig
val clear :
([ `POST ], unit,
unit, unit, unit,
unit) RPC_service.t
end
end

View File

@ -7,6 +7,7 @@
(* *)
(**************************************************************************)
module Ring = struct
type 'a raw =
| Empty of int
| Inited of {
@ -46,6 +47,7 @@ let clear r =
| Inited { data ; _ } ->
r := Empty (Array.length data)
let add_list r l = List.iter (add r) l
let last r =
@ -73,3 +75,68 @@ let last_exn r =
match last r with
| None -> raise Empty
| Some d -> d
end
include Ring
(** Ring Buffer Table *)
module type TABLE = sig
type t
type v
val create : int -> t
val clear : t -> unit
val add : t -> v -> unit
val mem : t -> v -> bool
val remove : t -> v -> unit
val elements : t -> v list
end
(* fixed size set of Peers id. If the set exceed the maximal allowed capacity, the
element that was added first is removed when a new one is added *)
module MakeTable (V: Hashtbl.HashedType) = struct
module Table = Hashtbl.Make(V)
type raw = {
size : int ;
mutable capacity : int ;
ring : V.t Ring.t ;
table : unit Table.t ;
}
type t = raw ref
type v = V.t
let create size = ref {
size;
capacity = 0;
ring = Ring.create size;
table = Table.create size }
let add ({contents = t } as tt) v =
assert (t.capacity <= t.size);
if t.capacity = t.size then begin
try Table.remove t.table (Ring.last_exn t.ring) with Ring.Empty -> assert false
end;
Ring.add t.ring v;
Table.replace t.table v ();
let capacity = if t.capacity = t.size then t.capacity else t.capacity + 1 in
tt := { ring = t.ring ; table = t.table; size = t.size ; capacity = capacity }
let mem t v = Table.mem !t.table v
let remove ({contents = t } as tt) v =
Table.remove t.table v;
let capacity = if t.capacity = 0 then t.capacity else t.capacity - 1 in
tt := { ring = t.ring ; table = t.table; size = t.size ; capacity = capacity }
let clear ({contents = t } as tt) =
tt := { t with
capacity = 0;
ring = Ring.create t.size;
table = Table.create t.size
}
let elements t = Ring.elements !t.ring
end

View File

@ -13,6 +13,8 @@
a fixed number of values of a same type. Values are never removed,
once the limit is reached, adding a value replaces the oldest one
in the ring buffer. *)
exception Empty
type 'a t
(** Allocates a ring buffer for a given number of values. *)
@ -33,8 +35,6 @@ val clear : 'a t -> unit
(** Retrieves the most recent value, or [None] when empty. *)
val last : 'a t -> 'a option
exception Empty
(** Same as {!last}, but raises {!Empty} when empty. *)
val last_exn : 'a t -> 'a
@ -43,3 +43,31 @@ val fold : 'a t -> init:'b -> f:('b -> 'a -> 'b) -> 'b
(** Retrieves the elements as a list, oldest first.. *)
val elements : 'a t -> 'a list
(** Ring Buffer Table *)
module type TABLE = sig
type t
type v
(** [create size] inizialize an empty ring *)
val create : int -> t
(** [retest t] remore all bindings from the current ring *)
val clear : t -> unit
(** [add t v] add a value to the ring. If the ring already contains size elements,
the first element is removed and [v] is added. *)
val add : t -> v -> unit
(** [mem t v] check if v is in the ring. O(1) *)
val mem : t -> v -> bool
(** [remove t v] remove one element from the table *)
val remove : t -> v -> unit
(** [elements t] return the list of elements currently in the ring *)
val elements : t -> v list
end
module MakeTable (V: Hashtbl.HashedType) : TABLE with type v = V.t