net/portmapper: check returned epoch from PMP and PCP protocols

If the epoch that we see during a Probe is less than the existing epoch,
it means that the gateway has either restarted or reset its
configuration, and an existing mapping is no longer valid. Reset any
saved mapping(s) if we detect this case so that a future
createOrGetMapping will not attempt to re-use it.

Updates #10597

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ie3cddaf625cb94a29885f7a1eeea25dbf6b97b47
pull/10733/head
Andrew Dunham 6 months ago
parent b084888e4d
commit fa3639783c

@ -54,8 +54,7 @@ type pcpMapping struct {
renewAfter time.Time renewAfter time.Time
goodUntil time.Time goodUntil time.Time
// TODO should this also contain an epoch? epoch uint32
// Doesn't seem to be used elsewhere, but can use it for validation at some point.
} }
func (p *pcpMapping) MappingType() string { return "pcp" } func (p *pcpMapping) MappingType() string { return "pcp" }
@ -140,6 +139,7 @@ func parsePCPMapResponse(resp []byte) (*pcpMapping, error) {
external: external, external: external,
renewAfter: now.Add(lifetime / 2), renewAfter: now.Add(lifetime / 2),
goodUntil: now.Add(lifetime), goodUntil: now.Add(lifetime),
epoch: res.Epoch,
} }
return mapping, nil return mapping, nil

@ -90,11 +90,14 @@ type Client struct {
lastProbe time.Time lastProbe time.Time
// The following PMP fields are populated during Probe
pmpPubIP netip.Addr // non-zero if known pmpPubIP netip.Addr // non-zero if known
pmpPubIPTime time.Time // time pmpPubIP last verified pmpPubIPTime time.Time // time pmpPubIP last verified
pmpLastEpoch uint32 pmpLastEpoch uint32
pcpSawTime time.Time // time we last saw PCP was available // The following PCP fields are populated during Probe
pcpSawTime time.Time // time we last saw PCP was available
pcpLastEpoch uint32
uPnPSawTime time.Time // time we last saw UPnP was available uPnPSawTime time.Time // time we last saw UPnP was available
uPnPMetas []uPnPDiscoResponse // UPnP UDP discovery responses uPnPMetas []uPnPDiscoResponse // UPnP UDP discovery responses
@ -324,9 +327,14 @@ func (c *Client) invalidateMappingsLocked(releaseOld bool) {
} }
c.mapping = nil c.mapping = nil
} }
c.pmpPubIP = netip.Addr{} c.pmpPubIP = netip.Addr{}
c.pmpPubIPTime = time.Time{} c.pmpPubIPTime = time.Time{}
c.pmpLastEpoch = 0
c.pcpSawTime = time.Time{} c.pcpSawTime = time.Time{}
c.pcpLastEpoch = 0
c.uPnPSawTime = time.Time{} c.uPnPSawTime = time.Time{}
c.uPnPMetas = nil c.uPnPMetas = nil
} }
@ -988,7 +996,9 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
if pres.OpCode == pcpOpReply|pcpOpAnnounce { if pres.OpCode == pcpOpReply|pcpOpAnnounce {
pcpHeard = true pcpHeard = true
c.mu.Lock() c.mu.Lock()
c.maybeInvalidatePCPMappingLocked(pres.Epoch) // must be before we write to c.pcp*
c.pcpSawTime = time.Now() c.pcpSawTime = time.Now()
c.pcpLastEpoch = pres.Epoch
c.mu.Unlock() c.mu.Unlock()
switch pres.ResultCode { switch pres.ResultCode {
case pcpCodeOK: case pcpCodeOK:
@ -1026,6 +1036,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
c.logf("[v1] Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch) c.logf("[v1] Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
res.PMP = true res.PMP = true
c.mu.Lock() c.mu.Lock()
c.maybeInvalidatePMPMappingLocked(pres.SecondsSinceEpoch) // must be before we write to c.pmp*
c.pmpPubIP = pres.PublicAddr c.pmpPubIP = pres.PublicAddr
c.pmpPubIPTime = time.Now() c.pmpPubIPTime = time.Now()
c.pmpLastEpoch = pres.SecondsSinceEpoch c.pmpLastEpoch = pres.SecondsSinceEpoch
@ -1051,6 +1062,57 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
} }
} }
func (c *Client) maybeInvalidatePMPMappingLocked(epoch uint32) {
if epoch == 0 || c.mapping == nil {
return
}
m, ok := c.mapping.(*pmpMapping)
if !ok {
return
}
if epoch >= m.epoch {
// Epoch increased, which is fine.
//
// TODO: we should more closely follow RFC6887 § 8.5 which also
// requires us to check the current time and the time that this
// epoch was received at.
return
}
// Epoch decreased, so invalidate the mapping and clear PMP fields.
c.logf("invalidating PMP mappings since returned epoch %d < stored epoch %d", epoch, m.epoch)
c.mapping = nil
c.pmpPubIP = netip.Addr{}
c.pmpPubIPTime = time.Time{}
c.pmpLastEpoch = 0
}
func (c *Client) maybeInvalidatePCPMappingLocked(epoch uint32) {
if epoch == 0 || c.mapping == nil {
return
}
m, ok := c.mapping.(*pcpMapping)
if !ok {
return
}
if epoch >= m.epoch {
// Epoch increased, which is fine.
//
// TODO: we should more closely follow RFC6887 § 8.5 which also
// requires us to check the current time and the time that this
// epoch was received at.
return
}
// Epoch decreased, so invalidate the mapping and clear PCP fields.
c.logf("invalidating PCP mappings since returned epoch %d < stored epoch %d", epoch, m.epoch)
c.mapping = nil
c.pcpSawTime = time.Time{}
c.pcpLastEpoch = 0
}
var pmpReqExternalAddrPacket = []byte{pmpVersion, pmpOpMapPublicAddr} // 0, 0 var pmpReqExternalAddrPacket = []byte{pmpVersion, pmpOpMapPublicAddr} // 0, 0
const ( const (

Loading…
Cancel
Save