lox_analysis.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. use crate::analysis::Analyzer;
  2. use lox_library::proto::{level_up::LEVEL_INTERVAL, trust_promotion::UNTRUSTED_INTERVAL};
  3. use std::cmp::min;
  4. // Get max value from slice, or 0 if empty
  5. fn max(nums: &[u32]) -> u32 {
  6. let mut max_num = 0;
  7. for i in nums {
  8. if *i > max_num {
  9. max_num = *i;
  10. }
  11. }
  12. max_num
  13. }
  14. // Safe u32 subtraction: x - y or 0 if y > x
  15. fn ssub(x: u32, y: u32) -> u32 {
  16. if y > x {
  17. 0
  18. } else {
  19. x - y
  20. }
  21. }
  22. /// Analyzer for Lox bridges based on how long they've been active
  23. pub struct LoxAnalyzer {
  24. // simple variable threshold from 0-4 or something
  25. harshness: u32,
  26. }
  27. impl Analyzer for LoxAnalyzer {
  28. // At this stage, we expect 0-10 users, so 0, 8, or 16 bridge-ips
  29. fn stage_one(
  30. &self,
  31. age: u32,
  32. _confidence: f64,
  33. bridge_ips: &[u32],
  34. bridge_ips_today: u32,
  35. _negative_reports: &[u32],
  36. negative_reports_today: u32,
  37. ) -> bool {
  38. // Get max bridge_ips we've seen
  39. let max_bridge_ips = max(bridge_ips);
  40. if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
  41. return true;
  42. }
  43. // If we have too many negative reports, consider the bridge
  44. // blocked
  45. self.too_many_negative_reports(negative_reports_today)
  46. }
  47. fn stage_two(
  48. &self,
  49. age: u32,
  50. _confidence: f64,
  51. bridge_ips: &[u32],
  52. bridge_ips_today: u32,
  53. _negative_reports: &[u32],
  54. negative_reports_today: u32,
  55. ) -> bool {
  56. // Get max bridge_ips we've seen
  57. let max_bridge_ips = max(bridge_ips);
  58. if self.too_few_bridge_ips(age, max_bridge_ips, bridge_ips_today, 0) {
  59. return true;
  60. }
  61. // If we have too many negative reports, consider the bridge
  62. // blocked
  63. self.too_many_negative_reports(negative_reports_today)
  64. }
  65. fn stage_three(
  66. &self,
  67. age: u32,
  68. _confidence: f64,
  69. bridge_ips: &[u32],
  70. bridge_ips_today: u32,
  71. _negative_reports: &[u32],
  72. negative_reports_today: u32,
  73. _positive_reports: &[u32],
  74. positive_reports_today: u32,
  75. ) -> bool {
  76. // Get max bridge_ips we've seen
  77. let max_bridge_ips = max(bridge_ips);
  78. if self.too_few_bridge_ips(
  79. age,
  80. max_bridge_ips,
  81. bridge_ips_today,
  82. positive_reports_today,
  83. ) {
  84. return true;
  85. }
  86. // If we have too many negative reports, consider the bridge
  87. // blocked
  88. self.too_many_negative_reports(negative_reports_today)
  89. }
  90. }
  91. impl LoxAnalyzer {
  92. pub fn new(harshness: u32) -> Self {
  93. Self { harshness }
  94. }
  95. // Maximum number of negative reports without considering the bridge blocked
  96. fn too_many_negative_reports(&self, negative_reports_today: u32) -> bool {
  97. // If we have more negative reports than 4 - harshness
  98. negative_reports_today > 4 - self.harshness
  99. // harshness: 4, blocked if 1 negative report
  100. // harshness: 3, blocked if 2 negative reports
  101. // harshness: 2, blocked if 3 negative reports
  102. // harshness: 1, blocked if 4 negative reports
  103. // harshness: 0, blocked if 5 negative reports
  104. }
  105. // Based on the age of the bridge and the max bridge-ips observed, do we
  106. // have too few today?
  107. fn too_few_bridge_ips(
  108. &self,
  109. days: u32,
  110. max_bridge_ips: u32,
  111. bridge_ips_today: u32,
  112. positive_reports_today: u32,
  113. ) -> bool {
  114. if days <= UNTRUSTED_INTERVAL {
  115. // We expect 0-10 users
  116. if self.harshness == 4 {
  117. // In the most extreme case, mark any bridge with 0 connections
  118. bridge_ips_today == 0
  119. } else if self.harshness >= 2 {
  120. // With medium harshness, mark any bridge that has had
  121. // 9+ connections and now sees 0
  122. max_bridge_ips > 8 && bridge_ips_today == 0
  123. } else {
  124. // With low harshness, we cannot make a judgement from
  125. // bridge stats
  126. false
  127. }
  128. } else if days <= UNTRUSTED_INTERVAL + LEVEL_INTERVAL[1] {
  129. // We expect 1-30 users
  130. let threshold = min(max_bridge_ips, 32);
  131. // If we're at least 4 - self.harshness bins below the
  132. // maximum value we've seen or 32 if we've seen a lot of
  133. // connections, consider the bridge blocked.
  134. bridge_ips_today < ssub(threshold, (4 - self.harshness) * 8)
  135. // Examples:
  136. // max 64 connections, harshness = 4
  137. // Return true if today's count is less than 32
  138. // max 32 connections, harshness = 4
  139. // Return true if today's count is less than 32
  140. // max 32 connections, harshness = 3
  141. // Return true if today's count is less than 24
  142. // max 32 connections, harshness = 2
  143. // Return true if today's count is less than 16
  144. // max 8 connections, harshness = 4
  145. // Return true if today's count is less than 8
  146. // max 8 connections, harshness = 3
  147. // Return false
  148. } else {
  149. // Similar, but consider positive reports as well
  150. let threshold = min(max_bridge_ips, 32);
  151. // We allow positive reports to increase the threshold for
  152. // considering the bridge blocked. 8 positive reports
  153. // (rounded up) remove 1 level reduction from the threshold
  154. let threshold = ssub(
  155. threshold,
  156. (4 - self.harshness + (positive_reports_today + 7) / 8) * 8,
  157. );
  158. // For example, suppose we've seen 32+ connections.
  159. //
  160. // If we have harshness 4, we mark the bridge blocked if it
  161. // has fewer than 32 connections.
  162. //
  163. // If we have harshness 4 but have seen 1-7 positive
  164. // reports, we mark the bridge blocked only if it has fewer
  165. // than 24 connections.
  166. //
  167. // 25 positive reports reduce all thresholds to 0, meaning
  168. // bridges will never be marked blocked from bridge stats
  169. // if the adversary can submit 25 positive reports.
  170. // Consider the bridge blocked if we have too few connections
  171. bridge_ips_today < threshold
  172. }
  173. }
  174. }