aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 0f903d9206dbf21b6d5bb971e37b95f39996b554 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use dhcp4d::lease::{Lease, LeaseDummyManager, LeaseManager};

use std::io;
use std::net::{SocketAddr, SocketAddrV4, UdpSocket};

use anyhow::{anyhow, bail};
use dhcproto::v4::{DhcpOption, Flags, Message, MessageType, Opcode, OptionCode};
use dhcproto::{Decodable, Decoder, Encodable, Encoder};

fn main() -> io::Result<()> {
    let sock = UdpSocket::bind("0.0.0.0:67")?;

    sock.set_broadcast(true)?;

    loop {
        let mut buf = [0; 1024];
        let (n, remote) = sock.recv_from(&mut buf)?;
        let buf = &buf[..n];

        let remote = match remote {
            SocketAddr::V4(addr) => addr,
            _ => {
                unreachable!();
            }
        };

        match handle_request(&sock, buf, remote) {
            Ok(_) => {}
            Err(e) => eprintln!("erroneous request from {}: {}", remote, e),
        }
    }
}

fn handle_request(sock: &UdpSocket, buf: &[u8], remote: SocketAddrV4) -> anyhow::Result<()> {
    let chaddr = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    let lease_mgr = LeaseDummyManager::new(None);

    let msg = Message::decode(&mut Decoder::new(buf))?;

    let op = msg.opcode();
    match op {
        Opcode::BootRequest => {
            let xid = msg.xid();
            let opts = msg.opts();

            let msg_type = opts.msg_type().ok_or(anyhow!("no message type given"))?;

            match msg_type {
                MessageType::Discover => {
                    let client_id = match opts
                        .get(OptionCode::ClientIdentifier)
                        .ok_or(anyhow!("no client id"))?
                    {
                        DhcpOption::ClientIdentifier(id) => id,
                        _ => bail!("expected ClientIdentifier"),
                    };

                    let lease = obtain_lease(lease_mgr, client_id)
                        .ok_or(anyhow!("no free addresses available"))?;

                    let mut resp = Message::default();
                    let opts = resp
                        .set_flags(Flags::default().set_broadcast())
                        .set_opcode(Opcode::BootReply)
                        .set_xid(xid)
                        .set_siaddr(lease.address)
                        .set_chaddr(chaddr)
                        .opts_mut();

                    opts.insert(DhcpOption::MessageType(MessageType::Offer));

                    let mut resp_buf = Vec::new();
                    resp.encode(&mut Encoder::new(&mut resp_buf))?;

                    let n = sock.send_to(&resp_buf, remote)?;

                    if n != resp_buf.len() {
                        Err(anyhow!("partial response"))
                    } else {
                        let cid = client_id
                            .iter()
                            .map(|octet| format!("{:x}", octet))
                            .reduce(|acc, octet| acc + &octet)
                            .ok_or(anyhow!("zero-length client id"))?;

                        println!(
                            "offering {} to client ID {} for {:?}",
                            lease.address, cid, lease.lease_time
                        );

                        Ok(())
                    }
                }
                _ => Err(anyhow!("invalid message type {:?}", msg_type,)),
            }
        }
        _ => Err(anyhow!("invalid opcode {:?}", op)),
    }
}

fn obtain_lease<T: LeaseManager>(lease_mgr: T, client_id: &[u8]) -> Option<Lease> {
    lease_mgr.persistent_free_address(client_id)
}