net: Add link-local addressing support

Code based on networking/zcip.c in busybox
commit 8531d76a15890c2c535908ce888b2e2aed35b172

Signed-off-by: Joe Hershberger <joe.hershberger@ni.com>
This commit is contained in:
Joe Hershberger 2012-05-23 08:00:12 +00:00
parent 228041893c
commit d22c338e07
8 changed files with 490 additions and 2 deletions

10
README
View File

@ -785,6 +785,8 @@ The following options need to be configured:
CONFIG_CMD_JFFS2 * JFFS2 Support
CONFIG_CMD_KGDB * kgdb
CONFIG_CMD_LDRINFO ldrinfo (display Blackfin loader)
CONFIG_CMD_LINK_LOCAL * link-local IP address auto-configuration
(169.254.*.*)
CONFIG_CMD_LOADB loadb
CONFIG_CMD_LOADS loads
CONFIG_CMD_MD5SUM print md5 message digest
@ -1633,6 +1635,14 @@ The following options need to be configured:
the DHCP timeout and retry process takes a longer than
this delay.
- Link-local IP address negotiation:
Negotiate with other link-local clients on the local network
for an address that doesn't require explicit configuration.
This is especially useful if a DHCP server cannot be guaranteed
to exist in all environments that the device must operate.
See doc/README.link-local for more information.
- CDP Options:
CONFIG_CDP_DEVICE_ID

View File

@ -428,3 +428,34 @@ U_BOOT_CMD(
);
#endif /* CONFIG_CMD_DNS */
#if defined(CONFIG_CMD_LINK_LOCAL)
static int do_link_local(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
char tmp[22];
if (NetLoop(LINKLOCAL) < 0)
return 1;
NetOurGatewayIP = 0;
ip_to_string(NetOurGatewayIP, tmp);
setenv("gatewayip", tmp);
ip_to_string(NetOurSubnetMask, tmp);
setenv("netmask", tmp);
ip_to_string(NetOurIP, tmp);
setenv("ipaddr", tmp);
setenv("llipaddr", tmp); /* store this for next time */
return 0;
}
U_BOOT_CMD(
linklocal, 1, 1, do_link_local,
"acquire a network IP address using the link-local protocol",
""
);
#endif /* CONFIG_CMD_LINK_LOCAL */

76
doc/README.link-local Normal file
View File

@ -0,0 +1,76 @@
------------------------------------------
Link-local IP address auto-configuration
------------------------------------------
Negotiate with other link-local clients on the local network
for an address that doesn't require explicit configuration.
This is especially useful if a DHCP server cannot be guaranteed
to exist in all environments that the device must operate.
This is an implementation of RFC3927.
----------
Commands
----------
When CONFIG_CMD_LINK_LOCAL is defined in the board config file,
the "linklocal" command is available. This running this will
take approximately 5 seconds while the address is negotiated.
------------------------
Environment interation
------------------------
The "llipaddr" variable is set with the most recently
negotiated address and is preferred in future negotiations.
The "ipaddr", "netmask", and "gatewayip" variables are set
after successful negotiation to enable network access.
-------------
Limitations
-------------
RFC3927 requires that addresses are continuously checked to
avoid conflicts, however this can only happen when the NetLoop
is getting called. It is possible for a conflict to go undetected
until a command that accesses the network is executed.
Using NetConsole is one way to ensure that NetLoop is always
processing packets and monitoring for conflicts.
This is also not a concern if the feature is use to connect
directly to another machine that may not be running a DHCP server.
----------------
Example script
----------------
This script allows use of DHCP and/or Link-local controlled
by env variables. It depends on CONFIG_CMD_LINK_LOCAL, CONFIG_CMD_DHCP,
and CONFIG_BOOTP_MAY_FAIL.
If both fail or are disabled, static settings are used.
#define CONFIG_EXTRA_ENV_SETTINGS \
"ipconfigcmd=if test \\\"$dhcpenabled\\\" -ne 0;" \
"then " \
"dhcpfail=0;dhcp || dhcpfail=1;" \
"else " \
"dhcpfail=-1;" \
"fi;" \
"if test \\\"$linklocalenabled\\\" -ne 0 -a " \
"\\\"$dhcpfail\\\" -ne 0;" \
"then " \
"linklocal;" \
"llfail=0;" \
"else " \
"llfail=-1;" \
"fi;" \
"if test \\\"$llfail\\\" -ne 0 -a " \
"\\\"$dhcpfail\\\" -ne 0; " \
"then " \
"setenv ipaddr $sipaddr; " \
"setenv netmask $snetmask; " \
"setenv gatewayip $sgatewayip; " \
"fi;\0" \

