Skip to content

Commit

Permalink
Properly parse network addresses. (denoland#1515)
Browse files Browse the repository at this point in the history
  • Loading branch information
ry committed Jan 14, 2019
1 parent 9e9550c commit 3c1a0ad
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 15 deletions.
5 changes: 2 additions & 3 deletions js/net_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ testPerm({ net: true }, function netListenClose() {
});

testPerm({ net: true }, async function netDialListen() {
const addr = "127.0.0.1:4500";
const listener = deno.listen("tcp", addr);
const listener = deno.listen("tcp", ":4500");
listener.accept().then(async conn => {
await conn.write(new Uint8Array([1, 2, 3]));
conn.close();
});
const conn = await deno.dial("tcp", addr);
const conn = await deno.dial("tcp", "127.0.0.1:4500");
const buf = new Uint8Array(1024);
const readResult = await conn.read(buf);
assertEqual(3, readResult.nread);
Expand Down
21 changes: 20 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use hyper;

pub use msg::ErrorKind;
use resolve_addr::ResolveAddrError;

use hyper;
use std;
use std::fmt;
use std::io;
Expand Down Expand Up @@ -149,6 +152,22 @@ impl From<hyper::Error> for DenoError {
}
}

impl From<ResolveAddrError> for DenoError {
fn from(e: ResolveAddrError) -> Self {
match e {
ResolveAddrError::Syntax => Self {
repr: Repr::Simple(
ErrorKind::InvalidInput,
"invalid address syntax".to_string(),
),
},
ResolveAddrError::Resolution(io_err) => Self {
repr: Repr::IoErr(io_err),
},
}
}
}

pub fn bad_resource() -> DenoError {
new(ErrorKind::BadResource, String::from("bad resource id"))
}
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub mod msg_util;
pub mod ops;
pub mod permissions;
mod repl;
pub mod resolve_addr;
pub mod resources;
pub mod snapshot;
mod tokio_util;
Expand Down
23 changes: 12 additions & 11 deletions src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use isolate::Op;
use libdeno;
use msg;
use msg_util;
use resolve_addr::resolve_addr;
use resources;
use resources::Resource;
use version;
Expand All @@ -28,15 +29,14 @@ use resources::table_entries;
use std;
use std::convert::From;
use std::fs;
use std::net::{Shutdown, SocketAddr};
use std::net::Shutdown;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use std::sync::Arc;
use std::time::UNIX_EPOCH;
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -1241,8 +1241,7 @@ fn op_listen(
// https://github.com/rust-lang-nursery/rust-clippy/issues/1684
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
Box::new(futures::future::result((move || {
// TODO properly parse addr
let addr = SocketAddr::from_str(address).unwrap();
let addr = resolve_addr(address).wait()?;

let listener = TcpListener::bind(&addr)?;
let resource = resources::add_tcp_listener(listener);
Expand Down Expand Up @@ -1325,15 +1324,17 @@ fn op_dial(
let cmd_id = base.cmd_id();
let inner = base.inner_as_dial().unwrap();
let network = inner.network().unwrap();
assert_eq!(network, "tcp");
assert_eq!(network, "tcp"); // TODO Support others.
let address = inner.address().unwrap();

// TODO properly parse addr
let addr = SocketAddr::from_str(address).unwrap();

let op = TcpStream::connect(&addr)
.map_err(|err| err.into())
.and_then(move |tcp_stream| new_conn(cmd_id, tcp_stream));
let op =
resolve_addr(address)
.map_err(DenoError::from)
.and_then(move |addr| {
TcpStream::connect(&addr)
.map_err(DenoError::from)
.and_then(move |tcp_stream| new_conn(cmd_id, tcp_stream))
});
Box::new(op)
}

Expand Down
165 changes: 165 additions & 0 deletions src/resolve_addr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

use futures::Async;
use futures::Future;
use futures::Poll;
use std::error::Error;
use std::fmt;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;

/// Go-style network address parsing. Returns a future.
/// Examples:
/// "192.0.2.1:25"
/// ":80"
/// "[2001:db8::1]:80"
/// "198.51.100.1:80"
/// "deno.land:443"
pub fn resolve_addr(address: &str) -> ResolveAddrFuture {
ResolveAddrFuture {
address: address.to_string(),
}
}

#[derive(Debug)]
pub enum ResolveAddrError {
Syntax,
Resolution(std::io::Error),
}

impl fmt::Display for ResolveAddrError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.description())
}
}

impl Error for ResolveAddrError {
fn description(&self) -> &str {
match self {
ResolveAddrError::Syntax => "invalid address syntax",
ResolveAddrError::Resolution(e) => e.description(),
}
}
}

pub struct ResolveAddrFuture {
address: String,
}

impl Future for ResolveAddrFuture {
type Item = SocketAddr;
type Error = ResolveAddrError;

fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// The implementation of this is not actually async at the moment,
// however we intend to use async DNS resolution in the future and
// so we expose this as a future instead of Result.
match split(&self.address) {
None => Err(ResolveAddrError::Syntax),
Some(addr_port_pair) => {
// I absolutely despise the .to_socket_addrs() API.
let r = addr_port_pair
.to_socket_addrs()
.map_err(|e| ResolveAddrError::Resolution(e));

r.and_then(|mut iter| match iter.next() {
Some(a) => Ok(Async::Ready(a)),
None => panic!("There should be at least one result"),
})
}
}
}
}

fn split<'a>(address: &'a str) -> Option<(&'a str, u16)> {
address.rfind(":").and_then(|i| {
let (a, p) = address.split_at(i);
// Default to localhost if given just the port. Example: ":80"
let addr = if a.len() > 0 { a } else { "0.0.0.0" };
// If this looks like an ipv6 IP address. Example: "[2001:db8::1]"
// Then we remove the brackets.
let addr = if addr.starts_with('[') && addr.ends_with(']') {
let l = addr.len() - 1;
addr.get(1..l).unwrap()
} else {
addr
};

let p = p.trim_left_matches(':');
match p.parse::<u16>() {
Err(_) => None,
Ok(port) => Some((addr, port)),
}
})
}

#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddrV4;
use std::net::SocketAddrV6;

#[test]
fn split1() {
assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80)));
}

#[test]
fn split2() {
assert_eq!(split(":80"), Some(("0.0.0.0", 80)));
}

#[test]
fn split3() {
assert_eq!(split("no colon"), None);
}

#[test]
fn split4() {
assert_eq!(split("deno.land:443"), Some(("deno.land", 443)));
}

#[test]
fn split5() {
assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080)));
}

#[test]
fn resolve_addr1() {
let expected =
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80));
let actual = resolve_addr("127.0.0.1:80").wait().unwrap();
assert_eq!(actual, expected);
}

#[test]
fn resolve_addr3() {
let expected =
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25));
let actual = resolve_addr("192.0.2.1:25").wait().unwrap();
assert_eq!(actual, expected);
}

#[test]
fn resolve_addr_ipv6() {
let expected = SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
8080,
0,
0,
));
let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap();
assert_eq!(actual, expected);
}

#[test]
fn resolve_addr_err() {
let r = resolve_addr("not-a-real-domain.blahblah:8080").wait();
match r {
Err(ResolveAddrError::Resolution(_)) => {} // expected
_ => assert!(false),
}
}
}

0 comments on commit 3c1a0ad

Please sign in to comment.