devip: fix ifc recursive rlock() deadlock

ipiput4() and ipiput6() are called with the incoming interface rlocked
while ipoput4() and ipoput6() also rlock() the outgoing interface once
a route has been found. it is common that the incoming and outgoing
interfaces are the same recusive rlocking().

the deadlock happens when a reader holds the rlock for the incoming interface,
then ip/ipconfig tries to add a new address, trying to wlock the interface.
as there are still active readers on the ifc, ip/ipconfig process gets queued
on the inteface RWlock.

now the reader finds the outgoing route which has the same interface as the
incoming packet and tries to rlock the ifc again. but now theres a writer
queued, so we also go to sleep waiting four outselfs to release the lock.

the solution is to never wait for the outgoing interface rlock, but instead
use non-queueing canrlock() and if it cannot be acquired, discard the packet.
This commit is contained in:
cinap_lenrek 2020-05-10 22:51:40 +02:00
parent dbfec06bf1
commit 27fc79b04b
3 changed files with 10 additions and 3 deletions

View file

@ -123,7 +123,10 @@ ipoput4(Fs *f, Block *bp, int gating, int ttl, int tos, Routehint *rh)
else
gate = r->v4.gate;
rlock(ifc);
if(!canrlock(ifc)){
ip->stats[OutDiscards]++;
goto free;
}
if(waserror()){
runlock(ifc);
nexterror();

View file

@ -1167,7 +1167,8 @@ findipifc(Fs *f, uchar *local, uchar *remote, int type)
xspec = 0;
for(cp = f->ipifc->conv; *cp != nil; cp++){
ifc = (Ipifc*)(*cp)->ptcl;
rlock(ifc);
if(!canrlock(ifc))
continue;
for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
if(type & Runi){
if(ipcmp(remote, lifc->local) == 0){

View file

@ -76,7 +76,10 @@ ipoput6(Fs *f, Block *bp, int gating, int ttl, int tos, Routehint *rh)
else
gate = r->v6.gate;
rlock(ifc);
if(!canrlock(ifc)){
ip->stats[OutDiscards]++;
goto free;
}
if(waserror()){
runlock(ifc);
nexterror();