Add IGMP, prepend_buffer/parse_buffer, split UDP header, discovery tests, test all
This commit is contained in:
@@ -356,15 +356,17 @@ func findTestDevice() (string, error) {
|
|||||||
|
|
||||||
func cmdTestGroup(args []string) error {
|
func cmdTestGroup(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("usage: picomap test <list|run> [args...]")
|
return fmt.Errorf("usage: picomap test <list|run|all> [args...]")
|
||||||
}
|
}
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "list":
|
case "list":
|
||||||
return cmdTestList(args[1:])
|
return cmdTestList(args[1:])
|
||||||
case "run":
|
case "run":
|
||||||
return cmdTestRun(args[1:])
|
return cmdTestRun(args[1:])
|
||||||
|
case "all":
|
||||||
|
return cmdTestAll(args[1:])
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("usage: picomap test <list|run> [args...]")
|
return fmt.Errorf("usage: picomap test <list|run|all> [args...]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +391,51 @@ func cmdTestList(_ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cmdTestAll(_ []string) error {
|
||||||
|
dev, err := findTestDevice()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := client.NewSerial(dev, 10*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
list, err := c.ListTests()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := slog.With("dev", dev)
|
||||||
|
failed := 0
|
||||||
|
for _, name := range list.Names {
|
||||||
|
log.Info("running test", "name", name)
|
||||||
|
result, err := c.Test(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error", "name", name, "err", err)
|
||||||
|
failed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, msg := range result.Messages {
|
||||||
|
log.Info("remote", "name", name, "msg", msg)
|
||||||
|
}
|
||||||
|
if result.Pass {
|
||||||
|
log.Info("PASS", "name", name)
|
||||||
|
} else {
|
||||||
|
log.Error("FAIL", "name", name)
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if failed > 0 {
|
||||||
|
log.Error("tests failed", "count", failed)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
log.Info("all tests passed", "count", len(list.Names))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func cmdTestRun(args []string) error {
|
func cmdTestRun(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("usage: picomap test run <name>")
|
return fmt.Errorf("usage: picomap test run <name>")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ set(LIB_SOURCES
|
|||||||
lib/dispatch.cpp
|
lib/dispatch.cpp
|
||||||
lib/handlers.cpp
|
lib/handlers.cpp
|
||||||
lib/icmp.cpp
|
lib/icmp.cpp
|
||||||
|
lib/igmp.cpp
|
||||||
lib/ipv4.cpp
|
lib/ipv4.cpp
|
||||||
lib/net.cpp
|
lib/net.cpp
|
||||||
lib/tusb_config.cpp
|
lib/tusb_config.cpp
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
|
|
||||||
namespace arp {
|
namespace arp {
|
||||||
|
|
||||||
struct __attribute__((packed)) packet {
|
struct __attribute__((packed)) header {
|
||||||
eth::header eth;
|
|
||||||
uint16_t htype;
|
uint16_t htype;
|
||||||
uint16_t ptype;
|
uint16_t ptype;
|
||||||
uint8_t hlen;
|
uint8_t hlen;
|
||||||
@@ -19,7 +18,7 @@ struct __attribute__((packed)) packet {
|
|||||||
eth::mac_addr tha;
|
eth::mac_addr tha;
|
||||||
ipv4::ip4_addr tpa;
|
ipv4::ip4_addr tpa;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(packet) == 42);
|
static_assert(sizeof(header) == 28);
|
||||||
|
|
||||||
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
|||||||
@@ -17,4 +17,12 @@ struct __attribute__((packed)) header {
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(header) == 14);
|
static_assert(sizeof(header) == 14);
|
||||||
|
|
||||||
|
template <typename Buf>
|
||||||
|
void prepend(Buf& buf, const mac_addr& dst, const mac_addr& src, uint16_t ethertype) {
|
||||||
|
auto* h = buf.template prepend<header>();
|
||||||
|
h->dst = dst;
|
||||||
|
h->src = src;
|
||||||
|
h->ethertype = ethertype;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace eth
|
} // namespace eth
|
||||||
|
|||||||
@@ -21,10 +21,20 @@ void handle(std::span<const uint8_t> frame, span_writer& tx,
|
|||||||
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
std::function<void(std::span<const uint8_t>)> send_raw);
|
std::function<void(std::span<const uint8_t>)> send_raw);
|
||||||
|
|
||||||
size_t build_echo_request(std::span<uint8_t> buf,
|
template <typename Buf>
|
||||||
|
void prepend_echo_request(Buf& buf,
|
||||||
eth::mac_addr src_mac, ipv4::ip4_addr src_ip,
|
eth::mac_addr src_mac, ipv4::ip4_addr src_ip,
|
||||||
eth::mac_addr dst_mac, ipv4::ip4_addr dst_ip,
|
eth::mac_addr dst_mac, ipv4::ip4_addr dst_ip,
|
||||||
uint16_t id, uint16_t seq);
|
uint16_t id, uint16_t seq) {
|
||||||
|
auto* e = buf.template prepend<echo>();
|
||||||
|
e->type = 8;
|
||||||
|
e->code = 0;
|
||||||
|
e->checksum = 0;
|
||||||
|
e->id = id;
|
||||||
|
e->seq = seq;
|
||||||
|
e->checksum = ipv4::checksum(e, sizeof(echo));
|
||||||
|
ipv4::prepend(buf, dst_mac, src_mac, src_ip, dst_ip, 1, sizeof(echo));
|
||||||
|
}
|
||||||
|
|
||||||
bool parse_echo_reply(std::span<const uint8_t> frame, ipv4::ip4_addr& src_ip, uint16_t expected_id);
|
bool parse_echo_reply(std::span<const uint8_t> frame, ipv4::ip4_addr& src_ip, uint16_t expected_id);
|
||||||
|
|
||||||
|
|||||||
64
firmware/include/igmp.h
Normal file
64
firmware/include/igmp.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
#include "eth.h"
|
||||||
|
#include "ipv4.h"
|
||||||
|
#include "span_writer.h"
|
||||||
|
|
||||||
|
namespace igmp {
|
||||||
|
|
||||||
|
static constexpr ipv4::ip4_addr PICOMAP_DISCOVERY_GROUP = {239, 112, 77, 1};
|
||||||
|
static constexpr ipv4::ip4_addr ALL_HOSTS = {224, 0, 0, 1};
|
||||||
|
|
||||||
|
struct __attribute__((packed)) message {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t max_resp_time;
|
||||||
|
uint16_t checksum;
|
||||||
|
ipv4::ip4_addr group;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(message) == 8);
|
||||||
|
|
||||||
|
eth::mac_addr mac_for_ip(const ipv4::ip4_addr& group);
|
||||||
|
bool is_member(const ipv4::ip4_addr& ip);
|
||||||
|
bool is_member_mac(const eth::mac_addr& mac);
|
||||||
|
|
||||||
|
void join(const ipv4::ip4_addr& group,
|
||||||
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw);
|
||||||
|
|
||||||
|
void send_all_reports(eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw);
|
||||||
|
|
||||||
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw);
|
||||||
|
|
||||||
|
template <typename Buf>
|
||||||
|
void prepend_report(Buf& buf, const eth::mac_addr& src_mac, ipv4::ip4_addr src_ip,
|
||||||
|
const ipv4::ip4_addr& group) {
|
||||||
|
auto* m = buf.template prepend<message>();
|
||||||
|
m->type = 0x16;
|
||||||
|
m->max_resp_time = 0;
|
||||||
|
m->checksum = 0;
|
||||||
|
m->group = group;
|
||||||
|
m->checksum = ipv4::checksum(m, sizeof(message));
|
||||||
|
ipv4::prepend(buf, mac_for_ip(group), src_mac, src_ip, group, 2, sizeof(message), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Buf>
|
||||||
|
void prepend_query(Buf& buf, const eth::mac_addr& src_mac, ipv4::ip4_addr src_ip,
|
||||||
|
const ipv4::ip4_addr& group) {
|
||||||
|
ipv4::ip4_addr dst_ip = (group == ipv4::ip4_addr{0, 0, 0, 0}) ? ALL_HOSTS : group;
|
||||||
|
auto* m = buf.template prepend<message>();
|
||||||
|
m->type = 0x11;
|
||||||
|
m->max_resp_time = 100;
|
||||||
|
m->checksum = 0;
|
||||||
|
m->group = group;
|
||||||
|
m->checksum = ipv4::checksum(m, sizeof(message));
|
||||||
|
ipv4::prepend(buf, mac_for_ip(dst_ip), src_mac, src_ip, dst_ip, 2, sizeof(message), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_report(std::span<const uint8_t> frame, ipv4::ip4_addr& group);
|
||||||
|
|
||||||
|
} // namespace igmp
|
||||||
@@ -19,7 +19,6 @@ inline std::string to_string(const ip4_addr& ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct __attribute__((packed)) header {
|
struct __attribute__((packed)) header {
|
||||||
eth::header eth;
|
|
||||||
uint8_t ver_ihl;
|
uint8_t ver_ihl;
|
||||||
uint8_t dscp_ecn;
|
uint8_t dscp_ecn;
|
||||||
uint16_t total_len;
|
uint16_t total_len;
|
||||||
@@ -31,24 +30,32 @@ struct __attribute__((packed)) header {
|
|||||||
ip4_addr src;
|
ip4_addr src;
|
||||||
ip4_addr dst;
|
ip4_addr dst;
|
||||||
|
|
||||||
size_t ip_header_len() const { return (ver_ihl & 0x0F) * 4; }
|
size_t header_len() const { return (ver_ihl & 0x0F) * 4; }
|
||||||
size_t ip_total_len() const { return __builtin_bswap16(total_len); }
|
size_t total() const { return __builtin_bswap16(total_len); }
|
||||||
const uint8_t* ip_start() const { return reinterpret_cast<const uint8_t*>(&ver_ihl); }
|
|
||||||
uint8_t* ip_start() { return reinterpret_cast<uint8_t*>(&ver_ihl); }
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(header) == 34);
|
static_assert(sizeof(header) == 20);
|
||||||
|
|
||||||
struct __attribute__((packed)) udp_header {
|
|
||||||
header ip;
|
|
||||||
uint16_t src_port;
|
|
||||||
uint16_t dst_port;
|
|
||||||
uint16_t length;
|
|
||||||
uint16_t checksum;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(udp_header) == 42);
|
|
||||||
|
|
||||||
uint16_t checksum(const void* data, size_t len);
|
uint16_t checksum(const void* data, size_t len);
|
||||||
|
|
||||||
|
template <typename Buf>
|
||||||
|
void prepend(Buf& buf, const eth::mac_addr& dst_mac, const eth::mac_addr& src_mac,
|
||||||
|
ip4_addr src_ip, ip4_addr dst_ip, uint8_t protocol,
|
||||||
|
size_t payload_len, uint8_t ttl = 64) {
|
||||||
|
auto* h = buf.template prepend<header>();
|
||||||
|
h->ver_ihl = 0x45;
|
||||||
|
h->dscp_ecn = 0;
|
||||||
|
h->total_len = __builtin_bswap16(sizeof(header) + payload_len);
|
||||||
|
h->identification = 0;
|
||||||
|
h->flags_frag = 0;
|
||||||
|
h->ttl = ttl;
|
||||||
|
h->protocol = protocol;
|
||||||
|
h->checksum = 0;
|
||||||
|
h->src = src_ip;
|
||||||
|
h->dst = dst_ip;
|
||||||
|
h->checksum = checksum(h, sizeof(header));
|
||||||
|
eth::prepend(buf, dst_mac, src_mac, eth::ETH_IPV4);
|
||||||
|
}
|
||||||
|
|
||||||
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
eth::mac_addr our_mac, ip4_addr our_ip, ip4_addr subnet_broadcast,
|
eth::mac_addr our_mac, ip4_addr our_ip, ip4_addr subnet_broadcast,
|
||||||
std::function<void(std::span<const uint8_t>)> send_raw,
|
std::function<void(std::span<const uint8_t>)> send_raw,
|
||||||
|
|||||||
32
firmware/include/parse_buffer.h
Normal file
32
firmware/include/parse_buffer.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
class parse_buffer {
|
||||||
|
const uint8_t* m_data;
|
||||||
|
size_t m_remaining;
|
||||||
|
|
||||||
|
public:
|
||||||
|
parse_buffer(std::span<const uint8_t> data)
|
||||||
|
: m_data(data.data()), m_remaining(data.size()) {}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
const T* consume() {
|
||||||
|
if (m_remaining < sizeof(T)) return nullptr;
|
||||||
|
auto* p = reinterpret_cast<const T*>(m_data);
|
||||||
|
m_data += sizeof(T);
|
||||||
|
m_remaining -= sizeof(T);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool skip(size_t len) {
|
||||||
|
if (m_remaining < len) return false;
|
||||||
|
m_data += len;
|
||||||
|
m_remaining -= len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const uint8_t> remaining() const { return {m_data, m_remaining}; }
|
||||||
|
size_t remaining_size() const { return m_remaining; }
|
||||||
|
};
|
||||||
37
firmware/include/prepend_buffer.h
Normal file
37
firmware/include/prepend_buffer.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
class prepend_buffer {
|
||||||
|
uint8_t m_buf[N];
|
||||||
|
size_t m_start = N / 2;
|
||||||
|
size_t m_end = N / 2;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T>
|
||||||
|
T* prepend() {
|
||||||
|
m_start -= sizeof(T);
|
||||||
|
return reinterpret_cast<T*>(m_buf + m_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* append(size_t len) {
|
||||||
|
uint8_t* p = m_buf + m_end;
|
||||||
|
m_end += len;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append_copy(std::span<const uint8_t> data) {
|
||||||
|
memcpy(append(data.size()), data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* payload_ptr() { return m_buf + m_end; }
|
||||||
|
|
||||||
|
uint8_t* data() { return m_buf + m_start; }
|
||||||
|
const uint8_t* data() const { return m_buf + m_start; }
|
||||||
|
size_t size() const { return m_end - m_start; }
|
||||||
|
|
||||||
|
std::span<const uint8_t> span() const { return {data(), size()}; }
|
||||||
|
};
|
||||||
29
firmware/include/udp.h
Normal file
29
firmware/include/udp.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include "eth.h"
|
||||||
|
#include "ipv4.h"
|
||||||
|
|
||||||
|
namespace udp {
|
||||||
|
|
||||||
|
struct __attribute__((packed)) header {
|
||||||
|
uint16_t src_port;
|
||||||
|
uint16_t dst_port;
|
||||||
|
uint16_t length;
|
||||||
|
uint16_t checksum;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(header) == 8);
|
||||||
|
|
||||||
|
template <typename Buf>
|
||||||
|
void prepend(Buf& buf, const eth::mac_addr& dst_mac, const eth::mac_addr& src_mac,
|
||||||
|
ipv4::ip4_addr src_ip, ipv4::ip4_addr dst_ip,
|
||||||
|
uint16_t src_port, uint16_t dst_port,
|
||||||
|
size_t payload_len, uint8_t ttl = 64) {
|
||||||
|
auto* u = buf.template prepend<header>();
|
||||||
|
u->src_port = src_port;
|
||||||
|
u->dst_port = dst_port;
|
||||||
|
u->length = __builtin_bswap16(sizeof(header) + payload_len);
|
||||||
|
u->checksum = 0;
|
||||||
|
ipv4::prepend(buf, dst_mac, src_mac, src_ip, dst_ip, 17, sizeof(header) + payload_len, ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace udp
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "arp.h"
|
#include "arp.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
#include "prepend_buffer.h"
|
||||||
|
|
||||||
namespace arp {
|
namespace arp {
|
||||||
|
|
||||||
@@ -10,32 +12,31 @@ static constexpr uint16_t ARP_OP_REPLY = __builtin_bswap16(2);
|
|||||||
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
std::function<void(std::span<const uint8_t>)> send_raw) {
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
if (frame.size() < sizeof(packet)) return;
|
parse_buffer pb(frame);
|
||||||
auto& pkt = *reinterpret_cast<const packet*>(frame.data());
|
pb.consume<eth::header>();
|
||||||
|
auto* arp_hdr = pb.consume<header>();
|
||||||
|
if (!arp_hdr) return;
|
||||||
|
|
||||||
if (pkt.htype != ARP_HTYPE_ETH) return;
|
if (arp_hdr->htype != ARP_HTYPE_ETH) return;
|
||||||
if (pkt.ptype != ARP_PTYPE_IPV4) return;
|
if (arp_hdr->ptype != ARP_PTYPE_IPV4) return;
|
||||||
if (pkt.hlen != 6 || pkt.plen != 4) return;
|
if (arp_hdr->hlen != 6 || arp_hdr->plen != 4) return;
|
||||||
if (pkt.oper != ARP_OP_REQUEST) return;
|
if (arp_hdr->oper != ARP_OP_REQUEST) return;
|
||||||
if (pkt.tpa != our_ip) return;
|
if (arp_hdr->tpa != our_ip) return;
|
||||||
if (sizeof(packet) > tx.capacity()) return;
|
|
||||||
|
|
||||||
auto& reply = *reinterpret_cast<packet*>(tx.data());
|
prepend_buffer<128> buf;
|
||||||
reply = {};
|
auto* reply = buf.template prepend<header>();
|
||||||
reply.eth.dst = pkt.eth.src;
|
reply->htype = ARP_HTYPE_ETH;
|
||||||
reply.eth.src = our_mac;
|
reply->ptype = ARP_PTYPE_IPV4;
|
||||||
reply.eth.ethertype = eth::ETH_ARP;
|
reply->hlen = 6;
|
||||||
reply.htype = ARP_HTYPE_ETH;
|
reply->plen = 4;
|
||||||
reply.ptype = ARP_PTYPE_IPV4;
|
reply->oper = ARP_OP_REPLY;
|
||||||
reply.hlen = 6;
|
reply->sha = our_mac;
|
||||||
reply.plen = 4;
|
reply->spa = our_ip;
|
||||||
reply.oper = ARP_OP_REPLY;
|
reply->tha = arp_hdr->sha;
|
||||||
reply.sha = our_mac;
|
reply->tpa = arp_hdr->spa;
|
||||||
reply.spa = our_ip;
|
eth::prepend(buf, arp_hdr->sha, our_mac, eth::ETH_ARP);
|
||||||
reply.tha = pkt.sha;
|
|
||||||
reply.tpa = pkt.spa;
|
|
||||||
|
|
||||||
send_raw({tx.data(), sizeof(packet)});
|
send_raw(buf.span());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace arp
|
} // namespace arp
|
||||||
|
|||||||
@@ -6,14 +6,22 @@
|
|||||||
#include "usb_cdc.h"
|
#include "usb_cdc.h"
|
||||||
#include "timer_queue.h"
|
#include "timer_queue.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
#include "igmp.h"
|
||||||
#include "debug_log.h"
|
#include "debug_log.h"
|
||||||
#include "hardware/sync.h"
|
#include "hardware/sync.h"
|
||||||
|
|
||||||
static timer_queue timers;
|
static timer_queue timers;
|
||||||
|
|
||||||
|
static void igmp_reannounce() {
|
||||||
|
auto& ns = net_get_state();
|
||||||
|
igmp::send_all_reports(ns.mac, ns.ip, net_send_raw);
|
||||||
|
dispatch_schedule_ms(60000, igmp_reannounce);
|
||||||
|
}
|
||||||
|
|
||||||
void dispatch_init() {
|
void dispatch_init() {
|
||||||
tusb_init();
|
tusb_init();
|
||||||
net_init();
|
net_init();
|
||||||
|
dispatch_schedule_ms(60000, igmp_reannounce);
|
||||||
dlog("dispatch_init complete");
|
dlog("dispatch_init complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +1,62 @@
|
|||||||
#include "icmp.h"
|
#include "icmp.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "ipv4.h"
|
#include "ipv4.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
#include "prepend_buffer.h"
|
||||||
|
|
||||||
namespace icmp {
|
namespace icmp {
|
||||||
|
|
||||||
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
std::function<void(std::span<const uint8_t>)> send_raw) {
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
auto& ip = *reinterpret_cast<const ipv4::header*>(frame.data());
|
parse_buffer pb(frame);
|
||||||
size_t ip_hdr_len = ip.ip_header_len();
|
auto* eth_hdr = pb.consume<eth::header>();
|
||||||
size_t ip_total = ip.ip_total_len();
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
|
if (!ip) return;
|
||||||
|
if (ip->protocol != 1) return;
|
||||||
|
|
||||||
if (sizeof(eth::header) + ip_total > frame.size()) return;
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
if (ip.protocol != 1) return;
|
if (options_len > 0 && !pb.skip(options_len)) return;
|
||||||
|
|
||||||
auto& icmp_pkt = *reinterpret_cast<const echo*>(frame.data() + sizeof(eth::header) + ip_hdr_len);
|
size_t icmp_len = ip->total() - ip->header_len();
|
||||||
size_t icmp_len = ip_total - ip_hdr_len;
|
if (pb.remaining_size() < icmp_len) return;
|
||||||
if (icmp_len < sizeof(echo)) return;
|
|
||||||
if (icmp_pkt.type != 8) return;
|
|
||||||
|
|
||||||
size_t reply_len = sizeof(eth::header) + ip_total;
|
auto* icmp_pkt = pb.consume<echo>();
|
||||||
if (reply_len > tx.capacity()) return;
|
if (!icmp_pkt) return;
|
||||||
|
if (icmp_pkt->type != 8) return;
|
||||||
|
|
||||||
memcpy(tx.data(), frame.data(), reply_len);
|
prepend_buffer<1514> buf;
|
||||||
auto& rip = *reinterpret_cast<ipv4::header*>(tx.data());
|
memcpy(buf.append(icmp_len), pb.remaining().data() - sizeof(echo), icmp_len);
|
||||||
rip.eth.dst = ip.eth.src;
|
|
||||||
rip.eth.src = our_mac;
|
|
||||||
rip.src = our_ip;
|
|
||||||
rip.dst = ip.src;
|
|
||||||
rip.ttl = 64;
|
|
||||||
rip.checksum = 0;
|
|
||||||
rip.checksum = ipv4::checksum(rip.ip_start(), ip_hdr_len);
|
|
||||||
|
|
||||||
auto& ricmp = *reinterpret_cast<echo*>(tx.data() + sizeof(eth::header) + ip_hdr_len);
|
auto* reply = reinterpret_cast<echo*>(buf.data());
|
||||||
ricmp.type = 0;
|
reply->type = 0;
|
||||||
ricmp.checksum = 0;
|
reply->checksum = 0;
|
||||||
ricmp.checksum = ipv4::checksum(&ricmp, icmp_len);
|
reply->checksum = ipv4::checksum(reply, icmp_len);
|
||||||
|
|
||||||
send_raw({tx.data(), reply_len});
|
ipv4::prepend(buf, eth_hdr->src, our_mac, our_ip, ip->src, 1, icmp_len);
|
||||||
}
|
send_raw(buf.span());
|
||||||
|
|
||||||
size_t build_echo_request(std::span<uint8_t> buf,
|
|
||||||
eth::mac_addr src_mac, ipv4::ip4_addr src_ip,
|
|
||||||
eth::mac_addr dst_mac, ipv4::ip4_addr dst_ip,
|
|
||||||
uint16_t id, uint16_t seq) {
|
|
||||||
size_t total = sizeof(ipv4::header) + sizeof(echo);
|
|
||||||
if (buf.size() < total) return 0;
|
|
||||||
|
|
||||||
memset(buf.data(), 0, total);
|
|
||||||
|
|
||||||
auto& ip = *reinterpret_cast<ipv4::header*>(buf.data());
|
|
||||||
ip.eth.dst = dst_mac;
|
|
||||||
ip.eth.src = src_mac;
|
|
||||||
ip.eth.ethertype = eth::ETH_IPV4;
|
|
||||||
ip.ver_ihl = 0x45;
|
|
||||||
ip.dscp_ecn = 0;
|
|
||||||
ip.total_len = __builtin_bswap16(20 + sizeof(echo));
|
|
||||||
ip.identification = 0;
|
|
||||||
ip.flags_frag = 0;
|
|
||||||
ip.ttl = 64;
|
|
||||||
ip.protocol = 1;
|
|
||||||
ip.checksum = 0;
|
|
||||||
ip.src = src_ip;
|
|
||||||
ip.dst = dst_ip;
|
|
||||||
ip.checksum = ipv4::checksum(ip.ip_start(), 20);
|
|
||||||
|
|
||||||
auto& icmp_pkt = *reinterpret_cast<echo*>(buf.data() + sizeof(ipv4::header));
|
|
||||||
icmp_pkt.type = 8;
|
|
||||||
icmp_pkt.code = 0;
|
|
||||||
icmp_pkt.checksum = 0;
|
|
||||||
icmp_pkt.id = id;
|
|
||||||
icmp_pkt.seq = seq;
|
|
||||||
icmp_pkt.checksum = ipv4::checksum(&icmp_pkt, sizeof(echo));
|
|
||||||
|
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parse_echo_reply(std::span<const uint8_t> frame, ipv4::ip4_addr& src_ip, uint16_t expected_id) {
|
bool parse_echo_reply(std::span<const uint8_t> frame, ipv4::ip4_addr& src_ip, uint16_t expected_id) {
|
||||||
if (frame.size() < sizeof(ipv4::header)) return false;
|
parse_buffer pb(frame);
|
||||||
auto& ip = *reinterpret_cast<const ipv4::header*>(frame.data());
|
auto* eth_hdr = pb.consume<eth::header>();
|
||||||
if ((ip.ver_ihl >> 4) != 4) return false;
|
if (!eth_hdr) return false;
|
||||||
if (ip.eth.ethertype != eth::ETH_IPV4) return false;
|
if (eth_hdr->ethertype != eth::ETH_IPV4) return false;
|
||||||
if (ip.protocol != 1) return false;
|
|
||||||
|
|
||||||
size_t ip_hdr_len = ip.ip_header_len();
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
if (sizeof(eth::header) + ip_hdr_len + sizeof(echo) > frame.size()) return false;
|
if (!ip) return false;
|
||||||
|
if ((ip->ver_ihl >> 4) != 4) return false;
|
||||||
|
if (ip->protocol != 1) return false;
|
||||||
|
|
||||||
auto& icmp_pkt = *reinterpret_cast<const echo*>(frame.data() + sizeof(eth::header) + ip_hdr_len);
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
if (icmp_pkt.type != 0) return false;
|
if (options_len > 0 && !pb.skip(options_len)) return false;
|
||||||
if (icmp_pkt.id != expected_id) return false;
|
|
||||||
|
|
||||||
src_ip = ip.src;
|
auto* icmp_pkt = pb.consume<echo>();
|
||||||
|
if (!icmp_pkt) return false;
|
||||||
|
if (icmp_pkt->type != 0) return false;
|
||||||
|
if (icmp_pkt->id != expected_id) return false;
|
||||||
|
|
||||||
|
src_ip = ip->src;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
firmware/lib/igmp.cpp
Normal file
109
firmware/lib/igmp.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "igmp.h"
|
||||||
|
#include <vector>
|
||||||
|
#include "ipv4.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
#include "prepend_buffer.h"
|
||||||
|
|
||||||
|
namespace igmp {
|
||||||
|
|
||||||
|
struct group_entry {
|
||||||
|
ipv4::ip4_addr ip;
|
||||||
|
eth::mac_addr mac;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<group_entry> groups;
|
||||||
|
|
||||||
|
eth::mac_addr mac_for_ip(const ipv4::ip4_addr& group) {
|
||||||
|
return {0x01, 0x00, 0x5E,
|
||||||
|
static_cast<uint8_t>(group[1] & 0x7F), group[2], group[3]};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_member(const ipv4::ip4_addr& ip) {
|
||||||
|
if (ip == ALL_HOSTS) return true;
|
||||||
|
for (auto& g : groups)
|
||||||
|
if (g.ip == ip) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_member_mac(const eth::mac_addr& mac) {
|
||||||
|
static constexpr eth::mac_addr ALL_HOSTS_MAC = {0x01, 0x00, 0x5E, 0x00, 0x00, 0x01};
|
||||||
|
if (mac == ALL_HOSTS_MAC) return true;
|
||||||
|
for (auto& g : groups)
|
||||||
|
if (g.mac == mac) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_report(const ipv4::ip4_addr& group,
|
||||||
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
|
prepend_buffer<128> buf;
|
||||||
|
prepend_report(buf, our_mac, our_ip, group);
|
||||||
|
send_raw(buf.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
void join(const ipv4::ip4_addr& group,
|
||||||
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
|
for (auto& g : groups)
|
||||||
|
if (g.ip == group) return;
|
||||||
|
groups.push_back({group, mac_for_ip(group)});
|
||||||
|
send_report(group, our_mac, our_ip, send_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_all_reports(eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
|
for (auto& g : groups)
|
||||||
|
send_report(g.ip, our_mac, our_ip, send_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
|
eth::mac_addr our_mac, ipv4::ip4_addr our_ip,
|
||||||
|
std::function<void(std::span<const uint8_t>)> send_raw) {
|
||||||
|
parse_buffer pb(frame);
|
||||||
|
pb.consume<eth::header>();
|
||||||
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
|
if (!ip) return;
|
||||||
|
|
||||||
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
|
if (options_len > 0 && !pb.skip(options_len)) return;
|
||||||
|
|
||||||
|
auto* msg = pb.consume<message>();
|
||||||
|
if (!msg) return;
|
||||||
|
if (msg->type != 0x11) return;
|
||||||
|
|
||||||
|
if (msg->group == ipv4::ip4_addr{0, 0, 0, 0}) {
|
||||||
|
for (auto& g : groups)
|
||||||
|
send_report(g.ip, our_mac, our_ip, send_raw);
|
||||||
|
} else {
|
||||||
|
for (auto& g : groups) {
|
||||||
|
if (g.ip == msg->group) {
|
||||||
|
send_report(g.ip, our_mac, our_ip, send_raw);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_report(std::span<const uint8_t> frame, ipv4::ip4_addr& group) {
|
||||||
|
parse_buffer pb(frame);
|
||||||
|
auto* eth_hdr = pb.consume<eth::header>();
|
||||||
|
if (!eth_hdr) return false;
|
||||||
|
if (eth_hdr->ethertype != eth::ETH_IPV4) return false;
|
||||||
|
|
||||||
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
|
if (!ip) return false;
|
||||||
|
if ((ip->ver_ihl >> 4) != 4) return false;
|
||||||
|
if (ip->protocol != 2) return false;
|
||||||
|
|
||||||
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
|
if (options_len > 0 && !pb.skip(options_len)) return false;
|
||||||
|
|
||||||
|
auto* msg = pb.consume<message>();
|
||||||
|
if (!msg) return false;
|
||||||
|
if (msg->type != 0x16) return false;
|
||||||
|
|
||||||
|
group = msg->group;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace igmp
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "ipv4.h"
|
#include "ipv4.h"
|
||||||
#include "icmp.h"
|
#include "icmp.h"
|
||||||
|
#include "igmp.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
|
||||||
namespace ipv4 {
|
namespace ipv4 {
|
||||||
|
|
||||||
@@ -17,26 +19,34 @@ uint16_t checksum(const void* data, size_t len) {
|
|||||||
return __builtin_bswap16(~sum);
|
return __builtin_bswap16(~sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ip_match_or_broadcast(const ip4_addr& dst, const ip4_addr& our_ip, const ip4_addr& subnet_broadcast) {
|
static bool ip_match(const ip4_addr& dst, const ip4_addr& our_ip, const ip4_addr& subnet_broadcast) {
|
||||||
return dst == our_ip || dst == IP_BROADCAST_ALL || dst == subnet_broadcast;
|
return dst == our_ip || dst == IP_BROADCAST_ALL || dst == subnet_broadcast || igmp::is_member(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
void handle(std::span<const uint8_t> frame, span_writer& tx,
|
||||||
eth::mac_addr our_mac, ip4_addr our_ip, ip4_addr subnet_broadcast,
|
eth::mac_addr our_mac, ip4_addr our_ip, ip4_addr subnet_broadcast,
|
||||||
std::function<void(std::span<const uint8_t>)> send_raw,
|
std::function<void(std::span<const uint8_t>)> send_raw,
|
||||||
std::function<void(std::span<const uint8_t>, span_writer&)> handle_udp) {
|
std::function<void(std::span<const uint8_t>, span_writer&)> handle_udp) {
|
||||||
if (frame.size() < sizeof(header)) return;
|
parse_buffer pb(frame);
|
||||||
auto& ip = *reinterpret_cast<const header*>(frame.data());
|
pb.consume<eth::header>();
|
||||||
if ((ip.ver_ihl >> 4) != 4) return;
|
auto* ip = pb.consume<header>();
|
||||||
|
if (!ip) return;
|
||||||
|
if ((ip->ver_ihl >> 4) != 4) return;
|
||||||
|
|
||||||
switch (ip.protocol) {
|
size_t options_len = ip->header_len() - sizeof(header);
|
||||||
|
if (options_len > 0 && !pb.skip(options_len)) return;
|
||||||
|
|
||||||
|
switch (ip->protocol) {
|
||||||
case 1:
|
case 1:
|
||||||
if (!ip_match_or_broadcast(ip.dst, our_ip, subnet_broadcast))
|
if (!ip_match(ip->dst, our_ip, subnet_broadcast))
|
||||||
return;
|
return;
|
||||||
icmp::handle(frame, tx, our_mac, our_ip, send_raw);
|
icmp::handle(frame, tx, our_mac, our_ip, send_raw);
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
igmp::handle(frame, tx, our_mac, our_ip, send_raw);
|
||||||
|
break;
|
||||||
case 17:
|
case 17:
|
||||||
if (!ip_match_or_broadcast(ip.dst, our_ip, subnet_broadcast))
|
if (!ip_match(ip->dst, our_ip, subnet_broadcast))
|
||||||
return;
|
return;
|
||||||
handle_udp(frame, tx);
|
handle_udp(frame, tx);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include "eth.h"
|
#include "eth.h"
|
||||||
#include "arp.h"
|
#include "arp.h"
|
||||||
#include "ipv4.h"
|
#include "ipv4.h"
|
||||||
|
#include "udp.h"
|
||||||
|
#include "igmp.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
#include "prepend_buffer.h"
|
||||||
#include "w6300.h"
|
#include "w6300.h"
|
||||||
#include "debug_log.h"
|
#include "debug_log.h"
|
||||||
|
|
||||||
@@ -23,58 +27,40 @@ void net_send_raw(std::span<const uint8_t> data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void handle_udp(std::span<const uint8_t> frame, span_writer& tx) {
|
static void handle_udp(std::span<const uint8_t> frame, span_writer& tx) {
|
||||||
if (frame.size() < sizeof(ipv4::udp_header)) return;
|
parse_buffer pb(frame);
|
||||||
auto& pkt = *reinterpret_cast<const ipv4::udp_header*>(frame.data());
|
auto* eth_hdr = pb.consume<eth::header>();
|
||||||
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
|
if (!ip) return;
|
||||||
|
|
||||||
if (pkt.dst_port != PICOMAP_PORT) return;
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
|
if (options_len > 0 && !pb.skip(options_len)) return;
|
||||||
|
|
||||||
|
auto* uhdr = pb.consume<udp::header>();
|
||||||
|
if (!uhdr) return;
|
||||||
|
if (uhdr->dst_port != PICOMAP_PORT) return;
|
||||||
if (!msg_handler) return;
|
if (!msg_handler) return;
|
||||||
|
|
||||||
size_t udp_len = __builtin_bswap16(pkt.length);
|
size_t udp_len = __builtin_bswap16(uhdr->length);
|
||||||
if (udp_len < 8) return;
|
if (udp_len < sizeof(udp::header)) return;
|
||||||
if (sizeof(eth::header) + pkt.ip.ip_total_len() < sizeof(ipv4::udp_header) + udp_len - 8) return;
|
size_t payload_len = udp_len - sizeof(udp::header);
|
||||||
|
if (pb.remaining_size() < payload_len) return;
|
||||||
|
|
||||||
size_t payload_len = udp_len - 8;
|
eth::mac_addr dst_mac = eth_hdr->src;
|
||||||
|
ipv4::ip4_addr dst_ip = ip->src;
|
||||||
|
uint16_t dst_port = uhdr->src_port;
|
||||||
|
|
||||||
eth::mac_addr dst_mac = pkt.ip.eth.src;
|
msg_handler(pb.remaining().subspan(0, payload_len),
|
||||||
ipv4::ip4_addr dst_ip = pkt.ip.src;
|
|
||||||
uint16_t dst_port = pkt.src_port;
|
|
||||||
|
|
||||||
msg_handler(frame.subspan(sizeof(ipv4::udp_header), payload_len),
|
|
||||||
[dst_mac, dst_ip, dst_port](std::span<const uint8_t> resp_data) {
|
[dst_mac, dst_ip, dst_port](std::span<const uint8_t> resp_data) {
|
||||||
size_t ip_total = 20 + 8 + resp_data.size();
|
prepend_buffer<1514> buf;
|
||||||
size_t reply_len = sizeof(eth::header) + ip_total;
|
buf.append_copy(resp_data);
|
||||||
uint8_t reply_buf[1514];
|
udp::prepend(buf, dst_mac, state.mac, state.ip, dst_ip,
|
||||||
if (reply_len > sizeof(reply_buf)) return;
|
PICOMAP_PORT, dst_port, resp_data.size());
|
||||||
|
net_send_raw(buf.span());
|
||||||
auto& rip = *reinterpret_cast<ipv4::header*>(reply_buf);
|
|
||||||
rip.eth.dst = dst_mac;
|
|
||||||
rip.eth.src = state.mac;
|
|
||||||
rip.eth.ethertype = eth::ETH_IPV4;
|
|
||||||
rip.ver_ihl = 0x45;
|
|
||||||
rip.dscp_ecn = 0;
|
|
||||||
rip.total_len = __builtin_bswap16(ip_total);
|
|
||||||
rip.identification = 0;
|
|
||||||
rip.flags_frag = 0;
|
|
||||||
rip.ttl = 64;
|
|
||||||
rip.protocol = 17;
|
|
||||||
rip.checksum = 0;
|
|
||||||
rip.src = state.ip;
|
|
||||||
rip.dst = dst_ip;
|
|
||||||
rip.checksum = ipv4::checksum(rip.ip_start(), 20);
|
|
||||||
|
|
||||||
auto& rudp = *reinterpret_cast<ipv4::udp_header*>(reply_buf);
|
|
||||||
rudp.src_port = PICOMAP_PORT;
|
|
||||||
rudp.dst_port = dst_port;
|
|
||||||
rudp.length = __builtin_bswap16(8 + resp_data.size());
|
|
||||||
rudp.checksum = 0;
|
|
||||||
|
|
||||||
memcpy(reply_buf + sizeof(ipv4::udp_header), resp_data.data(), resp_data.size());
|
|
||||||
net_send_raw({reply_buf, reply_len});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool mac_match(const eth::mac_addr& dst) {
|
static bool mac_match(const eth::mac_addr& dst) {
|
||||||
return dst == state.mac || dst == eth::MAC_BROADCAST;
|
return dst == state.mac || dst == eth::MAC_BROADCAST || igmp::is_member_mac(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void process_frame(std::span<const uint8_t> frame, span_writer& tx) {
|
static void process_frame(std::span<const uint8_t> frame, span_writer& tx) {
|
||||||
@@ -121,6 +107,8 @@ bool net_init() {
|
|||||||
w6300::open_socket(raw_socket, w6300::protocol::macraw, w6300::sock_flag::none);
|
w6300::open_socket(raw_socket, w6300::protocol::macraw, w6300::sock_flag::none);
|
||||||
w6300::set_interrupt_mask(w6300::ik_sock_0);
|
w6300::set_interrupt_mask(w6300::ik_sock_0);
|
||||||
|
|
||||||
|
igmp::join(igmp::PICOMAP_DISCOVERY_GROUP, state.mac, state.ip, net_send_raw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,28 +5,110 @@
|
|||||||
#include "pico/time.h"
|
#include "pico/time.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "icmp.h"
|
#include "icmp.h"
|
||||||
|
#include "igmp.h"
|
||||||
|
#include "udp.h"
|
||||||
|
#include "parse_buffer.h"
|
||||||
|
#include "prepend_buffer.h"
|
||||||
|
|
||||||
static ResponseTest test_discovery(const responder&) {
|
static void test_discovery_igmp(const responder& resp) {
|
||||||
ResponseTest resp;
|
auto& ns = net_get_state();
|
||||||
resp.pass = true;
|
|
||||||
resp.messages.push_back("TODO: rewrite as deferred test");
|
prepend_buffer<128> buf;
|
||||||
return resp;
|
igmp::prepend_query(buf, ns.mac, ns.ip, igmp::PICOMAP_DISCOVERY_GROUP);
|
||||||
|
net_send_raw(buf.span());
|
||||||
|
|
||||||
|
auto timer = std::make_shared<timer_handle>(nullptr);
|
||||||
|
auto cb = std::make_shared<std::function<void(std::span<const uint8_t>)>>();
|
||||||
|
*cb = [resp, timer, cb](std::span<const uint8_t> frame) {
|
||||||
|
ipv4::ip4_addr group;
|
||||||
|
if (!igmp::parse_report(frame, group)) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (group != igmp::PICOMAP_DISCOVERY_GROUP) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch_cancel_timer(*timer);
|
||||||
|
resp.respond(ResponseTest{true, {"got IGMP report for " + ipv4::to_string(group)}});
|
||||||
|
};
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
|
||||||
|
*timer = dispatch_schedule_ms(5000, [resp]() {
|
||||||
|
resp.respond(ResponseTest{false, {"no IGMP report within 5s"}});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_discovery_info(const responder& resp) {
|
||||||
|
auto& ns = net_get_state();
|
||||||
|
|
||||||
|
eth::mac_addr mcast_mac = igmp::mac_for_ip(igmp::PICOMAP_DISCOVERY_GROUP);
|
||||||
|
static constexpr uint16_t PICOMAP_PORT = __builtin_bswap16(28781);
|
||||||
|
|
||||||
|
prepend_buffer<1514> buf;
|
||||||
|
uint8_t* payload = buf.payload_ptr();
|
||||||
|
span_writer out(payload, 1024);
|
||||||
|
RequestInfo req_msg;
|
||||||
|
auto encoded = encode_response_into(out, 0xFFFF, req_msg);
|
||||||
|
if (!encoded) {
|
||||||
|
resp.respond(ResponseTest{false, {"encode RequestInfo failed"}});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf.append(*encoded);
|
||||||
|
size_t payload_len = *encoded;
|
||||||
|
|
||||||
|
udp::prepend(buf, mcast_mac, ns.mac, ns.ip, igmp::PICOMAP_DISCOVERY_GROUP,
|
||||||
|
PICOMAP_PORT, PICOMAP_PORT, payload_len, 1);
|
||||||
|
net_send_raw(buf.span());
|
||||||
|
|
||||||
|
ipv4::ip4_addr our_ip = ns.ip;
|
||||||
|
|
||||||
|
auto timer = std::make_shared<timer_handle>(nullptr);
|
||||||
|
auto cb = std::make_shared<std::function<void(std::span<const uint8_t>)>>();
|
||||||
|
*cb = [resp, our_ip, timer, cb](std::span<const uint8_t> frame) {
|
||||||
|
parse_buffer pb(frame);
|
||||||
|
auto* eth_hdr = pb.consume<eth::header>();
|
||||||
|
if (!eth_hdr || eth_hdr->ethertype != eth::ETH_IPV4) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* ip = pb.consume<ipv4::header>();
|
||||||
|
if (!ip || ip->protocol != 17) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t options_len = ip->header_len() - sizeof(ipv4::header);
|
||||||
|
if (options_len > 0 && !pb.skip(options_len)) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* uhdr = pb.consume<udp::header>();
|
||||||
|
if (!uhdr || uhdr->src_port != __builtin_bswap16(28781)) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ip->src == our_ip) {
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch_cancel_timer(*timer);
|
||||||
|
resp.respond(ResponseTest{true, {"got info response from " + ipv4::to_string(ip->src)}});
|
||||||
|
};
|
||||||
|
net_add_frame_callback(*cb);
|
||||||
|
|
||||||
|
*timer = dispatch_schedule_ms(5000, [resp]() {
|
||||||
|
resp.respond(ResponseTest{false, {"no info response within 5s"}});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_ping(const responder& resp, ipv4::ip4_addr dst_ip) {
|
static void test_ping(const responder& resp, ipv4::ip4_addr dst_ip) {
|
||||||
auto& ns = net_get_state();
|
auto& ns = net_get_state();
|
||||||
uint16_t ping_id = 0x1234;
|
uint16_t ping_id = 0x1234;
|
||||||
|
|
||||||
uint8_t tx_buf[128];
|
prepend_buffer<128> buf;
|
||||||
size_t len = icmp::build_echo_request(
|
icmp::prepend_echo_request(buf, ns.mac, ns.ip,
|
||||||
std::span{tx_buf}, ns.mac, ns.ip,
|
eth::MAC_BROADCAST, dst_ip, ping_id, 1);
|
||||||
eth::MAC_BROADCAST, dst_ip, ping_id, 1);
|
net_send_raw(buf.span());
|
||||||
if (len == 0) {
|
|
||||||
resp.respond(ResponseTest{false, {"build_echo_request failed"}});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
net_send_raw(std::span<const uint8_t>{tx_buf, len});
|
|
||||||
|
|
||||||
ipv4::ip4_addr our_ip = ns.ip;
|
ipv4::ip4_addr our_ip = ns.ip;
|
||||||
|
|
||||||
@@ -69,7 +151,8 @@ struct test_entry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const std::unordered_map<std::string_view, test_entry> tests = {
|
static const std::unordered_map<std::string_view, test_entry> tests = {
|
||||||
{"discovery", {test_discovery, nullptr}},
|
{"discovery_igmp", {nullptr, test_discovery_igmp}},
|
||||||
|
{"discovery_info", {nullptr, test_discovery_info}},
|
||||||
{"ping_subnet", {nullptr, test_ping_subnet}},
|
{"ping_subnet", {nullptr, test_ping_subnet}},
|
||||||
{"ping_global", {nullptr, test_ping_global}},
|
{"ping_global", {nullptr, test_ping_global}},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user