[THUSC 2019工程] C. 数据链路层协议数据处理

上题题解

这一次用到 T1 提供的 IP 和 MAC 地址了!快快用 map 存下来方便查找:

template<typename T,size_t n> const auto array_hash=[](std::array<T,n> const& f){
    return std::_Hash_impl::hash(f.data(),f.size());
};

std::unordered_map<std::array<uint8_t,4>,std::array<uint8_t,6>,
    decltype(array_hash<uint8_t,4>)> const eths({
        {{10,2,1,1},{0x98,0x01,0x29,0x00,0x00,1}},
        {{10,2,2,1},{0x98,0x01,0x29,0x00,0x00,2}},
        {{10,2,3,1},{0x98,0x01,0x29,0x00,0x00,3}},
        {{10,2,4,1},{0x98,0x01,0x29,0x00,0x00,4}},
        {{10,2,5,1},{0x98,0x01,0x29,0x00,0x00,5}},
        {{10,2,6,1},{0x98,0x01,0x29,0x00,0x00,6}},
        {{10,2,7,1},{0x98,0x01,0x29,0x00,0x00,7}},
        {{10,2,8,1},{0x98,0x01,0x29,0x00,0x00,8}},
        {{10,2,9,1},{0x98,0x01,0x29,0x00,0x00,9}},
        {{10,2,10,1},{0x98,0x01,0x29,0x00,0x00,10}},
        {{10,2,11,1},{0x98,0x01,0x29,0x00,0x00,11}},
        {{10,2,12,1},{0x98,0x01,0x29,0x00,0x00,12}},
        {{10,2,13,1},{0x98,0x01,0x29,0x00,0x00,13}},
        {{10,2,14,1},{0x98,0x01,0x29,0x00,0x00,14}},
        {{10,2,15,1},{0x98,0x01,0x29,0x00,0x00,15}},
        {{10,2,16,1},{0x98,0x01,0x29,0x00,0x00,16}},
    #ifndef ONLINE_JUDGE
        {{10,2,12,82},{0x98,0x01,0x29,0x00,0x00,114}},
    #endif
    },16,array_hash<uint8_t,4>);

里面的 ifdef 是干什么的?“由于提供样例会大幅降低本题难度,故不提供样例”,我们需要自己生成样例调试。

好消息是,使用 Wireshark 调试上一题时我们发现有一个目标 IP 为 10.2.12.82 的 ARP 请求,我们可以直接使用上一题的样例。因此,添加一个 ifdef,里面包含了本样例的 IP 与一个虚构的 MAC 地址方便调试,提交时自动删除。

题目需要我们自己构造包了,我们添加一些构造函数和转换为二进制的成员函数:

struct Throwable{};
struct PackageCheckError:Throwable{};
struct UnknownTargetError:Throwable{};

pcap_hdr::pcap_hdr(void):magic_number(0xA1B2C3D4),version_major(2),
    version_minor(4),thiszone(0),sigfigs(0),snaplen(262144),
    network(1){}
pcaprec_hdr::pcaprec_hdr(size_t len):ts_sec(),ts_usec(),incl_len(len),orig_len(len){}
pcaprec(std::vector<uint8_t> const& d):
    header(d.size()),data(d){}
pcap::pcap(void):header(),data(){}
pcap::pcap(pcap_hdr const& p):header(p),data(){}
ethernet_frame::ethernet_frame(std::array<uint8_t,6> const& _desMAC,std::array<uint8_t,6> const& _srcMAC,EtherType _t,std::vector<uint8_t> const& _d):
    destinationMAC(_desMAC),sourceMAC(_srcMAC),etherType(_t),data(_d),fcs(){ // getbuffer 中直接取了 fcs 的值,直接调用初始化访问未分配内存产生 UB
    fcs=get_fcs(tobuffer());
}
std::vector<uint8_t> ethernet_frame::tobuffer(void){
    std::vector<uint8_t> res{
        destinationMAC[0],destinationMAC[1],destinationMAC[2],destinationMAC[3],destinationMAC[4],destinationMAC[5],
        sourceMAC[0],sourceMAC[1],sourceMAC[2],sourceMAC[3],sourceMAC[4],sourceMAC[5],
        uint8_t(uint16_t(etherType)>>8),uint8_t(uint16_t(etherType)>>0)
    };
    res.insert(res.end(),data.begin(),data.end());
    while (res.size()<64-4) res.push_back(0);  // 长度至少为 64
    res.insert(res.end(),{uint8_t(fcs>>24),uint8_t(fcs>>16),uint8_t(fcs>>8),uint8_t(fcs>>0)});
    return res;
}

另外,构造以太网帧需要计算 FCS,因此我们将该过程从 check 函数中提出来:

