Skip to content

Commit

Permalink
Merge pull request #2926 from cesanta/arp
Browse files Browse the repository at this point in the history
Fix gw ARP. Make ARP probe public. Add ARP event
  • Loading branch information
scaprile authored Oct 10, 2024
2 parents 244536e + 504ef94 commit f77a7ad
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 94 deletions.
71 changes: 46 additions & 25 deletions mongoose.c
Original file line number Diff line number Diff line change
Expand Up @@ -5107,7 +5107,7 @@ static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
return n;
}

static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) {
struct eth *eth = (struct eth *) ifp->tx.buf;
struct arp *arp = (struct arp *) (eth + 1);
memset(eth->dst, 255, sizeof(eth->dst));
Expand All @@ -5118,6 +5118,7 @@ static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
arp->plen = 4;
arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha));
ether_output(ifp, PDIFF(eth, arp + 1));
}

Expand All @@ -5126,7 +5127,9 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
arp_ask(ifp, ifp->gw); // unsolicited GW ARP request
} else if (ifp->state == MG_TCPIP_STATE_IP) {
MG_ERROR(("Got IP"));
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request
} else if (ifp->state == MG_TCPIP_STATE_UP) {
MG_ERROR(("Link up"));
srand((unsigned int) mg_millis());
Expand Down Expand Up @@ -5276,8 +5279,12 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
} else if (pkt->arp->op == mg_htons(2)) {
if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
if (pkt->arp->spa == ifp->gw) {
// Got response for the GW ARP request. Set ifp->gwmac
// Got response for the GW ARP request. Set ifp->gwmac and IP -> READY
memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
if (ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY;
onstatechange(ifp);
}
} else {
struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
if (c != NULL && c->is_arplooking) {
Expand Down Expand Up @@ -5352,7 +5359,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
// assume DHCP server = router until ARP resolves
memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
ifp->state = MG_TCPIP_STATE_READY; // BOUND state
ifp->state = MG_TCPIP_STATE_IP; // BOUND state
uint64_t rand;
mg_random(&rand, sizeof(rand));
srand((unsigned int) (rand + mg_millis()));
Expand Down Expand Up @@ -5400,7 +5407,7 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
res.magic = pkt->dhcp->magic;
res.xid = pkt->dhcp->xid;
if (ifp->enable_get_gateway) {
ifp->gw = res.yiaddr;
ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's
memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
}
tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
Expand Down Expand Up @@ -5785,6 +5792,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
if (pkt.eth->type == mg_htons(0x806)) {
pkt.arp = (struct arp *) (pkt.eth + 1);
if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated
mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw);
rx_arp(ifp, &pkt);
} else if (pkt.eth->type == mg_htons(0x86dd)) {
pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
Expand Down Expand Up @@ -5816,40 +5824,53 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {

#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
if (expired_1000ms) {
const char *names[] = {"down", "up", "req", "ready"};
const char *names[] = {"down", "up", "req", "ip", "ready"};
MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
ifp->ndrop, ifp->nerr));
}
#endif
// Handle gw ARP request timeout, order is important
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC
onstatechange(ifp);
}
// Handle physical interface up/down status
if (expired_1000ms && ifp->driver->up) {
bool up = ifp->driver->up(ifp);
bool current = ifp->state != MG_TCPIP_STATE_DOWN;
if (up != current) {
ifp->state = up == false ? MG_TCPIP_STATE_DOWN
: ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_READY;
if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
if (up != current) { // link state has changed
ifp->state = up == false ? MG_TCPIP_STATE_DOWN
: ifp->enable_dhcp_client || ifp->ip == 0
? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_IP;
onstatechange(ifp);
} else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
ifp->ip) {
ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP
onstatechange(ifp);
}
if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
}
if (ifp->state == MG_TCPIP_STATE_DOWN) return;

// DHCP RFC-2131 (4.4)
if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP
onstatechange(ifp);
} else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
((ifp->now / 1000) % 60) == 0) {
// hack: 30 min before deadline, try to rebind (4.3.6) every min
tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
} // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
if (ifp->enable_dhcp_client && expired_1000ms) {
if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP
onstatechange(ifp);
} else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
((ifp->now / 1000) % 60) == 0) {
// hack: 30 min before deadline, try to rebind (4.3.6) every min
tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
} // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
}
}

