Loading README.md +69 −9 Original line number Diff line number Diff line Loading @@ -16,8 +16,9 @@ Note: part of this section should probably be moved into code documentation. A `filter` can be: - `addr:<IP address>` - `ff:<fingerprints file>` - `fl:<flag>` - `fp:<fingprint` - `fp:<fingerprint>` - `p:<port>` - `v:<tor version>` A filter can be `exclude` (boolean), ie. not matching a filter, with the Loading @@ -28,6 +29,10 @@ Note: part of this section should probably be moved into code documentation. The output are the rules for `approved-routers.conf` in the form `!badexit <fp>`. If the filter is `ff`, it'll also include in `approved-routers.conf` the fingerprints that weren't found in the consensus. If a fingerprint could not be parsed, it'll be ignored but printed to the stdout. Examples: - `config badexit 25`, output: Loading Loading @@ -152,26 +157,54 @@ Note: part of this section should probably be moved into code documentation. - `reject`: `Generate reject rule(s)` for the DirAuths. The parameters are a ticket number (in `bad-relay-reports` repo) and optionally some filters. It works as the previous command except that: - it also generates rules for `bad.conf` in the form `AuthDirReject <ip>`. - instead of generating rules for `approved-routers.conf` in the form `!badexit <fp>`, it generates rules like `!reject <fp>`. It works as the previous command but instead of generating rules for `approved-routers.conf` in the form `!badexit <fp>`, it generates rules like `!reject <fp>`. eg: - `config reject 25 p:8888`, output: ```bash [+] Rules for bad.conf: [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 !reject 2BC31B73E0000B66981F7734D2B1F2C16C27D0BB !reject 673510F48FA7EBE1C21A9A32566AB9B7AA8EFC48 !reject 94A8976E00C68ED23695D0668D87B3E7F126AF62 ----- [+] Found 3 relays: [FindFilter { exclude: false, filter: Port(8888) }] ``` - `rejectbad <ticket_number> [filters]` : `Generate reject rule(s), writing to bad.conf too`. It works as the previous command but it also generates rules for `bad.conf` in the form `AuthDirReject <ip>` plus comments with the corresponding relays' fingerprints. eg: - `config rejectbad 25 p:8888`, output: ```bash [+] Rules for torrc.d/bad.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 # Fingerprints: # 2BC31B73E0000B66981F7734D2B1F2C16C27D0BB # 5BE999DDB0916332AC21CE4AE9CED29FD7AAB284 # 94A8976E00C68ED23695D0668D87B3E7F126AF62 AuthDirReject 65.109.16.131 AuthDirReject 104.200.30.152 AuthDirReject 2600:3c03::f03c:93ff:fecc:2d20 AuthDirReject 155.248.213.203 ----- [+] Rules for approved-routers.conf: [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 Loading @@ -184,9 +217,36 @@ Note: part of this section should probably be moved into code documentation. ``` - `config rejectbad 25 ff:testdata/fps.txt`, output: ```bash Errors parsing testdata/fps.txt: Wrong fingerprint length: 0123456789abcdef0123456789abcdef0123456 Errors parsing testdata/fps.txt: Wrong fingerprint length: 0123456789abcdef0123456789abcdef0123456 [+] Rules for torrc.d/bad.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 # Fingerprints: # 0011BD2485AD45D984EC4159C88FC066E5E3300E AuthDirReject 162.247.74.201 ----- [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 !reject 0011BD2485AD45D984EC4159C88FC066E5E3300E !reject 0123456789ABCDEF0123456789ABCDEF01234567 ----- [+] Found 1 relays: [FindFilter { exclude: false, filter: FpsFileFilter([Rsa("0011bd2485ad45d984ec4159c88fc066e5e3300e"), Rsa("0123456789abcdef0123456789abcdef01234567")]) }] ``` - `middleonly`: `Generate middleonly rule(s)` for the DirAuths. - The parameters are a ticket number (in `bad-relay-reports` repo) and - optionally some filters. The parameters are a ticket number (in `bad-relay-reports` repo) and optionally some filters. It works as the previous commands except that: - it does not generate rules for `bad.conf`. Loading src/commands/config.rs +87 −14 Original line number Diff line number Diff line Loading @@ -5,11 +5,12 @@ use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::prelude::*; use std::path::Path; use std::path; use structopt::StructOpt; use crate::commands::err::Error; use crate::commands::find; use crate::commands::util; use crate::commands::RunnableOffline; static GITLAB_BUG_URL: &str = Loading Loading @@ -55,8 +56,16 @@ pub struct ConfigCommand { fn fmt_addr_rule(prefix: &str, relay: &tor_netdir::Relay<'_>) -> String { let rules: Vec<_> = relay .rs() // This returns all the ORPort IPs (v4 and v6) .orport_addrs() .map(|a| format!("{} {}", prefix, a.ip())) .map(|a| { if a.is_ipv4() { format!("{} {}", prefix, a.ip()) } else { // Enclose v6 IPs in brackets format!("{} [{}]", prefix, a.ip()) } }) .collect(); [rules.join("\n"), "\n".to_string()].concat() } Loading Loading @@ -104,7 +113,7 @@ impl BadCommand { } fn open_file(&self, fname: &str) -> Result<File, Error> { let path = Path::new(fname); let path = path::Path::new(fname); let parent = path .parent() .ok_or_else(|| Error::WrongParent(fname.to_string()))?; Loading Loading @@ -133,6 +142,20 @@ impl BadCommand { Ok(()) } fn print_missing_fps( &self, prefix: &str, file: &mut File, fps: Vec<String>, ) -> Result<(), anyhow::Error> { for fp in fps { let rule = format!("{} {}\n", prefix, fp); print!("{}", rule); file.write_all(rule.as_bytes())?; } Ok(()) } fn generate( &self, netdir: &tor_netdir::NetDir, Loading @@ -141,8 +164,8 @@ impl BadCommand { let relays = find::FindCommand::new(&self.filters).filter(netdir); // Do not create bad.conf config when there is not token for it, as it // is the case for `middleonly` argument. if !tokens.0.is_empty() { // is the case for `middleonly` argument or when no relays were found. if !tokens.0.is_empty() && !relays.is_empty() { // When token.0 is present, write also into BAD_PATH let fname = BAD_PATH; let mut file = self.open_file(fname)?; Loading @@ -158,21 +181,52 @@ impl BadCommand { )?; // Print the addresses when there's the `AuthDirReject` token self.print_rules(tokens.0, &mut file, fmt_addr_rule, &relays)?; self.print_footer(); } // Write into APPROVED_ROUTERS_PATH in any case // If the filter is `FpsFileFilter`, print all the parsed fingerprints // from the file even if the weren't found in the consensus. let found_fps = util::relays2fps(&relays); let missing_fps = self.missing_fps(found_fps); // Write into APPROVED_ROUTERS_PATH if there're found relays or // missing ones. if !relays.is_empty() || !missing_fps.is_empty() { let fname = APPROVED_ROUTERS_PATH; let mut file = self.open_file(fname)?; self.print_header(fname, &mut file)?; self.print_rules(tokens.1, &mut file, fmt_fp_rule, &relays)?; if !missing_fps.is_empty() { self.print_missing_fps(tokens.1, &mut file, missing_fps)?; } self.print_footer(); } println!("[+] Found {} relays: {:?}", relays.len(), self.filters); Ok(()) } /// Return the relays' fingerprints in the `FpsFileFiler` filter that were /// not found. /// Since `self` doesn't have a vector with the parsed fingeprints from /// the file, it is needed to parse the filters to obtain them. /// fn missing_fps(&self, found_fps: Vec<String>) -> Vec<String> { // NOTE: The following iterations and loops could be less verbose // Create a vector with the missing fps from the previous vector. let mut fps = Vec::new(); for find_filter in &self.filters { if let find::Filter::FpsFileFilter(relay_fingerprints) = &find_filter.filter { for relay_fingerprint in relay_fingerprints { if !found_fps.contains(&relay_fingerprint.to_string()) { fps.push(relay_fingerprint.to_string()) } } } } fps } } #[async_trait] Loading Loading @@ -216,8 +270,8 @@ mod tests { let file = bad_command.open_file(fname); assert!(file.is_err()); } #[test] #[test] fn print_header_ok() { let binding = temp_dir().join("approved-routers.conf"); let fname = binding.to_str().unwrap(); Loading @@ -233,4 +287,23 @@ mod tests { let result = bad_command.print_header(fname, &mut file); assert!(result.is_ok()); } #[test] fn missing_relays_ok() { let path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("testdata/fps.txt"); let bad_command = BadCommand { ticket: 1, filters: Vec::from([find::FindFilter::new( false, find::Filter::FpsFileFilter(util::fpfile2fps(&path).unwrap()), )]), }; let found_fps = vec!["0011BD2485AD45D984EC4159C88FC066E5E3300E".to_string()]; let missing_fps = bad_command.missing_fps(found_fps); let expected_missing_fps = vec!["0123456789ABCDEF0123456789ABCDEF01234567".to_string()]; assert!(expected_missing_fps == missing_fps); } } src/commands/find.rs +10 −1 Original line number Diff line number Diff line Loading @@ -29,12 +29,13 @@ pub enum Filter { Version(String), /// Port policy PortPolicyFilter(PortPolicy), FpsFileFilter(Vec<util::RelayFingerprint>), } #[derive(Debug, Clone)] pub struct FindFilter { exclude: bool, filter: Filter, pub filter: Filter, } impl FindFilter { Loading Loading @@ -66,6 +67,11 @@ impl FindFilter { Filter::PortPolicyFilter(pp) => &**relay.md().ipv4_policy() == pp, // ^ this is `&Arc<PortPolicy>`, 1st dereference `Arc`, // then `&`, then add `&` to match `&PortPolicy` // The following will try to find the relay in the list of parsed // fingerprints from a file. Filter::FpsFileFilter(ff) => { ff.iter().any(|fp| fp.match_relay(relay)) } }; ret ^= self.exclude; ret Loading Loading @@ -143,6 +149,9 @@ impl FromStr for FindFilter { "pf" | "portpolicyfile" => Filter::PortPolicyFilter( util::portpolicyfile2portpolicy(Path::new(kv.1))?, ), "ff" | "fingerprintfile" => { Filter::FpsFileFilter(util::fpfile2fps(Path::new(kv.1))?) } _ => return Err(Error::UnrecognizedFilter(kv.0.to_string())), }; return Ok(FindFilter::new(exclude, filter)); Loading src/commands/util.rs +41 −2 Original line number Diff line number Diff line Loading @@ -42,8 +42,10 @@ impl FromStr for RelayFingerprint { impl fmt::Display for RelayFingerprint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { RelayFingerprint::Rsa(rsa) => rsa, RelayFingerprint::Ed(ed) => ed, // Convert to uppercase so that when they are printend or converted // `to_string`, they're always uppercase RelayFingerprint::Rsa(rsa) => rsa.to_uppercase(), RelayFingerprint::Ed(ed) => ed.to_uppercase(), }; write!(f, "{}", s) } Loading Loading @@ -87,6 +89,43 @@ pub fn portpolicyfile2portpolicy(path: &Path) -> Result<PortPolicy, Error> { Ok(portpolicy) } /// Convert fingerprints from a file into a Vector of RelayFingerprints /// pub fn fpfile2fps(path: &Path) -> Result<Vec<RelayFingerprint>, Error> { let pathbuf = PathBuf::from(path); let content = read_to_string(pathbuf)?; let parts: Vec<_> = content.split_whitespace().collect(); let (fingerprints, errors): (Vec<_>, Vec<_>) = parts .iter() .map(|part| part.parse::<RelayFingerprint>()) .partition(Result::is_ok); if !errors.is_empty() { println!( "Errors parsing {}: {}", path.display(), errors .iter() .map(|e| e.as_ref().unwrap_err().to_string()) .collect::<Vec<_>>() .join("\n") ); } Ok(fingerprints.into_iter().map(Result::unwrap).collect()) } /// Convert a Relay Vector into an String Vectors, with the relays' Rsa /// fingerprints. /// pub fn relays2fps(relays: &[tor_netdir::Relay]) -> Vec<String> { let found_fps: Vec<_> = relays .iter() .map(|relay| { relay.rsa_id().to_string().replace('$', "").to_uppercase() }) .collect(); found_fps } fn get_version(r: &tor_netdir::Relay) -> String { // Can't `unwrap_or` cause can't create `Version` data type r.rs() Loading testdata/fps.txt 0 → 100644 +3 −0 Original line number Diff line number Diff line 0011bd2485ad45d984ec4159c88fc066e5e3300e 0123456789abcdef0123456789abcdef0123456 0123456789abcdef0123456789abcdef01234567 Loading
README.md +69 −9 Original line number Diff line number Diff line Loading @@ -16,8 +16,9 @@ Note: part of this section should probably be moved into code documentation. A `filter` can be: - `addr:<IP address>` - `ff:<fingerprints file>` - `fl:<flag>` - `fp:<fingprint` - `fp:<fingerprint>` - `p:<port>` - `v:<tor version>` A filter can be `exclude` (boolean), ie. not matching a filter, with the Loading @@ -28,6 +29,10 @@ Note: part of this section should probably be moved into code documentation. The output are the rules for `approved-routers.conf` in the form `!badexit <fp>`. If the filter is `ff`, it'll also include in `approved-routers.conf` the fingerprints that weren't found in the consensus. If a fingerprint could not be parsed, it'll be ignored but printed to the stdout. Examples: - `config badexit 25`, output: Loading Loading @@ -152,26 +157,54 @@ Note: part of this section should probably be moved into code documentation. - `reject`: `Generate reject rule(s)` for the DirAuths. The parameters are a ticket number (in `bad-relay-reports` repo) and optionally some filters. It works as the previous command except that: - it also generates rules for `bad.conf` in the form `AuthDirReject <ip>`. - instead of generating rules for `approved-routers.conf` in the form `!badexit <fp>`, it generates rules like `!reject <fp>`. It works as the previous command but instead of generating rules for `approved-routers.conf` in the form `!badexit <fp>`, it generates rules like `!reject <fp>`. eg: - `config reject 25 p:8888`, output: ```bash [+] Rules for bad.conf: [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 !reject 2BC31B73E0000B66981F7734D2B1F2C16C27D0BB !reject 673510F48FA7EBE1C21A9A32566AB9B7AA8EFC48 !reject 94A8976E00C68ED23695D0668D87B3E7F126AF62 ----- [+] Found 3 relays: [FindFilter { exclude: false, filter: Port(8888) }] ``` - `rejectbad <ticket_number> [filters]` : `Generate reject rule(s), writing to bad.conf too`. It works as the previous command but it also generates rules for `bad.conf` in the form `AuthDirReject <ip>` plus comments with the corresponding relays' fingerprints. eg: - `config rejectbad 25 p:8888`, output: ```bash [+] Rules for torrc.d/bad.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 # Fingerprints: # 2BC31B73E0000B66981F7734D2B1F2C16C27D0BB # 5BE999DDB0916332AC21CE4AE9CED29FD7AAB284 # 94A8976E00C68ED23695D0668D87B3E7F126AF62 AuthDirReject 65.109.16.131 AuthDirReject 104.200.30.152 AuthDirReject 2600:3c03::f03c:93ff:fecc:2d20 AuthDirReject 155.248.213.203 ----- [+] Rules for approved-routers.conf: [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 Loading @@ -184,9 +217,36 @@ Note: part of this section should probably be moved into code documentation. ``` - `config rejectbad 25 ff:testdata/fps.txt`, output: ```bash Errors parsing testdata/fps.txt: Wrong fingerprint length: 0123456789abcdef0123456789abcdef0123456 Errors parsing testdata/fps.txt: Wrong fingerprint length: 0123456789abcdef0123456789abcdef0123456 [+] Rules for torrc.d/bad.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 # Fingerprints: # 0011BD2485AD45D984EC4159C88FC066E5E3300E AuthDirReject 162.247.74.201 ----- [+] Rules for approved-routers.d/approved-routers.conf: ----- # Ticket: https://gitlab.torproject.org/tpo/network-health/bad-relay-reports/-/issues/25 !reject 0011BD2485AD45D984EC4159C88FC066E5E3300E !reject 0123456789ABCDEF0123456789ABCDEF01234567 ----- [+] Found 1 relays: [FindFilter { exclude: false, filter: FpsFileFilter([Rsa("0011bd2485ad45d984ec4159c88fc066e5e3300e"), Rsa("0123456789abcdef0123456789abcdef01234567")]) }] ``` - `middleonly`: `Generate middleonly rule(s)` for the DirAuths. - The parameters are a ticket number (in `bad-relay-reports` repo) and - optionally some filters. The parameters are a ticket number (in `bad-relay-reports` repo) and optionally some filters. It works as the previous commands except that: - it does not generate rules for `bad.conf`. Loading
src/commands/config.rs +87 −14 Original line number Diff line number Diff line Loading @@ -5,11 +5,12 @@ use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::prelude::*; use std::path::Path; use std::path; use structopt::StructOpt; use crate::commands::err::Error; use crate::commands::find; use crate::commands::util; use crate::commands::RunnableOffline; static GITLAB_BUG_URL: &str = Loading Loading @@ -55,8 +56,16 @@ pub struct ConfigCommand { fn fmt_addr_rule(prefix: &str, relay: &tor_netdir::Relay<'_>) -> String { let rules: Vec<_> = relay .rs() // This returns all the ORPort IPs (v4 and v6) .orport_addrs() .map(|a| format!("{} {}", prefix, a.ip())) .map(|a| { if a.is_ipv4() { format!("{} {}", prefix, a.ip()) } else { // Enclose v6 IPs in brackets format!("{} [{}]", prefix, a.ip()) } }) .collect(); [rules.join("\n"), "\n".to_string()].concat() } Loading Loading @@ -104,7 +113,7 @@ impl BadCommand { } fn open_file(&self, fname: &str) -> Result<File, Error> { let path = Path::new(fname); let path = path::Path::new(fname); let parent = path .parent() .ok_or_else(|| Error::WrongParent(fname.to_string()))?; Loading Loading @@ -133,6 +142,20 @@ impl BadCommand { Ok(()) } fn print_missing_fps( &self, prefix: &str, file: &mut File, fps: Vec<String>, ) -> Result<(), anyhow::Error> { for fp in fps { let rule = format!("{} {}\n", prefix, fp); print!("{}", rule); file.write_all(rule.as_bytes())?; } Ok(()) } fn generate( &self, netdir: &tor_netdir::NetDir, Loading @@ -141,8 +164,8 @@ impl BadCommand { let relays = find::FindCommand::new(&self.filters).filter(netdir); // Do not create bad.conf config when there is not token for it, as it // is the case for `middleonly` argument. if !tokens.0.is_empty() { // is the case for `middleonly` argument or when no relays were found. if !tokens.0.is_empty() && !relays.is_empty() { // When token.0 is present, write also into BAD_PATH let fname = BAD_PATH; let mut file = self.open_file(fname)?; Loading @@ -158,21 +181,52 @@ impl BadCommand { )?; // Print the addresses when there's the `AuthDirReject` token self.print_rules(tokens.0, &mut file, fmt_addr_rule, &relays)?; self.print_footer(); } // Write into APPROVED_ROUTERS_PATH in any case // If the filter is `FpsFileFilter`, print all the parsed fingerprints // from the file even if the weren't found in the consensus. let found_fps = util::relays2fps(&relays); let missing_fps = self.missing_fps(found_fps); // Write into APPROVED_ROUTERS_PATH if there're found relays or // missing ones. if !relays.is_empty() || !missing_fps.is_empty() { let fname = APPROVED_ROUTERS_PATH; let mut file = self.open_file(fname)?; self.print_header(fname, &mut file)?; self.print_rules(tokens.1, &mut file, fmt_fp_rule, &relays)?; if !missing_fps.is_empty() { self.print_missing_fps(tokens.1, &mut file, missing_fps)?; } self.print_footer(); } println!("[+] Found {} relays: {:?}", relays.len(), self.filters); Ok(()) } /// Return the relays' fingerprints in the `FpsFileFiler` filter that were /// not found. /// Since `self` doesn't have a vector with the parsed fingeprints from /// the file, it is needed to parse the filters to obtain them. /// fn missing_fps(&self, found_fps: Vec<String>) -> Vec<String> { // NOTE: The following iterations and loops could be less verbose // Create a vector with the missing fps from the previous vector. let mut fps = Vec::new(); for find_filter in &self.filters { if let find::Filter::FpsFileFilter(relay_fingerprints) = &find_filter.filter { for relay_fingerprint in relay_fingerprints { if !found_fps.contains(&relay_fingerprint.to_string()) { fps.push(relay_fingerprint.to_string()) } } } } fps } } #[async_trait] Loading Loading @@ -216,8 +270,8 @@ mod tests { let file = bad_command.open_file(fname); assert!(file.is_err()); } #[test] #[test] fn print_header_ok() { let binding = temp_dir().join("approved-routers.conf"); let fname = binding.to_str().unwrap(); Loading @@ -233,4 +287,23 @@ mod tests { let result = bad_command.print_header(fname, &mut file); assert!(result.is_ok()); } #[test] fn missing_relays_ok() { let path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("testdata/fps.txt"); let bad_command = BadCommand { ticket: 1, filters: Vec::from([find::FindFilter::new( false, find::Filter::FpsFileFilter(util::fpfile2fps(&path).unwrap()), )]), }; let found_fps = vec!["0011BD2485AD45D984EC4159C88FC066E5E3300E".to_string()]; let missing_fps = bad_command.missing_fps(found_fps); let expected_missing_fps = vec!["0123456789ABCDEF0123456789ABCDEF01234567".to_string()]; assert!(expected_missing_fps == missing_fps); } }
src/commands/find.rs +10 −1 Original line number Diff line number Diff line Loading @@ -29,12 +29,13 @@ pub enum Filter { Version(String), /// Port policy PortPolicyFilter(PortPolicy), FpsFileFilter(Vec<util::RelayFingerprint>), } #[derive(Debug, Clone)] pub struct FindFilter { exclude: bool, filter: Filter, pub filter: Filter, } impl FindFilter { Loading Loading @@ -66,6 +67,11 @@ impl FindFilter { Filter::PortPolicyFilter(pp) => &**relay.md().ipv4_policy() == pp, // ^ this is `&Arc<PortPolicy>`, 1st dereference `Arc`, // then `&`, then add `&` to match `&PortPolicy` // The following will try to find the relay in the list of parsed // fingerprints from a file. Filter::FpsFileFilter(ff) => { ff.iter().any(|fp| fp.match_relay(relay)) } }; ret ^= self.exclude; ret Loading Loading @@ -143,6 +149,9 @@ impl FromStr for FindFilter { "pf" | "portpolicyfile" => Filter::PortPolicyFilter( util::portpolicyfile2portpolicy(Path::new(kv.1))?, ), "ff" | "fingerprintfile" => { Filter::FpsFileFilter(util::fpfile2fps(Path::new(kv.1))?) } _ => return Err(Error::UnrecognizedFilter(kv.0.to_string())), }; return Ok(FindFilter::new(exclude, filter)); Loading
src/commands/util.rs +41 −2 Original line number Diff line number Diff line Loading @@ -42,8 +42,10 @@ impl FromStr for RelayFingerprint { impl fmt::Display for RelayFingerprint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { RelayFingerprint::Rsa(rsa) => rsa, RelayFingerprint::Ed(ed) => ed, // Convert to uppercase so that when they are printend or converted // `to_string`, they're always uppercase RelayFingerprint::Rsa(rsa) => rsa.to_uppercase(), RelayFingerprint::Ed(ed) => ed.to_uppercase(), }; write!(f, "{}", s) } Loading Loading @@ -87,6 +89,43 @@ pub fn portpolicyfile2portpolicy(path: &Path) -> Result<PortPolicy, Error> { Ok(portpolicy) } /// Convert fingerprints from a file into a Vector of RelayFingerprints /// pub fn fpfile2fps(path: &Path) -> Result<Vec<RelayFingerprint>, Error> { let pathbuf = PathBuf::from(path); let content = read_to_string(pathbuf)?; let parts: Vec<_> = content.split_whitespace().collect(); let (fingerprints, errors): (Vec<_>, Vec<_>) = parts .iter() .map(|part| part.parse::<RelayFingerprint>()) .partition(Result::is_ok); if !errors.is_empty() { println!( "Errors parsing {}: {}", path.display(), errors .iter() .map(|e| e.as_ref().unwrap_err().to_string()) .collect::<Vec<_>>() .join("\n") ); } Ok(fingerprints.into_iter().map(Result::unwrap).collect()) } /// Convert a Relay Vector into an String Vectors, with the relays' Rsa /// fingerprints. /// pub fn relays2fps(relays: &[tor_netdir::Relay]) -> Vec<String> { let found_fps: Vec<_> = relays .iter() .map(|relay| { relay.rsa_id().to_string().replace('$', "").to_uppercase() }) .collect(); found_fps } fn get_version(r: &tor_netdir::Relay) -> String { // Can't `unwrap_or` cause can't create `Version` data type r.rs() Loading
testdata/fps.txt 0 → 100644 +3 −0 Original line number Diff line number Diff line 0011bd2485ad45d984ec4159c88fc066e5e3300e 0123456789abcdef0123456789abcdef0123456 0123456789abcdef0123456789abcdef01234567