From eab6bc52de53a90879d8af325df0a9e3b349b2da Mon Sep 17 00:00:00 2001 From: Samuel Date: Sat, 31 Jan 2026 19:19:59 +0100 Subject: [PATCH] Code d'exemple WIFI/DHCP/ServeurWeb --- .vscode/tasks.json | 22 +-- CMakeLists.txt | 63 +++++-- dhcpserver/LICENSE | 21 +++ dhcpserver/dhcpserver.c | 309 ++++++++++++++++++++++++++++++++ dhcpserver/dhcpserver.h | 49 +++++ dnsserver/dnsserver.c | 235 ++++++++++++++++++++++++ dnsserver/dnsserver.h | 20 +++ lwipopts.h | 10 ++ lwipopts_examples_common.h | 92 ++++++++++ main.c | 33 ---- picow_access_point.c | 358 +++++++++++++++++++++++++++++++++++++ 11 files changed, 1154 insertions(+), 58 deletions(-) create mode 100644 dhcpserver/LICENSE create mode 100644 dhcpserver/dhcpserver.c create mode 100644 dhcpserver/dhcpserver.h create mode 100644 dnsserver/dnsserver.c create mode 100644 dnsserver/dnsserver.h create mode 100644 lwipopts.h create mode 100644 lwipopts_examples_common.h delete mode 100644 main.c create mode 100644 picow_access_point.c diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c98bf9b..86e9a2f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,23 +2,23 @@ "tasks": [ { "type": "shell", - "command": "cd build; cmake ../; make", - "label": "CMake in build/", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": false - } - }, - { - "type": "shell", - "command": "cd build; cmake ../; make Flash", + "command": "mkdir -p build; cd build; cmake -DPICO_BOARD=pico_w ..; make Flash", "label": "CMake & Make & Flash", "problemMatcher": [], "group": { "kind": "build", "isDefault": true } + }, + { + "type": "shell", + "command": "rm -rf build/*", + "label": "clean", + "problemMatcher": [], + "group": { + "kind":"build", + "isDefault": false + } } ], "version": "2.0.0" diff --git a/CMakeLists.txt b/CMakeLists.txt index f9d47f6..49b22f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13) include(pico_sdk_import.cmake) -project(Modele_RPiPico C CXX ASM) +project(picow_access_point_background C CXX ASM) set(CMAKE_C_STNDARD 11) set(CMAKE_CXX_STANDARD 17) @@ -10,22 +10,57 @@ set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR}) pico_sdk_init() -add_executable(Modele_RPiPico - main.c -) +add_executable(picow_access_point_background + picow_access_point.c + dhcpserver/dhcpserver.c + dnsserver/dnsserver.c + ) -target_link_libraries(Modele_RPiPico - hardware_uart - pico_stdlib - pico_multicore -) +target_include_directories(picow_access_point_background PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ${CMAKE_CURRENT_LIST_DIR}/dhcpserver + ${CMAKE_CURRENT_LIST_DIR}/dnsserver + ) -pico_enable_stdio_usb(Modele_RPiPico 1) -pico_enable_stdio_uart(Modele_RPiPico 1) +target_link_libraries(picow_access_point_background + pico_cyw43_arch_lwip_threadsafe_background + pico_stdlib + ) +# You can change the address below to change the address of the access point +pico_configure_ip4_address(picow_access_point_background PRIVATE + CYW43_DEFAULT_IP_AP_ADDRESS 192.168.4.1 + ) +pico_add_extra_outputs(picow_access_point_background) -pico_add_extra_outputs(Modele_RPiPico) +add_executable(picow_access_point_poll + picow_access_point.c + dhcpserver/dhcpserver.c + dnsserver/dnsserver.c + ) +target_include_directories(picow_access_point_poll PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ${CMAKE_CURRENT_LIST_DIR}/dhcpserver + ${CMAKE_CURRENT_LIST_DIR}/dnsserver + ) +target_link_libraries(picow_access_point_poll + pico_cyw43_arch_lwip_poll + pico_stdlib + ) +# You can change the address below to change the address of the access point +pico_configure_ip4_address(picow_access_point_poll PRIVATE + CYW43_DEFAULT_IP_AP_ADDRESS 192.168.4.1 + ) +pico_add_extra_outputs(picow_access_point_poll) + + +pico_enable_stdio_usb(picow_access_point_background 1) +pico_enable_stdio_uart(picow_access_point_background 1) + +pico_add_extra_outputs(picow_access_point_background) add_custom_target(Flash - DEPENDS Modele_RPiPico - COMMAND sudo picotool load -f ${PROJECT_BINARY_DIR}/Modele_RPiPico.uf2 + DEPENDS picow_access_point_background + COMMAND sudo picotool load -f ${PROJECT_BINARY_DIR}/picow_access_point_background.uf2 ) diff --git a/dhcpserver/LICENSE b/dhcpserver/LICENSE new file mode 100644 index 0000000..8f9b52c --- /dev/null +++ b/dhcpserver/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2022 Damien P. George + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/dhcpserver/dhcpserver.c b/dhcpserver/dhcpserver.c new file mode 100644 index 0000000..2061d04 --- /dev/null +++ b/dhcpserver/dhcpserver.c @@ -0,0 +1,309 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// For DHCP specs see: +// https://www.ietf.org/rfc/rfc2131.txt +// https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions + +#include +#include +#include + +#include "cyw43_config.h" +#include "dhcpserver.h" +#include "lwip/udp.h" + +#define DHCPDISCOVER (1) +#define DHCPOFFER (2) +#define DHCPREQUEST (3) +#define DHCPDECLINE (4) +#define DHCPACK (5) +#define DHCPNACK (6) +#define DHCPRELEASE (7) +#define DHCPINFORM (8) + +#define DHCP_OPT_PAD (0) +#define DHCP_OPT_SUBNET_MASK (1) +#define DHCP_OPT_ROUTER (3) +#define DHCP_OPT_DNS (6) +#define DHCP_OPT_HOST_NAME (12) +#define DHCP_OPT_REQUESTED_IP (50) +#define DHCP_OPT_IP_LEASE_TIME (51) +#define DHCP_OPT_MSG_TYPE (53) +#define DHCP_OPT_SERVER_ID (54) +#define DHCP_OPT_PARAM_REQUEST_LIST (55) +#define DHCP_OPT_MAX_MSG_SIZE (57) +#define DHCP_OPT_VENDOR_CLASS_ID (60) +#define DHCP_OPT_CLIENT_ID (61) +#define DHCP_OPT_END (255) + +#define PORT_DHCP_SERVER (67) +#define PORT_DHCP_CLIENT (68) + +#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds + +#define MAC_LEN (6) +#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +typedef struct { + uint8_t op; // message opcode + uint8_t htype; // hardware address type + uint8_t hlen; // hardware address length + uint8_t hops; + uint32_t xid; // transaction id, chosen by client + uint16_t secs; // client seconds elapsed + uint16_t flags; + uint8_t ciaddr[4]; // client IP address + uint8_t yiaddr[4]; // your IP address + uint8_t siaddr[4]; // next server IP address + uint8_t giaddr[4]; // relay agent IP address + uint8_t chaddr[16]; // client hardware address + uint8_t sname[64]; // server host name + uint8_t file[128]; // boot file name + uint8_t options[312]; // optional parameters, variable, starts with magic +} dhcp_msg_t; + +static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + // family is AF_INET + // type is SOCK_DGRAM + + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + + // Register callback + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + + return 0; // success +} + +static void dhcp_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dhcp_socket_bind(struct udp_pcb **udp, uint16_t port) { + // TODO convert lwIP errors to errno + return udp_bind(*udp, IP_ANY_TYPE, port); +} + +static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *nif, const void *buf, size_t len, uint32_t ip, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + + ip_addr_t dest; + IP4_ADDR(ip_2_ip4(&dest), ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err; + if (nif != NULL) { + err = udp_sendto_if(*udp, p, &dest, port, nif); + } else { + err = udp_sendto(*udp, p, &dest, port); + } + + pbuf_free(p); + + if (err != ERR_OK) { + return err; + } + + return len; +} + +static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { + for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) { + if (opt[i] == cmd) { + return &opt[i]; + } + i += 2 + opt[i + 1]; + } + return NULL; +} + +static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, const void *data) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = n; + memcpy(o, data, n); + *opt = o + n; +} + +static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 1; + *o++ = val; + *opt = o; +} + +static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 4; + *o++ = val >> 24; + *o++ = val >> 16; + *o++ = val >> 8; + *o++ = val; + *opt = o; +} + +static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dhcp_server_t *d = arg; + (void)upcb; + (void)src_addr; + (void)src_port; + + // This is around 548 bytes + dhcp_msg_t dhcp_msg; + + #define DHCP_MIN_SIZE (240 + 3) + if (p->tot_len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0); + if (len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + dhcp_msg.op = DHCPOFFER; + memcpy(&dhcp_msg.yiaddr, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 4); + + uint8_t *opt = (uint8_t *)&dhcp_msg.options; + opt += 4; // assume magic cookie: 99, 130, 83, 99 + + uint8_t *msgtype = opt_find(opt, DHCP_OPT_MSG_TYPE); + if (msgtype == NULL) { + // A DHCP package without MSG_TYPE? + goto ignore_request; + } + + switch (msgtype[2]) { + case DHCPDISCOVER: { + int yi = DHCPS_MAX_IP; + for (int i = 0; i < DHCPS_MAX_IP; ++i) { + if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, use this IP address + yi = i; + break; + } + if (yi == DHCPS_MAX_IP) { + // Look for a free IP address + if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP available + yi = i; + } + uint32_t expiry = d->lease[i].expiry << 16 | 0xffff; + if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) { + // IP expired, reuse it + memset(d->lease[i].mac, 0, MAC_LEN); + yi = i; + } + } + } + if (yi == DHCPS_MAX_IP) { + // No more IP addresses left + goto ignore_request; + } + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER); + break; + } + + case DHCPREQUEST: { + uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP); + if (o == NULL) { + // Should be NACK + goto ignore_request; + } + if (memcmp(o + 2, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 3) != 0) { + // Should be NACK + goto ignore_request; + } + uint8_t yi = o[5] - DHCPS_BASE_IP; + if (yi >= DHCPS_MAX_IP) { + // Should be NACK + goto ignore_request; + } + if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, ok to use this IP address + } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP unused, ok to use this IP address + memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN); + } else { + // IP already in use + // Should be NACK + goto ignore_request; + } + d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16; + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK); + printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n", + dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5], + dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]); + break; + } + + default: + goto ignore_request; + } + + opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); + opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &ip4_addr_get_u32(ip_2_ip4(&d->nm))); + opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // aka gateway; can have multiple addresses + opt_write_n(&opt, DHCP_OPT_DNS, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // this server is the dns + opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); + *opt++ = DHCP_OPT_END; + struct netif *nif = ip_current_input_netif(); + dhcp_socket_sendto(&d->udp, nif, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT); + +ignore_request: + pbuf_free(p); +} + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { + ip_addr_copy(d->ip, *ip); + ip_addr_copy(d->nm, *nm); + memset(d->lease, 0, sizeof(d->lease)); + if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) { + return; + } + dhcp_socket_bind(&d->udp, PORT_DHCP_SERVER); +} + +void dhcp_server_deinit(dhcp_server_t *d) { + dhcp_socket_free(&d->udp); +} diff --git a/dhcpserver/dhcpserver.h b/dhcpserver/dhcpserver.h new file mode 100644 index 0000000..2349d2e --- /dev/null +++ b/dhcpserver/dhcpserver.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H +#define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H + +#include "lwip/ip_addr.h" + +#define DHCPS_BASE_IP (16) +#define DHCPS_MAX_IP (8) + +typedef struct _dhcp_server_lease_t { + uint8_t mac[6]; + uint16_t expiry; +} dhcp_server_lease_t; + +typedef struct _dhcp_server_t { + ip_addr_t ip; + ip_addr_t nm; + dhcp_server_lease_t lease[DHCPS_MAX_IP]; + struct udp_pcb *udp; +} dhcp_server_t; + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm); +void dhcp_server_deinit(dhcp_server_t *d); + +#endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H diff --git a/dnsserver/dnsserver.c b/dnsserver/dnsserver.c new file mode 100644 index 0000000..029870b --- /dev/null +++ b/dnsserver/dnsserver.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "dnsserver.h" +#include "lwip/udp.h" + +#define PORT_DNS_SERVER 53 +#define DUMP_DATA 0 + +#define DEBUG_printf(...) +#define ERROR_printf printf + +typedef struct dns_header_t_ { + uint16_t id; + uint16_t flags; + uint16_t question_count; + uint16_t answer_record_count; + uint16_t authority_record_count; + uint16_t additional_record_count; +} dns_header_t; + +#define MAX_DNS_MSG_SIZE 300 + +static int dns_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + return ERR_OK; +} + +static void dns_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dns_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) { + ip_addr_t addr; + IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err = udp_bind(*udp, &addr, port); + if (err != ERR_OK) { + ERROR_printf("dns failed to bind to port %u: %d", port, err); + assert(false); + } + return err; +} + +#if DUMP_DATA +static void dump_bytes(const uint8_t *bptr, uint32_t len) { + unsigned int i = 0; + + for (i = 0; i < len;) { + if ((i & 0x0f) == 0) { + printf("\n"); + } else if ((i & 0x07) == 0) { + printf(" "); + } + printf("%02x ", bptr[i++]); + } + printf("\n"); +} +#endif + +static int dns_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, const ip_addr_t *dest, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + ERROR_printf("DNS: Failed to send message out of memory\n"); + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + err_t err = udp_sendto(*udp, p, dest, port); + + pbuf_free(p); + + if (err != ERR_OK) { + ERROR_printf("DNS: Failed to send message %d\n", err); + return err; + } + +#if DUMP_DATA + dump_bytes(buf, len); +#endif + return len; +} + +static void dns_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dns_server_t *d = arg; + DEBUG_printf("dns_server_process %u\n", p->tot_len); + + uint8_t dns_msg[MAX_DNS_MSG_SIZE]; + dns_header_t *dns_hdr = (dns_header_t*)dns_msg; + + size_t msg_len = pbuf_copy_partial(p, dns_msg, sizeof(dns_msg), 0); + if (msg_len < sizeof(dns_header_t)) { + goto ignore_request; + } + +#if DUMP_DATA + dump_bytes(dns_msg, msg_len); +#endif + + uint16_t flags = lwip_ntohs(dns_hdr->flags); + uint16_t question_count = lwip_ntohs(dns_hdr->question_count); + + DEBUG_printf("len %d\n", msg_len); + DEBUG_printf("dns flags 0x%x\n", flags); + DEBUG_printf("dns question count 0x%x\n", question_count); + + // flags from rfc1035 + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + // Check QR indicates a query + if (((flags >> 15) & 0x1) != 0) { + DEBUG_printf("Ignoring non-query\n"); + goto ignore_request; + } + + // Check for standard query + if (((flags >> 11) & 0xf) != 0) { + DEBUG_printf("Ignoring non-standard query\n"); + goto ignore_request; + } + + // Check question count + if (question_count < 1) { + DEBUG_printf("Invalid question count\n"); + goto ignore_request; + } + + // Print the question + DEBUG_printf("question: "); + const uint8_t *question_ptr_start = dns_msg + sizeof(dns_header_t); + const uint8_t *question_ptr_end = dns_msg + msg_len; + const uint8_t *question_ptr = question_ptr_start; + while(question_ptr < question_ptr_end) { + if (*question_ptr == 0) { + question_ptr++; + break; + } else { + if (question_ptr > question_ptr_start) { + DEBUG_printf("."); + } + int label_len = *question_ptr++; + if (label_len > 63) { + DEBUG_printf("Invalid label\n"); + goto ignore_request; + } + DEBUG_printf("%.*s", label_len, question_ptr); + question_ptr += label_len; + } + } + DEBUG_printf("\n"); + + // Check question length + if (question_ptr - question_ptr_start > 255) { + DEBUG_printf("Invalid question length\n"); + goto ignore_request; + } + + // Skip QNAME and QTYPE + question_ptr += 4; + + // Generate answer + uint8_t *answer_ptr = dns_msg + (question_ptr - dns_msg); + *answer_ptr++ = 0xc0; // pointer + *answer_ptr++ = question_ptr_start - dns_msg; // pointer to question + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // host address + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // Internet class + + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 60; // ttl 60s + + *answer_ptr++ = 0; + *answer_ptr++ = 4; // length + memcpy(answer_ptr, &d->ip.addr, 4); // use our address + answer_ptr += 4; + + dns_hdr->flags = lwip_htons( + 0x1 << 15 | // QR = response + 0x1 << 10 | // AA = authoritative + 0x1 << 7); // RA = authenticated + dns_hdr->question_count = lwip_htons(1); + dns_hdr->answer_record_count = lwip_htons(1); + dns_hdr->authority_record_count = 0; + dns_hdr->additional_record_count = 0; + + // Send the reply + DEBUG_printf("Sending %d byte reply to %s:%d\n", answer_ptr - dns_msg, ipaddr_ntoa(src_addr), src_port); + dns_socket_sendto(&d->udp, &dns_msg, answer_ptr - dns_msg, src_addr, src_port); + +ignore_request: + pbuf_free(p); +} + +void dns_server_init(dns_server_t *d, ip_addr_t *ip) { + if (dns_socket_new_dgram(&d->udp, d, dns_server_process) != ERR_OK) { + DEBUG_printf("dns server failed to start\n"); + return; + } + if (dns_socket_bind(&d->udp, 0, PORT_DNS_SERVER) != ERR_OK) { + DEBUG_printf("dns server failed to bind\n"); + return; + } + ip_addr_copy(d->ip, *ip); + DEBUG_printf("dns server listening on port %d\n", PORT_DNS_SERVER); +} + +void dns_server_deinit(dns_server_t *d) { + dns_socket_free(&d->udp); +} diff --git a/dnsserver/dnsserver.h b/dnsserver/dnsserver.h new file mode 100644 index 0000000..d23534c --- /dev/null +++ b/dnsserver/dnsserver.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _DNSSERVER_H_ +#define _DNSSERVER_H_ + +#include "lwip/ip_addr.h" + +typedef struct dns_server_t_ { + struct udp_pcb *udp; + ip_addr_t ip; +} dns_server_t; + +void dns_server_init(dns_server_t *d, ip_addr_t *ip); +void dns_server_deinit(dns_server_t *d); + +#endif diff --git a/lwipopts.h b/lwipopts.h new file mode 100644 index 0000000..8571ed5 --- /dev/null +++ b/lwipopts.h @@ -0,0 +1,10 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +#endif diff --git a/lwipopts_examples_common.h b/lwipopts_examples_common.h new file mode 100644 index 0000000..3cc083c --- /dev/null +++ b/lwipopts_examples_common.h @@ -0,0 +1,92 @@ +#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H +#define _LWIPOPTS_EXAMPLE_COMMONH_H + + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#ifndef MEM_SIZE +#define MEM_SIZE 4000 +#endif +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +#endif /* __LWIPOPTS_H__ */ diff --git a/main.c b/main.c deleted file mode 100644 index cc2822d..0000000 --- a/main.c +++ /dev/null @@ -1,33 +0,0 @@ -/***** - * Copyright (c) 2023 - Poivron Robotique - * - * SPDX-License-Identifier: BSD-3-Clause -*/ -#include "pico/stdlib.h" -#include -bool bouton_Presser = false; -void main(void) -{ - stdio_init_all(); - gpio_init(8); - gpio_init(9); - gpio_set_function(8,GPIO_IN); - gpio_set_function(9,GPIO_OUT); - gpio_set_function(25,GPIO_OUT); - gpio_pull_down(8); - gpio_put(9,true); - gpio_put(25,true); - sleep_ms(5000); - printf("kartoffen\n"); - while(1){ - bouton_Presser = gpio_get(8); - printf(">a:%d\n", bouton_Presser ); - - gpio_put(25,bouton_Presser); - if(bouton_Presser){ - printf("bouton a ete presse\n"); - } - - - } -} diff --git a/picow_access_point.c b/picow_access_point.c new file mode 100644 index 0000000..c902fe8 --- /dev/null +++ b/picow_access_point.c @@ -0,0 +1,358 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/cyw43_arch.h" +#include "pico/stdlib.h" + +#include "lwip/pbuf.h" +#include "lwip/tcp.h" + +#include "dhcpserver.h" +#include "dnsserver.h" + +#define TCP_PORT 80 +#define DEBUG_printf printf +#define POLL_TIME_S 5 +#define HTTP_GET "GET" +#define HTTP_RESPONSE_HEADERS "HTTP/1.1 %d OK\nContent-Length: %d\nContent-Type: text/html; charset=utf-8\nConnection: close\n\n" +#define LED_TEST_BODY "