View File

@ -395,7 +395,7 @@ extern int NetRestartWrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, DNS, NFS, CDP, NETCONS, SNTP,
TFTPSRV, TFTPPUT
TFTPSRV, TFTPPUT, LINKLOCAL
};
/* from net/net.c */

View File

@ -32,15 +32,17 @@ COBJS-$(CONFIG_CMD_NET) += bootp.o
COBJS-$(CONFIG_CMD_CDP) += cdp.o
COBJS-$(CONFIG_CMD_DNS) += dns.o
COBJS-$(CONFIG_CMD_NET) += eth.o
COBJS-$(CONFIG_CMD_LINK_LOCAL) += link_local.o
COBJS-$(CONFIG_CMD_NET) += net.o
COBJS-$(CONFIG_BOOTP_RANDOM_DELAY) += net_rand.o
COBJS-$(CONFIG_CMD_LINK_LOCAL) += net_rand.o
COBJS-$(CONFIG_CMD_NFS) += nfs.o
COBJS-$(CONFIG_CMD_PING) += ping.o
COBJS-$(CONFIG_CMD_RARP) += rarp.o
COBJS-$(CONFIG_CMD_SNTP) += sntp.o
COBJS-$(CONFIG_CMD_NET) += tftp.o
COBJS := $(COBJS-y)
COBJS := $(sort $(COBJS-y))
SRCS := $(COBJS:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS))

332
net/link_local.c Normal file
View File

