private CompletableFuture<Void> requestPortForward(final PortMapping portMapping) {

    CompletableFuture<Void> upnpQueryFuture = new CompletableFuture<>();

    return externalIpQueryFuture.thenCompose(
        address -> {
          // note that this future is a dependency of externalIpQueryFuture, so it must be completed
          // by now
          RemoteService service = getService(SERVICE_TYPE_WAN_IP_CONNECTION).join();

          // at this point, we should have the local address we discovered the IGD on,
          // so we can prime the NewInternalClient field if it was omitted
          if (null == portMapping.getInternalClient()) {
            portMapping.setInternalClient(discoveredOnLocalAddress.get());
          }

          // our query, which will be handled asynchronously by the jupnp library
          PortMappingAdd callback =
              new PortMappingAdd(service, portMapping) {
                /**
                 * Because the underlying jupnp library omits generics info in this method
                 * signature, we must too when we override it.
                 */
                @Override
                @SuppressWarnings("rawtypes")
                public void success(final ActionInvocation invocation) {
                  LOG.info(
                      "Port forward request for {} {} -> {} succeeded.",
                      portMapping.getProtocol(),
                      portMapping.getInternalPort(),
                      portMapping.getExternalPort());

                  synchronized (forwardedPorts) {
                    final NatServiceType natServiceType =
                        NatServiceType.fromString(portMapping.getDescription());
                    final NatPortMapping natPortMapping =
                        new NatPortMapping(
                            natServiceType,
                            NetworkProtocol.valueOf(portMapping.getProtocol().name()),
                            portMapping.getInternalClient(),
                            portMapping.getRemoteHost(),
                            portMapping.getExternalPort().getValue().intValue(),
                            portMapping.getInternalPort().getValue().intValue());
                    forwardedPorts.add(natPortMapping);
                  }

                  upnpQueryFuture.complete(null);
                }

                /**
                 * Because the underlying jupnp library omits generics info in this method
                 * signature, we must too when we override it.
                 */
                @Override
                @SuppressWarnings("rawtypes")
                public void failure(
                    final ActionInvocation invocation,
                    final UpnpResponse operation,
                    final String msg) {
                  LOG.warn(
                      "Port forward request for {} {} -> {} failed: {}",
                      portMapping.getProtocol(),
                      portMapping.getInternalPort(),
                      portMapping.getExternalPort(),
                      msg);
                  upnpQueryFuture.completeExceptionally(new Exception(msg));
                }
              };

          LOG.info(
              "Requesting port forward for {} {} -> {}",
              portMapping.getProtocol(),
              portMapping.getInternalPort(),
              portMapping.getExternalPort());

          upnpService.getControlPoint().execute(callback);

          return upnpQueryFuture;
        });
  }
Sends a UPnP request to the discovered IGD to request a port forward.

@param portMapping is a portMapping object describing the desired port mapping parameters.
@return A CompletableFuture that can be used to query the result (or error).
nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java