#include "lwip/opt.h"
#if LWIP_IPV4 && LWIP_ACD
#include <string.h>
#include "lwip/acd.h"
#include "lwip/prot/acd.h"
#define ACD_FOREACH(acd, acd_list) for ((acd) = acd_list; (acd) != NULL; (acd) = (acd)->next)
#define ACD_TICKS_PER_SECOND (1000 / ACD_TMR_INTERVAL)
#ifdef LWIP_RAND
#define LWIP_ACD_RAND(netif, acd) LWIP_RAND()
#else
#ifdef LWIP_AUTOIP_RAND
#include "lwip/autoip.h"
#define LWIP_ACD_RAND(netif, acd) LWIP_AUTOIP_RAND(netif)
#else
#define LWIP_ACD_RAND(netif, acd) ((((u32_t)((netif->hwaddr[5]) & 0xff) << 24) | \
((u32_t)((netif->hwaddr[3]) & 0xff) << 16) | \
((u32_t)((netif->hwaddr[2]) & 0xff) << 8) | \
((u32_t)((netif->hwaddr[4]) & 0xff))) + \
(acd->sent_num))
#endif
#endif
#define ACD_RANDOM_PROBE_WAIT(netif, acd) (LWIP_ACD_RAND(netif, acd) % \
(PROBE_WAIT * ACD_TICKS_PER_SECOND))
#define ACD_RANDOM_PROBE_INTERVAL(netif, acd) ((LWIP_ACD_RAND(netif, acd) % \
((PROBE_MAX - PROBE_MIN) * ACD_TICKS_PER_SECOND)) + \
(PROBE_MIN * ACD_TICKS_PER_SECOND ))
static void acd_restart(struct netif *netif, struct acd *acd);
static void acd_handle_arp_conflict(struct netif *netif, struct acd *acd);
static void acd_put_in_passive_mode(struct netif *netif, struct acd *acd);
err_t
acd_add(struct netif *netif, struct acd *acd,
acd_conflict_callback_t acd_conflict_callback)
{
struct acd *acd2;
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("acd_conflict_callback != NULL", acd_conflict_callback != NULL);
acd->acd_conflict_callback = acd_conflict_callback;
for (acd2 = netif->acd_list; acd2 != NULL; acd2 = acd2->next) {
if (acd2 == acd) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_add(): acd already added to list\n"));
return ERR_OK;
}
}
acd->next = netif->acd_list;
netif->acd_list = acd;
return ERR_OK;
}
void
acd_remove(struct netif *netif, struct acd *acd)
{
struct acd *acd2, *prev = NULL;
LWIP_ASSERT_CORE_LOCKED();
for (acd2 = netif->acd_list; acd2 != NULL; acd2 = acd2->next) {
if (acd2 == acd) {
if (prev) {
prev->next = acd->next;
} else {
netif->acd_list = acd->next;
}
return;
}
prev = acd2;
}
LWIP_ASSERT(("acd_remove(): acd not on list\n"), 0);
}
err_t
acd_start(struct netif *netif, struct acd *acd, ip4_addr_t ipaddr)
{
err_t result = ERR_OK;
LWIP_UNUSED_ARG(netif);
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_start(netif=%p) %c%c%"U16_F"\n",
(void *)netif, netif->name[0],
netif->name[1], (u16_t)netif->num));
acd->sent_num = 0;
acd->lastconflict = 0;
ip4_addr_copy(acd->ipaddr, ipaddr);
acd->state = ACD_STATE_PROBE_WAIT;
acd->ttw = (u16_t)(ACD_RANDOM_PROBE_WAIT(netif, acd));
return result;
}
err_t
acd_stop(struct acd *acd)
{
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("acd_stop\n"));
if (acd != NULL) {
acd->state = ACD_STATE_OFF;
}
return ERR_OK;
}
void
acd_network_changed_link_down(struct netif *netif)
{
struct acd *acd;
ACD_FOREACH(acd, netif->acd_list) {
acd_stop(acd);
}
}
void
acd_tmr(void)
{
struct netif *netif;
struct acd *acd;
NETIF_FOREACH(netif) {
ACD_FOREACH(acd, netif->acd_list) {
if (acd->lastconflict > 0) {
acd->lastconflict--;
}
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE,
("acd_tmr() ACD-State: %"U16_F", ttw=%"U16_F"\n",
(u16_t)(acd->state), acd->ttw));
if (acd->ttw > 0) {
acd->ttw--;
}
switch (acd->state) {
case ACD_STATE_PROBE_WAIT:
case ACD_STATE_PROBING:
if (acd->ttw == 0) {
acd->state = ACD_STATE_PROBING;
etharp_acd_probe(netif, &acd->ipaddr);
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE,
("acd_tmr() PROBING Sent Probe\n"));
acd->sent_num++;
if (acd->sent_num >= PROBE_NUM) {
acd->state = ACD_STATE_ANNOUNCE_WAIT;
acd->sent_num = 0;
acd->ttw = (u16_t)(ANNOUNCE_WAIT * ACD_TICKS_PER_SECOND);
} else {
acd->ttw = (u16_t)(ACD_RANDOM_PROBE_INTERVAL(netif, acd));
}
}
break;
case ACD_STATE_ANNOUNCE_WAIT:
case ACD_STATE_ANNOUNCING:
if (acd->ttw == 0) {
if (acd->sent_num == 0) {
acd->state = ACD_STATE_ANNOUNCING;
acd->num_conflicts = 0;
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_tmr(): changing state to ANNOUNCING: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
ip4_addr1_16(&acd->ipaddr), ip4_addr2_16(&acd->ipaddr),
ip4_addr3_16(&acd->ipaddr), ip4_addr4_16(&acd->ipaddr)));
}
etharp_acd_announce(netif, &acd->ipaddr);
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE,
("acd_tmr() ANNOUNCING Sent Announce\n"));
acd->ttw = ANNOUNCE_INTERVAL * ACD_TICKS_PER_SECOND;
acd->sent_num++;
if (acd->sent_num >= ANNOUNCE_NUM) {
acd->state = ACD_STATE_ONGOING;
acd->sent_num = 0;
acd->ttw = 0;
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_tmr(): changing state to ONGOING: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
ip4_addr1_16(&acd->ipaddr), ip4_addr2_16(&acd->ipaddr),
ip4_addr3_16(&acd->ipaddr), ip4_addr4_16(&acd->ipaddr)));
acd->acd_conflict_callback(netif, ACD_IP_OK);
}
}
break;
case ACD_STATE_RATE_LIMIT:
if (acd->ttw == 0) {
acd_stop(acd);
acd->acd_conflict_callback(netif, ACD_RESTART_CLIENT);
}
break;
default:
break;
}
}
}
}
static void
acd_restart(struct netif *netif, struct acd *acd)
{
acd->num_conflicts++;
acd->acd_conflict_callback(netif, ACD_DECLINE);
if (acd->num_conflicts >= MAX_CONFLICTS) {
acd->state = ACD_STATE_RATE_LIMIT;
acd->ttw = (u16_t)(RATE_LIMIT_INTERVAL * ACD_TICKS_PER_SECOND);
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
("acd_restart(): rate limiting initiated. too many conflicts\n"));
}
else {
acd_stop(acd);
acd->acd_conflict_callback(netif, ACD_RESTART_CLIENT);
}
}
void
acd_arp_reply(struct netif *netif, struct etharp_hdr *hdr)
{
struct acd *acd;
ip4_addr_t sipaddr, dipaddr;
struct eth_addr netifaddr;
SMEMCPY(netifaddr.addr, netif->hwaddr, ETH_HWADDR_LEN);
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE, ("acd_arp_reply()\n"));
ACD_FOREACH(acd, netif->acd_list) {
switch(acd->state) {
case ACD_STATE_OFF:
case ACD_STATE_RATE_LIMIT:
default:
break;
case ACD_STATE_PROBE_WAIT:
case ACD_STATE_PROBING:
case ACD_STATE_ANNOUNCE_WAIT:
if ((ip4_addr_eq(&sipaddr, &acd->ipaddr)) ||
(ip4_addr_isany_val(sipaddr) &&
ip4_addr_eq(&dipaddr, &acd->ipaddr) &&
!eth_addr_eq(&netifaddr, &hdr->shwaddr))) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
("acd_arp_reply(): Probe Conflict detected\n"));
acd_restart(netif, acd);
}
break;
case ACD_STATE_ANNOUNCING:
case ACD_STATE_ONGOING:
case ACD_STATE_PASSIVE_ONGOING:
if (ip4_addr_eq(&sipaddr, &acd->ipaddr) &&
!eth_addr_eq(&netifaddr, &hdr->shwaddr)) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
("acd_arp_reply(): Conflicting ARP-Packet detected\n"));
acd_handle_arp_conflict(netif, acd);
}
break;
}
}
}
static void
acd_handle_arp_conflict(struct netif *netif, struct acd *acd)
{
if (acd->state == ACD_STATE_PASSIVE_ONGOING) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_handle_arp_conflict(): conflict when we are in passive mode -> back off\n"));
acd_stop(acd);
acd->acd_conflict_callback(netif, ACD_DECLINE);
}
else {
if (acd->lastconflict > 0) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_handle_arp_conflict(): conflict within DEFEND_INTERVAL -> retreating\n"));
acd_restart(netif, acd);
} else {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_handle_arp_conflict(): we are defending, send ARP Announce\n"));
etharp_acd_announce(netif, &acd->ipaddr);
acd->lastconflict = DEFEND_INTERVAL * ACD_TICKS_PER_SECOND;
}
}
}
static void
acd_put_in_passive_mode(struct netif *netif, struct acd *acd)
{
switch(acd->state) {
case ACD_STATE_OFF:
case ACD_STATE_PASSIVE_ONGOING:
default:
break;
case ACD_STATE_PROBE_WAIT:
case ACD_STATE_PROBING:
case ACD_STATE_ANNOUNCE_WAIT:
case ACD_STATE_RATE_LIMIT:
acd_stop(acd);
acd->acd_conflict_callback(netif, ACD_DECLINE);
break;
case ACD_STATE_ANNOUNCING:
case ACD_STATE_ONGOING:
acd->state = ACD_STATE_PASSIVE_ONGOING;
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_put_in_passive_mode()\n"));
break;
}
}
void
acd_netif_ip_addr_changed(struct netif *netif, const ip_addr_t *old_addr,
const ip_addr_t *new_addr)
{
struct acd *acd;
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_netif_ip_addr_changed(): Address changed\n"));
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_netif_ip_addr_changed(): old address = %s\n", ipaddr_ntoa(old_addr)));
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_netif_ip_addr_changed(): new address = %s\n", ipaddr_ntoa(new_addr)));
if (ip_addr_isany(old_addr) || ip_addr_isany(new_addr)) {
return;
}
ACD_FOREACH(acd, netif->acd_list) {
if(ip4_addr_eq(&acd->ipaddr, ip_2_ip4(old_addr))) {
if (ip_addr_islinklocal(old_addr) && !ip_addr_islinklocal(new_addr)) {
LWIP_DEBUGF(ACD_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("acd_netif_ip_addr_changed(): changed from LL to routable address\n"));
acd_put_in_passive_mode(netif, acd);
}
}
}
}
#endif