// Read data from the network
Expand Down Expand Up @@ -5943,7 +5964,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
ifp->mtu = MG_TCPIP_MTU_DEFAULT;
mgr->extraconnsize = sizeof(struct connstate);
if (ifp->ip == 0) ifp->enable_dhcp_client = true;
memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set best-effort to bcast
mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
// MG_EPHEMERAL_PORT_BASE to 65535
Expand Down Expand Up @@ -5996,7 +6017,7 @@ void mg_connect_resolved(struct mg_connection *c) {
rem_ip != ifp->gw) { // skip if gw (onstatechange -> READY -> ARP)
// If we're in the same LAN, fire an ARP lookup.
MG_DEBUG(("%lu ARP lookup...", c->id));
arp_ask(ifp, rem_ip);
mg_tcpip_arp_request(ifp, rem_ip, NULL);
settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1;
} else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
Expand Down
8 changes: 6 additions & 2 deletions mongoose.h
Original file line number Diff line number Diff line change
Expand Up @@ -2739,6 +2739,8 @@ enum {
MG_TCPIP_EV_ST_CHG, // state change uint8_t * (&ifp->state)
MG_TCPIP_EV_DHCP_DNS, // DHCP DNS assignment uint32_t *ipaddr
MG_TCPIP_EV_DHCP_SNTP, // DHCP SNTP assignment uint32_t *ipaddr
MG_TCPIP_EV_ARP, // Got ARP packet struct mg_str *
MG_TCPIP_EV_TIMER_1S, // 1 second timer NULL
MG_TCPIP_EV_USER // Starting ID for user events
};

Expand Down Expand Up @@ -2775,13 +2777,15 @@ struct mg_tcpip_if {
uint8_t state; // Current state
#define MG_TCPIP_STATE_DOWN 0 // Interface is down
#define MG_TCPIP_STATE_UP 1 // Interface is up
#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP
#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned
#define MG_TCPIP_STATE_REQ 2 // Interface is up, DHCP REQUESTING state
#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned
#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work
};

void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
void mg_tcpip_free(struct mg_tcpip_if *);
void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac);

extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
Expand Down
71 changes: 46 additions & 25 deletions src/net_builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
return n;
}

static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) {
struct eth *eth = (struct eth *) ifp->tx.buf;
struct arp *arp = (struct arp *) (eth + 1);
memset(eth->dst, 255, sizeof(eth->dst));
Expand All @@ -196,6 +196,7 @@ static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
arp->plen = 4;
arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha));
ether_output(ifp, PDIFF(eth, arp + 1));
}

