001 // vim: set ft=c:
002 
003 #include "::/Adam/Net/Socket"
004 
005 #define BOOTREQUEST             0x01
006 #define BOOTREPLY               0x02
007 
008 #define HTYPE_ETHERNET          0x01
009 
010 #define HLEN_ETHERNET           6
011 
012 #define DHCP_OPTION_SUBNET_MASK   1
013 #define DHCP_OPTION_ROUTER        3
014 #define DHCP_OPTION_DNS           6
015 #define DHCP_OPTION_DOMAIN_NAME   15
016 #define DHCP_OPTION_REQUESTED_IP  50
017 #define DHCP_OPTION_MSGTYPE       53
018 #define DHCP_OPTION_SERVER_ID     54
019 #define DHCP_OPTION_PARAMLIST     55
020 
021 #define DHCP_COOKIE             0x63825363
022 #define DHCP_MSGTYPE_DISCOVER   0x01
023 #define DHCP_MSGTYPE_OFFER      0x02
024 #define DHCP_MSGTYPE_REQUEST    0x03
025 #define DHCP_MSGTYPE_ACK        0x05
026 
027 class CDhcpHeader {
028   U8 op;
029   U8 htype;
030   U8 hlen;
031   U8 hops;
032   U32 xid;
033   U16 secs;
034   U16 flags;
035   U32 ciaddr;
036   U32 yiaddr;
037   U32 siaddr;
038   U32 giaddr;
039   U8 chaddr[16];
040   U8 sname[64];
041   U8 file[128];
042 };
043 
044 class CDhcpDiscoverOptions {
045   U32 cookie;
046   // DHCP Message Type
047   U8 dmt_type;
048   U8 dmt_length;
049   U8 dmt;
050   // DHCP Parameter Request List
051   U8 prl_type;
052   U8 prl_length;
053   U8 prl[4];
054 
055   U8 end;
056 };
057 
058 class CDhcpRequestOptions {
059   U32 cookie;
060   // DHCP Message Type
061   U8 dmt_type;
062   U8 dmt_length;
063   U8 dmt;
064   // DHCP Requested IP
065   U8 requested_ip_type;
066   U8 requested_ip_length;
067   U32 requested_ip;
068   // DHCP Server Identifier
069   U8 server_id_type;
070   U8 server_id_length;
071   U32 server_id;
072 
073   U8 end;
074 };
075 
076 U32 DhcpBeginTransaction() {
077   return RandU32();
078 }
079 
080 I64 DhcpSendDiscover(U32 xid) {
081   U8* frame;
082   I64 index = UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67,
083       sizeof(CDhcpHeader) + sizeof(CDhcpDiscoverOptions));
084 
085   if (index < 0)
086     return index;
087 
088   CDhcpHeader* dhcp = frame;
089   MemSet(dhcp, 0, sizeof(CDhcpHeader));
090   dhcp->op = BOOTREQUEST;
091   dhcp->htype = HTYPE_ETHERNET;
092   dhcp->hlen = HLEN_ETHERNET;
093   dhcp->hops = 0;
094   dhcp->xid = htonl(xid);
095   dhcp->secs = 0;
096   dhcp->flags = htons(0x8000);
097   dhcp->ciaddr = 0;
098   dhcp->yiaddr = 0;
099   dhcp->siaddr = 0;
100   dhcp->giaddr = 0;
101   MemCpy(dhcp->chaddr, EthernetGetAddress(), 6);
102 
103   CDhcpDiscoverOptions* opts = frame + sizeof(CDhcpHeader);
104   opts->cookie = htonl(DHCP_COOKIE);
105   opts->dmt_type = DHCP_OPTION_MSGTYPE;
106   opts->dmt_length = 1;
107   opts->dmt = DHCP_MSGTYPE_DISCOVER;
108   opts->prl_type = DHCP_OPTION_PARAMLIST;
109   opts->prl_length = 4;
110   opts->prl[0] = DHCP_OPTION_SUBNET_MASK;
111   opts->prl[1] = DHCP_OPTION_ROUTER;
112   opts->prl[2] = DHCP_OPTION_DNS;
113   opts->prl[3] = DHCP_OPTION_DOMAIN_NAME;
114   opts->end = 0xff;
115 
116   return UdpPacketFinish(index);
117 }
118 
119 I64 DhcpSendRequest(U32 xid, U32 requested_ip, U32 siaddr) {
120   U8* frame;
121   I64 index = UdpPacketAlloc(&frame, 0x00000000, 68, 0xffffffff, 67,
122       sizeof(CDhcpHeader) + sizeof(CDhcpRequestOptions));
123 
124   if (index < 0)
125     return index;
126 
127   CDhcpHeader* dhcp = frame;
128   MemSet(dhcp, 0, sizeof(CDhcpHeader));
129   dhcp->op = BOOTREQUEST;
130   dhcp->htype = HTYPE_ETHERNET;
131   dhcp->hlen = HLEN_ETHERNET;
132   dhcp->hops = 0;
133   dhcp->xid = htonl(xid);
134   dhcp->secs = 0;
135   dhcp->flags = htons(0x0000);
136   dhcp->ciaddr = 0;
137   dhcp->yiaddr = 0;
138   dhcp->siaddr = htonl(siaddr);
139   dhcp->giaddr = 0;
140   MemCpy(dhcp->chaddr, EthernetGetAddress(), 6);
141 
142   CDhcpRequestOptions* opts = frame + sizeof(CDhcpHeader);
143   opts->cookie = htonl(DHCP_COOKIE);
144   opts->dmt_type = DHCP_OPTION_MSGTYPE;
145   opts->dmt_length = 1;
146   opts->dmt = DHCP_MSGTYPE_REQUEST;
147   opts->requested_ip_type = DHCP_OPTION_REQUESTED_IP;
148   opts->requested_ip_length = 4;
149   opts->requested_ip = htonl(requested_ip);
150   opts->server_id_type = DHCP_OPTION_SERVER_ID;
151   opts->server_id_length = 4;
152   opts->server_id = htonl(siaddr);
153   opts->end = 0xff;
154 
155   return UdpPacketFinish(index);
156 }
157 
158 I64 DhcpParseBegin(U8** data_inout, I64* length_inout, CDhcpHeader** hdr_out) {
159   U8* data = *data_inout;
160   I64 length = *length_inout;
161 
162   if (length < sizeof(CDhcpHeader) + 4) {
163     //"DhcpParseBegin: too short\n";
164     return -1;
165   }
166 
167   U32* p_cookie = data + sizeof(CDhcpHeader);
168 
169   if (ntohl(*p_cookie) != DHCP_COOKIE) {
170     //"DhcpParseBegin: cookie %08Xh != %08Xh\n", ntohl(*p_cookie), DHCP_COOKIE;
171     return -1;
172   }
173 
174   *hdr_out = data;
175   *data_inout = data + (sizeof(CDhcpHeader) + 4);
176   *length_inout = length - (sizeof(CDhcpHeader) + 4);
177   return 0;
178 }
179 
180 I64 DhcpParseOption(U8** data_inout, I64* length_inout, U8* type_out, U8* value_length_out, U8** value_out) {
181   U8* data = *data_inout;
182   I64 length = *length_inout;
183 
184   if (length < 2 || length < 2 + data[1]) {
185     //"DhcpParseOption: too short\n";
186     return -1;
187   }
188 
189   if (data[0] == 0xff)
190     return 0;
191 
192   *type_out = data[0];
193   *value_length_out = data[1];
194   *value_out = data + 2;
195 
196   *data_inout = data + (2 + *value_length_out);
197   *length_inout = length - (2 + *value_length_out);
198   return data[0];
199 }
200 
201 I64 DhcpParseOffer(U32 xid, U8* data, I64 length, U32* yiaddr_out,
202     U32* dns_ip_out, U32* router_ip_out, U32* subnet_mask_out) {
203   CDhcpHeader* hdr;
204   I64 error = DhcpParseBegin(&data, &length, &hdr);
205   if (error < 0) return error;
206 
207   if (ntohl(hdr->xid) != xid)
208     return -1;
209 
210   Bool have_type = FALSE;
211   Bool have_dns = FALSE;
212   Bool have_router = FALSE;
213   Bool have_subnet = FALSE;
214 
215   while (length) {
216     U8 type, value_length;
217     U8* value;
218 
219     error = DhcpParseOption(&data, &length, &type, &value_length, &value);
220     //"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0];
221     if (error < 0) return error;
222     if (error == 0) break;
223 
224     if (type == DHCP_OPTION_MSGTYPE && value_length == 1 && value[0] == DHCP_MSGTYPE_OFFER)
225       have_type = TRUE;
226 
227     if (type == DHCP_OPTION_DNS && value_length == 4) {
228       *dns_ip_out = ntohl(*(value(U32*)));
229       have_dns = TRUE;
230     }
231 
232     if (type == DHCP_OPTION_ROUTER && value_length == 4) {
233       *router_ip_out = ntohl(*(value(U32*)));
234       have_router = TRUE;
235     }
236 
237     if (type == DHCP_OPTION_SUBNET_MASK && value_length == 4) {
238       *subnet_mask_out = ntohl(*(value(U32*)));
239       have_subnet = TRUE;
240     }
241   }
242 
243   //"DhcpParseOffer: end %d %d %d %d\n", have_type, have_dns, have_subnet, have_router;
244 
245   if (have_type && have_dns && have_subnet && have_router) {
246     *yiaddr_out = ntohl(hdr->yiaddr);
247     return 0;
248   }
249   else
250     return -1;
251 }
252 
253 I64 DhcpParseAck(U32 xid, U8* data, I64 length) {
254   CDhcpHeader* hdr;
255   I64 error = DhcpParseBegin(&data, &length, &hdr);
256   if (error < 0) return error;
257 
258   if (ntohl(hdr->xid) != xid)
259     return -1;
260 
261   while (length) {
262     U8 type, value_length;
263     U8* value;
264 
265     error = DhcpParseOption(&data, &length, &type, &value_length, &value);
266     //"%d, %02Xh, %d, %02Xh...\n", error, type, value_length, value[0];
267     if (error < 0) return error;
268     if (error == 0) break;
269 
270     if (type == DHCP_OPTION_MSGTYPE && value_length == 1 && value[0] == DHCP_MSGTYPE_ACK)
271       return 0;
272   }
273 
274   return -1;
275 }