Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions appl/lib/dhcpclient.b
Original file line number Diff line number Diff line change
Expand Up @@ -745,8 +745,25 @@ newsession(net: string, ctlifc: ref Sys->FD, device: string, req: ref Bootconf):
if(e != nil)
return (nil, e);
s.mac = mac;
if((e = openconv(s)) != nil)
if((e = opensession(s)) != nil)
return (nil, e);
return (s, nil);
}

#
# The conversation on the client port, and its reader. A lease's
# watchdog closes it while it waits and opens it again to talk: the
# port is a machine's, not an interface's, so a client that held it
# for the whole lease -- the radio's, on the bench Pi -- refused every
# other interface's DHCP for as long as the lease ran, and a cable
# plugged in after boot could not get an address.
#
opensession(s: ref Session): string
{
if(s.dfd != nil)
return nil;
if((e := openconv(s)) != nil)
return e;
#
# Buffered: a reply that lands after this exchange has moved on
# must not leave the reader blocked for ever on a send nobody
Expand All @@ -756,7 +773,7 @@ newsession(net: string, ctlifc: ref Sys->FD, device: string, req: ref Bootconf):
pc := chan of int;
spawn reader(s.dfd, s.rc, pc);
s.rpid = <-pc;
return (s, nil);
return nil;
}

closesession(s: ref Session)
Expand Down Expand Up @@ -1557,10 +1574,13 @@ watchdog(s: ref Session, pc: chan of int)
t1 = lease / 2;
if(t2 <= t1 || t2 >= lease)
t2 = (lease * 7) / 8;
closesession(s); # the port is free while the lease runs
if(napsecs(s, t1))
break;
e := "";
if(s.conf.serverid != nil)
e := opensession(s);
if(e != nil)
;
else if(s.conf.serverid != nil)
e = renew(s, s.conf.serverid, 2);
else
e = "no server identifier";
Expand All @@ -1576,9 +1596,11 @@ watchdog(s: ref Session, pc: chan of int)
if(e == "DHCPNAK")
e = "refused";
else{
closesession(s);
if(napsecs(s, t2 - t1))
break;
e = renew(s, Bcast, 2); # rebinding
if((e = opensession(s)) == nil)
e = renew(s, Bcast, 2); # rebinding
if(e == "DHCPNAK")
e = "refused";
}
Expand All @@ -1596,6 +1618,13 @@ watchdog(s: ref Session, pc: chan of int)
for(;;){
if(s.halt)
break;
if((oe := opensession(s)) != nil){
notify(s, nil, oe);
closesession(s);
if(napsecs(s, Retrywait))
break;
continue;
}
(c, de) := discover(s);
if(de == nil){
if(s.ctlifc != nil && (ae := applycfg(s.net, s.ctlifc, c)) != nil){
Expand All @@ -1607,6 +1636,7 @@ watchdog(s: ref Session, pc: chan of int)
break;
}
notify(s, nil, de);
closesession(s);
if(napsecs(s, Retrywait))
break;
}
Expand All @@ -1621,7 +1651,7 @@ watchdog(s: ref Session, pc: chan of int)
if(dst == nil)
dst = Bcast;
(ok, ci) := parsev4(s.conf.ip);
if(ok){
if(ok && opensession(s) == nil){
s.xid = mkxid();
sendmsg(s, dst, Release, ci, nil, s.conf.get(Oserverid));
}
Expand Down
187 changes: 179 additions & 8 deletions os/init/etherusb.b
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ dev: string;
ntpserver: string; # "ep3.0"
ctl: ref Sys->FD; # #u/usb/<dev>/ctl
ep0: ref Sys->FD; # #u/usb/<dev>/data -- the control endpoint
ipifc: ref Sys->FD; # the ipifc netconfig bound /net/ether0 to; the link watcher's
ipaddr, ipmask: string; # the address on it now; nil when there is none
ipifcno: string; # its number, for the messages
kernelnif := 0; # -k: the link is a kernel driver's, reported in its stats
reqid := 1;

init(nil: ref Draw->Context, argv: list of string)
Expand Down Expand Up @@ -623,13 +627,15 @@ init(nil: ref Draw->Context, argv: list of string)
#
# Close the control endpoint, explicitly. It is a global of
# this module, and the module lives as long as any process of
# it does -- the DHCP, ping and time readers block for a good
# while after this returns -- so without this the endpoint,
# which is exclusive-open, stayed open long after the driver
# was done with it and a second run for the same device was
# refused with "already in use".
# it does -- the DHCP, ping and time readers, and now the link
# watcher -- so without this the endpoint, which is
# exclusive-open, stayed open and a second run for the same
# device was refused with "already in use". The watcher opens
# it for each poll.
#
ep0 = nil;
if(ipifc != nil)
spawn linkwatch(ipifcno);
return;
}
# a kernel with no #l has no Ethernet: there is nothing to fall back to
Expand Down Expand Up @@ -880,7 +886,10 @@ kernellink()
return;
}
sys->print("etherusb: serving /net/ether0 (kernel link driver, %s)\n", a);
kernelnif = 1;
netconfig();
if(ipifc != nil)
spawn linkwatch(ipifcno);
}