@ -0,0 +1,332 @@
/*
* RFC3927 ZeroConf IPv4 Link-Local addressing
* (see <http://www.zeroconf.org/>)
*
* Copied from BusyBox - networking/zcip.c
*
* Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com)
* Copyright (C) 2004 by David Brownell
* Copyright (C) 2010 by Joe Hershberger
*
* Licensed under the GPL v2 or later
*/
#include <common.h>
#include <net.h>
#include "arp.h"
#include "net_rand.h"
/* We don't need more than 32 bits of the counter */
#define MONOTONIC_MS() ((unsigned)get_timer(0) * (1000 / CONFIG_SYS_HZ))
enum {
/* 169.254.0.0 */
LINKLOCAL_ADDR = 0xa9fe0000,
IN_CLASSB_NET = 0xffff0000,
IN_CLASSB_HOST = 0x0000ffff,
/* protocol timeout parameters, specified in seconds */
PROBE_WAIT = 1,
PROBE_MIN = 1,
PROBE_MAX = 2,
PROBE_NUM = 3,
MAX_CONFLICTS = 10,
RATE_LIMIT_INTERVAL = 60,
ANNOUNCE_WAIT = 2,
ANNOUNCE_NUM = 2,
ANNOUNCE_INTERVAL = 2,
DEFEND_INTERVAL = 10
};
/* States during the configuration process. */
static enum ll_state_t {
PROBE = 0,
RATE_LIMIT_PROBE,
ANNOUNCE,
MONITOR,
DEFEND,
DISABLED
} state = DISABLED;
static IPaddr_t ip;
static int timeout_ms = -1;
static unsigned deadline_ms;
static unsigned conflicts;
static unsigned nprobes;
static unsigned nclaims;
static int ready;
static void link_local_timeout(void);
/**
* Pick a random link local IP address on 169.254/16, except that
* the first and last 256 addresses are reserved.
*/
static IPaddr_t pick(void)
{
unsigned tmp;
do {
tmp = rand() & IN_CLASSB_HOST;
} while (tmp > (IN_CLASSB_HOST - 0x0200));
return (IPaddr_t) htonl((LINKLOCAL_ADDR + 0x0100) + tmp);
}
/**
* Return milliseconds of random delay, up to "secs" seconds.
*/
static inline unsigned random_delay_ms(unsigned secs)
{
return rand() % (secs * 1000);
}
static void configure_wait(void)
{
if (timeout_ms == -1)
return;
/* poll, being ready to adjust current timeout */
if (!timeout_ms)
timeout_ms = random_delay_ms(PROBE_WAIT);
/* set deadline_ms to the point in time when we timeout */
deadline_ms = MONOTONIC_MS() + timeout_ms;
debug("...wait %d %s nprobes=%u, nclaims=%u\n",
timeout_ms, eth_get_name(), nprobes, nclaims);
NetSetTimeout(timeout_ms, link_local_timeout);
}
void link_local_start(void)
{
ip = getenv_IPaddr("llipaddr");
if (ip != 0 && (ip & IN_CLASSB_NET) != LINKLOCAL_ADDR) {
puts("invalid link address");
net_set_state(NETLOOP_FAIL);
return;
}
NetOurSubnetMask = IN_CLASSB_NET;
srand_mac();
if (ip == 0)
ip = pick();
state = PROBE;
timeout_ms = 0;
conflicts = 0;
nprobes = 0;
nclaims = 0;
ready = 0;
configure_wait();
}
static void link_local_timeout(void)
{
switch (state) {
case PROBE:
/* timeouts in the PROBE state mean no conflicting ARP packets
have been received, so we can progress through the states */
if (nprobes < PROBE_NUM) {
nprobes++;
debug("probe/%u %s@%pI4\n",
nprobes, eth_get_name(), &ip);
arp_raw_request(0, NetEtherNullAddr, ip);
timeout_ms = PROBE_MIN * 1000;
timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN);
} else {
/* Switch to announce state */
state = ANNOUNCE;
nclaims = 0;
debug("announce/%u %s@%pI4\n",
nclaims, eth_get_name(), &ip);
arp_raw_request(ip, NetOurEther, ip);
timeout_ms = ANNOUNCE_INTERVAL * 1000;
}
break;
case RATE_LIMIT_PROBE:
/* timeouts in the RATE_LIMIT_PROBE state mean no conflicting
ARP packets have been received, so we can move immediately
to the announce state */
state = ANNOUNCE;
nclaims = 0;
debug("announce/%u %s@%pI4\n",
nclaims, eth_get_name(), &ip);
arp_raw_request(ip, NetOurEther, ip);
timeout_ms = ANNOUNCE_INTERVAL * 1000;
break;
case ANNOUNCE:
/* timeouts in the ANNOUNCE state mean no conflicting ARP
packets have been received, so we can progress through
the states */
if (nclaims < ANNOUNCE_NUM) {
nclaims++;
debug("announce/%u %s@%pI4\n",
nclaims, eth_get_name(), &ip);
arp_raw_request(ip, NetOurEther, ip);
timeout_ms = ANNOUNCE_INTERVAL * 1000;
} else {
/* Switch to monitor state */
state = MONITOR;
printf("Successfully assigned %pI4\n", &ip);
NetCopyIP(&NetOurIP, &ip);
ready = 1;
conflicts = 0;
timeout_ms = -1;
/* Never timeout in the monitor state */
NetSetTimeout(0, NULL);
/* NOTE: all other exit paths should deconfig ... */
net_set_state(NETLOOP_SUCCESS);
return;
}
break;
case DEFEND:
/* We won! No ARP replies, so just go back to monitor */
state = MONITOR;
timeout_ms = -1;
conflicts = 0;
break;
default:
/* Invalid, should never happen. Restart the whole protocol */
state = PROBE;
ip = pick();
timeout_ms = 0;
nprobes = 0;
nclaims = 0;
break;
}
configure_wait();
}
void link_local_receive_arp(struct arp_hdr *arp, int len)
{
int source_ip_conflict;
int target_ip_conflict;
if (state == DISABLED)
return;
/* We need to adjust the timeout in case we didn't receive a
conflicting packet. */
if (timeout_ms > 0) {
unsigned diff = deadline_ms - MONOTONIC_MS();
if ((int)(diff) < 0) {
/* Current time is greater than the expected timeout
time. This should never happen */
debug("missed an expected timeout\n");
timeout_ms = 0;
} else {
debug("adjusting timeout\n");
timeout_ms = diff | 1; /* never 0 */
}
}
/*
* XXX Don't bother with ethernet link just yet
if ((fds[0].revents & POLLIN) == 0) {
if (fds[0].revents & POLLERR) {
// FIXME: links routinely go down;
// this shouldn't necessarily exit.
bb_error_msg("iface %s is down", eth_get_name());
if (ready) {
run(argv, "deconfig", &ip);
}
return EXIT_FAILURE;
}
continue;
}
*/
debug("%s recv arp type=%d, op=%d,\n",
eth_get_name(), ntohs(arp->ar_pro),
ntohs(arp->ar_op));
debug("\tsource=%pM %pI4\n",
&arp->ar_sha,
&arp->ar_spa);
debug("\ttarget=%pM %pI4\n",
&arp->ar_tha,
&arp->ar_tpa);
if (arp->ar_op != htons(ARPOP_REQUEST)
&& arp->ar_op != htons(ARPOP_REPLY)
) {
configure_wait();
return;
}
source_ip_conflict = 0;
target_ip_conflict = 0;
if (memcmp(&arp->ar_spa, &ip, ARP_PLEN) == 0
&& memcmp(&arp->ar_sha, NetOurEther, ARP_HLEN) != 0
) {
source_ip_conflict = 1;
}
if (arp->ar_op == htons(ARPOP_REQUEST)
&& memcmp(&arp->ar_tpa, &ip, ARP_PLEN) == 0
&& memcmp(&arp->ar_tha, NetOurEther, ARP_HLEN) != 0
) {
target_ip_conflict = 1;
}
debug("state = %d, source ip conflict = %d, target ip conflict = %d\n",
state, source_ip_conflict, target_ip_conflict);
switch (state) {
case PROBE:
case ANNOUNCE:
/* When probing or announcing, check for source IP conflicts
and other hosts doing ARP probes (target IP conflicts). */
if (source_ip_conflict || target_ip_conflict) {
conflicts++;
state = PROBE;
if (conflicts >= MAX_CONFLICTS) {
debug("%s ratelimit\n", eth_get_name());
timeout_ms = RATE_LIMIT_INTERVAL * 1000;
state = RATE_LIMIT_PROBE;
}
/* restart the whole protocol */
ip = pick();
timeout_ms = 0;
nprobes = 0;
nclaims = 0;
}
break;
case MONITOR:
/* If a conflict, we try to defend with a single ARP probe */
if (source_ip_conflict) {
debug("monitor conflict -- defending\n");
state = DEFEND;
timeout_ms = DEFEND_INTERVAL * 1000;
arp_raw_request(ip, NetOurEther, ip);
}
break;
case DEFEND:
/* Well, we tried. Start over (on conflict) */
if (source_ip_conflict) {
state = PROBE;
debug("defend conflict -- starting over\n");
ready = 0;
NetOurIP = 0;
/* restart the whole protocol */
ip = pick();
timeout_ms = 0;
nprobes = 0;
nclaims = 0;
}
break;
default:
/* Invalid, should never happen. Restart the whole protocol */
debug("invalid state -- starting over\n");
state = PROBE;
ip = pick();
timeout_ms = 0;
nprobes = 0;
nclaims = 0;
break;
}
configure_wait();
}

