Browse Source

Add WiFi AP mode

This allows to choose, when running cmake, whether the bootloader will connect to an existing AP (default behavior)
or fire up its own (by passing -DPICOWOTA_WIFI_AP=1) by using the same provided credentials.
DHCP requests are handled, with these settings the subnet is 192.168.4.1/24.
Tomi 1 year ago
parent
commit
d39bd485ad
5 changed files with 409 additions and 5 deletions
  1. 10
    2
      CMakeLists.txt
  2. 21
    0
      dhcpserver/LICENSE
  3. 300
    0
      dhcpserver/dhcpserver.c
  4. 49
    0
      dhcpserver/dhcpserver.h
  5. 29
    3
      main.c

+ 10
- 2
CMakeLists.txt View File

@@ -36,6 +36,7 @@ pico_sdk_init()
36 36
 add_executable(picowota
37 37
 	main.c
38 38
 	tcp_comm.c
39
+	dhcpserver/dhcpserver.c
39 40
 )
40 41
 
41 42
 function(target_cl_options option)
@@ -51,8 +52,9 @@ target_link_options(picowota PRIVATE "LINKER:--gc-sections")
51 52
 
52 53
 pico_add_extra_outputs(picowota)
53 54
 
54
-# Needed so that lwip can find lwipopts.h
55
-target_include_directories(picowota PRIVATE ${CMAKE_CURRENT_LIST_DIR})
55
+target_include_directories(picowota PRIVATE
56
+	${CMAKE_CURRENT_LIST_DIR} # Needed so that lwip can find lwipopts.h
57
+	${CMAKE_CURRENT_LIST_DIR}/dhcpserver)
56 58
 
57 59
 pico_enable_stdio_usb(picowota 1)
58 60
 
@@ -92,6 +94,12 @@ endif ()
92 94
 target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_SSID=${PICOWOTA_WIFI_SSID})
93 95
 target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_PASS=${PICOWOTA_WIFI_PASS})
94 96
 
97
+# Use the WiFi AP mode upon request
98
+if (PICOWOTA_WIFI_AP)
99
+	target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_AP=1)
100
+	message("Building in WiFi AP mode.")
101
+endif()
102
+
95 103
 # Provide a helper to build a standalone target
96 104
 function(picowota_build_standalone NAME)
97 105
 	get_target_property(PICOWOTA_SRC_DIR picowota SOURCE_DIR)

+ 21
- 0
dhcpserver/LICENSE View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2013-2022 Damien P. George
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+THE SOFTWARE.

+ 300
- 0
dhcpserver/dhcpserver.c View File

