<-- Home |--rust

Update Hosts in Rust中操作hosts文件

Rust启动

说真的,我挺不喜欢锈粉(Rustaceans)那股味道的,就好像我家两个原神粉(老婆和孩子)天天攻击我说魔兽世界是垃圾游戏一样,我,我,我打,我打扫卫生去……

但是,有时候,有时候,我会相信一切有尽头,相聚离开都有时候,没有什么会永垂不锈

前几天,我想搞一个各种语言GCD的玩意,看看各个语言的泛型约束方式和实现的比较,结果搞起Scala的时候,发现Scala 3现在用Coursier来管理依赖,搞半天下载不动。好嘛,那我就更新hosts文件。结果,有一些hosts下载下来domain对应的ip有好几个,能访问不能访问的都有,还要自己一个一个试,那也太坑了吧。。。

一怒之下,Rust,启动!

代码

Cargo大法好,Rust保平安。

工程文件

 1[package]
 2name = "updatehosts4r"
 3version = "0.1.0"
 4edition = "2024"
 5
 6[dependencies]
 7reqwest = { version = "0.11", features = ["blocking"] }
 8regex = "1.10"
 9crossbeam = "0.8"
10chrono = "0.4"

不知道为什么cargo new的时候,给我生成的edition是2024年……

依赖的几个库,reqwest是用来下载hosts文件的,regex是模式匹配的,用于解析ping命令的输出,crossbeam是用来并发操作的。chrono是用来获取当前时间的。

程序功能

程序的功能很简单,就是下载hosts文件,然后解析出域名和对应的ip,然后ping这些ip,选择最快的ip,然后更新hosts文件。

graph TD
    A[下载hosts文件] --> B[解析出域名和对应的ip]
    B --> C[ping这些ip]
    C --> D[选择最快的ip]
    D --> E[更新hosts文件]
    E --> F[刷新DNS缓存]

稍微好玩一点就是有:下载hosts文件、ping每个域名对应的多个ip选择最快的一个。

下载文件

关于reqwest(这个名字简直笑死我了……顺便吐槽下Rust糟糕的社区Crates.io奇葩的管理政策),我这里用的是blocking模式,因为就一个文件,下载时间很短,没必要异步。>

1let resp = reqwest::blocking::get("https://cdn.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts")?;

这个调用的最后有一个?,是一个语法糖,代表着如果get如果返回了一个正确的值,则该值被提取了赋值给resp,否则程序会中断执行下面的语句提前返回,把错误向上扩散。我们还是偏好显式地处理错误,如同下面地代码。

 1        match get(*url) {
 2            Ok(response) => {
 3                if response.status().is_success() {
 4                    let content = response.text()?;
 5                    let mut file = File::create(TEMP_FILE_PATH)?;
 6                    file.write_all(content.as_bytes())?;
 7                    println!("Successfully downloaded from: {}", url);
 8                    return Ok(());
 9                }
10            }
11            Err(e) => {
12                println!("Failed to download from {}: {}", url, e);
13                continue;
14            }
15        }

get方法返回一个Result<Response>,这个match语句是处理Result的典型用法,整挺好。

当然,这里地场景实在是太简单。

并发ping

并发ping的场景,我这里用的是crossbeamunbounded通道。这个通道的容量是无限的,所以可以无限制地发送消息。示例代码如下:

 1use crossbeam_channel::unbounded;
 2
 3// Create a channel of unbounded capacity.
 4let (s, r) = unbounded();
 5
 6// Send a message into the channel.
 7s.send("Hello, world!").unwrap();
 8
 9// Receive the message from the channel.
10assert_eq!(r.recv(), Ok("Hello, world!"));