Hello from Pico.

Led is %s

Turn led %s" +#define LED_PARAM "led=%d" +#define LED_TEST "/ledtest" +#define LED_GPIO 0 +#define HTTP_RESPONSE_REDIRECT "HTTP/1.1 302 Redirect\nLocation: http://%s" LED_TEST "\n\n" + +typedef struct TCP_SERVER_T_ { + struct tcp_pcb *server_pcb; + bool complete; + ip_addr_t gw; +} TCP_SERVER_T; + +typedef struct TCP_CONNECT_STATE_T_ { + struct tcp_pcb *pcb; + int sent_len; + char headers[128]; + char result[256]; + int header_len; + int result_len; + ip_addr_t *gw; +} TCP_CONNECT_STATE_T; + +static err_t tcp_close_client_connection(TCP_CONNECT_STATE_T *con_state, struct tcp_pcb *client_pcb, err_t close_err) { + if (client_pcb) { + assert(con_state && con_state->pcb == client_pcb); + tcp_arg(client_pcb, NULL); + tcp_poll(client_pcb, NULL, 0); + tcp_sent(client_pcb, NULL); + tcp_recv(client_pcb, NULL); + tcp_err(client_pcb, NULL); + err_t err = tcp_close(client_pcb); + if (err != ERR_OK) { + DEBUG_printf("close failed %d, calling abort\n", err); + tcp_abort(client_pcb); + close_err = ERR_ABRT; + } + if (con_state) { + free(con_state); + } + } + return close_err; +} + +static void tcp_server_close(TCP_SERVER_T *state) { + if (state->server_pcb) { + tcp_arg(state->server_pcb, NULL); + tcp_close(state->server_pcb); + state->server_pcb = NULL; + } +} + +static err_t tcp_server_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { + TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T*)arg; + DEBUG_printf("tcp_server_sent %u\n", len); + con_state->sent_len += len; + if (con_state->sent_len >= con_state->header_len + con_state->result_len) { + DEBUG_printf("all done\n"); + return tcp_close_client_connection(con_state, pcb, ERR_OK); + } + return ERR_OK; +} + +static int test_server_content(const char *request, const char *params, char *result, size_t max_result_len) { + int len = 0; + if (strncmp(request, LED_TEST, sizeof(LED_TEST) - 1) == 0) { + // Get the state of the led + bool value; + cyw43_gpio_get(&cyw43_state, LED_GPIO, &value); + int led_state = value; + + // See if the user changed it + if (params) { + int led_param = sscanf(params, LED_PARAM, &led_state); + if (led_param == 1) { + if (led_state) { + // Turn led on + cyw43_gpio_set(&cyw43_state, LED_GPIO, true); + } else { + // Turn led off + cyw43_gpio_set(&cyw43_state, LED_GPIO, false); + } + } + } + // Generate result + if (led_state) { + len = snprintf(result, max_result_len, LED_TEST_BODY, "ON", 0, "OFF"); + } else { + len = snprintf(result, max_result_len, LED_TEST_BODY, "OFF", 1, "ON"); + } + } + return len; +} + +err_t tcp_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { + TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T*)arg; + if (!p) { + DEBUG_printf("connection closed\n"); + return tcp_close_client_connection(con_state, pcb, ERR_OK); + } + assert(con_state && con_state->pcb == pcb); + if (p->tot_len > 0) { + DEBUG_printf("tcp_server_recv %d err %d\n", p->tot_len, err); +#if 0 + for (struct pbuf *q = p; q != NULL; q = q->next) { + DEBUG_printf("in: %.*s\n", q->len, q->payload); + } +#endif + // Copy the request into the buffer + pbuf_copy_partial(p, con_state->headers, p->tot_len > sizeof(con_state->headers) - 1 ? sizeof(con_state->headers) - 1 : p->tot_len, 0); + + // Handle GET request + if (strncmp(HTTP_GET, con_state->headers, sizeof(HTTP_GET) - 1) == 0) { + char *request = con_state->headers + sizeof(HTTP_GET); // + space + char *params = strchr(request, '?'); + if (params) { + if (*params) { + char *space = strchr(request, ' '); + *params++ = 0; + if (space) { + *space = 0; + } + } else { + params = NULL; + } + } + + // Generate content + con_state->result_len = test_server_content(request, params, con_state->result, sizeof(con_state->result)); + DEBUG_printf("Request: %s?%s\n", request, params); + DEBUG_printf("Result: %d\n", con_state->result_len); + + // Check we had enough buffer space + if (con_state->result_len > sizeof(con_state->result) - 1) { + DEBUG_printf("Too much result data %d\n", con_state->result_len); + return tcp_close_client_connection(con_state, pcb, ERR_CLSD); + } + + // Generate web page + if (con_state->result_len > 0) { + con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADERS, + 200, con_state->result_len); + if (con_state->header_len > sizeof(con_state->headers) - 1) { + DEBUG_printf("Too much header data %d\n", con_state->header_len); + return tcp_close_client_connection(con_state, pcb, ERR_CLSD); + } + } else { + // Send redirect + con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_REDIRECT, + ipaddr_ntoa(con_state->gw)); + DEBUG_printf("Sending redirect %s", con_state->headers); + } + + // Send the headers to the client + con_state->sent_len = 0; + err_t err = tcp_write(pcb, con_state->headers, con_state->header_len, 0); + if (err != ERR_OK) { + DEBUG_printf("failed to write header data %d\n", err); + return tcp_close_client_connection(con_state, pcb, err); + } + + // Send the body to the client + if (con_state->result_len) { + err = tcp_write(pcb, con_state->result, con_state->result_len, 0); + if (err != ERR_OK) { + DEBUG_printf("failed to write result data %d\n", err); + return tcp_close_client_connection(con_state, pcb, err); + } + } + } + tcp_recved(pcb, p->tot_len); + } + pbuf_free(p); + return ERR_OK; +} + +static err_t tcp_server_poll(void *arg, struct tcp_pcb *pcb) { + TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T*)arg; + DEBUG_printf("tcp_server_poll_fn\n"); + return tcp_close_client_connection(con_state, pcb, ERR_OK); // Just disconnect clent? +} + +static void tcp_server_err(void *arg, err_t err) { + TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T*)arg; + if (err != ERR_ABRT) { + DEBUG_printf("tcp_client_err_fn %d\n", err); + tcp_close_client_connection(con_state, con_state->pcb, err); + } +} + +static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { + TCP_SERVER_T *state = (TCP_SERVER_T*)arg; + if (err != ERR_OK || client_pcb == NULL) { + DEBUG_printf("failure in accept\n"); + return ERR_VAL; + } + DEBUG_printf("client connected\n"); + + // Create the state for the connection + TCP_CONNECT_STATE_T *con_state = calloc(1, sizeof(TCP_CONNECT_STATE_T)); + if (!con_state) { + DEBUG_printf("failed to allocate connect state\n"); + return ERR_MEM; + } + con_state->pcb = client_pcb; // for checking + con_state->gw = &state->gw; + + // setup connection to client + tcp_arg(client_pcb, con_state); + tcp_sent(client_pcb, tcp_server_sent); + tcp_recv(client_pcb, tcp_server_recv); + tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2); + tcp_err(client_pcb, tcp_server_err); + + return ERR_OK; +} + +static bool tcp_server_open(void *arg, const char *ap_name) { + TCP_SERVER_T *state = (TCP_SERVER_T*)arg; + DEBUG_printf("starting server on port %d\n", TCP_PORT); + + struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); + if (!pcb) { + DEBUG_printf("failed to create pcb\n"); + return false; + } + + err_t err = tcp_bind(pcb, IP_ANY_TYPE, TCP_PORT); + if (err) { + DEBUG_printf("failed to bind to port %d\n",TCP_PORT); + return false; + } + + state->server_pcb = tcp_listen_with_backlog(pcb, 1); + if (!state->server_pcb) { + DEBUG_printf("failed to listen\n"); + if (pcb) { + tcp_close(pcb); + } + return false; + } + + tcp_arg(state->server_pcb, state); + tcp_accept(state->server_pcb, tcp_server_accept); + + printf("Try connecting to '%s' (press 'd' to disable access point)\n", ap_name); + return true; +} + +void key_pressed_func(void *param) { + assert(param); + TCP_SERVER_T *state = (TCP_SERVER_T*)param; + int key = getchar_timeout_us(0); // get any pending key press but don't wait + if (key == 'd' || key == 'D') { + cyw43_arch_lwip_begin(); + cyw43_arch_disable_ap_mode(); + cyw43_arch_lwip_end(); + state->complete = true; + } +} + +int main() { + stdio_init_all(); + + TCP_SERVER_T *state = calloc(1, sizeof(TCP_SERVER_T)); + if (!state) { + DEBUG_printf("failed to allocate state\n"); + return 1; + } + + if (cyw43_arch_init()) { + DEBUG_printf("failed to initialise\n"); + return 1; + } + + // Get notified if the user presses a key + stdio_set_chars_available_callback(key_pressed_func, state); + + const char *ap_name = "picow_test"; +#if 1 + const char *password = "password"; +#else + const char *password = NULL; +#endif + + cyw43_arch_enable_ap_mode(ap_name, password, CYW43_AUTH_WPA2_AES_PSK); + + #if LWIP_IPV6 + #define IP(x) ((x).u_addr.ip4) + #else + #define IP(x) (x) + #endif + + ip4_addr_t mask; + IP(state->gw).addr = PP_HTONL(CYW43_DEFAULT_IP_AP_ADDRESS); + IP(mask).addr = PP_HTONL(CYW43_DEFAULT_IP_MASK); + + #undef IP + + // Start the dhcp server + dhcp_server_t dhcp_server; + dhcp_server_init(&dhcp_server, &state->gw, &mask); + + // Start the dns server + dns_server_t dns_server; + dns_server_init(&dns_server, &state->gw); + + if (!tcp_server_open(state, ap_name)) { + DEBUG_printf("failed to open server\n"); + return 1; + } + + state->complete = false; + while(!state->complete) { + // the following #ifdef is only here so this same example can be used in multiple modes; + // you do not need it in your code +#if PICO_CYW43_ARCH_POLL + // if you are using pico_cyw43_arch_poll, then you must poll periodically from your + // main loop (not from a timer interrupt) to check for Wi-Fi driver or lwIP work that needs to be done. + cyw43_arch_poll(); + // you can poll as often as you like, however if you have nothing else to do you can + // choose to sleep until either a specified time, or cyw43_arch_poll() has work to do: + cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); +#else + // if you are not using pico_cyw43_arch_poll, then Wi-FI driver and lwIP work + // is done via interrupt in the background. This sleep is just an example of some (blocking) + // work you might be doing. + sleep_ms(1000); +#endif + } + tcp_server_close(state); + dns_server_deinit(&dns_server); + dhcp_server_deinit(&dhcp_server); + cyw43_arch_deinit(); + printf("Test complete\n"); + return 0; +}