static uint64_t ethernet_frame::query_fcs_byte(uint8_t x){
    static std::array<uint64_t,256> mem={};
    static constexpr int64_t G=0b100000100110000010001110110110111;
    if (mem[x]) return mem[x];
    if (x==0) return 0;
    uint8_t y=x;
    for (uint8_t i=7;i<8;--i) if ((x>>i)&1)
        x^=uint8_t(G<<i>>32),
        mem[y]^=G<<i;
    return mem[y];
}
static uint32_t ethernet_frame::get_fcs(std::vector<uint8_t> f){
    std::fill_n(f.end()-4,4,0);
    for (uint8_t& i:f) i=bitreverse(i);
    f[0]=~f[0],f[1]=~f[1],f[2]=~f[2],f[3]=~f[3];
    for (size_t i=4;i<f.size();++i){
        int64_t x=query_fcs_byte(f[i-4]);
        f[i-4]^=uint8_t(x>>(40-8)),
        f[i-3]^=uint8_t(x>>(40-16)),
        f[i-2]^=uint8_t(x>>(40-24)),
        f[i-1]^=uint8_t(x>>(40-32)),
        f[i-0]^=uint8_t(x>>(40-40));
    }
    uint32_t res=~(
        (bitreverse((uint32_t)f.rbegin()[3])<<24u)+
        (bitreverse((uint32_t)f.rbegin()[2])<<16u)+
        (bitreverse((uint32_t)f.rbegin()[1])<<8u)+
        (bitreverse((uint32_t)f.rbegin()[0])<<0u)
    );
    return res;
}
bool ethernet_frame::check(void){return fcs==get_fcs(tobuffer());}

ARP 的结构体和 main 函数相当好写,远小于上一题难度了。比葫芦画瓢就好,真没啥可说的。注意一下构造 Response 时到底应该填什么 IP 或 MAC 就可以。

struct arp{
    uint16_t hardware_type; // 总是 1
    uint16_t protocol_type; // 总是 0x0800
    uint8_t hlen; // 总是 0x6
    uint8_t plen; // 总是 0x4
    enum class OPCode:uint16_t{request=1,response=2} opcode;
    std::array<uint8_t,6> sha;
    std::array<uint8_t,4> spa;
    std::array<uint8_t,6> tha;
    std::array<uint8_t,4> tpa;

    struct OPCodeNotRequest:UnknownTargetError{};
    struct IPNotFound:UnknownTargetError{};

    arp(OPCode _type,std::array<uint8_t,6> const& _sha,std::array<uint8_t,4> const& _spa,std::array<uint8_t,6> const& _tha,std::array<uint8_t,4> const& _tpa):
        hardware_type(1),protocol_type(0x0800),hlen(6),plen(4),
        opcode(_type),sha(_sha),spa(_spa),tha(_tha),tpa(_tpa){}
    arp(ethernet_frame const& f):arp(f.data.begin()){}
    arp(std::vector<uint8_t>::const_iterator beg):
        hardware_type((beg[0]<<8)+(beg[1]<<0)),
        protocol_type((beg[2]<<8)+(beg[3]<<0)),
        hlen(beg[4]),plen(beg[5]),
        opcode(OPCode((beg[6]<<8)+(beg[7]<<0))),
        sha{beg[8],beg[9],beg[10],beg[11],beg[12],beg[13]},
        spa{beg[14],beg[15],beg[16],beg[17]},
        tha{beg[18],beg[19],beg[20],beg[21],beg[22],beg[23]},
        tpa{beg[24],beg[25],beg[26],beg[27]}{
        assert(hardware_type==1&&protocol_type==0x0800&&hlen==6&&plen==4);
    }
    std::vector<uint8_t> tobuffer(void){
        return {
            uint8_t(hardware_type>>8),uint8_t(hardware_type>>0),
            uint8_t(protocol_type>>8),uint8_t(protocol_type>>0),
            hlen,plen,uint8_t((uint16_t)opcode>>8),uint8_t((uint16_t)opcode>>0),
            sha[0],sha[1],sha[2],sha[3],
            sha[4],sha[5],spa[0],spa[1],
            spa[2],spa[3],tha[0],tha[1],
            tha[2],tha[3],tha[4],tha[5],
            tpa[0],tpa[1],tpa[2],tpa[3]
        };
    }
    arp get_response(void){
        if (opcode!=OPCode::request) throw OPCodeNotRequest();
        auto it=eths.find(tpa);
        if (it==eths.end()) throw IPNotFound();
        return arp(OPCode::response,it->second,it->first,sha,spa);
    };
};
int main(void){
    std::ios::sync_with_stdio(false),std::cin.tie(nullptr),std::cout.tie(nullptr);
    std::vector<uint8_t> buf((std::istreambuf_iterator<char>(fin)),std::istreambuf_iterator<char>());
    pcap f(buf.begin(),buf.end()),g;
    for (auto const& i:f.data)
        try{
            ethernet_frame e(i);
            if (e.etherType!=ethernet_frame::EtherType::arp) continue;
            arp s(e);
            arp t(s.get_response());
            ethernet_frame f(
                t.tha,t.sha,ethernet_frame::EtherType::arp,
                t.tobuffer()
            );
            g.data.emplace_back(f.tobuffer());
        }catch (Throwable const&){} // 忽略校验错误、发送者
    fout<<g;
}

代码 共 331 行。吸氧和不吸氧差别还挺大的。 别贺了没啥意思……

posted @ 2024-04-12 10:44  MrPython  阅读(12)  评论(0)    收藏  举报  来源