在我们的程序中,主要考虑ping域名有时候需要等待挺久,就算是设置了超时1s,有的domain有四个ip,需要ping四次,所以需要并发。当一个线程完成对应某个域名的所有ip的计时后,把时间最短的结果发送到通道中,然后主进程从通道中接收结果,并更新字典记录。

 1fn select_fastest_ips(hosts_map: &HashMap<String, Vec<String>>) -> HashMap<String, (String, u32)> {
 2    let mut result = HashMap::new();
 3    let (tx, rx) = unbounded();
 4    
 5    for (domain, ips) in hosts_map {
 6        let domain = domain.clone();
 7        let ips = ips.clone();
 8        let tx = tx.clone();
 9        
10        thread::spawn(move || {
11            let mut min_time = MAX_PING_TIME;
12            let mut fastest_ip = None;
13
14            for ip in ips {
15                match ping_ip(&ip) {
16                    Ok(time) => {
17                        let time_str = if time == MAX_PING_TIME {
18                            "timeout".to_string()
19                        } else {
20                            format!("{} ms", time)
21                        };
22                        let domain_str = format!(" {} [{}]", domain, ip);
23                        println!("{:<55} ==> {}", domain_str, time_str);
24                        if time < min_time {
25                            min_time = time;
26                            fastest_ip = Some(ip);
27                        }
28                    },
29                    Err(e) => {
30                        println!("{} {} ==> {}", domain, ip, e);
31                        continue;
32                    }
33                }
34            }
35
36            if let Some(ip) = fastest_ip {
37                tx.send((domain, (ip, min_time))).unwrap();
38            }
39        });
40    }
41
42    drop(tx);
43    
44    while let Ok((domain, ip_time)) = rx.recv() {
45        result.insert(domain, ip_time);
46    }
47
48    result
49}

这里,在每个线程中,需要使用的几个变量就是domain,ips,和用于通讯的tx,我们采用了thread::spawn来创建线程,并利用move关键字把变量所有权转移给线程。也正是因为如此,我们在启动线程之前,需要把domainipstx克隆一份共线程内部使用。

并且,drop(tx)会释放主线程的tx,而每个线程中的clone只会赋值一个通道的句柄,并且这个对句柄的应用会在线程结束时自动释放。最终这个结构实现了所有线程工作完成后,主进程才从通道中接收结果的阻塞等待才会结束。这个过程更好的用下面的程序演示:

这个程序需要两个依赖:

1cargo add crossbeam rand

源代码很简单。

 1use crossbeam::channel::unbounded;
 2use std::thread;
 3use rand::Rng;
 4use std::time::Duration;
 5
 6fn main() {
 7    let (tx, rx) = unbounded();
 8    
 9    // 创建3个工作线程
10    for i in 0..3 {
11        let tx = tx.clone();
12        thread::spawn(move || {
13            println!("Thread {} starting", i);
14            // sleep 1~10 s
15            let sleep_time = rand::rng().random_range(1..=10);
16            thread::sleep(Duration::from_secs(sleep_time));
17            tx.send(format!("Thread {} sleep {} s", i, sleep_time)).unwrap();
18            println!("Thread {} ended", i);
19            // 线程结束,tx的克隆在这里被丢弃
20        });
21    }
22    
23    // 丢弃主线程的tx
24    drop(tx);
25    
26    // 接收所有消息
27    for received in rx {
28        println!("Received: {}", received);
29    }
30}

一次运行可能像这样。

 1cargo run
 2# Thread 1 starting
 3# Thread 0 starting
 4# Thread 2 starting
 5# Thread 2 ended
 6# Received: Thread 2 sleep 6 s
 7# Thread 1 ended
 8# Received: Thread 1 sleep 8 s
 9# Received: Thread 0 sleep 8 s
10# Thread 0 ended

但是朋友们请一定要把程序中的线程数改为300,睡眠秒数改为1..=100,然后运行一下,真的比较有意思。想到这里,我感觉可以用Rust锈化一个传说中的Sleep排序,简直不要太美哦……

其它部分

其它对于文件的读取和写入、命令行的操作,意思不大,就不赘述了。

源代码

