github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/cli/opts/port.go (about)

     1  package opts
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types/swarm"
    11  	"github.com/docker/go-connections/nat"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  const (
    16  	portOptTargetPort    = "target"
    17  	portOptPublishedPort = "published"
    18  	portOptProtocol      = "protocol"
    19  	portOptMode          = "mode"
    20  )
    21  
    22  // PortOpt represents a port config in swarm mode.
    23  type PortOpt struct {
    24  	ports []swarm.PortConfig
    25  }
    26  
    27  // Set a new port value
    28  // nolint: gocyclo
    29  func (p *PortOpt) Set(value string) error {
    30  	longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	if longSyntax {
    35  		csvReader := csv.NewReader(strings.NewReader(value))
    36  		fields, err := csvReader.Read()
    37  		if err != nil {
    38  			return err
    39  		}
    40  
    41  		pConfig := swarm.PortConfig{}
    42  		for _, field := range fields {
    43  			parts := strings.SplitN(field, "=", 2)
    44  			if len(parts) != 2 {
    45  				return fmt.Errorf("invalid field %s", field)
    46  			}
    47  
    48  			key := strings.ToLower(parts[0])
    49  			value := strings.ToLower(parts[1])
    50  
    51  			switch key {
    52  			case portOptProtocol:
    53  				if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) && value != string(swarm.PortConfigProtocolSCTP) {
    54  					return fmt.Errorf("invalid protocol value %s", value)
    55  				}
    56  
    57  				pConfig.Protocol = swarm.PortConfigProtocol(value)
    58  			case portOptMode:
    59  				if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
    60  					return fmt.Errorf("invalid publish mode value %s", value)
    61  				}
    62  
    63  				pConfig.PublishMode = swarm.PortConfigPublishMode(value)
    64  			case portOptTargetPort:
    65  				tPort, err := strconv.ParseUint(value, 10, 16)
    66  				if err != nil {
    67  					return err
    68  				}
    69  
    70  				pConfig.TargetPort = uint32(tPort)
    71  			case portOptPublishedPort:
    72  				pPort, err := strconv.ParseUint(value, 10, 16)
    73  				if err != nil {
    74  					return err
    75  				}
    76  
    77  				pConfig.PublishedPort = uint32(pPort)
    78  			default:
    79  				return fmt.Errorf("invalid field key %s", key)
    80  			}
    81  		}
    82  
    83  		if pConfig.TargetPort == 0 {
    84  			return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
    85  		}
    86  
    87  		if pConfig.PublishMode == "" {
    88  			pConfig.PublishMode = swarm.PortConfigPublishModeIngress
    89  		}
    90  
    91  		if pConfig.Protocol == "" {
    92  			pConfig.Protocol = swarm.PortConfigProtocolTCP
    93  		}
    94  
    95  		p.ports = append(p.ports, pConfig)
    96  	} else {
    97  		// short syntax
    98  		portConfigs := []swarm.PortConfig{}
    99  		ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
   100  		if err != nil {
   101  			return err
   102  		}
   103  		for _, portBindings := range portBindingMap {
   104  			for _, portBinding := range portBindings {
   105  				if portBinding.HostIP != "" {
   106  					return fmt.Errorf("hostip is not supported")
   107  				}
   108  			}
   109  		}
   110  
   111  		for port := range ports {
   112  			portConfig, err := ConvertPortToPortConfig(port, portBindingMap)
   113  			if err != nil {
   114  				return err
   115  			}
   116  			portConfigs = append(portConfigs, portConfig...)
   117  		}
   118  		p.ports = append(p.ports, portConfigs...)
   119  	}
   120  	return nil
   121  }
   122  
   123  // Type returns the type of this option
   124  func (p *PortOpt) Type() string {
   125  	return "port"
   126  }
   127  
   128  // String returns a string repr of this option
   129  func (p *PortOpt) String() string {
   130  	ports := []string{}
   131  	for _, port := range p.ports {
   132  		repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
   133  		ports = append(ports, repr)
   134  	}
   135  	return strings.Join(ports, ", ")
   136  }
   137  
   138  // Value returns the ports
   139  func (p *PortOpt) Value() []swarm.PortConfig {
   140  	return p.ports
   141  }
   142  
   143  // ConvertPortToPortConfig converts ports to the swarm type
   144  func ConvertPortToPortConfig(
   145  	port nat.Port,
   146  	portBindings map[nat.Port][]nat.PortBinding,
   147  ) ([]swarm.PortConfig, error) {
   148  	ports := []swarm.PortConfig{}
   149  
   150  	for _, binding := range portBindings[port] {
   151  		if binding.HostIP != "" && binding.HostIP != "0.0.0.0" {
   152  			logrus.Warnf("ignoring IP-address (%s:%s:%s) service will listen on '0.0.0.0'", binding.HostIP, binding.HostPort, port)
   153  		}
   154  
   155  		startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
   156  
   157  		if err != nil && binding.HostPort != "" {
   158  			return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
   159  		}
   160  
   161  		for i := startHostPort; i <= endHostPort; i++ {
   162  			ports = append(ports, swarm.PortConfig{
   163  				// TODO Name: ?
   164  				Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
   165  				TargetPort:    uint32(port.Int()),
   166  				PublishedPort: uint32(i),
   167  				PublishMode:   swarm.PortConfigPublishModeIngress,
   168  			})
   169  		}
   170  	}
   171  	return ports, nil
   172  }