github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/network/portrange.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // PortRange represents a single range of ports.
    16  type PortRange struct {
    17  	FromPort int
    18  	ToPort   int
    19  	Protocol string
    20  }
    21  
    22  // IsValid determines if the port range is valid.
    23  func (p PortRange) Validate() error {
    24  	proto := strings.ToLower(p.Protocol)
    25  	if proto != "tcp" && proto != "udp" {
    26  		return errors.Errorf(`invalid protocol %q, expected "tcp" or "udp"`, proto)
    27  	}
    28  	err := errors.Errorf(
    29  		"invalid port range %d-%d/%s",
    30  		p.FromPort,
    31  		p.ToPort,
    32  		p.Protocol,
    33  	)
    34  	switch {
    35  	case p.FromPort > p.ToPort:
    36  		return err
    37  	case p.FromPort < 1 || p.FromPort > 65535:
    38  		return err
    39  	case p.ToPort < 1 || p.ToPort > 65535:
    40  		return err
    41  	}
    42  	return nil
    43  }
    44  
    45  // ConflictsWith determines if the two port ranges conflict.
    46  func (a PortRange) ConflictsWith(b PortRange) bool {
    47  	if a.Protocol != b.Protocol {
    48  		return false
    49  	}
    50  	return a.ToPort >= b.FromPort && b.ToPort >= a.FromPort
    51  }
    52  
    53  func (p PortRange) String() string {
    54  	if p.FromPort == p.ToPort {
    55  		return fmt.Sprintf("%d/%s", p.FromPort, strings.ToLower(p.Protocol))
    56  	}
    57  	return fmt.Sprintf("%d-%d/%s", p.FromPort, p.ToPort, strings.ToLower(p.Protocol))
    58  }
    59  
    60  func (p PortRange) GoString() string {
    61  	return p.String()
    62  }
    63  
    64  type portRangeSlice []PortRange
    65  
    66  func (p portRangeSlice) Len() int      { return len(p) }
    67  func (p portRangeSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    68  func (p portRangeSlice) Less(i, j int) bool {
    69  	p1 := p[i]
    70  	p2 := p[j]
    71  	if p1.Protocol != p2.Protocol {
    72  		return p1.Protocol < p2.Protocol
    73  	}
    74  	if p1.FromPort != p2.FromPort {
    75  		return p1.FromPort < p2.FromPort
    76  	}
    77  	return p1.ToPort < p2.ToPort
    78  }
    79  
    80  // SortPortRanges sorts the given ports, first by protocol, then by number.
    81  func SortPortRanges(portRanges []PortRange) {
    82  	sort.Sort(portRangeSlice(portRanges))
    83  }
    84  
    85  // CollapsePorts collapses a slice of ports into port ranges.
    86  //
    87  // NOTE(dimitern): This is deprecated and should be removed when
    88  // possible. It still exists, because in a few places slices of Ports
    89  // are converted to PortRanges internally.
    90  func CollapsePorts(ports []Port) (result []PortRange) {
    91  	// First, convert ports to ranges, then sort them.
    92  	var portRanges []PortRange
    93  	for _, p := range ports {
    94  		portRanges = append(portRanges, PortRange{p.Number, p.Number, p.Protocol})
    95  	}
    96  	SortPortRanges(portRanges)
    97  	fromPort := 0
    98  	toPort := 0
    99  	protocol := ""
   100  	// Now merge single port ranges while preserving the order.
   101  	for _, pr := range portRanges {
   102  		if fromPort == 0 {
   103  			// new port range
   104  			fromPort = pr.FromPort
   105  			toPort = pr.ToPort
   106  			protocol = pr.Protocol
   107  		} else if pr.FromPort == toPort+1 && protocol == pr.Protocol {
   108  			// continuing port range
   109  			toPort = pr.FromPort
   110  		} else {
   111  			// break in port range
   112  			result = append(result,
   113  				PortRange{
   114  					Protocol: protocol,
   115  					FromPort: fromPort,
   116  					ToPort:   toPort,
   117  				})
   118  			fromPort = pr.FromPort
   119  			toPort = pr.ToPort
   120  			protocol = pr.Protocol
   121  		}
   122  	}
   123  	if fromPort != 0 {
   124  		result = append(result, PortRange{
   125  			Protocol: protocol,
   126  			FromPort: fromPort,
   127  			ToPort:   toPort,
   128  		})
   129  
   130  	}
   131  	return
   132  }
   133  
   134  // ParsePortRange builds a PortRange from the provided string. If the
   135  // string does not include a protocol then "tcp" is used. Validate()
   136  // gets called on the result before returning. If validation fails the
   137  // invalid PortRange is still returned.
   138  // Example strings: "80/tcp", "443", "12345-12349/udp".
   139  func ParsePortRange(inPortRange string) (PortRange, error) {
   140  	// Extract the protocol.
   141  	protocol := "tcp"
   142  	parts := strings.SplitN(inPortRange, "/", 2)
   143  	if len(parts) == 2 {
   144  		inPortRange = parts[0]
   145  		protocol = parts[1]
   146  	}
   147  
   148  	// Parse the ports.
   149  	portRange, err := parsePortRange(inPortRange)
   150  	if err != nil {
   151  		return portRange, errors.Trace(err)
   152  	}
   153  	portRange.Protocol = protocol
   154  
   155  	return portRange, portRange.Validate()
   156  }
   157  
   158  // MustParsePortRange converts a raw port-range string into a PortRange.
   159  // If the string is invalid, the function panics.
   160  func MustParsePortRange(portRange string) PortRange {
   161  	portrange, err := ParsePortRange(portRange)
   162  	if err != nil {
   163  		panic(err)
   164  	}
   165  	return portrange
   166  }
   167  
   168  func parsePortRange(portRange string) (PortRange, error) {
   169  	var result PortRange
   170  	var start, end int
   171  	parts := strings.Split(portRange, "-")
   172  	if len(parts) > 2 {
   173  		return result, errors.Errorf("invalid port range %q", portRange)
   174  	}
   175  
   176  	if len(parts) == 1 {
   177  		port, err := strconv.Atoi(parts[0])
   178  		if err != nil {
   179  			return result, errors.Annotatef(err, "invalid port %q", portRange)
   180  		}
   181  		start = port
   182  		end = port
   183  	} else {
   184  		var err error
   185  		if start, err = strconv.Atoi(parts[0]); err != nil {
   186  			return result, errors.Annotatef(err, "invalid port %q", parts[0])
   187  		}
   188  		if end, err = strconv.Atoi(parts[1]); err != nil {
   189  			return result, errors.Annotatef(err, "invalid port %q", parts[1])
   190  		}
   191  	}
   192  
   193  	result = PortRange{
   194  		FromPort: start,
   195  		ToPort:   end,
   196  	}
   197  	return result, nil
   198  }
   199  
   200  // ParsePortRanges splits the provided string on commas and extracts a
   201  // PortRange from each part of the split string. Whitespace is ignored.
   202  // Example strings: "80/tcp", "80,443,1234/udp", "123-456, 25/tcp".
   203  func ParsePortRanges(inPortRanges string) ([]PortRange, error) {
   204  	var portRanges []PortRange
   205  	for _, portRange := range strings.Split(inPortRanges, ",") {
   206  		portRange, err := ParsePortRange(strings.TrimSpace(portRange))
   207  		if err != nil {
   208  			return portRanges, errors.Trace(err)
   209  		}
   210  		portRanges = append(portRanges, portRange)
   211  	}
   212  	return portRanges, nil
   213  }