工程文件下面就是src文件夹,里面就是源文件main.rs

  1use std::fs::File;
  2use std::io::{Write, BufRead, BufReader, Read};
  3use std::collections::HashMap;
  4use std::process::Command;
  5use std::str::FromStr;
  6use std::thread;
  7use reqwest::blocking::get;
  8use regex::Regex;
  9use crossbeam::channel::unbounded;
 10use chrono;
 11
 12// Constants
 13const URLS: [&str; 4] = [
 14    "https://cdn.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts",
 15    "https://fastly.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts",
 16    "https://testingcf.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts",
 17    "https://gcore.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts",
 18];
 19
 20const HOST_FILE_PATH: &str = "c:/windows/system32/drivers/etc/hosts";
 21const TEMP_FILE_PATH: &str = "temp_hosts.txt";
 22const TEMP_HOSTS_FILE_PATH: &str = "temp_hosts";
 23const MAX_PING_TIME: u32 = std::u32::MAX;
 24
 25// File operations
 26fn download_hosts_file() -> Result<(), Box<dyn std::error::Error>> {
 27    for url in URLS.iter() {
 28        println!("Trying to download from: {}", url);
 29        match get(*url) {
 30            Ok(response) => {
 31                if response.status().is_success() {
 32                    let content = response.text()?;
 33                    let mut file = File::create(TEMP_FILE_PATH)?;
 34                    file.write_all(content.as_bytes())?;
 35                    println!("Successfully downloaded from: {}", url);
 36                    return Ok(());
 37                }
 38            }
 39            Err(e) => {
 40                println!("Failed to download from {}: {}", url, e);
 41                continue;
 42            }
 43        }
 44    }
 45    Err("All download attempts failed".into())
 46}
 47
 48fn parse_hosts_file() -> Result<HashMap<String, Vec<String>>, Box<dyn std::error::Error>> {
 49    let file = File::open(TEMP_FILE_PATH)?;
 50    let reader = BufReader::new(file);
 51    let mut hosts_map: HashMap<String, Vec<String>> = HashMap::new();
 52
 53    for line in reader.lines() {
 54        let line = line?;
 55        let line = line.trim();
 56        
 57        if line.is_empty() || line.starts_with('#') {
 58            continue;
 59        }
 60
 61        let parts: Vec<&str> = line.split_whitespace().collect();
 62        if parts.len() >= 2 {
 63            let ip = parts[0].to_string();
 64            let domain = parts[1].to_string();
 65
 66            hosts_map.entry(domain)
 67                .or_insert_with(Vec::new)
 68                .push(ip);
 69        }
 70    }
 71
 72    Ok(hosts_map)
 73}
 74
 75fn write_hosts_file(domain_ips: &HashMap<String, (String, u32)>) -> Result<(), Box<dyn std::error::Error>> {
 76    let mut file = File::create(TEMP_HOSTS_FILE_PATH)?;
 77    
 78    writeln!(file, "# Generated by updatehosts4r")?;
 79    writeln!(file, "# Date: {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"))?;
 80    writeln!(file)?;
 81    
 82    for (domain, (ip, time)) in domain_ips {
 83        writeln!(file, "{}    {}    # ping: {}ms", ip, domain, time)?;
 84    }
 85    
 86    Ok(())
 87}
 88
 89// Network operations
 90fn ping_ip(ip: &str) -> Result<u32, Box<dyn std::error::Error>> {
 91    let output = Command::new("ping")
 92        .args(["-n", "4", "-w", "1000", ip])
 93        .output()?;
 94
 95    let output_str = String::from_utf8_lossy(&output.stdout);
 96    
 97    if !output.status.success() {
 98        return Ok(MAX_PING_TIME);
 99    }
100
101    let mut total_time = 0;
102    let mut count = 0;
103    let re = Regex::new(r"=(\d+)ms").unwrap();
104
105    for line in output_str.lines() {
106        if line.contains(ip) && line.contains("TTL=") {
107            if let Some(caps) = re.captures(line) {
108                if let Some(time_str) = caps.get(1) {
109                    if let Ok(time) = u32::from_str(time_str.as_str()) {
110                        total_time += time;
111                        count += 1;
112                    }
113                }
114            }
115        }
116    }
117
118    if count == 0 {
119        Ok(MAX_PING_TIME)
120    } else {
121        Ok(total_time / count)
122    }
123}
124
125fn select_fastest_ips(hosts_map: &HashMap<String, Vec<String>>) -> HashMap<String, (String, u32)> {
126    let mut result = HashMap::new();
127    let (tx, rx) = unbounded();
128    
129    for (domain, ips) in hosts_map {
130        let domain = domain.clone();
131        let ips = ips.clone();
132        let tx = tx.clone();
133        
134        thread::spawn(move || {
135            let mut min_time = MAX_PING_TIME;
136            let mut fastest_ip = None;
137
138            for ip in ips {
139                match ping_ip(&ip) {
140                    Ok(time) => {
141                        let time_str = if time == MAX_PING_TIME {
142                            "timeout".to_string()
143                        } else {
144                            format!("{} ms", time)
145                        };
146                        let domain_str = format!(" {} [{}]", domain, ip);
147                        println!("{:<55} ==> {}", domain_str, time_str);
148                        if time < min_time {
149                            min_time = time;
150                            fastest_ip = Some(ip);
151                        }
152                    },
153                    Err(e) => {
154                        println!("{} {} ==> {}", domain, ip, e);
155                        continue;
156                    }
157                }
158            }
159
160            if let Some(ip) = fastest_ip {
161                tx.send((domain, (ip, min_time))).unwrap();
162            }
163        });
164    }
165
166    drop(tx);
167    
168    while let Ok((domain, ip_time)) = rx.recv() {
169        result.insert(domain, ip_time);
170    }
171
172    result
173}
174
175// System operations
176fn copy_and_flush_dns() -> Result<(), Box<dyn std::error::Error>> {
177    let mut hosts_content = Vec::new();
178    File::open(TEMP_HOSTS_FILE_PATH)?.read_to_end(&mut hosts_content)?;
179
180    let mut system_hosts = File::create(HOST_FILE_PATH)?;
181    system_hosts.write_all(&hosts_content)?;
182
183    let output = Command::new("ipconfig")
184        .arg("/flushdns")
185        .output()?;
186
187    if !output.status.success() {
188        return Err("Failed to flush DNS cache".into());
189    }
190
191    Ok(())
192}
193
194// Main workflow
195fn run_select_fastest_ips() -> Result<(), Box<dyn std::error::Error>> {
196    let hosts_map = parse_hosts_file()?;
197    if hosts_map.is_empty() {
198        return Err("Hosts map is empty".into());
199    }
200    
201    println!("Total domains: {}", hosts_map.len());
202    
203    let fastest_ips = select_fastest_ips(&hosts_map);
204    
205    println!("\nFastest IPs selected for {} domains", fastest_ips.len());
206    println!("\nResults (domain -> ip -> ping time):");
207    for (domain, (ip, time)) in &fastest_ips {
208        println!("{:>40} -> {:<15} -> {}ms", domain, ip, time);
209    }
210    
211    println!("\nWriting results to hosts file...");
212    write_hosts_file(&fastest_ips)?;
213    println!("Done!");
214    
215    Ok(())
216}
217
218// Main function
219fn main() {
220    if let Err(e) = download_hosts_file() {
221        println!("Error: {}", e);
222        return;
223    }
224
225    if let Err(e) = run_select_fastest_ips() {
226        println!("Error: {}", e);
227        return;
228    }
229
230    match copy_and_flush_dns() {
231        Ok(_) => println!("\nHosts file updated and DNS cache flushed successfully!"),
232        Err(e) => println!("\nWarning: Failed to update system hosts file: {}", e),
233    }
234
235    // Clean up temporary files
236    if let Err(e) = std::fs::remove_file(TEMP_FILE_PATH) {
237        println!("Error deleting temp file: {}", e);
238    } else {
239        println!("Temp file deleted");
240    }
241    
242    if let Err(e) = std::fs::remove_file(TEMP_HOSTS_FILE_PATH) {
243        println!("Error deleting temp hosts file: {}", e);
244    } else {
245        println!("Temp hosts file deleted");
246    }
247
248    // show press any key to exit
249    println!("Press any key to exit...");
250    let mut input = String::new();
251    std::io::stdin().read_line(&mut input).unwrap();
252}
253
254// Tests
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_ping_ip_success() {
261        let result = ping_ip("39.156.70.46");
262        assert!(result.is_ok());
263        let avg_time = result.unwrap();
264        println!("Acutal Ping time: {}ms {}", avg_time, MAX_PING_TIME);
265        assert!(avg_time < MAX_PING_TIME, "Ping time should be less than MAX_PING_TIME");
266        assert!(avg_time > 0, "Ping time should be greater than 0");
267    }
268
269    #[test]
270    fn test_ping_ip_invalid() {
271        let result = ping_ip("256.256.256.256");
272        assert!(result.is_ok());
273        assert_eq!(result.unwrap(), MAX_PING_TIME);
274    }
275
276    #[test]
277    fn test_ping_ip_unreachable() {
278        let result = ping_ip("192.168.255.255");
279        assert!(result.is_ok());
280        assert_eq!(result.unwrap(), MAX_PING_TIME);
281    }
282
283    #[test]
284    fn test_select_fastest_ips() {
285        assert!(run_select_fastest_ips().is_ok());
286    }
287}

