CCF 202104-3 DHCP服务器


------------恢复内容开始------------

报文格式

为了便于实现,我们简化地规定 DHCP 数据报文的格式如下:

<发送主机> <接收主机> <报文类型>  <过期时刻>

DHCP 数据报文的各个部分由空格分隔,其各个部分的定义如下:

  • 发送主机:是发送报文的主机名,主机名是由小写字母、数字组成的字符串,唯一地表示了一个主机;
  • 接收主机:当有特定的接收主机时,是接收报文的主机名;当没有特定的接收主机时,为一个星号(*);
  • 报文类型:是三个大写字母,取值如下:
    • DIS:表示 Discover 报文;
    • OFR:表示 Offer 报文;
    • REQ:表示 Request 报文;
    • ACK:表示 Ack 报文;
    • NAK:表示 Nak 报文;
  • IP 地址,是一个非负整数:
    • 对于 Discover 报文,该部分在发送的时候为 0,在接收的时候忽略;
    • 对于其它报文,为正整数,表示一个 IP 地址;
  • 过期时刻,是一个非负整数:
    • 对于 Offer、Ack 报文,是一个正整数,表示服务器授予客户端的 IP 地址的过期时刻;
    • 对于 Discover、Request 报文,若为正整数,表示客户端期望服务器授予的过期时刻;
    • 对于其它报文,该部分在发送的时候为 0,在接收的时候忽略。

服务器配置

为了 DHCP 服务器能够正确分配 IP 地址,DHCP 需要接受如下配置:

  • 地址池大小

:表示能够分配给客户端的 IP 地址的数目,且能分配的 IP 地址是 、

  • :表示运行 DHCP 服务器的主机名。

分配策略

当客户端请求 IP 地址时,首先检查此前是否给该客户端分配过 IP 地址,且该 IP 地址在此后没有被分配给其它客户端。如果是这样的情况,则直接将 IP 地址分配给它,否则,
总是分配给它最小的尚未占用过的那个 IP 地址。如果这样的地址不存在,则分配给它最小的此时未被占用的那个 IP 地址。如果这样的地址也不存在,说明地址池已经分配完毕,因此拒绝分配地址。

实现细节

在 DHCP 启动时,首先初始化 IP 地址池,将所有地址设置状态为未分配,占用者为空,并清零过期时刻。
其中地址的状态有未分配、待分配、占用、过期四种。
处于未分配状态的 IP 地址没有占用者,而其余三种状态的 IP 地址均有一名占用者。
处于待分配和占用状态的 IP 地址拥有一个大于零的过期时刻。在到达该过期时刻时,若该地址的状态是待分配,则该地址的状态会自动变为未分配,且占用者清空,过期时刻清零;否则该地址的状态会由占用自动变为过期,且过期时刻清零。处于未分配和过期状态的 IP 地址过期时刻为零,即没有过期时刻。

对于收到的报文,设其收到的时刻为

。处理细节如下:

  1. 判断接收主机是否为本机,或者为 *,若不是,则判断类型是否为 Request,若不是,则不处理;
  2. 若类型不是 Discover、Request 之一,则不处理;
  3. 若接收主机为 *,但类型不是 Discover,或接收主机是本机,但类型是 Discover,则不处理。

对于 Discover 报文,按照下述方法处理:

  1. 检查是否有占用者为发送主机的 IP 地址:
    • 若有,则选取该 IP 地址;
    • 若没有,则选取最小的状态为未分配的 IP 地址;
    • 若没有,则选取最小的状态为过期的 IP 地址;
    • 若没有,则不处理该报文,处理结束;
  2. 将该 IP 地址状态设置为待分配,占用者设置为发送主机;
  3. 若报文中过期时刻为 0 ,则设置过期时刻为
  1. ;否则根据报文中的过期时刻和收到报文的时刻计算过期时间,判断是否超过上下限:若没有超过,则设置过期时刻为报文中的过期时刻;否则则根据超限情况设置为允许的最早或最晚的过期时刻;
  2. 向发送主机发送 Offer 报文,其中,IP 地址为选定的 IP 地址,过期时刻为所设定的过期时刻。

