Inital commit

This commit is contained in:
Kevin Cotugno 2018-11-29 20:03:10 -08:00
commit 03e18b00b1
9 changed files with 517 additions and 0 deletions

1
.clippy.toml Normal file
View File

@ -0,0 +1 @@
single-char-binding-names-threshold = 9

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

130
Cargo.lock generated Normal file
View File

@ -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"

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "rip"
version = "0.1.0"
authors = ["Kevin Cotugno <kevin@kevincotugno.com>"]
[[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"

22
LICENSE Normal file
View File

@ -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.

1
README.md Normal file
View File

@ -0,0 +1 @@
# Rip

51
src/bin/client.rs Normal file
View File

@ -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<IpAddr, 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()),
};
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)
}

36
src/bin/server.rs Normal file
View File

@ -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)
}

255
src/rip/lib.rs Normal file
View File

@ -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 <kevin@kevincotugno.com>";
pub const DEFAULT_PORT: u16 = 44353;
const MAPPED_IPV4_KEY: u16 = 0xFFFF;
pub fn run_client(dest: SocketAddr) -> Result<IpAddr, String> {
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<SocketAddr, String> {
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> {
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<IpAddr, String> {
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<IpAddr, String> {
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<IpAddr, String> {
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)))
}
}
}
}
}