运行

1cargo run 
2cargo build -release
3./target/release/updatehosts4r

当然,这个程序仅仅适用于Windows系统。linux下面更简单,但是我们WSL党根本不需要。后面可以把这个exe文件设置一个管理员权限运行(右键属性,设置如下图中箭头所示)。否则就要打开一个管理员权限的命令行运行上面的命令。如果没有权限,那就没办法更改hosts文件了。

设置管理员权限

下面是运行的输出,包括了下载的域名和对应的ip以及ping的时间。目前程序里设置了1s的超时。

Trying to download from: https://cdn.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts
Successfully downloaded from: https://cdn.jsdelivr.net/gh/ittuann/GitHub-IP-hosts@main/hosts
Total domains: 37
 github.blog [192.0.66.2]                               ==> 81 ms
 pipelines.actions.githubusercontent.com [13.107.42.16] ==> 119 ms
 github.githubassets.com [185.199.108.154]              ==> 265 ms
 alive.github.com [140.82.113.25]                       ==> 289 ms
 education.github.com [140.82.112.21]                   ==> 276 ms
 copilot-proxy.githubusercontent.com [20.85.130.105]    ==> 280 ms
 github-cloud.s3.amazonaws.com [16.15.184.238]          ==> 274 ms
 codeload.github.com [140.82.114.10]                    ==> 273 ms
 github.community [140.82.113.17]                       ==> 291 ms
 origin-tracker.githubusercontent.com [140.82.113.21]   ==> 274 ms
 github.com [140.82.112.4]                              ==> 280 ms
 central.github.com [140.82.112.22]                     ==> 274 ms
 github.global.ssl.fastly.net [151.101.1.194]           ==> 279 ms
 github.io [185.199.108.153]                            ==> 281 ms
 gist.github.com [140.82.112.3]                         ==> 282 ms
 githubcopilot.com [140.82.112.18]                      ==> 275 ms
 collector.github.com [140.82.112.21]                   ==> 279 ms
 githubstatus.com [185.199.108.153]                     ==> 279 ms
 api.github.com [140.82.112.6]                          ==> 280 ms
 github-com.s3.amazonaws.com [16.15.193.138]            ==> 286 ms
 live.github.com [140.82.113.25]                        ==> 294 ms
 github.githubassets.com [185.199.109.154]              ==> 271 ms
 education.github.com [140.82.113.21]                   ==> 269 ms
 github-cloud.s3.amazonaws.com [16.15.192.141]          ==> 283 ms
 github.community [140.82.113.18]                       ==> 275 ms
 github.com [140.82.114.3]                              ==> 282 ms
 github.global.ssl.fastly.net [151.101.129.194]         ==> 269 ms
 central.github.com [140.82.113.22]                     ==> 281 ms
 origin-tracker.githubusercontent.com [140.82.114.21]   ==> 283 ms
 github.io [185.199.109.153]                            ==> 267 ms
 githubstatus.com [185.199.109.153]                     ==> 261 ms
 githubcopilot.com [140.82.113.17]                      ==> 280 ms
 collector.github.com [140.82.114.22]                   ==> 273 ms
 gist.github.com [140.82.113.4]                         ==> 287 ms
 api.github.com [140.82.113.6]                          ==> 285 ms
 live.github.com [140.82.113.26]                        ==> 280 ms
 github-com.s3.amazonaws.com [3.5.0.87]                 ==> 293 ms
 avatars4.githubusercontent.com [185.199.108.133]       ==> timeout
 objects.githubusercontent.com [185.199.108.133]        ==> timeout
 avatars2.githubusercontent.com [185.199.108.133]       ==> timeout
 favicons.githubusercontent.com [185.199.108.133]       ==> timeout
 raw.githubusercontent.com [185.199.108.133]            ==> timeout
 cloud.githubusercontent.com [185.199.108.133]          ==> timeout
 user-images.githubusercontent.com [185.199.108.133]    ==> timeout
 avatars1.githubusercontent.com [185.199.108.133]       ==> timeout
 camo.githubusercontent.com [185.199.108.133]           ==> timeout
 media.githubusercontent.com [185.199.108.133]          ==> timeout
 avatars0.githubusercontent.com [185.199.108.133]       ==> timeout
 avatars5.githubusercontent.com [185.199.108.133]       ==> timeout
 avatars.githubusercontent.com [185.199.108.133]        ==> timeout
 avatars3.githubusercontent.com [185.199.108.133]       ==> timeout
 desktop.githubusercontent.com [185.199.108.133]        ==> timeout
 github.map.fastly.net [185.199.108.133]                ==> timeout
 github.githubassets.com [185.199.110.154]              ==> 254 ms
 github-com.s3.amazonaws.com [3.5.27.67]                ==> 297 ms
 github.global.ssl.fastly.net [151.101.193.194]         ==> 241 ms
 githubstatus.com [185.199.110.153]                     ==> 243 ms
 github.io [185.199.110.153]                            ==> 236 ms
 github.githubassets.com [185.199.111.154]              ==> 278 ms
 github-cloud.s3.amazonaws.com [3.5.27.149]             ==> timeout
 avatars4.githubusercontent.com [185.199.109.133]       ==> timeout
 camo.githubusercontent.com [185.199.109.133]           ==> timeout
 avatars3.githubusercontent.com [185.199.109.133]       ==> timeout
 avatars.githubusercontent.com [185.199.109.133]        ==> timeout
 github.map.fastly.net [185.199.109.133]                ==> timeout
 media.githubusercontent.com [185.199.109.133]          ==> timeout
 favicons.githubusercontent.com [185.199.109.133]       ==> timeout
 avatars5.githubusercontent.com [185.199.109.133]       ==> timeout
 cloud.githubusercontent.com [185.199.109.133]          ==> timeout
 raw.githubusercontent.com [185.199.109.133]            ==> timeout
 objects.githubusercontent.com [185.199.109.133]        ==> timeout
 avatars2.githubusercontent.com [185.199.109.133]       ==> timeout
 avatars0.githubusercontent.com [185.199.109.133]       ==> timeout
 avatars1.githubusercontent.com [185.199.109.133]       ==> timeout
 user-images.githubusercontent.com [185.199.109.133]    ==> timeout
 desktop.githubusercontent.com [185.199.109.133]        ==> timeout
 githubstatus.com [185.199.111.153]                     ==> 257 ms
 github.io [185.199.111.153]                            ==> 258 ms
 github.global.ssl.fastly.net [151.101.65.194]          ==> 267 ms
 avatars4.githubusercontent.com [185.199.110.133]       ==> 251 ms
 camo.githubusercontent.com [185.199.110.133]           ==> 251 ms
 github.map.fastly.net [185.199.110.133]                ==> 249 ms
 avatars3.githubusercontent.com [185.199.110.133]       ==> 251 ms
 media.githubusercontent.com [185.199.110.133]          ==> 252 ms
 avatars.githubusercontent.com [185.199.110.133]        ==> 248 ms
 raw.githubusercontent.com [185.199.110.133]            ==> 248 ms
 favicons.githubusercontent.com [185.199.110.133]       ==> 249 ms
 avatars2.githubusercontent.com [185.199.110.133]       ==> 247 ms
 avatars5.githubusercontent.com [185.199.110.133]       ==> 250 ms
 objects.githubusercontent.com [185.199.110.133]        ==> 249 ms
 cloud.githubusercontent.com [185.199.110.133]          ==> 249 ms
 avatars1.githubusercontent.com [185.199.110.133]       ==> 254 ms
 user-images.githubusercontent.com [185.199.110.133]    ==> 256 ms
 desktop.githubusercontent.com [185.199.110.133]        ==> 257 ms
 avatars0.githubusercontent.com [185.199.110.133]       ==> 255 ms
 github-com.s3.amazonaws.com [3.5.29.107]               ==> timeout
 github-cloud.s3.amazonaws.com [3.5.28.239]             ==> timeout
 avatars4.githubusercontent.com [185.199.111.133]       ==> timeout
 avatars.githubusercontent.com [185.199.111.133]        ==> timeout
 raw.githubusercontent.com [185.199.111.133]            ==> timeout
 avatars0.githubusercontent.com [185.199.111.133]       ==> timeout
 objects.githubusercontent.com [185.199.111.133]        ==> timeout
 avatars2.githubusercontent.com [185.199.111.133]       ==> timeout
 cloud.githubusercontent.com [185.199.111.133]          ==> timeout
 github.map.fastly.net [185.199.111.133]                ==> timeout
 camo.githubusercontent.com [185.199.111.133]           ==> timeout
 avatars3.githubusercontent.com [185.199.111.133]       ==> timeout
 user-images.githubusercontent.com [185.199.111.133]    ==> timeout
 media.githubusercontent.com [185.199.111.133]          ==> timeout
 avatars1.githubusercontent.com [185.199.111.133]       ==> timeout
 desktop.githubusercontent.com [185.199.111.133]        ==> timeout
 avatars5.githubusercontent.com [185.199.111.133]       ==> timeout
 favicons.githubusercontent.com [185.199.111.133]       ==> timeout