hexval(c: int): int
Expand Down Expand Up @@ -923,6 +932,8 @@ netconfig()
sys->print("etherusb: bind ether failed: %r\n");
return;
}
ipifc = ifc;
ipifcno = ifcno;
#
# An address is needed before DHCP can be spoken, because the
# stack will not send from an interface that has none. 0.0.0.0 is
Expand All @@ -936,16 +947,37 @@ netconfig()
# machine with an empty ethernet socket could not get an address
# at all.
#
# the family's own word on the link, where it has one: lanphy has
# already asked a LAN78xx, but an RNDIS adapter was never asked
if(!nocarrier && linkstate() == 0)
nocarrier = 1;
if(nocarrier){
sys->print("etherusb: no carrier; leaving /net/ether0 unconfigured\n");
sys->print("etherusb: no carrier; leaving /net/ether0 unconfigured until a link comes up\n");
return;
}
ifcup(1, ifcno);
}

#
# The address, from DHCP, and the default route through it. At boot
# (first) a network with no DHCP server is taken to be QEMU's user
# network and given its fixed addresses, and the clock is set and the
# path checked; when a cable comes back (linkwatch) none of that: no
# answer means try again at the next poll, never an invented address.
#
ifcup(first: int, ifcno: string): int
{
ifc := ipifc;
if(sys->fprint(ifc, "add 0.0.0.0 0.0.0.0") < 0){
sys->print("etherusb: add 0.0.0.0 failed: %r\n");
return;
return -1;
}
(addr, mask, gw) := dhcp();
if(addr == nil){
if(!first){
sys->fprint(ifc, "remove 0.0.0.0 0.0.0.0");
return -1;
}
sys->print("etherusb: no DHCP answer; falling back to QEMU's addresses\n");
addr = "10.0.2.15";
mask = "255.255.255.0";
Expand All @@ -955,8 +987,10 @@ netconfig()
sys->fprint(ifc, "remove 0.0.0.0 0.0.0.0");
if(sys->fprint(ifc, "add %s %s", addr, mask) < 0){
sys->print("etherusb: add address failed: %r\n");
return;
return -1;
}
ipaddr = addr;
ipmask = mask;
sys->print("etherusb: %s mask %s on ipifc %s\n", addr, mask, ifcno);

if(gw != ""){
Expand Down Expand Up @@ -988,6 +1022,8 @@ netconfig()
# address, because there is no resolver on this machine and a
# clock that is right matters more than avoiding a literal.
#
if(!first)
return 0;
setclock(getenv("ntpserver") :: ntpserver :: gw :: Pubntp :: nil);

#
Expand All @@ -1014,6 +1050,141 @@ netconfig()
if(gw != "")
pingout(gw);
pingout("8.8.8.8");
return 0;
}

#
# Hot plugging a cable.
#
# The link was read once, at start, and never again: a cable pulled
# left its address on an interface that could not carry it -- and with
# the radio on the same subnet, the stack kept routing that subnet out
# of the dead port, so the machine fell off the network although Wi-Fi
# was up -- and a cable plugged in later, or put back, was never
# configured at all. Only a reboot brought the wire back.
#
# So the driver stays and polls the link. Down: the address goes, and
# its subnet with it, so the radio's interface carries the traffic. Up:
# DHCP again, the address and the default route back, wired preferred;
# no answer is tried again at the next poll.
#
# The control endpoint is exclusive-open, and a second run for the same
# adapter (unplugged and plugged back) has to have it: holding it here
# refused that run. So it is opened for each poll and closed again, and
# the watcher ends when it cannot have it (another run does, or the
# adapter has gone) or when its interface is no longer bound to
# /net/ether0 (a new run unbound it as stale).
#
Linkpoll: con 2000;

linkwatch(ifcno: string)
{
up := !nocarrier && ipaddr != nil;
for(;;){
sys->sleep(Linkpoll);
if(!ours(ifcno)){
sys->print("etherusb: ipifc %s is no longer this run's; link watch ended\n", ifcno);
return;
}
if(!kernelnif){
ep0 = sys->open("/usb/usb/" + dev + "/data", Sys->ORDWR);
if(ep0 == nil){
sys->print("etherusb: link watch ended: %r\n");
return;
}
}
st := linkstate();
ep0 = nil;
if(st < 0){
sys->print("etherusb: link watch ended: %r\n");
return;
}
if(st == up && (!up || ipaddr != nil))
continue;
if(st){
if(!up)
sys->print("etherusb: link up\n");
up = 1;
if(ipaddr == nil && ifcup(0, ifcno) < 0)
continue; # no lease yet; asked again next poll
}else{
up = 0;
ifcdown();
}
}
}

# is ipifc n still bound to /net/ether0 (the first line of its status)
ours(n: string): int
{
st := readfile("/net/ipifc/" + n + "/status");
want := "device /net/ether0 ";
return len st >= len want && st[0:len want] == want;
}

# "link: 1" in the kernel driver's netif stats
niflink(): int
{
st := readfile("/net/ether0/stats");
if(st == nil){
sys->werrstr("no /net/ether0/stats");
return -1;
}
for(i := 0; i + 6 < len st; i++)
if((i == 0 || st[i-1] == '\n') && st[i:i+6] == "link: ")
return st[i+6] == '1';
sys->werrstr("no link line in /net/ether0/stats");
return -1;
}

ifcdown()
{
if(ipaddr == nil){
sys->print("etherusb: link down\n");
return;
}
if(sys->fprint(ipifc, "remove %s %s", ipaddr, ipmask) < 0)
sys->print("etherusb: link down; removing %s failed: %r\n", ipaddr);
else
sys->print("etherusb: link down; %s removed from /net/ether0\n", ipaddr);
ipaddr = ipmask = nil;
}

#
# 1 up, 0 down, -1 the adapter cannot be asked (gone, or a family that
# cannot say -- then there is nothing to watch).
#
Oidconnect: con 16r00010114; # OID_GEN_MEDIA_CONNECT_STATUS: 0 connected, 1 not

linkstate(): int
{
# a kernel driver (GENET, GEM, virtio) keeps it in the netif
if(kernelnif)
return niflink();
case family.name {
"lan78xx" =>
# BMSR latches link-down: the first read clears a stale
# drop, the second is now (lanphy says the same)
(e, nil) := lanmiird(Mbmsr);
if(e < 0)
return -1;
sr: int;
(e, sr) = lanmiird(Mbmsr);
if(e < 0)
return -1;
if(sr & Mbmsrlink)
return 1;
return 0;
"rndis" =>
v := array[4] of byte;
if(rndisquery(Oidconnect, v) < 0)
return -1;
if(get4(v, 0) == big 0)
return 1;
return 0;
}
sys->werrstr("no link state for " + family.name);
return -1;
}

#
Expand Down
Loading
Loading