Expand All @@ -204,7 +205,9 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
arp_ask(ifp, ifp->gw); // unsolicited GW ARP request
} else if (ifp->state == MG_TCPIP_STATE_IP) {
MG_ERROR(("Got IP"));
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request
} else if (ifp->state == MG_TCPIP_STATE_UP) {
MG_ERROR(("Link up"));
srand((unsigned int) mg_millis());
Expand Down Expand Up @@ -354,8 +357,12 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
} else if (pkt->arp->op == mg_htons(2)) {
if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
if (pkt->arp->spa == ifp->gw) {
// Got response for the GW ARP request. Set ifp->gwmac
// Got response for the GW ARP request. Set ifp->gwmac and IP -> READY
memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
if (ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY;
onstatechange(ifp);
}
} else {
struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
if (c != NULL && c->is_arplooking) {
Expand Down Expand Up @@ -430,7 +437,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
// assume DHCP server = router until ARP resolves
memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
ifp->state = MG_TCPIP_STATE_READY; // BOUND state
ifp->state = MG_TCPIP_STATE_IP; // BOUND state
uint64_t rand;
mg_random(&rand, sizeof(rand));
srand((unsigned int) (rand + mg_millis()));
Expand Down Expand Up @@ -478,7 +485,7 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
res.magic = pkt->dhcp->magic;
res.xid = pkt->dhcp->xid;
if (ifp->enable_get_gateway) {
ifp->gw = res.yiaddr;
ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's
memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
}
tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
Expand Down Expand Up @@ -863,6 +870,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
if (pkt.eth->type == mg_htons(0x806)) {
pkt.arp = (struct arp *) (pkt.eth + 1);
if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated
mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw);
rx_arp(ifp, &pkt);
} else if (pkt.eth->type == mg_htons(0x86dd)) {
pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
Expand Down Expand Up @@ -894,40 +902,53 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {

#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
if (expired_1000ms) {
const char *names[] = {"down", "up", "req", "ready"};
const char *names[] = {"down", "up", "req", "ip", "ready"};
MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
ifp->ndrop, ifp->nerr));
}
#endif
// Handle gw ARP request timeout, order is important
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC
onstatechange(ifp);
}
// Handle physical interface up/down status
if (expired_1000ms && ifp->driver->up) {
bool up = ifp->driver->up(ifp);
bool current = ifp->state != MG_TCPIP_STATE_DOWN;
if (up != current) {
ifp->state = up == false ? MG_TCPIP_STATE_DOWN
: ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_READY;
if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
if (up != current) { // link state has changed
ifp->state = up == false ? MG_TCPIP_STATE_DOWN
: ifp->enable_dhcp_client || ifp->ip == 0
? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_IP;
onstatechange(ifp);
} else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
ifp->ip) {
ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP
onstatechange(ifp);
}
if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
}
if (ifp->state == MG_TCPIP_STATE_DOWN) return;

// DHCP RFC-2131 (4.4)
if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP
onstatechange(ifp);
} else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
((ifp->now / 1000) % 60) == 0) {
// hack: 30 min before deadline, try to rebind (4.3.6) every min
tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
} // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
if (ifp->enable_dhcp_client && expired_1000ms) {
if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP
onstatechange(ifp);
} else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
((ifp->now / 1000) % 60) == 0) {
// hack: 30 min before deadline, try to rebind (4.3.6) every min
tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
} // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
}
}

// Read data from the network
Expand Down Expand Up @@ -1021,7 +1042,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
ifp->mtu = MG_TCPIP_MTU_DEFAULT;
mgr->extraconnsize = sizeof(struct connstate);
if (ifp->ip == 0) ifp->enable_dhcp_client = true;
memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set best-effort to bcast
mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
// MG_EPHEMERAL_PORT_BASE to 65535
Expand Down Expand Up @@ -1074,7 +1095,7 @@ void mg_connect_resolved(struct mg_connection *c) {
rem_ip != ifp->gw) { // skip if gw (onstatechange -> READY -> ARP)
// If we're in the same LAN, fire an ARP lookup.
MG_DEBUG(("%lu ARP lookup...", c->id));
arp_ask(ifp, rem_ip);
mg_tcpip_arp_request(ifp, rem_ip, NULL);
settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1;
} else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
Expand Down
8 changes: 6 additions & 2 deletions src/net_builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ enum {
MG_TCPIP_EV_ST_CHG, // state change uint8_t * (&ifp->state)
MG_TCPIP_EV_DHCP_DNS, // DHCP DNS assignment uint32_t *ipaddr
MG_TCPIP_EV_DHCP_SNTP, // DHCP SNTP assignment uint32_t *ipaddr
MG_TCPIP_EV_ARP, // Got ARP packet struct mg_str *
MG_TCPIP_EV_TIMER_1S, // 1 second timer NULL
MG_TCPIP_EV_USER // Starting ID for user events
};

Expand Down Expand Up @@ -58,13 +60,15 @@ struct mg_tcpip_if {
uint8_t state; // Current state
#define MG_TCPIP_STATE_DOWN 0 // Interface is down
#define MG_TCPIP_STATE_UP 1 // Interface is up
#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP
#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned
#define MG_TCPIP_STATE_REQ 2 // Interface is up, DHCP REQUESTING state
#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned
#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work
};

void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
void mg_tcpip_free(struct mg_tcpip_if *);
void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac);

extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
Expand Down
Loading

0 comments on commit f77a7ad

Please sign in to comment.