goupnp

repository·main·Indexed 19 days ago

https://github.com/huin/goupnp

A UPnP (Universal Plug and Play) client library for Go. It provides tools for service discovery via SSDP and communication via SOAP, including core packages for HTTPU, SSDP, and SOAP. The library includes specialized Device Control Protocol (DCP) packages such as av1, internetgateway1, and internetgateway2 for tasks like port forwarding and external IP discovery on consumer routers.

Tokens
5.3K
Snippets
19
Records
27
Agent score
66%

What's inside goupnp

  1. Overview of goupnp core components

    main

    The goupnp library is composed of several specialized packages that work together to provide UPnP functionality:

    • goupnp: The core library containing data structures and utilities used by the implemented Device Control Protocols (DCPs).
    • httpu: The HTTPU implementation that underlies SSDP.
    • ssdp: The SSDP (Simple Service Discovery Protocol) client implementation used to discover UPnP services on a network.
    • soap: The SOAP (Simple Object Access Protocol) client implementation used to communicate with discovered services.

    For specific device implementations, you should use the specialized DCP packages such as av1, internetgateway1, or internetgateway2.

  2. Experimental v2alpha API

    main

    The v2alpha subdirectory contains experimental work for a future version 2 API.

    Warning:

    • v2alpha is unstable and subject to breaking changes.
    • The v2alpha directory may be deleted in the future.
    • The current v1 API remains stable in its existing location.
  3. Interact with Internet Gateways for Port Forwarding and IP Discovery

    main

    To interact with consumer routers (e.g., for requesting an external IP address or setting up port forwarding), you should use the internetgateway1 or internetgateway2 packages. Because different routers implement different UPnP standards, it is a best practice to attempt to discover multiple client types in parallel and select the one that is available.

    Commonly used discovery functions include:

    • internetgateway2.NewWANIPConnection1Clients()
    • internetgateway2.NewWANIPConnection2Clients()
    • internetgateway2.NewWANPPPConnection1Clients()

    All these clients share similar method signatures for core tasks like GetExternalIPAddress and AddPortMapping, allowing you to wrap them in a common interface for easier use in your application.

    type RouterClient interface {
    	AddPortMapping(
    		NewRemoteHost string,
    		NewExternalPort uint16,
    		NewProtocol string,
    		NewInternalPort uint16,
    		NewInternalClient string,
    		NewEnabled bool,
    		NewPortMappingDescription string,
    		NewLeaseDuration uint32,
    	) (err error)
    
    	GetExternalIPAddress() (
    		NewExternalIPAddress string,
    		err error,
    	) 
    
    	LocalAddr() net.IP
    }
  4. Support additional UPnP devices and services

    main

    To add support for a new UPnP service, you can follow these steps:

    1. Add the service to the dcpMetadata whitelist located in cmd/goupnpdcpgen/metadata.go.
    2. Regenerate the source code using the goupnpdcpgen tool.
    3. Commit the newly generated source code.

    It is recommended to test the service against your specific hardware and report any issues or minimal working functionality to the project's issue tracker.

  5. Regenerate DCP generated source code

    main

    If you need to regenerate the source code for the Device Control Protocols (DCPs), follow these steps:

    1. Build the code generator using goupnpdcpgen.
    2. Run the go generate command for the project.
    go get -u github.com/huin/goupnp/cmd/goupnpdcpgen
    go generate ./...
  6. Understand the MaybeRootDevice structure

    main

    The MaybeRootDevice struct is the result returned by discovery functions. Because discovery involves both network searching and probing individual devices, it uses this wrapper to handle partial successes.

    Fields:

    • USN: The Unique Service Name of the device.
    • Root: A pointer to the RootDevice object. This is non-nil only if Err is nil.
    • Location: The *url.URL where the device was discovered. Useful for subsequent DeviceByURL calls.
    • LocalAddr: The net.IP address from which the device was discovered (if provided in the SSDP header).
    • Err: Any error encountered while probing the specific device discovered at Location.
  7. Restore legacy non-UTF8 encoding behavior

    main

    A breaking change was introduced to how non-UTF8 encodings are handled to remove a heavy dependency on golang.org/x/net/html/charset. If your application requires the old behavior, you must manually assign charset.NewReaderLabel to the goupnp.CharsetReaderFault variable during initialization.

    import (
      "golang.org/x/net/html/charset"
      "github.com/huin/goupnp"
    )
    
    func init() {
      // should be modified before goupnp libraries are in use.
      goupnp.CharsetReaderFault = charset.NewReaderLabel
    }
  8. Use AddPortMapping to forward ports on a router

    main

    The AddPortMapping method allows you to request that a router forwards a specific external port to a local device on your LAN.

    Parameters:

    • NewRemoteHost: The IP address of the remote host (usually an empty string "" to allow any host).
    • NewExternalPort: The port number on the external (Internet-facing) interface to expose.
    • NewProtocol: The protocol to use, typically "TCP" or "UDP".
    • NewInternalPort: The port number on the local LAN device to forward to.
    • NewInternalClient: The local IP address of the device on the LAN (can be obtained via client.LocalAddr().String()).
    • NewEnabled: A boolean indicating if the mapping should be active.
    • NewPortMappingDescription: A string description for the mapping (e.g., your application name).
    • NewLeaseDuration: The duration in seconds for which the port forward should remain active.
    err := client.AddPortMapping(
    	"",           // NewRemoteHost
    	1234,         // NewExternalPort
    	"TCP",       // NewProtocol
    	1234,         // NewInternalPort
    	"192.168.1.5", // NewInternalClient
    	true,         // NewEnabled
    	"MyProgram", // NewPortMappingDescription
    	3600,         // NewLeaseDuration
    )
  9. Configure the default HTTP client and CharsetReader

    main

    You can customize how goupnp fetches XML data from UPnP servers by modifying the following global variables:

    • HTTPClientDefault: An *http.Client used for fetching XML. It defaults to http.DefaultClient. Overriding this allows you to control timeouts, proxies, or transport settings.
    • CharsetReaderDefault: A function used to decode non-UTF8 encodings from UPnP servers. It takes a charset string and an io.Reader and returns an io.Reader.
  10. Handle SOAP errors and SOAPError type

    main

    Errors returned by the client are often of type *SOAPError. You can check for general SOAP errors using errors.Is(err, client.ErrSOAP).

    SOAPError provides:

    • description: A string describing the error from a SOAP perspective (e.g., "SOAP fault", "encoding envelope").
    • cause: The underlying error that triggered the SOAP error.
    import (
    	"errors"
    	"github.com/huin/goupnp/v2alpha/soap/client"
    )
    
    err := soapClient.Do(ctx, in, out)
    if errors.Is(err, client.ErrSOAP) {
        var soapErr *client.SOAPError
        if errors.As(err, &soapErr) {
            fmt.Printf("SOAP Error: %s, Cause: %v\n", soapErr.Error(), soapErr.Unwrap())
        }
    }
  11. Retrieve a RootDevice by URL with DeviceByURLCtx

    main

    If you already have the location URL of a UPnP device, use DeviceByURLCtx to fetch and parse its XML description into a RootDevice object. This allows you to access the device's metadata and service information directly.

    import (
    	"context"
    	"net/url"
    	"github.com/huin/goupnp"
    )
    
    func getDevice(locURL string) (*goupnp.RootDevice, error) {
    	loc, err := url.Parse(locURL)
    	if err != nil {
    		return nil, err
    	}
    
    	return goupnp.DeviceByURLCtx(context.Background(), loc)
    }