commit 03e18b00b1faf4f7c3ec4cd2707c2ebbba2aa130 Author: Kevin Cotugno Date: Thu Nov 29 20:03:10 2018 -0800 Inital commit diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..cf48252 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +single-char-binding-names-threshold = 9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6618e3d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,130 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_syscall" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rip" +version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311" +"checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6b4fb81 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rip" +version = "0.1.0" +authors = ["Kevin Cotugno "] + +[[bin]] +name = "rip" +path = "src/bin/client.rs" + +[[bin]] +name = "rip-server" +path = "src/bin/server.rs" + +[lib] +name = "rip" +path = "src/rip/lib.rs" + +[dependencies] +clap = "~2.32" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1b224e5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright 2018 Kevin Cotugno + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0492628 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Rip diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..1e47422 --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,51 @@ +extern crate clap; +use clap::{App, Arg, ArgMatches}; +use std::io::Write; +use std::net::IpAddr; + +fn main() { + let port = rip::DEFAULT_PORT.to_string(); + let args = App::new(rip::NAME) + .version(rip::VERSION) + .author(rip::AUTHOR) + .about(rip::ABOUT) + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .help("Set a different port from the default of 44353") + .default_value(&port) + .takes_value(true), + ).arg( + Arg::with_name("host") + .help("Sets the Rip server host") + .required(true) + .index(1), + ).get_matches(); + + match run(&args) { + Ok(ip) => { + print!("{}", ip); + std::io::stdout().flush().unwrap(); + } + Err(err) => { + println!("{}", err); + std::process::exit(1); + } + } +} + +fn run(args: &ArgMatches) -> Result { + let port_str = args.value_of("port").unwrap(); + let port: u16 = match port_str.parse() { + Ok(v) => v, + Err(_) => return Err("Port must be a valid unsigned 16bit integer".to_string()), + }; + + let dest = match rip::parse_socket_addr(args.value_of("host").unwrap(), port) { + Ok(v) => v, + Err(err) => return Err(err), + }; + + rip::run_client(dest) +} diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..da6db91 --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,36 @@ +extern crate clap; +use clap::{App, Arg, ArgMatches}; + +const NAME: &str = "rip-server"; + +fn main() { + let port = rip::DEFAULT_PORT.to_string(); + let args = App::new(NAME) + .version(rip::VERSION) + .author(rip::AUTHOR) + .about(rip::ABOUT) + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .help("Set a different port from the default of 44353") + .default_value(&port) + .takes_value(true), + ).get_matches(); + + let result = run(&args); + if result.is_err() { + println!("{}", result.err().unwrap()); + std::process::exit(1); + } +} + +fn run(args: &ArgMatches) -> Result<(), String> { + let port_str = args.value_of("port").unwrap(); + let port: u16 = match port_str.parse() { + Ok(v) => v, + Err(_) => return Err("Port must be a valid unsigned 16bit integer".to_string()), + }; + + rip::run_server(port) +} diff --git a/src/rip/lib.rs b/src/rip/lib.rs new file mode 100644 index 0000000..530af7f --- /dev/null +++ b/src/rip/lib.rs @@ -0,0 +1,255 @@ +use std::io; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; +use std::time::Duration; + +pub const NAME: &str = "rip"; +pub const VERSION: &str = "0.1.0"; +pub const ABOUT: &str = "Rip is a simple client/server to retrieving a public IP via UDP."; +pub const AUTHOR: &str = "Kevin Cotugno "; + +pub const DEFAULT_PORT: u16 = 44353; + +const MAPPED_IPV4_KEY: u16 = 0xFFFF; + +pub fn run_client(dest: SocketAddr) -> Result { + let socket = match open_socket(dest.is_ipv6(), 0) { + Ok(v) => v, + Err(err) => return Err(format!("{}", err)), + }; + + socket + .set_read_timeout(Some(Duration::from_secs(10))) + .unwrap(); + + do_request(dest, &socket) +} + +pub fn run_server(port: u16) -> Result<(), String> { + let socket = match open_socket(true, port) { + Ok(v) => v, + Err(err) => return Err(format!("{}", err)), + }; + eprintln!("Listening on: {:?}", socket); + + let mut unused = [0; 1024]; + loop { + let (_, src) = match socket.recv_from(&mut unused) { + Ok(v) => v, + Err(err) => return Err(format!("Failed to read message: {}", err)), + }; + eprintln!("Request from: {}", format!("{}", src.ip())); + + let (msg, size) = build_msg(src.ip()); + let res = socket.send_to(&msg[..size], src); + if res.is_err() { + eprintln!( + "error sending response to {}, with err: ", + res.err().unwrap() + ); + } + } +} + +pub fn parse_socket_addr(host: &str, port: u16) -> Result { + match parse_ip(host) { + Ok(ip) => Ok(SocketAddr::new(ip, port)), + Err(v) => Err(v), + } +} + +fn open_socket(ipv6: bool, port: u16) -> io::Result { + UdpSocket::bind(if ipv6 { + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) + } else { + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port) + }) +} + +fn parse_ip(host: &str) -> Result { + match host.parse() { + Ok(ip) => Ok(ip), + Err(err) => Err(format!( + "Unable to parse host: {}, with error: {}", + host, err + )), + } +} + +fn do_request(dest: SocketAddr, socket: &UdpSocket) -> Result { + if socket.connect(dest).is_err() { + return Err(format!( + "Unable to connect: {}", + socket.take_error().unwrap().unwrap() + )); + } + + if socket.send(&[]).is_err() { + return Err(format!( + "Failed to send request: {}", + socket.take_error().unwrap().unwrap() + )); + } + + let mut buf = [0; 1024]; + let count = match socket.recv(&mut buf) { + Ok(size) => size, + Err(error) => return Err(format!("Error reading response: {}", error)), + }; + + parse_raw_msg(&buf[..count]) +} + +fn parse_raw_msg(data: &[u8]) -> Result { + if data.is_empty() { + return Err(String::from("empty message")); + } + + match data[0] { + 4 => { + if data.len() > 4 { + Ok(IpAddr::V4(Ipv4Addr::new( + data[1], data[2], data[3], data[4], + ))) + } else { + println!("{}", data.len()); + Err(String::from("wrong number of octets for IPv4 address")) + } + } + 6 => { + if data.len() > 16 { + Ok(IpAddr::V6(Ipv6Addr::new( + rebuild_seg(data[1], data[2]), + rebuild_seg(data[3], data[4]), + rebuild_seg(data[5], data[6]), + rebuild_seg(data[7], data[8]), + rebuild_seg(data[9], data[10]), + rebuild_seg(data[11], data[12]), + rebuild_seg(data[13], data[14]), + rebuild_seg(data[15], data[16]), + ))) + } else { + Err(String::from("wrong number of segments for IPv6 address")) + } + } + _ => Err(String::from("invalid IP type")), + } +} + +fn rebuild_seg(i: u8, j: u8) -> u16 { + let mut x: u16 = u16::from(i); + x <<= 8; + x |= u16::from(j); + x +} + +fn build_msg(ip: IpAddr) -> ([u8; 17], usize) { + let mut msg = [0; 17]; + + let populate = |octets: &[u8], dest: &mut [u8]| { + if octets.len() == 4 { + dest[0] = 4; + } else { + dest[0] = 6; + } + + for (i, oct) in octets.iter().enumerate() { + dest[i + 1] = *oct; + } + }; + + let size = match ip { + IpAddr::V4(v4) => { + populate(&v4.octets()[..], &mut msg); + 5 + } + IpAddr::V6(v6) => { + let v4 = v6.to_ipv4(); + + if v4.is_some() && v6.segments()[5] == MAPPED_IPV4_KEY { + populate(&v4.unwrap().octets()[..], &mut msg); + 5 + } else { + populate(&v6.octets()[..], &mut msg); + 17 + } + } + }; + + (msg, size) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_valid_ipv4() -> Result<(), String> { + match parse_ip("1.2.3.4") { + Ok(v) => ipv4_with_octets(v, 1, 2, 3, 4), + Err(error) => Err(String::from(format!("Should be ok, {}", error))), + } + } + + #[test] + fn parses_valid_ipv6() -> Result<(), String> { + match parse_ip("2423:33:dfe3::1") { + Ok(v) => ipv6_with_segments(v, 0x2423, 0x33, 0xdfe3, 0, 0, 0, 0, 1), + Err(error) => Err(String::from(format!("Should be ok, {}", error))), + } + } + + #[test] + fn errors_invalid_ips() -> Result<(), String> { + let host = "2423:33:dfe3:invalid::1"; + match parse_ip(host) { + Ok(v) => Err(String::from(format!("Should have returned error, {}", v))), + Err(err) => { + let expected = format!( + "Unable to parse host: {}, with error: invalid IP address syntax", + host + ); + if err != expected { + Err(String::from(expected)) + } else { + Ok(()) + } + } + } + } + + fn ipv4_with_octets(ip: IpAddr, a: u8, b: u8, c: u8, d: u8) -> Result<(), String> { + match ip { + IpAddr::V4(addr) => { + if addr.octets() == [a, b, c, d] { + Ok(()) + } else { + Err(String::from(format!("octets do not match: {}", addr))) + } + } + IpAddr::V6(_) => Err(String::from("is a ipv6 address")), + } + } + + fn ipv6_with_segments( + ip: IpAddr, + a: u16, + b: u16, + c: u16, + d: u16, + e: u16, + f: u16, + g: u16, + h: u16, + ) -> Result<(), String> { + match ip { + IpAddr::V4(_) => Err(String::from("is a ipv4 address")), + IpAddr::V6(addr) => { + if addr.segments() == [a, b, c, d, e, f, g, h] { + Ok(()) + } else { + Err(String::from(format!("segments do not match: {}", addr))) + } + } + } + } +}