Fastest IPs selected for 37 domains

Results (domain -> ip -> ping time):
          avatars4.githubusercontent.com -> 185.199.110.133 -> 251ms
               raw.githubusercontent.com -> 185.199.110.133 -> 248ms
            github.global.ssl.fastly.net -> 151.101.193.194 -> 241ms
          avatars0.githubusercontent.com -> 185.199.110.133 -> 255ms
    origin-tracker.githubusercontent.com -> 140.82.113.21   -> 274ms
                    education.github.com -> 140.82.113.21   -> 269ms
                         live.github.com -> 140.82.113.26   -> 280ms
                    collector.github.com -> 140.82.114.22   -> 273ms
                              github.com -> 140.82.112.4    -> 280ms
                   github.map.fastly.net -> 185.199.110.133 -> 249ms
                          api.github.com -> 140.82.112.6    -> 280ms
          avatars2.githubusercontent.com -> 185.199.110.133 -> 247ms
       user-images.githubusercontent.com -> 185.199.110.133 -> 256ms
           github-cloud.s3.amazonaws.com -> 16.15.184.238   -> 274ms
          avatars1.githubusercontent.com -> 185.199.110.133 -> 254ms
             github-com.s3.amazonaws.com -> 16.15.193.138   -> 286ms
     copilot-proxy.githubusercontent.com -> 20.85.130.105   -> 280ms
           objects.githubusercontent.com -> 185.199.110.133 -> 249ms
          avatars3.githubusercontent.com -> 185.199.110.133 -> 251ms
              camo.githubusercontent.com -> 185.199.110.133 -> 251ms
           desktop.githubusercontent.com -> 185.199.110.133 -> 257ms
                         gist.github.com -> 140.82.112.3    -> 282ms
                     codeload.github.com -> 140.82.114.10   -> 273ms
                 github.githubassets.com -> 185.199.110.154 -> 254ms
                               github.io -> 185.199.110.153 -> 236ms
           avatars.githubusercontent.com -> 185.199.110.133 -> 248ms
             cloud.githubusercontent.com -> 185.199.110.133 -> 249ms
          favicons.githubusercontent.com -> 185.199.110.133 -> 249ms
          avatars5.githubusercontent.com -> 185.199.110.133 -> 250ms
                      central.github.com -> 140.82.112.22   -> 274ms
                       githubcopilot.com -> 140.82.112.18   -> 275ms
                        alive.github.com -> 140.82.113.25   -> 289ms
 pipelines.actions.githubusercontent.com -> 13.107.42.16    -> 119ms
                             github.blog -> 192.0.66.2      -> 81ms
             media.githubusercontent.com -> 185.199.110.133 -> 252ms
                        github.community -> 140.82.113.18   -> 275ms
                        githubstatus.com -> 185.199.110.153 -> 243ms

Writing results to hosts file...
Done!

Hosts file updated and DNS cache flushed successfully!
Temp file deleted
Temp hosts file deleted
Press any key to exit...

总结

反正最后我更新了hosts文件,把Coursier给装上了……

Scala 3

Rust,哦不,Scala,启动!


文章标签

|-->rust |-->hosts |-->reqwest |-->fs |-->cargo |-->crossbeam |-->github.com |-->Coursier


GitHub