Skip to main content

innernet_server/
lib.rs

1use anyhow::{anyhow, bail};
2use colored::*;
3use dialoguer::Confirm;
4use hyper::{http, server::conn::AddrStream, Body, Request, Response};
5use indoc::printdoc;
6use innernet_shared::{
7    get_local_addrs, update_hosts_file, AddCidrOpts, AddPeerOpts, DeleteCidrOpts,
8    EnableDisablePeerOpts, Endpoint, IoErrorContext, NetworkOpts, PeerContents, RenameCidrOpts,
9    RenamePeerOpts, INNERNET_PUBKEY_HEADER,
10};
11use ipnet::IpNet;
12use parking_lot::{Mutex, RwLock};
13use rusqlite::Connection;
14use serde::{Deserialize, Serialize};
15use std::{
16    collections::{HashMap, VecDeque},
17    convert::TryInto,
18    env,
19    fs::File,
20    io::prelude::*,
21    net::{IpAddr, SocketAddr, TcpListener},
22    ops::Deref,
23    path::{Path, PathBuf},
24    sync::Arc,
25    time::Duration,
26};
27use subtle::ConstantTimeEq;
28use wireguard_control::{Backend, Device, DeviceUpdate, InterfaceName, Key, PeerConfigBuilder};
29
30mod api;
31mod db;
32mod error;
33pub mod initialize;
34#[cfg(test)]
35mod test;
36mod util;
37
38use db::{DatabaseCidr, DatabasePeer};
39pub use error::ServerError;
40use innernet_shared::{prompts, wg, CidrTree, Error, Interface};
41
42const VERSION: &str = env!("CARGO_PKG_VERSION");
43
44type Db = Arc<Mutex<Connection>>;
45type Endpoints = Arc<RwLock<HashMap<String, SocketAddr>>>;
46
47#[derive(Clone)]
48pub struct Context {
49    pub db: Db,
50    pub endpoints: Endpoints,
51    pub interface: InterfaceName,
52    pub backend: Backend,
53    pub public_key: Key,
54}
55
56pub struct Session {
57    pub context: Context,
58    pub peer: DatabasePeer,
59}
60
61impl Session {
62    pub fn admin_capable(&self) -> bool {
63        self.peer.is_admin && self.user_capable()
64    }
65
66    pub fn user_capable(&self) -> bool {
67        !self.peer.is_disabled && self.peer.is_redeemed
68    }
69
70    pub fn redeemable(&self) -> bool {
71        !self.peer.is_disabled && !self.peer.is_redeemed
72    }
73}
74
75#[derive(Deserialize, Serialize, Debug)]
76#[serde(rename_all = "kebab-case")]
77pub struct ConfigFile {
78    /// The server's WireGuard key
79    pub private_key: String,
80
81    /// The listen port of the server
82    pub listen_port: u16,
83
84    /// The internal WireGuard IP address assigned to the server
85    pub address: IpAddr,
86
87    /// The CIDR prefix of the WireGuard network
88    pub network_cidr_prefix: u8,
89}
90
91impl ConfigFile {
92    pub fn write_to_path<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
93        let mut invitation_file = File::create(&path).with_path(&path)?;
94        innernet_shared::chmod(&invitation_file, 0o600)?;
95        invitation_file
96            .write_all(toml::to_string(self).unwrap().as_bytes())
97            .with_path(path)?;
98        Ok(())
99    }
100
101    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
102        let path = path.as_ref();
103        let file = File::open(path).with_path(path)?;
104        if innernet_shared::chmod(&file, 0o600)? {
105            println!(
106                "{} updated permissions for {} to 0600.",
107                "[!]".yellow(),
108                path.display()
109            );
110        }
111        Ok(toml::from_str(
112            &std::fs::read_to_string(path).with_path(path)?,
113        )?)
114    }
115}
116
117#[derive(Clone, Debug)]
118pub struct ServerConfig {
119    pub config_dir: PathBuf,
120    pub data_dir: PathBuf,
121}
122
123impl ServerConfig {
124    pub fn new(config_dir: PathBuf, data_dir: PathBuf) -> Self {
125        Self {
126            config_dir,
127            data_dir,
128        }
129    }
130
131    pub fn database_dir(&self) -> &Path {
132        &self.data_dir
133    }
134
135    pub fn database_path(&self, interface: &InterfaceName) -> PathBuf {
136        PathBuf::new()
137            .join(self.database_dir())
138            .join(interface.to_string())
139            .with_extension("db")
140    }
141
142    pub fn config_dir(&self) -> &Path {
143        &self.config_dir
144    }
145
146    pub fn config_path(&self, interface: &InterfaceName) -> PathBuf {
147        PathBuf::new()
148            .join(self.config_dir())
149            .join(interface.to_string())
150            .with_extension("conf")
151    }
152}
153
154fn open_database_connection(
155    interface: &InterfaceName,
156    conf: &ServerConfig,
157) -> Result<rusqlite::Connection, Error> {
158    let database_path = conf.database_path(interface);
159    if !Path::new(&database_path).exists() {
160        bail!(
161            "no database file found at {}",
162            database_path.to_string_lossy()
163        );
164    }
165
166    let conn = Connection::open(&database_path)?;
167    // Foreign key constraints aren't on in SQLite by default. Enable.
168    conn.pragma_update(None, "foreign_keys", 1)?;
169    db::auto_migrate(&conn)?;
170    Ok(conn)
171}
172
173pub fn add_peer(
174    interface: &InterfaceName,
175    conf: &ServerConfig,
176    opts: AddPeerOpts,
177    network: NetworkOpts,
178) -> Result<(), Error> {
179    let config = ConfigFile::from_file(conf.config_path(interface))?;
180    let conn = open_database_connection(interface, conf)?;
181    let peers = DatabasePeer::list(&conn)?
182        .into_iter()
183        .map(|dp| dp.inner)
184        .collect::<Vec<_>>();
185    let cidrs = DatabaseCidr::list(&conn)?;
186    let cidr_tree = CidrTree::new(&cidrs[..]);
187
188    if let Some(result) = innernet_shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
189        let (peer_request, keypair, target_path, mut target_file) = result;
190        let peer = DatabasePeer::create(&conn, peer_request)?;
191        if cfg!(not(test)) && Device::get(interface, network.backend).is_ok() {
192            // Update the current WireGuard interface with the new peers.
193            DeviceUpdate::new()
194                .add_peer(PeerConfigBuilder::from(&*peer))
195                .apply(interface, network.backend)
196                .map_err(|_| ServerError::WireGuard)?;
197
198            println!("adding to WireGuard interface: {}", &*peer);
199        }
200
201        let server_peer = DatabasePeer::get(&conn, 1)?;
202        prompts::write_peer_invitation(
203            (&mut target_file, &target_path),
204            interface,
205            &peer,
206            &server_peer,
207            &cidr_tree,
208            keypair,
209            &SocketAddr::new(config.address, config.listen_port),
210        )?;
211    } else {
212        println!("exited without creating peer.");
213    }
214
215    Ok(())
216}
217
218pub fn rename_peer(
219    interface: &InterfaceName,
220    conf: &ServerConfig,
221    opts: RenamePeerOpts,
222) -> Result<(), Error> {
223    let conn = open_database_connection(interface, conf)?;
224    let peers = DatabasePeer::list(&conn)?
225        .into_iter()
226        .map(|dp| dp.inner)
227        .collect::<Vec<_>>();
228
229    if let Some((peer_request, old_name)) = innernet_shared::prompts::rename_peer(&peers, &opts)? {
230        let mut db_peer = DatabasePeer::list(&conn)?
231            .into_iter()
232            .find(|p| p.name == old_name)
233            .ok_or_else(|| anyhow!("Peer not found."))?;
234        db_peer.update(&conn, peer_request)?;
235    } else {
236        println!("exited without creating peer.");
237    }
238
239    Ok(())
240}
241
242pub fn enable_or_disable_peer(
243    interface: &InterfaceName,
244    conf: &ServerConfig,
245    enable: bool,
246    network: NetworkOpts,
247    opts: EnableDisablePeerOpts,
248) -> Result<(), Error> {
249    let conn = open_database_connection(interface, conf)?;
250    let peers = DatabasePeer::list(&conn)?
251        .into_iter()
252        .map(|dp| dp.inner)
253        .collect::<Vec<_>>();
254
255    if let Some(peer) = prompts::enable_or_disable_peer(&peers[..], &opts, enable)? {
256        let mut db_peer = DatabasePeer::get(&conn, peer.id)?;
257        db_peer.update(
258            &conn,
259            PeerContents {
260                is_disabled: !enable,
261                ..peer.contents.clone()
262            },
263        )?;
264
265        if enable {
266            DeviceUpdate::new()
267                .add_peer(db_peer.deref().into())
268                .apply(interface, network.backend)
269                .map_err(|_| ServerError::WireGuard)?;
270        } else {
271            let public_key =
272                Key::from_base64(&peer.public_key).map_err(|_| ServerError::WireGuard)?;
273
274            DeviceUpdate::new()
275                .remove_peer_by_key(&public_key)
276                .apply(interface, network.backend)
277                .map_err(|_| ServerError::WireGuard)?;
278        }
279    } else {
280        log::info!("exiting without enabling or disabling peer.");
281    }
282
283    Ok(())
284}
285
286pub fn add_cidr(
287    interface: &InterfaceName,
288    conf: &ServerConfig,
289    opts: AddCidrOpts,
290) -> Result<(), Error> {
291    let conn = open_database_connection(interface, conf)?;
292    let cidrs = DatabaseCidr::list(&conn)?;
293    if let Some(cidr_request) = innernet_shared::prompts::add_cidr(&cidrs, &opts)? {
294        let cidr = DatabaseCidr::create(&conn, cidr_request)?;
295        printdoc!(
296            "
297            CIDR \"{cidr_name}\" added.
298
299            Right now, peers within {cidr_name} can only see peers in the same CIDR, and in
300            the special \"innernet-server\" CIDR that includes the innernet server peer.
301
302            You'll need to add more associations for peers in diffent CIDRs to communicate.
303            ",
304            cidr_name = cidr.name.bold()
305        );
306    } else {
307        println!("exited without creating CIDR.");
308    }
309
310    Ok(())
311}
312
313pub fn rename_cidr(
314    interface: &InterfaceName,
315    conf: &ServerConfig,
316    opts: RenameCidrOpts,
317) -> Result<(), Error> {
318    let conn = open_database_connection(interface, conf)?;
319    let cidrs = DatabaseCidr::list(&conn)?;
320
321    if let Some((cidr_request, old_name)) = innernet_shared::prompts::rename_cidr(&cidrs, &opts)? {
322        let db_cidr = DatabaseCidr::list(&conn)?
323            .into_iter()
324            .find(|c| c.name == old_name)
325            .ok_or_else(|| anyhow!("CIDR not found."))?;
326        db::DatabaseCidr::from(db_cidr).update(&conn, cidr_request)?;
327    } else {
328        println!("exited without renaming CIDR.");
329    }
330
331    Ok(())
332}
333
334pub fn delete_cidr(
335    interface: &InterfaceName,
336    conf: &ServerConfig,
337    args: DeleteCidrOpts,
338) -> Result<(), Error> {
339    println!("Fetching eligible CIDRs");
340    let conn = open_database_connection(interface, conf)?;
341    let cidrs = DatabaseCidr::list(&conn)?;
342    let peers = DatabasePeer::list(&conn)?
343        .into_iter()
344        .map(|dp| dp.inner)
345        .collect::<Vec<_>>();
346
347    let cidr_id = prompts::delete_cidr(&cidrs, &peers, &args)?;
348
349    println!("Deleting CIDR...");
350    DatabaseCidr::delete(&conn, cidr_id)?;
351
352    println!("CIDR deleted.");
353
354    Ok(())
355}
356
357pub fn uninstall(
358    interface: &InterfaceName,
359    conf: &ServerConfig,
360    network: NetworkOpts,
361    yes: bool,
362) -> Result<(), Error> {
363    if yes
364        || Confirm::with_theme(&*prompts::THEME)
365            .with_prompt(format!(
366                "Permanently delete network \"{}\"?",
367                interface.as_str_lossy().yellow()
368            ))
369            .default(false)
370            .interact()?
371    {
372        println!("{} bringing down interface (if up).", "[*]".dimmed());
373        wg::down(interface, network.backend).ok();
374        let config = conf.config_path(interface);
375        let data = conf.database_path(interface);
376        std::fs::remove_file(&config)
377            .with_path(&config)
378            .map_err(|e| println!("[!] {}", e.to_string().yellow()))
379            .ok();
380        std::fs::remove_file(&data)
381            .with_path(&data)
382            .map_err(|e| println!("[!] {}", e.to_string().yellow()))
383            .ok();
384        println!(
385            "{} network {} is uninstalled.",
386            "[*]".dimmed(),
387            interface.as_str_lossy().yellow()
388        );
389    }
390    Ok(())
391}
392
393fn spawn_endpoint_refresher(interface: InterfaceName, network: NetworkOpts) -> Endpoints {
394    let endpoints = Arc::new(RwLock::new(HashMap::new()));
395    tokio::task::spawn({
396        let endpoints = endpoints.clone();
397        async move {
398            let mut interval = tokio::time::interval(Duration::from_secs(10));
399            loop {
400                interval.tick().await;
401                if let Ok(info) = Device::get(&interface, network.backend) {
402                    for peer in info.peers {
403                        if let Some(endpoint) = peer.config.endpoint {
404                            endpoints
405                                .write()
406                                .insert(peer.config.public_key.to_base64(), endpoint);
407                        }
408                    }
409                }
410            }
411        }
412    });
413    endpoints
414}
415
416fn spawn_expired_invite_sweeper(db: Db) {
417    tokio::task::spawn(async move {
418        let mut interval = tokio::time::interval(Duration::from_secs(10));
419        loop {
420            interval.tick().await;
421            match DatabasePeer::delete_expired_invites(&db.lock()) {
422                Ok(deleted) if deleted > 0 => {
423                    log::info!("Deleted {} expired peer invitations.", deleted)
424                },
425                Err(e) => log::error!("Failed to delete expired peer invitations: {}", e),
426                _ => {},
427            }
428        }
429    });
430}
431
432fn spawn_hostfile_writer(db: Db, interface: InterfaceName, hosts_path: PathBuf) {
433    tokio::task::spawn({
434        async move {
435            let mut interval = tokio::time::interval(Duration::from_secs(10));
436            loop {
437                interval.tick().await;
438
439                match DatabasePeer::list_enabled(&db.lock()) {
440                    Ok(peers) => {
441                        if let Err(e) = update_hosts_file(
442                            &interface,
443                            &hosts_path,
444                            peers.into_iter().map(|peer| peer.inner),
445                        ) {
446                            log::error!("Failed to write hostfile: {}", e);
447                        }
448                    },
449                    Err(e) => {
450                        log::error!("Failed to list enabled peers for hostfile writing: {}", e)
451                    },
452                }
453            }
454        }
455    });
456}
457
458pub async fn serve(
459    interface: InterfaceName,
460    conf: &ServerConfig,
461    network: NetworkOpts,
462    hosts_path: Option<PathBuf>,
463) -> Result<(), Error> {
464    let config = ConfigFile::from_file(conf.config_path(&interface))?;
465    log::debug!("opening database connection...");
466    let conn = open_database_connection(&interface, conf)?;
467
468    let mut peers = DatabasePeer::list(&conn)?;
469    log::debug!("peers listed...");
470    let peer_configs = peers
471        .iter()
472        .map(|peer| peer.deref().into())
473        .collect::<Vec<PeerConfigBuilder>>();
474
475    log::info!("bringing up interface.");
476    wg::up(
477        &interface,
478        &config.private_key,
479        IpNet::new(config.address, config.network_cidr_prefix)?,
480        Some(config.listen_port),
481        None,
482        network,
483    )?;
484
485    DeviceUpdate::new()
486        .add_peers(&peer_configs)
487        .apply(&interface, network.backend)?;
488
489    log::info!("{} peers added to wireguard interface.", peers.len());
490
491    let candidates: Vec<Endpoint> = get_local_addrs()?
492        .map(|addr| SocketAddr::from((addr, config.listen_port)).into())
493        .collect();
494    let num_candidates = candidates.len();
495    let myself = peers
496        .iter_mut()
497        .find(|peer| peer.ip == config.address)
498        .expect("Couldn't find server peer in peer list.");
499    myself.update(
500        &conn,
501        PeerContents {
502            candidates,
503            ..myself.contents.clone()
504        },
505    )?;
506
507    log::info!(
508        "{} local candidates added to server peer config.",
509        num_candidates
510    );
511
512    let public_key = wireguard_control::Key::from_base64(&config.private_key)?.get_public();
513    let db = Arc::new(Mutex::new(conn));
514    let endpoints = spawn_endpoint_refresher(interface, network);
515    spawn_expired_invite_sweeper(db.clone());
516
517    if let Some(path) = hosts_path {
518        spawn_hostfile_writer(db.clone(), interface, path);
519    }
520
521    let context = Context {
522        db,
523        endpoints,
524        interface,
525        public_key,
526        backend: network.backend,
527    };
528
529    log::info!("innernet-server {} starting.", VERSION);
530
531    let listener = get_listener((config.address, config.listen_port).into(), &interface)?;
532
533    let make_svc = hyper::service::make_service_fn(move |socket: &AddrStream| {
534        let remote_addr = socket.remote_addr();
535        let context = context.clone();
536        async move {
537            Ok::<_, http::Error>(hyper::service::service_fn(move |req: Request<Body>| {
538                log::debug!("{} - {} {}", &remote_addr, req.method(), req.uri());
539                hyper_service(req, context.clone(), remote_addr)
540            }))
541        }
542    });
543
544    let server = hyper::Server::from_tcp(listener)?.serve(make_svc);
545
546    server.await?;
547
548    Ok(())
549}
550
551/// This function differs per OS, because different operating systems have
552/// opposing characteristics when binding to a specific IP address.
553/// On Linux, binding to a specific local IP address does *not* bind it to
554/// that IP's interface, allowing for spoofing attacks.
555///
556/// See https://github.com/tonarino/innernet/issues/26 for more details.
557#[cfg(target_os = "linux")]
558fn get_listener(addr: SocketAddr, interface: &InterfaceName) -> Result<TcpListener, Error> {
559    let listener = TcpListener::bind(addr)?;
560    listener.set_nonblocking(true)?;
561    let sock = socket2::Socket::from(listener);
562    sock.bind_device(Some(interface.as_str_lossy().as_bytes()))?;
563    Ok(sock.into())
564}
565
566/// BSD-likes do seem to bind to an interface when binding to an IP,
567/// according to the internet, but we may want to explicitly use
568/// IP_BOUND_IF in the future regardless. This isn't currently in
569/// the socket2 crate however, so we aren't currently using it.
570///
571/// See https://github.com/tonarino/innernet/issues/26 for more details.
572#[cfg(not(target_os = "linux"))]
573fn get_listener(addr: SocketAddr, _interface: &InterfaceName) -> Result<TcpListener, Error> {
574    let listener = TcpListener::bind(addr)?;
575    listener.set_nonblocking(true)?;
576    Ok(listener)
577}
578
579pub(crate) async fn hyper_service(
580    req: Request<Body>,
581    context: Context,
582    remote_addr: SocketAddr,
583) -> Result<Response<Body>, http::Error> {
584    // Break the path into components.
585    let components: VecDeque<_> = req
586        .uri()
587        .path()
588        .trim_start_matches('/')
589        .split('/')
590        .map(String::from)
591        .collect();
592
593    routes(req, context, remote_addr, components)
594        .await
595        .or_else(TryInto::try_into)
596}
597
598async fn routes(
599    req: Request<Body>,
600    context: Context,
601    remote_addr: SocketAddr,
602    mut components: VecDeque<String>,
603) -> Result<Response<Body>, ServerError> {
604    // Must be "/v1/[something]"
605    if components.pop_front().as_deref() != Some("v1") {
606        Err(ServerError::NotFound)
607    } else {
608        let session = get_session(&req, context, remote_addr.ip())?;
609        let component = components.pop_front();
610        match component.as_deref() {
611            Some("user") => api::user::routes(req, components, session).await,
612            Some("admin") => api::admin::routes(req, components, session).await,
613            _ => Err(ServerError::NotFound),
614        }
615    }
616}
617
618fn get_session(
619    req: &Request<Body>,
620    context: Context,
621    addr: IpAddr,
622) -> Result<Session, ServerError> {
623    let pubkey = req
624        .headers()
625        .get(INNERNET_PUBKEY_HEADER)
626        .ok_or(ServerError::Unauthorized)?;
627    let pubkey = pubkey.to_str().map_err(|_| ServerError::Unauthorized)?;
628    let pubkey = Key::from_base64(pubkey).map_err(|_| ServerError::Unauthorized)?;
629    if pubkey
630        .as_bytes()
631        .ct_eq(context.public_key.as_bytes())
632        .into()
633    {
634        let peer = DatabasePeer::get_from_ip(&context.db.lock(), addr).map_err(|e| match e {
635            rusqlite::Error::QueryReturnedNoRows => ServerError::Unauthorized,
636            e => ServerError::Database(e),
637        })?;
638
639        if !peer.is_disabled {
640            return Ok(Session { context, peer });
641        }
642    }
643
644    Err(ServerError::Unauthorized)
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650    use crate::test;
651    use anyhow::Result;
652    use hyper::StatusCode;
653    use std::path::Path;
654
655    #[test]
656    fn test_init_wizard() -> Result<(), Error> {
657        // This runs init_wizard().
658        let server = test::Server::new()?;
659
660        assert!(Path::new(&server.wg_conf_path()).exists());
661
662        Ok(())
663    }
664
665    #[tokio::test]
666    async fn test_with_session_disguised_with_headers() -> Result<(), Error> {
667        let server = test::Server::new()?;
668
669        let path = if cfg!(feature = "v6-test") {
670            format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
671        } else {
672            format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
673        };
674        let req = Request::builder()
675            .uri(path)
676            .header("Forwarded", format!("for={}", test::ADMIN_PEER_IP))
677            .header("X-Forwarded-For", test::ADMIN_PEER_IP)
678            .header("X-Real-IP", test::ADMIN_PEER_IP)
679            .body(Body::empty())
680            .unwrap();
681
682        // Request from an unknown IP, trying to disguise as an admin using HTTP headers.
683        let res = if cfg!(feature = "v6-test") {
684            server.raw_request("fd00:1337::1337", req).await
685        } else {
686            server.raw_request("10.80.80.80", req).await
687        };
688
689        // addr::remote() filter only look at remote_addr from TCP socket.
690        // HTTP headers are not considered. This also means that innernet
691        // server would not function behind an HTTP proxy.
692        assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
693
694        Ok(())
695    }
696
697    #[tokio::test]
698    async fn test_incorrect_public_key() -> Result<(), Error> {
699        let server = test::Server::new()?;
700
701        let key = Key::generate_private().get_public();
702
703        let path = if cfg!(feature = "v6-test") {
704            format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
705        } else {
706            format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
707        };
708        // Request from an unknown IP, trying to disguise as an admin using HTTP headers.
709        let req = Request::builder()
710            .uri(path)
711            .header(innernet_shared::INNERNET_PUBKEY_HEADER, key.to_base64())
712            .body(Body::empty())
713            .unwrap();
714        let res = if cfg!(feature = "v6-test") {
715            server.raw_request("fd00:1337::1337", req).await
716        } else {
717            server.raw_request("10.80.80.80", req).await
718        };
719
720        // addr::remote() filter only look at remote_addr from TCP socket.
721        // HTTP headers are not considered. This also means that innernet
722        // server would not function behind an HTTP proxy.
723        assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
724
725        Ok(())
726    }
727
728    #[tokio::test]
729    async fn test_unparseable_public_key() -> Result<(), Error> {
730        let server = test::Server::new()?;
731
732        let path = if cfg!(feature = "v6-test") {
733            format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
734        } else {
735            format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
736        };
737        let req = Request::builder()
738            .uri(path)
739            .header(innernet_shared::INNERNET_PUBKEY_HEADER, "!!!")
740            .body(Body::empty())
741            .unwrap();
742        let res = if cfg!(feature = "v6-test") {
743            server.raw_request("fd00:1337::1337", req).await
744        } else {
745            server.raw_request("10.80.80.80", req).await
746        };
747
748        // addr::remote() filter only look at remote_addr from TCP socket.
749        // HTTP headers are not considered. This also means that innernet
750        // server would not function behind an HTTP proxy.
751        assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
752
753        Ok(())
754    }
755}