Reply with ICMP dest. unreachable or TCP RST from unused ports.

This commit is contained in:
whitequark 2016-12-20 19:18:35 +00:00
parent a3481537d9
commit 5a64a87dbe
8 changed files with 219 additions and 55 deletions

View File

@ -31,7 +31,7 @@ The only supported internetworking protocol is IPv4.
* IPv4 options are **not** supported.
* ICMPv4 header checksum is supported.
* ICMPv4 echo requests and replies are supported.
* ICMPv4 destination unreachable message is **not** supported.
* ICMPv4 destination unreachable message is supported.
* ICMPv4 parameter problem message is **not** supported.
### UDP layer

View File

@ -15,19 +15,16 @@ fn main() {
let device = Tracer::<_, EthernetFrame<&[u8]>>::new(device);
let arp_cache = SliceArpCache::new(vec![Default::default(); 8]);
let hardware_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
let mut protocol_addrs = [IpAddress::v4(192, 168, 69, 1)];
let listen_address = IpAddress::v4(0, 0, 0, 0);
let endpoint = IpEndpoint::new(listen_address, 6969);
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketBuffer::new(vec![0; 2048])]);
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketBuffer::new(vec![0; 2048])]);
let endpoint = IpEndpoint::new(IpAddress::default(), 6969);
let udp_socket = UdpSocket::new(endpoint, udp_rx_buffer, udp_tx_buffer);
let mut sockets = [udp_socket];
let hardware_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
let protocol_addrs = [IpAddress::v4(192, 168, 69, 1)];
let sockets = [udp_socket];
let mut iface = EthernetInterface::new(device, arp_cache,
hardware_addr, &mut protocol_addrs[..], &mut sockets[..]);
hardware_addr, protocol_addrs, sockets);
loop {
match iface.poll() {

View File

@ -7,7 +7,8 @@ use wire::{EthernetAddress, EthernetProtocol, EthernetFrame};
use wire::{ArpPacket, ArpRepr, ArpOperation};
use wire::{IpAddress, IpProtocol};
use wire::{Ipv4Packet, Ipv4Repr};
use wire::{Icmpv4Packet, Icmpv4Repr};
use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
use wire::{TcpPacket, TcpRepr, TcpControl};
use socket::Socket;
use super::{ArpCache};
@ -115,7 +116,8 @@ impl<'a, 'b: 'a,
enum Response<'a> {
Nop,
Arp(ArpRepr),
Icmpv4(Ipv4Repr, Icmpv4Repr<'a>)
Icmpv4(Ipv4Repr, Icmpv4Repr<'a>),
Tcpv4(Ipv4Repr, TcpRepr<'a>)
}
// First, transmit any outgoing packets.
@ -168,7 +170,8 @@ impl<'a, 'b: 'a,
// Handle IP packets directed at us.
EthernetProtocol::Ipv4 => {
let ip_packet = try!(Ipv4Packet::new(eth_frame.payload()));
match try!(Ipv4Repr::parse(&ip_packet)) {
let ip_repr = try!(Ipv4Repr::parse(&ip_packet));
match ip_repr {
// Ignore IP packets not directed at us.
Ipv4Repr { dst_addr, .. } if !self.has_protocol_addr(dst_addr) => (),
@ -204,16 +207,58 @@ impl<'a, 'b: 'a,
// Try dispatching a packet to a socket.
Ipv4Repr { src_addr, dst_addr, protocol } => {
let mut handled = false;
for socket in self.sockets.borrow_mut() {
match socket.collect(&src_addr.into(), &dst_addr.into(),
protocol, ip_packet.payload()) {
Ok(()) => break,
Ok(()) => { handled = true; break }
Err(Error::Rejected) => continue,
Err(e) => return Err(e)
}
}
// FIXME: respond with ICMP destination unreachable here?
if !handled && protocol == IpProtocol::Tcp {
let tcp_packet = try!(TcpPacket::new(ip_packet.payload()));
let ip_reply_repr = Ipv4Repr {
src_addr: dst_addr,
dst_addr: src_addr,
protocol: IpProtocol::Tcp
};
let tcp_reply_repr = TcpRepr {
src_port: tcp_packet.dst_port(),
dst_port: tcp_packet.src_port(),
control: TcpControl::Rst,
seq_number: 0,
ack_number: Some(tcp_packet.seq_number() + 1),
window_len: 0,
payload: &[]
};
response = Response::Tcpv4(ip_reply_repr, tcp_reply_repr);
} else if !handled {
let reason;
if protocol == IpProtocol::Udp {
reason = Icmpv4DstUnreachable::PortUnreachable
} else {
reason = Icmpv4DstUnreachable::ProtoUnreachable
}
let mut data = [0; 8];
data.copy_from_slice(&ip_packet.payload()[0..8]);
let ip_reply_repr = Ipv4Repr {
src_addr: dst_addr,
dst_addr: src_addr,
protocol: IpProtocol::Icmp
};
let icmp_reply_repr = Icmpv4Repr::DstUnreachable {
reason: reason,
header: ip_repr,
length: ip_packet.payload().len(),
data: data
};
response = Response::Icmpv4(ip_reply_repr, icmp_reply_repr)
}
},
}
}
@ -222,6 +267,29 @@ impl<'a, 'b: 'a,
_ => return Err(Error::Unrecognized)
}
macro_rules! ip_response {
($tx_buffer:ident, $frame:ident, $ip_repr:ident, $length:expr) => ({
let dst_hardware_addr =
match self.arp_cache.lookup($ip_repr.dst_addr.into()) {
None => return Err(Error::Unaddressable),
Some(hardware_addr) => hardware_addr
};
let payload_len = $length;
let frame_len = EthernetFrame::<&[u8]>::buffer_len($ip_repr.buffer_len() +
payload_len);
$tx_buffer = try!(self.device.transmit(frame_len));
$frame = try!(EthernetFrame::new(&mut $tx_buffer));
$frame.set_src_addr(self.hardware_addr);
$frame.set_dst_addr(dst_hardware_addr);
$frame.set_ethertype(EthernetProtocol::Ipv4);
let mut ip_packet = try!(Ipv4Packet::new($frame.payload_mut()));
$ip_repr.emit(&mut ip_packet, payload_len);
ip_packet
})
}
match response {
Response::Arp(repr) => {
let tx_len = EthernetFrame::<&[u8]>::buffer_len(repr.buffer_len());
@ -241,26 +309,24 @@ impl<'a, 'b: 'a,
},
Response::Icmpv4(ip_repr, icmp_repr) => {
let dst_hardware_addr =
match self.arp_cache.lookup(ip_repr.dst_addr.into()) {
None => return Err(Error::Unaddressable),
Some(hardware_addr) => hardware_addr
};
let tx_len = EthernetFrame::<&[u8]>::buffer_len(ip_repr.buffer_len() +
icmp_repr.buffer_len());
let mut tx_buffer = try!(self.device.transmit(tx_len));
let mut frame = try!(EthernetFrame::new(&mut tx_buffer));
frame.set_src_addr(self.hardware_addr);
frame.set_dst_addr(dst_hardware_addr);
frame.set_ethertype(EthernetProtocol::Ipv4);
let mut ip_packet = try!(Ipv4Packet::new(frame.payload_mut()));
ip_repr.emit(&mut ip_packet, icmp_repr.buffer_len());
let mut tx_buffer;
let mut frame;
let mut ip_packet = ip_response!(tx_buffer, frame, ip_repr,
icmp_repr.buffer_len());
let mut icmp_packet = try!(Icmpv4Packet::new(ip_packet.payload_mut()));
icmp_repr.emit(&mut icmp_packet);
Ok(())
}
Response::Tcpv4(ip_repr, tcp_repr) => {
let mut tx_buffer;
let mut frame;
let mut ip_packet = ip_response!(tx_buffer, frame, ip_repr,
tcp_repr.buffer_len());
let mut tcp_packet = try!(TcpPacket::new(ip_packet.payload_mut()));
tcp_repr.emit(&mut tcp_packet,
&IpAddress::Ipv4(ip_repr.src_addr),
&IpAddress::Ipv4(ip_repr.dst_addr));
Ok(())
}

View File

@ -1,4 +1,3 @@
use core::cmp;
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};
@ -100,9 +99,7 @@ impl<T: AsRef<[u8]>> Frame<T> {
/// Return the length of a buffer required to hold a packet with the payload
/// of a given length.
pub fn buffer_len(payload_len: usize) -> usize {
// Minimal frame size is 64, but that includes FCS, which the network device
// is taking care of for us.
cmp::max(field::PAYLOAD.start + payload_len, 60)
field::PAYLOAD.start + payload_len
}
/// Return the destination address field.

View File

@ -3,6 +3,7 @@ use byteorder::{ByteOrder, NetworkEndian};
use Error;
use super::ip::checksum;
use super::{Ipv4Packet, Ipv4Repr};
enum_with_unknown! {
/// Internet protocol control message type.
@ -10,7 +11,7 @@ enum_with_unknown! {
/// Echo reply
EchoReply = 0,
/// Destination unreachable
DstUnreachable = 1,
DstUnreachable = 3,
/// Message redirect
Redirect = 5,
/// Echo request
@ -86,6 +87,47 @@ enum_with_unknown! {
}
}
impl fmt::Display for DstUnreachable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&DstUnreachable::NetUnreachable =>
write!(f, "destination network unreachable"),
&DstUnreachable::HostUnreachable =>
write!(f, "destination host unreachable"),
&DstUnreachable::ProtoUnreachable =>
write!(f, "destination protocol unreachable"),
&DstUnreachable::PortUnreachable =>
write!(f, "destination port unreachable"),
&DstUnreachable::FragRequired =>
write!(f, "fragmentation required, and DF flag set"),
&DstUnreachable::SrcRouteFailed =>
write!(f, "source route failed"),
&DstUnreachable::DstNetUnknown =>
write!(f, "destination network unknown"),
&DstUnreachable::DstHostUnknown =>
write!(f, "destination host unknown"),
&DstUnreachable::SrcHostIsolated =>
write!(f, "source host isolated"),
&DstUnreachable::NetProhibited =>
write!(f, "network administratively prohibited"),
&DstUnreachable::HostProhibited =>
write!(f, "host administratively prohibited"),
&DstUnreachable::NetUnreachToS =>
write!(f, "network unreachable for ToS"),
&DstUnreachable::HostUnreachToS =>
write!(f, "host unreachable for ToS"),
&DstUnreachable::CommProhibited =>
write!(f, "communication administratively prohibited"),
&DstUnreachable::HostPrecedViol =>
write!(f, "host precedence violation"),
&DstUnreachable::PrecedCutoff =>
write!(f, "precedence cutoff in effect"),
&DstUnreachable::Unknown(id) =>
write!(f, "{}", id)
}
}
}
enum_with_unknown! {
/// Internet protocol control message subtype for type "Redirect Message".
pub doc enum Redirect(u8) {
@ -135,6 +177,8 @@ mod field {
pub const CODE: usize = 1;
pub const CHECKSUM: Field = 2..4;
pub const UNUSED: Field = 4..8;
pub const ECHO_IDENT: Field = 4..6;
pub const ECHO_SEQNO: Field = 6..8;
}
@ -206,8 +250,9 @@ impl<T: AsRef<[u8]>> Packet<T> {
/// The result depends on the value of the message type field.
pub fn header_len(&self) -> usize {
match self.msg_type() {
Message::EchoRequest => field::ECHO_SEQNO.end,
Message::EchoReply => field::ECHO_SEQNO.end,
Message::EchoRequest => field::ECHO_SEQNO.end,
Message::EchoReply => field::ECHO_SEQNO.end,
Message::DstUnreachable => field::UNUSED.end,
_ => field::CHECKSUM.end // make a conservative assumption
}
}
@ -304,6 +349,12 @@ pub enum Repr<'a> {
seq_no: u16,
data: &'a [u8]
},
DstUnreachable {
reason: DstUnreachable,
header: Ipv4Repr,
length: usize,
data: [u8; 8]
},
#[doc(hidden)]
__Nonexhaustive
}
@ -320,6 +371,7 @@ impl<'a> Repr<'a> {
data: packet.data()
})
},
(Message::EchoReply, 0) => {
Ok(Repr::EchoReply {
ident: packet.echo_ident(),
@ -327,6 +379,25 @@ impl<'a> Repr<'a> {
data: packet.data()
})
},
(Message::DstUnreachable, code) => {
let ip_packet = try!(Ipv4Packet::new(packet.data()));
let ip_repr = try!(Ipv4Repr::parse(&ip_packet));
let mut data = [0; 8];
let payload = &packet.data()[ip_packet.header_len() as usize..];
if payload.len() < data.len() { return Err(Error::Truncated) }
data.copy_from_slice(&payload[0..8]);
let length = ip_packet.total_len() as usize - ip_packet.header_len() as usize;
Ok(Repr::DstUnreachable {
reason: DstUnreachable::from(code),
header: ip_repr,
length: length,
data: data
})
}
_ => Err(Error::Unrecognized)
}
}
@ -338,6 +409,9 @@ impl<'a> Repr<'a> {
&Repr::EchoReply { data, .. } => {
field::ECHO_SEQNO.end + data.len()
},
&Repr::DstUnreachable { header, data, .. } => {
field::UNUSED.end + header.buffer_len() + data.len()
}
&Repr::__Nonexhaustive => unreachable!()
}
}
@ -349,18 +423,33 @@ impl<'a> Repr<'a> {
match self {
&Repr::EchoRequest { ident, seq_no, data } => {
packet.set_msg_type(Message::EchoRequest);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
let data_len = cmp::min(packet.data_mut().len(), data.len());
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
},
&Repr::EchoReply { ident, seq_no, data } => {
packet.set_msg_type(Message::EchoReply);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
let data_len = cmp::min(packet.data_mut().len(), data.len());
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
},
&Repr::DstUnreachable { reason, header, length, data } => {
packet.set_msg_type(Message::DstUnreachable);
packet.set_msg_code(reason.into());
let mut ip_packet = Ipv4Packet::new(packet.data_mut())
.expect("undersized data");
header.emit(&mut ip_packet, length);
let mut payload = &mut ip_packet.into_inner()[header.buffer_len()..];
payload.copy_from_slice(&data[..])
}
&Repr::__Nonexhaustive => unreachable!()
}
packet.fill_checksum()
@ -371,10 +460,14 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match Repr::parse(self) {
Ok(repr) => write!(f, "{}", repr),
_ => {
try!(write!(f, "ICMPv4 (unrecognized)"));
try!(write!(f, " type={} code={} cksum={:#04x}",
self.msg_type(), self.msg_code(), self.checksum()));
Err(err) => {
try!(write!(f, "ICMPv4 ({})", err));
try!(write!(f, " type={:?}", self.msg_type()));
match self.msg_type() {
Message::DstUnreachable =>
try!(write!(f, " code={:?}", DstUnreachable::from(self.msg_code()))),
_ => try!(write!(f, " code={}", self.msg_code()))
}
Ok(())
}
}
@ -385,11 +478,14 @@ impl<'a> fmt::Display for Repr<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Repr::EchoRequest { ident, seq_no, data } =>
write!(f, "ICMPv4 Echo Request id={} seq={} len={}",
write!(f, "ICMPv4 echo request id={} seq={} len={}",
ident, seq_no, data.len()),
&Repr::EchoReply { ident, seq_no, data } =>
write!(f, "ICMPv4 Echo Reply id={} seq={} len={}",
write!(f, "ICMPv4 echo reply id={} seq={} len={}",
ident, seq_no, data.len()),
&Repr::DstUnreachable { reason, .. } =>
write!(f, "ICMPv4 destination unreachable ({})",
reason),
&Repr::__Nonexhaustive => unreachable!()
}
}
@ -400,14 +496,21 @@ use super::pretty_print::{PrettyPrint, PrettyIndent};
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
fn pretty_print(buffer: &AsRef<[u8]>, f: &mut fmt::Formatter,
indent: &mut PrettyIndent) -> fmt::Result {
match Packet::new(buffer) {
Err(err) => write!(f, "{}({})\n", indent, err),
Ok(packet) => write!(f, "{}{}\n", indent, packet)
let packet = match Packet::new(buffer) {
Err(err) => return write!(f, "{}({})\n", indent, err),
Ok(packet) => packet
};
try!(write!(f, "{}{}\n", indent, packet));
indent.increase();
match packet.msg_type() {
Message::DstUnreachable =>
super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent),
_ => Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -86,7 +86,7 @@ pub struct Endpoint {
impl Endpoint {
pub const INVALID: Endpoint = Endpoint { addr: Address::Invalid, port: 0 };
/// Create an internet endpoint address.
/// Create an endpoint address from given address and port.
pub fn new(addr: Address, port: u16) -> Endpoint {
Endpoint { addr: addr, port: port }
}

View File

@ -401,7 +401,7 @@ impl Repr {
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>,
payload_len: usize) {
packet.set_version(4);
packet.set_header_len(20);
packet.set_header_len(field::DST_ADDR.end as u8);
packet.set_dscp(0);
packet.set_ecn(0);
let total_len = packet.header_len() as u16 + payload_len as u16;
@ -423,8 +423,8 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match Repr::parse(self) {
Ok(repr) => write!(f, "{}", repr),
_ => {
try!(write!(f, "IPv4 (unrecognized)"));
Err(err) => {
try!(write!(f, "IPv4 ({})", err));
try!(write!(f, " src={} dst={} proto={} ttl={}",
self.src_addr(), self.dst_addr(), self.protocol(), self.ttl()));
if self.version() != 4 {
@ -471,12 +471,12 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
fn pretty_print(buffer: &AsRef<[u8]>, f: &mut fmt::Formatter,
indent: &mut PrettyIndent) -> fmt::Result {
let packet = match Packet::new(buffer) {
Err(err) => return write!(f, "{}({})\n", indent, err),
Err(err) => return write!(f, "{}({})\n", indent, err),
Ok(packet) => packet
};
try!(write!(f, "{}{}\n", indent, packet));
indent.increase();
indent.increase();
match packet.protocol() {
Protocol::Icmp =>
super::Icmpv4Packet::<&[u8]>::pretty_print(&packet.payload(), f, indent),

View File

@ -462,7 +462,7 @@ impl<'a> Repr<'a> {
packet.set_seq_number(self.seq_number);
packet.set_ack_number(self.ack_number.unwrap_or(0));
packet.set_window_len(self.window_len);
packet.set_header_len(20);
packet.set_header_len(field::URGENT.end as u8);
packet.clear_flags();
match self.control {
Control::None => (),
@ -470,6 +470,7 @@ impl<'a> Repr<'a> {
Control::Fin => packet.set_fin(true),
Control::Rst => packet.set_rst(true)
}
packet.set_ack(self.ack_number.is_some());
packet.payload_mut().copy_from_slice(self.payload);
packet.fill_checksum(src_addr, dst_addr)
}