@@ -0,0 +1,300 @@
1
+/*
2
+ * This file is part of the MicroPython project, http://micropython.org/
3
+ *
4
+ * The MIT License (MIT)
5
+ *
6
+ * Copyright (c) 2018-2019 Damien P. George
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+
27
+// For DHCP specs see:
28
+//  https://www.ietf.org/rfc/rfc2131.txt
29
+//  https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions
30
+
31
+#include <stdio.h>
32
+#include <string.h>
33
+#include <errno.h>
34
+
35
+#include "cyw43_config.h"
36
+#include "dhcpserver.h"
37
+#include "lwip/udp.h"
38
+
39
+#define DHCPDISCOVER    (1)
40
+#define DHCPOFFER       (2)
41
+#define DHCPREQUEST     (3)
42
+#define DHCPDECLINE     (4)
43
+#define DHCPACK         (5)
44
+#define DHCPNACK        (6)
45
+#define DHCPRELEASE     (7)
46
+#define DHCPINFORM      (8)
47
+
48
+#define DHCP_OPT_PAD                (0)
49
+#define DHCP_OPT_SUBNET_MASK        (1)
50
+#define DHCP_OPT_ROUTER             (3)
51
+#define DHCP_OPT_DNS                (6)
52
+#define DHCP_OPT_HOST_NAME          (12)
53
+#define DHCP_OPT_REQUESTED_IP       (50)
54
+#define DHCP_OPT_IP_LEASE_TIME      (51)
55
+#define DHCP_OPT_MSG_TYPE           (53)
56
+#define DHCP_OPT_SERVER_ID          (54)
57
+#define DHCP_OPT_PARAM_REQUEST_LIST (55)
58
+#define DHCP_OPT_MAX_MSG_SIZE       (57)
59
+#define DHCP_OPT_VENDOR_CLASS_ID    (60)
60
+#define DHCP_OPT_CLIENT_ID          (61)
61
+#define DHCP_OPT_END                (255)
62
+
63
+#define PORT_DHCP_SERVER (67)
64
+#define PORT_DHCP_CLIENT (68)
65
+
66
+#define DEFAULT_DNS MAKE_IP4(8, 8, 8, 8)
67
+#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds
68
+
69
+#define MAC_LEN (6)
70
+#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
71
+
72
+typedef struct {
73
+    uint8_t op; // message opcode
74
+    uint8_t htype; // hardware address type
75
+    uint8_t hlen; // hardware address length
76
+    uint8_t hops;
77
+    uint32_t xid; // transaction id, chosen by client
78
+    uint16_t secs; // client seconds elapsed
79
+    uint16_t flags;
80
+    uint8_t ciaddr[4]; // client IP address
81
+    uint8_t yiaddr[4]; // your IP address
82
+    uint8_t siaddr[4]; // next server IP address
83
+    uint8_t giaddr[4]; // relay agent IP address
84
+    uint8_t chaddr[16]; // client hardware address
85
+    uint8_t sname[64]; // server host name
86
+    uint8_t file[128]; // boot file name
87
+    uint8_t options[312]; // optional parameters, variable, starts with magic
88
+} dhcp_msg_t;
89
+
90
+static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) {
91
+    // family is AF_INET
92
+    // type is SOCK_DGRAM
93
+
94
+    *udp = udp_new();
95
+    if (*udp == NULL) {
96
+        return -ENOMEM;
97
+    }
98
+
99
+    // Register callback
100
+    udp_recv(*udp, cb_udp_recv, (void *)cb_data);
101
+
102
+    return 0; // success
103
+}
104
+
105
+static void dhcp_socket_free(struct udp_pcb **udp) {
106
+    if (*udp != NULL) {
107
+        udp_remove(*udp);
108
+        *udp = NULL;
109
+    }
110
+}
111
+
112
+static int dhcp_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) {
113
+    ip_addr_t addr;
114
+    IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff);
115
+    // TODO convert lwIP errors to errno
116
+    return udp_bind(*udp, &addr, port);
117
+}
118
+
119
+static int dhcp_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, uint32_t ip, uint16_t port) {
120
+    if (len > 0xffff) {
121
+        len = 0xffff;
122
+    }
123
+
124
+    struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
125
+    if (p == NULL) {
126
+        return -ENOMEM;
127
+    }
128
+
129
+    memcpy(p->payload, buf, len);
130
+
131
+    ip_addr_t dest;
132
+    IP4_ADDR(&dest, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff);
133
+    err_t err = udp_sendto(*udp, p, &dest, port);
134
+
135
+    pbuf_free(p);
136
+
137
+    if (err != ERR_OK) {
138
+        return err;
139
+    }
140
+
141
+    return len;
142
+}
143
+
144
+static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) {
145
+    for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) {
146
+        if (opt[i] == cmd) {
147
+            return &opt[i];
148
+        }
149
+        i += 2 + opt[i + 1];
150
+    }
151
+    return NULL;
152
+}
153
+
154
+static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, void *data) {
155
+    uint8_t *o = *opt;
156
+    *o++ = cmd;
157
+    *o++ = n;
158
+    memcpy(o, data, n);
159
+    *opt = o + n;
160
+}
161
+
162
+static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) {
163
+    uint8_t *o = *opt;
164
+    *o++ = cmd;
165
+    *o++ = 1;
166
+    *o++ = val;
167
+    *opt = o;
168
+}
169
+
170
+static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) {
171
+    uint8_t *o = *opt;
172
+    *o++ = cmd;
173
+    *o++ = 4;
174
+    *o++ = val >> 24;
175
+    *o++ = val >> 16;
176
+    *o++ = val >> 8;
177
+    *o++ = val;
178
+    *opt = o;
179
+}
180
+
181
+static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) {
182
+    dhcp_server_t *d = arg;
183
+    (void)upcb;
184
+    (void)src_addr;
185
+    (void)src_port;
186
+
187
+    // This is around 548 bytes
188
+    dhcp_msg_t dhcp_msg;
189
+
190
+    #define DHCP_MIN_SIZE (240 + 3)
191
+    if (p->tot_len < DHCP_MIN_SIZE) {
192
+        goto ignore_request;
193
+    }
194
+
195
+    size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0);
196
+    if (len < DHCP_MIN_SIZE) {
197
+        goto ignore_request;
198
+    }
199
+
200
+    dhcp_msg.op = DHCPOFFER;
201
+    memcpy(&dhcp_msg.yiaddr, &d->ip.addr, 4);
202
+
203
+    uint8_t *opt = (uint8_t *)&dhcp_msg.options;
204
+    opt += 4; // assume magic cookie: 99, 130, 83, 99
205
+
206
+    switch (opt[2]) {
207
+        case DHCPDISCOVER: {
208
+            int yi = DHCPS_MAX_IP;
209
+            for (int i = 0; i < DHCPS_MAX_IP; ++i) {
210
+                if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) {
211
+                    // MAC match, use this IP address
212
+                    yi = i;
213
+                    break;
214
+                }
215
+                if (yi == DHCPS_MAX_IP) {
216
+                    // Look for a free IP address
217
+                    if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) {
218
+                        // IP available
219
+                        yi = i;
220
+                    }
221
+                    uint32_t expiry = d->lease[i].expiry << 16 | 0xffff;
222
+                    if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) {
223
+                        // IP expired, reuse it
224
+                        memset(d->lease[i].mac, 0, MAC_LEN);
225
+                        yi = i;
226
+                    }
227
+                }
228
+            }
229
+            if (yi == DHCPS_MAX_IP) {
230
+                // No more IP addresses left
231
+                goto ignore_request;
232
+            }
233
+            dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi;
234
+            opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER);
235
+            break;
236
+        }
237
+
238
+        case DHCPREQUEST: {
239
+            uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP);
240
+            if (o == NULL) {
241
+                // Should be NACK
242
+                goto ignore_request;
243
+            }
244
+            if (memcmp(o + 2, &d->ip.addr, 3) != 0) {
245
+                // Should be NACK
246
+                goto ignore_request;
247
+            }
248
+            uint8_t yi = o[5] - DHCPS_BASE_IP;
249
+            if (yi >= DHCPS_MAX_IP) {
250
+                // Should be NACK
251
+                goto ignore_request;
252
+            }
253
+            if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) {
254
+                // MAC match, ok to use this IP address
255
+            } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) {
256
+                // IP unused, ok to use this IP address
257
+                memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN);
258
+            } else {
259
+                // IP already in use
260
+                // Should be NACK
261
+                goto ignore_request;
262
+            }
263
+            d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16;
264
+            dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi;
265
+            opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK);
266
+            printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n",
267
+                dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5],
268
+                dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]);
269
+            break;
270
+        }
271
+
272
+        default:
273
+            goto ignore_request;
274
+    }
275
+
276
+    opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &d->ip.addr);
277
+    opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &d->nm.addr);
278
+    opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &d->ip.addr); // aka gateway; can have mulitple addresses
279
+    opt_write_u32(&opt, DHCP_OPT_DNS, DEFAULT_DNS); // can have mulitple addresses
280
+    opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S);
281
+    *opt++ = DHCP_OPT_END;
282
+    dhcp_socket_sendto(&d->udp, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT);
283
+
284
+ignore_request:
285
+    pbuf_free(p);
286
+}
287
+
288
+void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) {
289
+    ip_addr_copy(d->ip, *ip);
290
+    ip_addr_copy(d->nm, *nm);
291
+    memset(d->lease, 0, sizeof(d->lease));
292
+    if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) {
293
+        return;
294
+    }
295
+    dhcp_socket_bind(&d->udp, 0, PORT_DHCP_SERVER);
296
+}
297
+
298
+void dhcp_server_deinit(dhcp_server_t *d) {
299
+    dhcp_socket_free(&d->udp);
300
+}

+ 49
- 0
dhcpserver/dhcpserver.h View File

@@ -0,0 +1,49 @@
1
+/*
2
+ * This file is part of the MicroPython project, http://micropython.org/
3
+ *
4
+ * The MIT License (MIT)
5
+ *
6
+ * Copyright (c) 2018-2019 Damien P. George
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+#ifndef MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H
27
+#define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H
28
+
29
+#include "lwip/ip_addr.h"
30
+
31
+#define DHCPS_BASE_IP (16)
32
+#define DHCPS_MAX_IP (8)
33
+
34
+typedef struct _dhcp_server_lease_t {
35
+    uint8_t mac[6];
36
+    uint16_t expiry;
37
+} dhcp_server_lease_t;
38
+
39
+typedef struct _dhcp_server_t {
40
+    ip_addr_t ip;
41
+    ip_addr_t nm;
42
+    dhcp_server_lease_t lease[DHCPS_MAX_IP];
43
+    struct udp_pcb *udp;
44
+} dhcp_server_t;
45
+
46
+void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm);
47
+void dhcp_server_deinit(dhcp_server_t *d);
48
+
49
+#endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H

+ 29
- 3
main.c View File

@@ -40,6 +40,11 @@
40 40
 #define DBG_PRINTF(...) { }
41 41
 #endif
42 42
 
43
+#if PICOWOTA_WIFI_AP == 1
44
+#include "dhcpserver.h"
45
+static dhcp_server_t dhcp_server;
46
+#endif
47
+
43 48
 #define QUOTE(name) #name
44 49
 #define STR(macro) QUOTE(macro)
45 50
 
@@ -557,6 +562,14 @@ static bool should_stay_in_bootloader()
557 562
 	return !gpio_get(BOOTLOADER_ENTRY_PIN) || wd_says_so;
558 563
 }
559 564
 
565
+static void network_deinit()
566
+{
567
+#if PICOWOTA_WIFI_AP == 1
568
+	dhcp_server_deinit(&dhcp_server);
569
+#endif
570
+	cyw43_arch_deinit();
571
+}
572
+
560 573
 int main()
561 574
 {
562 575
 	err_t err;
@@ -585,6 +598,18 @@ int main()
585 598
 		return 1;
586 599
 	}
587 600
 
601
+#if PICOWOTA_WIFI_AP == 1
602
+	cyw43_arch_enable_ap_mode(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK);
603
+	DBG_PRINTF("Enabled the WiFi AP.\n");
604
+
605
+	ip4_addr_t gw, mask;
606
+	IP4_ADDR(&gw, 192, 168, 4, 1);
607
+	IP4_ADDR(&mask, 255, 255, 255, 0);
608
+
609
+	dhcp_server_t dhcp_server;
610
+	dhcp_server_init(&dhcp_server, &gw, &mask);
611
+	DBG_PRINTF("Started the DHCP server.\n");
612
+#else
588 613
 	cyw43_arch_enable_sta_mode();
589 614
 
590 615
 	DBG_PRINTF("Connecting to WiFi...\n");
@@ -594,6 +619,7 @@ int main()
594 619
 	} else {
595 620
 		DBG_PRINTF("Connected.\n");
596 621
 	}
622
+#endif
597 623
 
598 624
 	critical_section_init(&critical_section);
599 625
 
@@ -629,13 +655,13 @@ int main()
629 655
 				break;
630 656
 			case EVENT_TYPE_REBOOT:
631 657
 				tcp_comm_server_close(tcp);
632
-				cyw43_arch_deinit();
658
+				network_deinit();
633 659
 				picowota_reboot(ev.reboot.to_bootloader);
634 660
 				/* Should never get here */
635 661
 				break;
636 662
 			case EVENT_TYPE_GO:
637 663
 				tcp_comm_server_close(tcp);
638
-				cyw43_arch_deinit();
664
+				network_deinit();
639 665
 				disable_interrupts();
640 666
 				reset_peripherals();
641 667
 				jump_to_vtor(ev.go.vtor);
@@ -648,6 +674,6 @@ int main()
648 674
 		sleep_ms(5);
649 675
 	}
650 676
 
651
-	cyw43_arch_deinit();
677
+	network_deinit();
652 678
 	return 0;
653 679
 }

Loading…
Cancel
Save