mirror of
				https://github.com/xmdhs/natupnp.git
				synced 2025-10-30 01:51:54 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			111 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			111 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package upnp
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"github.com/huin/goupnp/dcps/ocf/internetgateway2"
 | |
| 	"golang.org/x/sync/errgroup"
 | |
| )
 | |
| 
 | |
| func AddPortMapping(ctx context.Context,
 | |
| 	NewRemoteHost string,
 | |
| 	NewExternalPort uint16,
 | |
| 	NewProtocol string,
 | |
| 	NewInternalPort uint16,
 | |
| 	NewInternalClient string,
 | |
| 	NewEnabled bool,
 | |
| 	NewPortMappingDescription string,
 | |
| 	NewLeaseDuration uint32,
 | |
| ) error {
 | |
| 	clients, err := pickRouterClient(ctx)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("AddPortMapping: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	tasks, _ := errgroup.WithContext(ctx)
 | |
| 
 | |
| 	for _, v := range clients {
 | |
| 		v := v
 | |
| 		tasks.Go(func() error {
 | |
| 			return v.AddPortMapping(NewRemoteHost, NewExternalPort, NewProtocol, NewInternalPort, NewInternalClient, NewEnabled, NewPortMappingDescription, NewLeaseDuration)
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if err := tasks.Wait(); err != nil {
 | |
| 		return fmt.Errorf("AddPortMapping: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 	)
 | |
| }
 | |
| 
 | |
| func pickRouterClient(ctx context.Context) ([]routerClient, error) {
 | |
| 	tasks, _ := errgroup.WithContext(ctx)
 | |
| 	// Request each type of client in parallel, and return what is found.
 | |
| 	var ip1Clients []*internetgateway2.WANIPConnection1
 | |
| 	tasks.Go(func() error {
 | |
| 		var err error
 | |
| 		ip1Clients, _, err = internetgateway2.NewWANIPConnection1Clients()
 | |
| 		return err
 | |
| 	})
 | |
| 	var ip2Clients []*internetgateway2.WANIPConnection2
 | |
| 	tasks.Go(func() error {
 | |
| 		var err error
 | |
| 		ip2Clients, _, err = internetgateway2.NewWANIPConnection2Clients()
 | |
| 		return err
 | |
| 	})
 | |
| 	var ppp1Clients []*internetgateway2.WANPPPConnection1
 | |
| 	tasks.Go(func() error {
 | |
| 		var err error
 | |
| 		ppp1Clients, _, err = internetgateway2.NewWANPPPConnection1Clients()
 | |
| 		return err
 | |
| 	})
 | |
| 
 | |
| 	if err := tasks.Wait(); err != nil {
 | |
| 		return nil, fmt.Errorf("pickRouterClient: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Trivial handling for where we find exactly one device to talk to, you
 | |
| 	// might want to provide more flexible handling than this if multiple
 | |
| 	// devices are found.
 | |
| 	switch {
 | |
| 	case len(ip2Clients) == 1:
 | |
| 		return any2slice[routerClient](ip2Clients), nil
 | |
| 	case len(ip1Clients) == 1:
 | |
| 		return any2slice[routerClient](ip1Clients), nil
 | |
| 	case len(ppp1Clients) == 1:
 | |
| 		return any2slice[routerClient](ppp1Clients), nil
 | |
| 	default:
 | |
| 		return nil, fmt.Errorf("pickRouterClient: %w", ErrNoService)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var ErrNoService = errors.New("multiple or no services found")
 | |
| 
 | |
| func any2slice[T, E any](list []E) []T {
 | |
| 	nl := make([]T, 0, len(list))
 | |
| 	for _, v := range list {
 | |
| 		nl = append(nl, any(v).(T))
 | |
| 	}
 | |
| 	return nl
 | |
| }
 | 
