CS144Lab/libsponge/tcp_helpers/ipv4_header.cc
2021-10-19 18:08:12 -07:00

160 lines
5.2 KiB
C++

#include "ipv4_header.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <iomanip>
#include <sstream>
using namespace std;
//! \param[in,out] p is a NetParser from which the IP fields will be extracted
//! \returns a ParseResult indicating success or the reason for failure
//! \details It is important to check for (at least) the following potential errors
//! (but note that NetParser inherently checks for certain errors;
//! use that fact to your advantage!):
//!
//! - data stream is too short to contain a header
//! - wrong IP version number
//! - the header's `hlen` field is shorter than the minimum allowed
//! - there is less data in the header than the `doff` field claims
//! - there is less data in the full datagram than the `len` field claims
//! - the checksum is bad
ParseResult IPv4Header::parse(NetParser &p) {
Buffer original_serialized_version = p.buffer();
const size_t data_size = p.buffer().size();
if (data_size < IPv4Header::LENGTH) {
return ParseResult::PacketTooShort;
}
const uint8_t first_byte = p.u8();
ver = first_byte >> 4; // version
hlen = first_byte & 0x0f; // header length
tos = p.u8(); // type of service
len = p.u16(); // length
id = p.u16(); // id
const uint16_t fo_val = p.u16();
df = static_cast<bool>(fo_val & 0x4000); // don't fragment
mf = static_cast<bool>(fo_val & 0x2000); // more fragments
offset = fo_val & 0x1fff; // offset
ttl = p.u8(); // ttl
proto = p.u8(); // proto
cksum = p.u16(); // checksum
src = p.u32(); // source address
dst = p.u32(); // destination address
if (data_size < 4 * hlen) {
return ParseResult::PacketTooShort;
}
if (ver != 4) {
return ParseResult::WrongIPVersion;
}
if (hlen < 5) {
return ParseResult::HeaderTooShort;
}
if (data_size != len) {
return ParseResult::TruncatedPacket;
}
p.remove_prefix(hlen * 4 - IPv4Header::LENGTH);
if (p.error()) {
return p.get_error();
}
InternetChecksum check;
check.add({original_serialized_version.str().data(), size_t(4 * hlen)});
if (check.value()) {
return ParseResult::BadChecksum;
}
return ParseResult::NoError;
}
//! Serialize the IPv4Header to a string (does not recompute the checksum)
string IPv4Header::serialize() const {
// sanity checks
if (ver != 4) {
throw runtime_error("wrong IP version");
}
if (4 * hlen < IPv4Header::LENGTH) {
throw runtime_error("IP header too short");
}
string ret;
ret.reserve(4 * hlen);
const uint8_t first_byte = (ver << 4) | (hlen & 0xf);
NetUnparser::u8(ret, first_byte); // version and header length
NetUnparser::u8(ret, tos); // type of service
NetUnparser::u16(ret, len); // length
NetUnparser::u16(ret, id); // id
const uint16_t fo_val = (df ? 0x4000 : 0) | (mf ? 0x2000 : 0) | (offset & 0x1fff);
NetUnparser::u16(ret, fo_val); // flags and offset
NetUnparser::u8(ret, ttl); // time to live
NetUnparser::u8(ret, proto); // protocol number
NetUnparser::u16(ret, cksum); // checksum
NetUnparser::u32(ret, src); // src address
NetUnparser::u32(ret, dst); // dst address
ret.resize(4 * hlen); // expand header to advertised size
return ret;
}
uint16_t IPv4Header::payload_length() const { return len - 4 * hlen; }
//! \details This value is needed when computing the checksum of an encapsulated TCP segment.
//! ~~~{.txt}
//! 0 7 8 15 16 23 24 31
//! +--------+--------+--------+--------+
//! | source address |
//! +--------+--------+--------+--------+
//! | destination address |
//! +--------+--------+--------+--------+
//! | zero |protocol| payload length |
//! +--------+--------+--------+--------+
//! ~~~
uint32_t IPv4Header::pseudo_cksum() const {
uint32_t pcksum = (src >> 16) + (src & 0xffff); // source addr
pcksum += (dst >> 16) + (dst & 0xffff); // dest addr
pcksum += proto; // protocol
pcksum += payload_length(); // payload length
return pcksum;
}
//! \returns A string with the header's contents
std::string IPv4Header::to_string() const {
stringstream ss{};
ss << hex << boolalpha << "IP version: " << +ver << '\n'
<< "IP hdr len: " << +hlen << '\n'
<< "IP tos: " << +tos << '\n'
<< "IP dgram len: " << +len << '\n'
<< "IP id: " << +id << '\n'
<< "Flags: df: " << df << " mf: " << mf << '\n'
<< "Offset: " << +offset << '\n'
<< "TTL: " << +ttl << '\n'
<< "Protocol: " << +proto << '\n'
<< "Checksum: " << +cksum << '\n'
<< "Src addr: " << +src << '\n'
<< "Dst addr: " << +dst << '\n';
return ss.str();
}
std::string IPv4Header::summary() const {
stringstream ss{};
ss << hex << boolalpha << "IPv" << +ver << ", "
<< "len=" << +len << ", "
<< "protocol=" << +proto << ", " << (ttl >= 10 ? "" : "ttl=" + ::to_string(ttl) + ", ")
<< "src=" << inet_ntoa({htobe32(src)}) << ", "
<< "dst=" << inet_ntoa({htobe32(dst)});
return ss.str();
}