mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Expose canonical for reuse in the /api/open check
This commit is contained in:
parent
32cc041e96
commit
7db1d04562
2 changed files with 19 additions and 3 deletions
|
|
@ -21,6 +21,7 @@ use crate::{
|
||||||
ServerInfoResponse, SocketPacket, SocketPacketBody, SocketPacketType, SubscribeMessage,
|
ServerInfoResponse, SocketPacket, SocketPacketBody, SocketPacketType, SubscribeMessage,
|
||||||
WriteRequest, WriteResponse, PROTOCOL_VERSION, SERVER_VERSION,
|
WriteRequest, WriteResponse, PROTOCOL_VERSION, SERVER_VERSION,
|
||||||
},
|
},
|
||||||
|
origin::canonical,
|
||||||
util::{deserialize_msgpack, msgpack, msgpack_ok, serialize_msgpack},
|
util::{deserialize_msgpack, msgpack, msgpack_ok, serialize_msgpack},
|
||||||
},
|
},
|
||||||
web_api::{
|
web_api::{
|
||||||
|
|
@ -369,7 +370,11 @@ impl ApiService {
|
||||||
// trusted, and reachability is bounded by that hop's own authentication
|
// trusted, and reachability is bounded by that hop's own authentication
|
||||||
// (e.g. SSH keys or Tailscale ACLs). This gate only stops direct,
|
// (e.g. SSH keys or Tailscale ACLs). This gate only stops direct,
|
||||||
// unauthenticated peers.
|
// unauthenticated peers.
|
||||||
if !self.remote_addr.ip().is_loopback() {
|
//
|
||||||
|
// An IPv4 client reaching a dual-stack (`::`) bind appears as an
|
||||||
|
// IPv4-mapped IPv6 peer (`::ffff:127.0.0.1`), so canonicalize to the bare
|
||||||
|
// IPv4 form before the loopback test, matching `origin`'s handling.
|
||||||
|
if !canonical(self.remote_addr.ip()).is_loopback() {
|
||||||
return msgpack(
|
return msgpack(
|
||||||
ErrorResponse::forbidden("/api/open is only available to local clients"),
|
ErrorResponse::forbidden("/api/open is only available to local clients"),
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,9 @@ pub fn allowed_hosts(bind: IpAddr, port: u16, extra: &[String]) -> Option<Allowe
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collapses an IPv4-mapped IPv6 address (`::ffff:192.168.0.1`) to its IPv4 form
|
/// Collapses an IPv4-mapped IPv6 address (`::ffff:192.168.0.1`) to its IPv4 form
|
||||||
/// so it classifies and compares consistently with the bare IPv4 address.
|
/// so it classifies and compares consistently with the bare IPv4 address. Shared
|
||||||
fn canonical(ip: IpAddr) -> IpAddr {
|
/// with the `/api/open` peer check so it recognizes a mapped loopback peer too.
|
||||||
|
pub(crate) fn canonical(ip: IpAddr) -> IpAddr {
|
||||||
match ip {
|
match ip {
|
||||||
IpAddr::V6(v6) => v6.to_ipv4_mapped().map(IpAddr::V4).unwrap_or(ip),
|
IpAddr::V6(v6) => v6.to_ipv4_mapped().map(IpAddr::V4).unwrap_or(ip),
|
||||||
v4 => v4,
|
v4 => v4,
|
||||||
|
|
@ -474,6 +475,16 @@ mod test {
|
||||||
assert!(check_request_origin(&request, allowed.as_ref()).is_none());
|
assert!(check_request_origin(&request, allowed.as_ref()).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_collapses_ipv4_mapped_loopback() {
|
||||||
|
// The `/api/open` peer gate relies on this: an IPv4 loopback client
|
||||||
|
// reaching a dual-stack (`::`) bind arrives as `::ffff:127.0.0.1`, which
|
||||||
|
// is only recognized as loopback after canonicalization.
|
||||||
|
let mapped: IpAddr = "::ffff:127.0.0.1".parse().unwrap();
|
||||||
|
assert!(!mapped.is_loopback());
|
||||||
|
assert!(canonical(mapped).is_loopback());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_host_strips_brackets_and_zone_id() {
|
fn normalize_host_strips_brackets_and_zone_id() {
|
||||||
// Brackets and an IPv6 zone id must be removed so the result parses as an
|
// Brackets and an IPv6 zone id must be removed so the result parses as an
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue