|
| 1 | +// Copyright (C) 2020 Matthew Waters <matthew@centricular.com> |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 6 | +// option. This file may not be copied, modified, or distributed |
| 7 | +// except according to those terms. |
| 8 | + |
| 9 | +use std::fmt::Display; |
| 10 | +use std::net::SocketAddr; |
| 11 | + |
| 12 | +use std::io::{self, Read, Write}; |
| 13 | +use std::net::{TcpListener, UdpSocket}; |
| 14 | +use std::str::FromStr; |
| 15 | + |
| 16 | +use tracing_subscriber::EnvFilter; |
| 17 | + |
| 18 | +use tracing::{debug, error, info, warn}; |
| 19 | + |
| 20 | +use stun_types::attribute::*; |
| 21 | +use stun_types::message::*; |
| 22 | + |
| 23 | +use stun_proto::agent::{HandleStunReply, StunAgent, StunError}; |
| 24 | + |
| 25 | +fn warn_on_err<T, E>(res: Result<T, E>, default: T) -> T |
| 26 | +where |
| 27 | + E: Display, |
| 28 | +{ |
| 29 | + match res { |
| 30 | + Ok(v) => v, |
| 31 | + Err(e) => { |
| 32 | + warn!("{}", e); |
| 33 | + default |
| 34 | + } |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +fn handle_binding_request<'a>( |
| 39 | + msg: &Message, |
| 40 | + from: SocketAddr, |
| 41 | +) -> Result<MessageBuilder<'a>, StunError> { |
| 42 | + if let Some(error_msg) = Message::check_attribute_types(msg, &[Fingerprint::TYPE], &[]) { |
| 43 | + return Ok(error_msg); |
| 44 | + } |
| 45 | + |
| 46 | + let mut response = Message::builder_success(msg); |
| 47 | + response.add_attribute(&XorMappedAddress::new(from, msg.transaction_id()))?; |
| 48 | + response.add_fingerprint()?; |
| 49 | + Ok(response) |
| 50 | +} |
| 51 | + |
| 52 | +fn handle_incoming_data<'a>( |
| 53 | + data: &[u8], |
| 54 | + from: SocketAddr, |
| 55 | + stun_agent: &mut StunAgent, |
| 56 | +) -> Option<(MessageBuilder<'a>, SocketAddr)> { |
| 57 | + let msg = Message::from_bytes(data).ok()?; |
| 58 | + let reply = stun_agent.handle_stun(msg, from); |
| 59 | + match reply { |
| 60 | + HandleStunReply::Drop => None, |
| 61 | + HandleStunReply::StunResponse(_response) => { |
| 62 | + error!("received unexpected STUN response from {from}!"); |
| 63 | + None |
| 64 | + } |
| 65 | + HandleStunReply::IncomingStun(msg) => { |
| 66 | + info!("received from {}: {}", from, msg); |
| 67 | + if msg.has_class(MessageClass::Request) && msg.has_method(BINDING) { |
| 68 | + match handle_binding_request(&msg, from) { |
| 69 | + Ok(response) => { |
| 70 | + info!("sending response to {}: {:?}", from, response); |
| 71 | + return Some((response, from)); |
| 72 | + } |
| 73 | + Err(err) => warn!("error: {}", err), |
| 74 | + } |
| 75 | + } else { |
| 76 | + let mut response = Message::builder_error(&msg); |
| 77 | + response |
| 78 | + .add_attribute(&ErrorCode::new(400, "Bad Request").unwrap()) |
| 79 | + .unwrap(); |
| 80 | + return Some((response, from)); |
| 81 | + } |
| 82 | + None |
| 83 | + } |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +fn main() -> io::Result<()> { |
| 88 | + if let Ok(filter) = EnvFilter::try_from_default_env() { |
| 89 | + tracing_subscriber::fmt().with_env_filter(filter).init(); |
| 90 | + } |
| 91 | + |
| 92 | + let args: Vec<String> = std::env::args().collect(); |
| 93 | + let local_addr: SocketAddr = SocketAddr::from_str(if args.len() > 1 { |
| 94 | + &args[1] |
| 95 | + } else { |
| 96 | + "127.0.0.1:3478" |
| 97 | + }) |
| 98 | + .unwrap(); |
| 99 | + std::thread::spawn({ |
| 100 | + move || { |
| 101 | + let udp_socket = UdpSocket::bind(local_addr).unwrap(); |
| 102 | + let mut udp_stun_agent = |
| 103 | + StunAgent::builder(stun_proto::types::TransportType::Udp, local_addr).build(); |
| 104 | + |
| 105 | + loop { |
| 106 | + let mut data = [0; 1500]; |
| 107 | + let (len, from) = warn_on_err(udp_socket.recv_from(&mut data), (0, local_addr)); |
| 108 | + if let Some((response, to)) = |
| 109 | + handle_incoming_data(&data[..len], from, &mut udp_stun_agent) |
| 110 | + { |
| 111 | + warn_on_err(udp_socket.send_to(&response.build(), to), 0); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + }); |
| 116 | + |
| 117 | + let tcp_listener = TcpListener::bind(local_addr)?; |
| 118 | + let mut incoming = tcp_listener.incoming(); |
| 119 | + while let Some(Ok(mut stream)) = incoming.next() { |
| 120 | + std::thread::spawn(move || { |
| 121 | + let remote_addr = stream.peer_addr().unwrap(); |
| 122 | + let mut tcp_stun_agent = |
| 123 | + StunAgent::builder(stun_proto::types::TransportType::Tcp, local_addr) |
| 124 | + .remote_addr(remote_addr) |
| 125 | + .build(); |
| 126 | + // TODO: handle split writes/reads and request timeouts. |
| 127 | + let mut data = [0; 1500]; |
| 128 | + let size = warn_on_err(stream.read(&mut data), 0); |
| 129 | + if size == 0 { |
| 130 | + debug!("TCP connection with {remote_addr} closed"); |
| 131 | + return; |
| 132 | + } |
| 133 | + if let Some((response, to)) = |
| 134 | + handle_incoming_data(&data[..size], remote_addr, &mut tcp_stun_agent) |
| 135 | + { |
| 136 | + if let Ok(data) = tcp_stun_agent.send(response, to) { |
| 137 | + warn_on_err(stream.write_all(&data.data), ()); |
| 138 | + } |
| 139 | + } |
| 140 | + // XXX: Assumes that the stun packet arrives in a single packet |
| 141 | + stream.shutdown(std::net::Shutdown::Read).unwrap(); |
| 142 | + }); |
| 143 | + } |
| 144 | + |
| 145 | + Ok(()) |
| 146 | +} |
0 commit comments