diff --git a/README.md b/README.md index 87ea9ebe..d469962f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ The only supported medium is Ethernet. The only supported internetworking protocol is IPv4. * IPv4 header checksum is generated and validated. - * IPv4 time-to-live value is fixed at 64. * IPv4 fragmentation is **not** supported. * IPv4 options are **not** supported and are silently ignored. * IPv4 routes or default gateways are **not** supported. diff --git a/examples/ping.rs b/examples/ping.rs index c0266cee..032530c6 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -94,6 +94,7 @@ fn main() { dst_addr: remote_addr, protocol: IpProtocol::Icmp, payload_len: icmp_repr.buffer_len(), + ttl: 64 }; let raw_payload = socket diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 91ac2ea6..857148dd 100644 --- a/src/iface/ethernet.rs +++ b/src/iface/ethernet.rs @@ -378,7 +378,8 @@ impl<'a, 'b, 'c, DeviceT: Device + 'a> Interface<'a, 'b, 'c, DeviceT> { src_addr: ipv4_repr.dst_addr, dst_addr: ipv4_repr.src_addr, protocol: IpProtocol::Icmp, - payload_len: icmp_reply_repr.buffer_len() + payload_len: icmp_reply_repr.buffer_len(), + ttl: 64, }; Ok(Packet::Icmpv4(ipv4_reply_repr, icmp_reply_repr)) } @@ -403,7 +404,8 @@ impl<'a, 'b, 'c, DeviceT: Device + 'a> Interface<'a, 'b, 'c, DeviceT> { src_addr: ipv4_repr.dst_addr, dst_addr: ipv4_repr.src_addr, protocol: IpProtocol::Icmp, - payload_len: icmp_reply_repr.buffer_len() + payload_len: icmp_reply_repr.buffer_len(), + ttl: 64 }; Ok(Packet::Icmpv4(ipv4_reply_repr, icmp_reply_repr)) } @@ -448,7 +450,8 @@ impl<'a, 'b, 'c, DeviceT: Device + 'a> Interface<'a, 'b, 'c, DeviceT> { src_addr: ipv4_repr.dst_addr, dst_addr: ipv4_repr.src_addr, protocol: IpProtocol::Icmp, - payload_len: icmpv4_reply_repr.buffer_len() + payload_len: icmpv4_reply_repr.buffer_len(), + ttl: 64, }; Ok(Packet::Icmpv4(ipv4_reply_repr, icmpv4_reply_repr)) }, diff --git a/src/socket/raw.rs b/src/socket/raw.rs index fb5fcc37..68dacbc3 100644 --- a/src/socket/raw.rs +++ b/src/socket/raw.rs @@ -270,7 +270,8 @@ mod test { src_addr: Ipv4Address([10, 0, 0, 1]), dst_addr: Ipv4Address([10, 0, 0, 2]), protocol: IpProtocol::Unknown(IP_PROTO), - payload_len: 4 + payload_len: 4, + ttl: 64 }); const PACKET_BYTES: [u8; 24] = [ 0x45, 0x00, 0x00, 0x18, diff --git a/src/socket/tcp.rs b/src/socket/tcp.rs index bf3b9a30..a9f4463a 100644 --- a/src/socket/tcp.rs +++ b/src/socket/tcp.rs @@ -179,6 +179,8 @@ pub struct TcpSocket<'a> { timeout: Option, /// Interval at which keep-alive packets will be sent. keep_alive: Option, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + ttl: Option, /// Address passed to listen(). Listen address is set when listen() is called and /// used every time the socket is reset back to the LISTEN state. listen_address: IpAddress, @@ -236,6 +238,7 @@ impl<'a> TcpSocket<'a> { rx_buffer: rx_buffer, timeout: None, keep_alive: None, + ttl: None, listen_address: IpAddress::default(), local_endpoint: IpEndpoint::default(), remote_endpoint: IpEndpoint::default(), @@ -311,6 +314,33 @@ impl<'a> TcpSocket<'a> { } } + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_ttl](#method.set_ttl) method + pub fn ttl(&self) -> Option { + self.ttl + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set TTL value uses the default [IANA recommended] + /// value (`64`). + /// + /// # Panics + /// + /// This function panics if a TTL value of `0` is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_ttl(&mut self, ttl: Option) { + // A host MUST NOT send a datagram with a Time-to-Live (TTL) + // value of 0 + match ttl { + Some(0) => { panic!("A TTL value of 0 is invalid for a sent packet"); }, + catchall => self.ttl = catchall, + } + } + /// Return the local endpoint. #[inline] pub fn local_endpoint(&self) -> IpEndpoint { @@ -337,6 +367,7 @@ impl<'a> TcpSocket<'a> { self.rx_buffer.clear(); self.keep_alive = None; self.timeout = None; + self.ttl = None; self.listen_address = IpAddress::default(); self.local_endpoint = IpEndpoint::default(); self.remote_endpoint = IpEndpoint::default(); @@ -733,7 +764,8 @@ impl<'a> TcpSocket<'a> { src_addr: ip_repr.dst_addr(), dst_addr: ip_repr.src_addr(), protocol: IpProtocol::Tcp, - payload_len: reply_repr.buffer_len() + payload_len: reply_repr.buffer_len(), + ttl: 64 }; (ip_reply_repr, reply_repr) } @@ -1239,6 +1271,7 @@ impl<'a> TcpSocket<'a> { src_addr: self.local_endpoint.addr, dst_addr: self.remote_endpoint.addr, protocol: IpProtocol::Tcp, + ttl: self.ttl.unwrap_or(64), payload_len: 0 }.lower(&[])?; @@ -1447,7 +1480,8 @@ impl<'a> fmt::Write for TcpSocket<'a> { #[cfg(test)] mod test { - use wire::{IpAddress, Ipv4Address, IpCidr}; + use wire::{IpAddress, IpRepr}; + use wire::{Ipv4Address, IpCidr, Ipv4Repr}; use super::*; #[test] @@ -1479,7 +1513,8 @@ mod test { const SEND_IP_TEMPL: IpRepr = IpRepr::Unspecified { src_addr: LOCAL_IP, dst_addr: REMOTE_IP, - protocol: IpProtocol::Tcp, payload_len: 20 + protocol: IpProtocol::Tcp, payload_len: 20, + ttl: 64 }; const SEND_TEMPL: TcpRepr<'static> = TcpRepr { src_port: REMOTE_PORT, dst_port: LOCAL_PORT, @@ -1490,7 +1525,8 @@ mod test { }; const _RECV_IP_TEMPL: IpRepr = IpRepr::Unspecified { src_addr: REMOTE_IP, dst_addr: LOCAL_IP, - protocol: IpProtocol::Tcp, payload_len: 20 + protocol: IpProtocol::Tcp, payload_len: 20, + ttl: 64 }; const RECV_TEMPL: TcpRepr<'static> = TcpRepr { src_port: LOCAL_PORT, dst_port: REMOTE_PORT, @@ -1506,7 +1542,8 @@ mod test { src_addr: REMOTE_IP, dst_addr: LOCAL_IP, protocol: IpProtocol::Tcp, - payload_len: repr.buffer_len() + payload_len: repr.buffer_len(), + ttl: 64 }; trace!("send: {}", repr); @@ -3470,7 +3507,8 @@ mod test { src_addr: REMOTE_IP, dst_addr: LOCAL_IP, protocol: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len() + payload_len: tcp_repr.buffer_len(), + ttl: 64 }; assert!(s.accepts(&ip_repr, &tcp_repr)); @@ -3478,7 +3516,8 @@ mod test { src_addr: OTHER_IP, dst_addr: LOCAL_IP, protocol: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len() + payload_len: tcp_repr.buffer_len(), + ttl: 64 }; assert!(!s.accepts(&ip_repr_wrong_src, &tcp_repr)); @@ -3486,8 +3525,28 @@ mod test { src_addr: REMOTE_IP, dst_addr: OTHER_IP, protocol: IpProtocol::Tcp, - payload_len: tcp_repr.buffer_len() + payload_len: tcp_repr.buffer_len(), + ttl: 64 }; assert!(!s.accepts(&ip_repr_wrong_dst, &tcp_repr)); } + + #[test] + fn test_set_ttl() { + let mut s = socket_syn_received(); + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1520; + + s.set_ttl(Some(0x2a)); + assert_eq!(s.dispatch(0, &caps, |(ip_repr, _)| { + assert_eq!(ip_repr, IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address([10, 0, 0, 1]), + dst_addr: Ipv4Address([10, 0, 0, 2]), + protocol: IpProtocol::Tcp, + payload_len: 24, + ttl: 0x2a, + })); + Ok(()) + }), Ok(())); + } } diff --git a/src/socket/udp.rs b/src/socket/udp.rs index 4f57f193..efda8c77 100644 --- a/src/socket/udp.rs +++ b/src/socket/udp.rs @@ -63,6 +63,8 @@ pub struct UdpSocket<'a, 'b: 'a> { endpoint: IpEndpoint, rx_buffer: SocketBuffer<'a, 'b>, tx_buffer: SocketBuffer<'a, 'b>, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + ttl: Option } impl<'a, 'b> UdpSocket<'a, 'b> { @@ -74,6 +76,7 @@ impl<'a, 'b> UdpSocket<'a, 'b> { endpoint: IpEndpoint::default(), rx_buffer: rx_buffer, tx_buffer: tx_buffer, + ttl: None }) } @@ -94,6 +97,33 @@ impl<'a, 'b> UdpSocket<'a, 'b> { self.endpoint } + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_ttl](#method.set_ttl) method + pub fn ttl(&self) -> Option { + self.ttl + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set TTL value uses the default [IANA recommended] + /// value (`64`). + /// + /// # Panics + /// + /// This function panics if a TTL value of `0` is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_ttl(&mut self, ttl: Option) { + // A host MUST NOT send a datagram with a Time-to-Live (TTL) + // value of 0 + match ttl { + Some(0) => { panic!("A TTL value of 0 is invalid for a sent packet"); }, + catchall => self.ttl = catchall, + } + } + /// Bind the socket to the given endpoint. /// /// This function returns `Err(Error::Illegal)` if the socket was open @@ -200,6 +230,7 @@ impl<'a, 'b> UdpSocket<'a, 'b> { where F: FnOnce((IpRepr, UdpRepr)) -> Result<()> { let handle = self.handle; let endpoint = self.endpoint; + let ttl = self.ttl.unwrap_or(64); self.tx_buffer.dequeue_one_with(|packet_buf| { net_trace!("{}:{}:{}: sending {} octets", handle, endpoint, @@ -214,7 +245,8 @@ impl<'a, 'b> UdpSocket<'a, 'b> { src_addr: endpoint.addr, dst_addr: packet_buf.endpoint.addr, protocol: IpProtocol::Udp, - payload_len: repr.buffer_len() + payload_len: repr.buffer_len(), + ttl: ttl, }; emit((ip_repr, repr)) }) @@ -275,7 +307,8 @@ mod test { src_addr: LOCAL_IP, dst_addr: REMOTE_IP, protocol: IpProtocol::Udp, - payload_len: 8 + 6 + payload_len: 8 + 6, + ttl: 64, }; const LOCAL_UDP_REPR: UdpRepr = UdpRepr { src_port: LOCAL_PORT, @@ -337,7 +370,8 @@ mod test { src_addr: Ipv4Address([10, 0, 0, 2]), dst_addr: Ipv4Address([10, 0, 0, 1]), protocol: IpProtocol::Udp, - payload_len: 8 + 6 + payload_len: 8 + 6, + ttl: 64 }); const REMOTE_UDP_REPR: UdpRepr = UdpRepr { src_port: REMOTE_PORT, @@ -407,7 +441,8 @@ mod test { src_addr: Ipv4Address([10, 0, 0, 2]), dst_addr: Ipv4Address([10, 0, 0, 10]), protocol: IpProtocol::Udp, - payload_len: 8 + 6 + payload_len: 8 + 6, + ttl: 64 }); let mut port_bound_socket = socket(buffer(1), buffer(0)); @@ -418,4 +453,23 @@ mod test { assert_eq!(ip_bound_socket.bind(LOCAL_END), Ok(())); assert!(!ip_bound_socket.accepts(&ip_repr, &REMOTE_UDP_REPR)); } + + #[test] + fn test_set_ttl() { + let mut s = socket(buffer(0), buffer(1)); + assert_eq!(s.bind(LOCAL_END), Ok(())); + + s.set_ttl(Some(0x2a)); + assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(())); + assert_eq!(s.dispatch(|(ip_repr, _)| { + assert_eq!(ip_repr, IpRepr::Unspecified{ + src_addr: LOCAL_IP, + dst_addr: REMOTE_IP, + protocol: IpProtocol::Udp, + payload_len: 8 + 6, + ttl: 0x2a, + }); + Ok(()) + }), Ok(())); + } } diff --git a/src/wire/icmpv4.rs b/src/wire/icmpv4.rs index 1a409adc..802ebd46 100644 --- a/src/wire/icmpv4.rs +++ b/src/wire/icmpv4.rs @@ -423,6 +423,7 @@ impl<'a> Repr<'a> { dst_addr: ip_packet.dst_addr(), protocol: ip_packet.protocol(), payload_len: payload.len(), + ttl: ip_packet.ttl() }, data: payload }) diff --git a/src/wire/ip.rs b/src/wire/ip.rs index 7716bf40..1aaf1962 100644 --- a/src/wire/ip.rs +++ b/src/wire/ip.rs @@ -266,7 +266,8 @@ pub enum IpRepr { src_addr: Address, dst_addr: Address, protocol: Protocol, - payload_len: usize + payload_len: usize, + ttl: u8 }, Ipv4(Ipv4Repr), #[doc(hidden)] @@ -336,6 +337,15 @@ impl IpRepr { } } + /// Return the TTL value. + pub fn ttl(&self) -> u8 { + match self { + &IpRepr::Unspecified { ttl, .. } => ttl, + &IpRepr::Ipv4(Ipv4Repr { ttl, .. }) => ttl, + &IpRepr::__Nonexhaustive => unreachable!() + } + } + /// Convert an unspecified representation into a concrete one, or return /// `Err(Error::Unaddressable)` if not possible. /// @@ -347,20 +357,20 @@ impl IpRepr { &IpRepr::Unspecified { src_addr: Address::Ipv4(src_addr), dst_addr: Address::Ipv4(dst_addr), - protocol, payload_len + protocol, payload_len, ttl } => { Ok(IpRepr::Ipv4(Ipv4Repr { src_addr: src_addr, dst_addr: dst_addr, protocol: protocol, - payload_len: payload_len + payload_len: payload_len, ttl })) } &IpRepr::Unspecified { src_addr: Address::Unspecified, dst_addr: Address::Ipv4(dst_addr), - protocol, payload_len + protocol, payload_len, ttl } => { let mut src_addr = None; for cidr in fallback_src_addrs { @@ -374,9 +384,7 @@ impl IpRepr { } Ok(IpRepr::Ipv4(Ipv4Repr { src_addr: src_addr.ok_or(Error::Unaddressable)?, - dst_addr: dst_addr, - protocol: protocol, - payload_len: payload_len + dst_addr, protocol, payload_len, ttl })) } @@ -534,12 +542,14 @@ mod test { src_addr: IpAddress::Ipv4(ip_addr_a), dst_addr: IpAddress::Ipv4(ip_addr_b), protocol: proto, - payload_len + ttl: 0x2a, + payload_len, }.lower(&[]), Ok(IpRepr::Ipv4(Ipv4Repr{ src_addr: ip_addr_a, dst_addr: ip_addr_b, protocol: proto, + ttl: 0x2a, payload_len })) ); @@ -549,6 +559,7 @@ mod test { src_addr: IpAddress::Unspecified, dst_addr: IpAddress::Ipv4(ip_addr_b), protocol: proto, + ttl: 64, payload_len }.lower(&[]), Err(Error::Unaddressable) @@ -559,12 +570,14 @@ mod test { src_addr: IpAddress::Unspecified, dst_addr: IpAddress::Ipv4(ip_addr_b), protocol: proto, + ttl: 64, payload_len }.lower(&[IpCidr::new(IpAddress::Ipv4(ip_addr_a), 24)]), Ok(IpRepr::Ipv4(Ipv4Repr{ src_addr: ip_addr_a, dst_addr: ip_addr_b, protocol: proto, + ttl: 64, payload_len })) ); @@ -574,12 +587,14 @@ mod test { src_addr: ip_addr_a, dst_addr: ip_addr_b, protocol: proto, + ttl: 255, payload_len }).lower(&[]), Ok(IpRepr::Ipv4(Ipv4Repr{ src_addr: ip_addr_a, dst_addr: ip_addr_b, protocol: proto, + ttl: 255, payload_len })) ); @@ -589,6 +604,7 @@ mod test { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: ip_addr_b, protocol: proto, + ttl: 255, payload_len }).lower(&[]), Err(Error::Unaddressable) @@ -599,12 +615,14 @@ mod test { src_addr: Ipv4Address::UNSPECIFIED, dst_addr: ip_addr_b, protocol: proto, + ttl: 64, payload_len }).lower(&[IpCidr::new(IpAddress::Ipv4(ip_addr_a), 24)]), Ok(IpRepr::Ipv4(Ipv4Repr{ src_addr: ip_addr_a, dst_addr: ip_addr_b, protocol: proto, + ttl: 64, payload_len })) ); diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs index c4004bd2..1c934c9b 100644 --- a/src/wire/ipv4.rs +++ b/src/wire/ipv4.rs @@ -448,7 +448,8 @@ pub struct Repr { pub src_addr: Address, pub dst_addr: Address, pub protocol: Protocol, - pub payload_len: usize + pub payload_len: usize, + pub ttl: u8 } impl Repr { @@ -474,7 +475,8 @@ impl Repr { src_addr: packet.src_addr(), dst_addr: packet.dst_addr(), protocol: packet.protocol(), - payload_len: payload_len + payload_len: payload_len, + ttl: packet.ttl() }) } @@ -497,7 +499,7 @@ impl Repr { packet.set_more_frags(false); packet.set_dont_frag(true); packet.set_frag_offset(0); - packet.set_ttl(64); + packet.set_ttl(self.ttl); packet.set_protocol(self.protocol); packet.set_src_addr(self.src_addr); packet.set_dst_addr(self.dst_addr); @@ -722,7 +724,8 @@ mod test { src_addr: Address([0x11, 0x12, 0x13, 0x14]), dst_addr: Address([0x21, 0x22, 0x23, 0x24]), protocol: Protocol::Icmp, - payload_len: 4 + payload_len: 4, + ttl: 64 } } @@ -733,6 +736,17 @@ mod test { assert_eq!(repr, packet_repr()); } + #[test] + fn test_parse_bad_version() { + let mut bytes = vec![0; 24]; + bytes.copy_from_slice(&REPR_PACKET_BYTES[..]); + let mut packet = Packet::new(&mut bytes); + packet.set_version(6); + packet.fill_checksum(); + let packet = Packet::new(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet, &ChecksumCapabilities::default()), Err(Error::Malformed)); + } + #[test] fn test_parse_total_len_underflow() { let mut bytes = vec![0; 24]; diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 9b3b648e..8a9944a2 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -52,7 +52,8 @@ let repr = Ipv4Repr { src_addr: Ipv4Address::new(10, 0, 0, 1), dst_addr: Ipv4Address::new(10, 0, 0, 2), protocol: IpProtocol::Tcp, - payload_len: 10 + payload_len: 10, + ttl: 64 }; let mut buffer = vec![0; repr.buffer_len() + repr.payload_len]; { // emission