From e34f77ba0e4a6220c96010eaedc685b322323e14 Mon Sep 17 00:00:00 2001
From: Caleb Callaway <enlightened.despot@gmail.com>
Date: Sat, 18 Oct 2014 10:20:57 -0700
Subject: [PATCH] Enable portmapping for individual UPnP services

---
 internal/upnp/upnp.go | 73 +++++++++++++++++++++++++++++--------------
 1 file changed, 49 insertions(+), 24 deletions(-)

diff --git a/internal/upnp/upnp.go b/internal/upnp/upnp.go
index a6780bce2..5d2d529c1 100644
--- a/internal/upnp/upnp.go
+++ b/internal/upnp/upnp.go
@@ -472,22 +472,12 @@ func soapRequest(url, device, function, message string) ([]byte, error) {
 	return resp, nil
 }
 
-// Add a port mapping to the specified InternetGatewayDevice.
+// Add a port mapping to all relevant services on the specified InternetGatewayDevice.
+// Port mapping will fail and return an error if action is fails for _any_ of the relevant services.
+// For this reason, it is generally better to configure port mapping for each individual service instead. 
 func (n *IGD) AddPortMapping(protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
 	for _, service := range n.services {
-		tpl := `<u:AddPortMapping xmlns:u="%s">
-	<NewRemoteHost></NewRemoteHost>
-	<NewExternalPort>%d</NewExternalPort>
-	<NewProtocol>%s</NewProtocol>
-	<NewInternalPort>%d</NewInternalPort>
-	<NewInternalClient>%s</NewInternalClient>
-	<NewEnabled>1</NewEnabled>
-	<NewPortMappingDescription>%s</NewPortMappingDescription>
-	<NewLeaseDuration>%d</NewLeaseDuration>
-	</u:AddPortMapping>`
-		body := fmt.Sprintf(tpl, service.serviceURN, externalPort, protocol, internalPort, n.localIPAddress, description, timeout)
-
-		_, err := soapRequest(service.serviceURL, service.serviceURN, "AddPortMapping", body)
+		err := service.AddPortMapping(n.localIPAddress, protocol, externalPort, internalPort, description, timeout)
 		if err != nil {
 			return err
 		}
@@ -495,17 +485,12 @@ func (n *IGD) AddPortMapping(protocol Protocol, externalPort, internalPort int,
 	return nil
 }
 
-// Delete a port mapping from the specified InternetGatewayDevice.
-func (n *IGD) DeletePortMapping(protocol Protocol, externalPort int) (err error) {
+// Delete a port mapping from all relevant services on the specified InternetGatewayDevice.
+// Port mapping will fail and return an error if action is fails for _any_ of the relevant services.
+// For this reason, it is generally better to configure port mapping for each individual service instead. 
+func (n *IGD) DeletePortMapping(protocol Protocol, externalPort int) error {
 	for _, service := range n.services {
-		tpl := `<u:DeletePortMapping xmlns:u="%s">
-	<NewRemoteHost></NewRemoteHost>
-	<NewExternalPort>%d</NewExternalPort>
-	<NewProtocol>%s</NewProtocol>
-	</u:DeletePortMapping>`
-		body := fmt.Sprintf(tpl, service.serviceURN, externalPort, protocol)
-
-		_, err := soapRequest(service.serviceURL, service.serviceURN, "DeletePortMapping", body)
+		err := service.DeletePortMapping(protocol, externalPort)
 		if err != nil {
 			return err
 		}
@@ -547,6 +532,46 @@ type getExternalIPAddressResponse struct {
 	NewExternalIPAddress string `xml:"NewExternalIPAddress"`
 }
 
+// Add a port mapping to the specified IGD service.
+func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
+	tpl := `<u:AddPortMapping xmlns:u="%s">
+	<NewRemoteHost></NewRemoteHost>
+	<NewExternalPort>%d</NewExternalPort>
+	<NewProtocol>%s</NewProtocol>
+	<NewInternalPort>%d</NewInternalPort>
+	<NewInternalClient>%s</NewInternalClient>
+	<NewEnabled>1</NewEnabled>
+	<NewPortMappingDescription>%s</NewPortMappingDescription>
+	<NewLeaseDuration>%d</NewLeaseDuration>
+	</u:AddPortMapping>`
+	body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
+
+	_, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Delete a port mapping from the specified IGD service.
+func (s *IGDService) DeletePortMapping(protocol Protocol, externalPort int) error {
+	tpl := `<u:DeletePortMapping xmlns:u="%s">
+	<NewRemoteHost></NewRemoteHost>
+	<NewExternalPort>%d</NewExternalPort>
+	<NewProtocol>%s</NewProtocol>
+	</u:DeletePortMapping>`
+	body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol)
+
+	_, err := soapRequest(s.serviceURL, s.serviceURN, "DeletePortMapping", body)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // Query the IGD service for its external IP address.
 // Returns nil if the external IP address is invalid or undefined, along with any relevant errors
 func (s *IGDService) GetExternalIPAddress() (net.IP, error) {
-- 
GitLab