24
net/link_local.h Normal file
View File

@ -0,0 +1,24 @@
/*
* RFC3927 ZeroConf IPv4 Link-Local addressing
* (see <http://www.zeroconf.org/>)
*
* Copied from BusyBox - networking/zcip.c
*
* Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com)
* Copyright (C) 2004 by David Brownell
*
* Licensed under the GPL v2 or later
*/
#if defined(CONFIG_CMD_LINK_LOCAL)
#ifndef __LINK_LOCAL_H__
#define __LINK_LOCAL_H__
#include <common.h>
void link_local_receive_arp(struct arp_hdr *arp, int len);
void link_local_start(void);
#endif /* __LINK_LOCAL_H__ */
#endif

View File

@ -23,6 +23,12 @@
* - name of bootfile
* Next step: ARP
*
* LINK_LOCAL:
*
* Prerequisites: - own ethernet address
* We want: - own IP address
* Next step: ARP
*
* RARP:
*
* Prerequisites: - own ethernet address
@ -89,6 +95,7 @@
#if defined(CONFIG_CMD_DNS)
#include "dns.h"
#endif
#include "link_local.h"
#include "nfs.h"
#include "ping.h"
#include "rarp.h"
@ -401,6 +408,11 @@ restart:
case DNS:
DnsStart();
break;
#endif
#if defined(CONFIG_CMD_LINK_LOCAL)
case LINKLOCAL:
link_local_start();
break;
#endif
default:
break;
@ -1194,6 +1206,7 @@ common:
case BOOTP:
case CDP:
case DHCP:
case LINKLOCAL:
if (memcmp(NetOurEther, "\0\0\0\0\0\0", 6) == 0) {
int num = eth_get_dev_index();