func addPortMapping(nmgr *natManager, intaddr ma.Multiaddr) { nat := nmgr.NAT() if nat == nil { panic("natManager addPortMapping called without a nat.") } // first, check if the port mapping already exists. for _, mapping := range nat.Mappings() { if mapping.InternalAddr().Equal(intaddr) { return // it exists! return. } } ctx := context.TODO() lm := make(lgbl.DeferredMap) lm["internalAddr"] = func() interface{} { return intaddr.String() } defer log.EventBegin(ctx, "natMgrAddPortMappingWait", lm).Done() select { case <-nmgr.proc.Closing(): lm["outcome"] = "cancelled" return // no use. case <-nmgr.ready: // wait until it's ready. } // actually start the port map (sub-event because waiting may take a while) defer log.EventBegin(ctx, "natMgrAddPortMapping", lm).Done() // get the nat m, err := nat.NewMapping(intaddr) if err != nil { lm["outcome"] = "failure" lm["error"] = err return } extaddr, err := m.ExternalAddr() if err != nil { lm["outcome"] = "failure" lm["error"] = err return } lm["outcome"] = "success" lm["externalAddr"] = func() interface{} { return extaddr.String() } log.Infof("established nat port mapping: %s <--> %s", intaddr, extaddr) }
// NewMapping attemps to construct a mapping on protocol and internal port // It will also periodically renew the mapping until the returned Mapping // -- or its parent NAT -- is Closed. // // May not succeed, and mappings may change over time; // NAT devices may not respect our port requests, and even lie. // Clients should not store the mapped results, but rather always // poll our object for the latest mappings. func (nat *NAT) NewMapping(maddr ma.Multiaddr) (Mapping, error) { if nat == nil { return nil, fmt.Errorf("no nat available") } network, addr, err := manet.DialArgs(maddr) if err != nil { return nil, fmt.Errorf("DialArgs failed on addr:", maddr.String()) } switch network { case "tcp", "tcp4", "tcp6": network = "tcp" case "udp", "udp4", "udp6": network = "udp" default: return nil, fmt.Errorf("transport not supported by NAT: %s", network) } intports := strings.Split(addr, ":")[1] intport, err := strconv.Atoi(intports) if err != nil { return nil, err } m := &mapping{ nat: nat, proto: network, intport: intport, intaddr: maddr, } m.proc = goprocess.WithTeardown(func() error { nat.rmMapping(m) return nil }) nat.addMapping(m) m.proc.AddChild(periodic.Every(MappingDuration/3, func(worker goprocess.Process) { nat.establishMapping(m) })) // do it once synchronously, so first mapping is done right away, and before exiting, // allowing users -- in the optimistic case -- to use results right after. nat.establishMapping(m) return m, nil }