对于 Request 报文,按照下述方法处理:

  1. 检查接收主机是否为本机:
    • 若不是,则找到占用者为发送主机的所有 IP 地址,对于其中状态为待分配的,将其状态设置为未分配,并清空其占用者,清零其过期时刻,处理结束;
  2. 检查报文中的 IP 地址是否在地址池内,且其占用者为发送主机,若不是,则向发送主机发送 Nak 报文,处理结束;
  3. 无论该 IP 地址的状态为何,将该 IP 地址的状态设置为占用;
  4. 与 Discover 报文相同的方法,设置 IP 地址的过期时刻;
  5. 向发送主机发送 Ack 报文。

  这题本身没有太大的难度,主要困难在于静下心来读题。

  注意:每次处理报文时要先更新IP的状态。

  1 #include 
  2 #include 
  3 #include <string>
  4 using namespace std;
  5 int t[10010];//接收到报文的时刻
  6 int N, T_def, T_max, T_min;
  7 string H = "";//主机名
  8 void settings()
  9 {
 10     char tmp;
 11 
 12     while ((tmp = getchar()) != ' ')
 13     {
 14         N *= 10;
 15         N += (tmp - '0');
 16     }
 17 
 18     while ((tmp = getchar()) != ' ')
 19     {
 20         T_def *= 10;
 21         T_def += (tmp - '0');
 22     }
 23 
 24     while ((tmp = getchar()) != ' ')
 25     {
 26         T_max *= 10;
 27         T_max += (tmp - '0');
 28     }
 29 
 30     while ((tmp = getchar()) != ' ')
 31     {
 32         T_min *= 10;
 33         T_min += (tmp - '0');
 34     }
 35 
 36     
 37     while ((tmp = getchar()) != '\n')
 38     {
 39         //printf("%d\n", H.size());
 40         H = H + tmp;
 41     }
 42 }
 43 
 44 int cnt;
 45 string S_H, R_H, kind;//发送主机、接收主机、报文类型
 46 int overdue_T;
 47 int IP;
 48 int overdue_IP[10010];//某IP地址的过期时刻
 49 int state[10010];//IP状态    未分配、待分配、占用、过期————0、1、2、3
 50 string occupation[10010];//IP占用者
 51 
 52 void get_message()//接收报文
 53 {
 54     char tmp;
 55 
 56     //接收时刻
 57     while ((tmp = getchar()) != ' ')
 58     {
 59         t[cnt] *= 10;
 60         t[cnt] += (tmp - '0');
 61     }
 62 
 63     //发送主机
 64     S_H = "";
 65     while ((tmp = getchar()) != ' ')
 66     {
 67         S_H += tmp;
 68     }
 69 
 70     //接收主机
 71     R_H = "";
 72     while ((tmp = getchar()) != ' ')
 73     {
 74         R_H += tmp;
 75     }
 76 
 77     //报文类型
 78     kind = "";
 79     while ((tmp = getchar()) != ' ')
 80     {
 81         kind += tmp;
 82     }
 83 
 84     //IP
 85     IP = 0;
 86     while ((tmp = getchar()) != ' ')
 87     {
 88         IP *= 10;
 89         IP += (tmp - '0');
 90     }
 91 
 92     //过期时刻
 93     overdue_T = 0;
 94     while ((tmp = getchar()) != '\n')
 95     {
 96         overdue_T *= 10;
 97         overdue_T += (tmp - '0');
 98     }
 99 
100     //printf("%d %s %s %s %d %d\n", t[cnt], S_H.c_str(), R_H.c_str(), kind.c_str(), IP, overdue_T[cnt]);
101 }
102 
103 //更新IP的状态
104 void update()
105 {
106     for (int i = 1; i <= N; i++)
107     {
108         if (state[i] == 1 && overdue_IP[i] <= t[cnt])
109         {
110             state[i] = 0;
111             overdue_IP[i] = 0;
112             occupation[i] = "";
113         }
114         else if (state[i] == 2 && overdue_IP[i] <= t[cnt])
115         {
116             state[i] = 3;
117             overdue_IP[i] = 0;
118         }
119     }
120 }
121 void discover()
122 {
123     update();
124     IP = 0;
125     int IP_0 = 0, IP_3 = 0;//最小未分配、最小过期
126     for (int i = 1; i <= N; i++)
127     {
128         if (occupation[i] == S_H)
129         {
130             IP = i;
131             break;
132         }
133         if (!IP_0 && state[i] == 0)
134             IP_0 = i;
135         if (!IP_3 && state[i] == 3)
136             IP_3 = i;
137     }
138     if (!IP)
139     {
140         if (!IP_0 && !IP_3)
141             return;//不处理该报文
142         if (IP_0)
143             IP = IP_0;
144         else
145             IP = IP_3;
146     }
147 
148     state[IP] = 1;
149     occupation[IP] = S_H;
150 
151     if (!overdue_T)
152         overdue_IP[IP] = t[cnt] + T_def;
153     else
154     {
155         if (overdue_T > t[cnt] + T_max)
156             overdue_IP[IP] = t[cnt] + T_max;
157         else if (overdue_T < t[cnt] + T_min)
158             overdue_IP[IP] = t[cnt] + T_min;
159         else
160             overdue_IP[IP] = overdue_T;
161     }
162     
163     //向发送主机发送 Offer 报文
164     cout << H << " " << S_H << " OFR " << IP << " " << overdue_IP[IP] << endl;
165 }
166 
167 void request()
168 {
169     update();
170     if (R_H != H)
171     {
172         for (int i = 1; i <= N; i++)
173         {
174             if (occupation[i] == S_H && state[i] == 1)//找到占用者为发送主机的所有 IP 地址
175             {
176                 //对于其中状态为待分配的,将其状态设置为未分配,并清空其占用者,清零其过期时刻
177                 state[i] = 0;
178                 occupation[i] = "";
179                 overdue_IP[i] = 0;
180             }
181         }
182         return;
183     }
184     if (IP <= N && occupation[IP] == S_H)//检查报文中的 IP 地址是否在地址池内,且其占用者为发送主机
185     {
186         state[IP] = 2;
187 
188         if (!overdue_T)
189             overdue_IP[IP] = t[cnt] + T_def;
190         else
191         {
192             if (overdue_T > t[cnt] + T_max)
193                 overdue_IP[IP] = t[cnt] + T_max;
194             else if (overdue_T < t[cnt] + T_min)
195                 overdue_IP[IP] = t[cnt] + T_min;
196             else
197                 overdue_IP[IP] = overdue_T;
198         }
199         //向发送主机发送 Ack 报文
200         cout << H << " " << S_H << " ACK " << IP << " " << overdue_IP[IP] << endl;
201     }
202     else
203     {
204         //cout << overdue_T << ' ' << overdue_IP[IP] << endl;
205         //向发送主机发送 Nak 报文
206         cout << H << " " << S_H << " NAK " << IP << " 0" << endl;
207     }
208 }
209 
210 int main()
211 {
212     //printf("%s", H.c_str());
213     int n;
214     settings();
215     //printf("%d %d %d %d %s\n", N, T_def, T_max, T_min, H.c_str());
216     cin >> n;
217     getchar();
218     
219     for (cnt = 1; cnt <= n; cnt ++)
220     {
221         get_message();
222         if (R_H != H && R_H != "*" && kind != "REQ" || R_H == "*" && kind != "DIS" || R_H == H && kind == "DIS")
223             continue;
224         if (kind == "DIS")
225             discover();
226         else if(kind == "REQ")
227             request();
228     }
229     return 0;
230 }

相关