github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/runner/jujuc/ports.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package jujuc 5 6 import ( 7 "fmt" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/gnuflag" 15 16 jujucmd "github.com/juju/juju/cmd" 17 ) 18 19 const ( 20 portFormat = "<port>[/<protocol>] or <from>-<to>[/<protocol>] or icmp" 21 22 portExp = "(?:[0-9]+)" 23 protoExp = "(?:[a-z0-9]+)" 24 portPlusProto = portExp + "(?:-" + portExp + ")?(/" + protoExp + ")?" 25 ) 26 27 var validPortOrRange = regexp.MustCompile("^icmp|" + portPlusProto + "$") 28 29 type port struct { 30 number int 31 protocol string 32 } 33 34 func (p port) validate() error { 35 proto := strings.ToLower(p.protocol) 36 if proto == "icmp" { 37 if p.number == -1 { 38 return nil 39 } 40 return errors.Errorf(`protocol "icmp" doesn't support any ports; got "%v"`, p.number) 41 } 42 if p.number < 1 || p.number > 65535 { 43 return errors.Errorf(`port must be in the range [1, 65535]; got "%v"`, p.number) 44 } 45 if proto != "tcp" && proto != "udp" && proto != "icmp" { 46 return errors.Errorf(`protocol must be "tcp", "udp", or "icmp"; got %q`, p.protocol) 47 } 48 return nil 49 } 50 51 type portRange struct { 52 fromPort, toPort int 53 protocol string 54 } 55 56 func (pr portRange) validate() error { 57 if pr.fromPort == pr.toPort { 58 return port{pr.fromPort, pr.protocol}.validate() 59 } 60 if pr.fromPort > pr.toPort { 61 return errors.Errorf( 62 "invalid port range %d-%d/%s; expected fromPort <= toPort", 63 pr.fromPort, pr.toPort, pr.protocol, 64 ) 65 } 66 proto := strings.ToLower(pr.protocol) 67 if proto == "icmp" { 68 if pr.fromPort == pr.toPort && pr.fromPort == -1 { 69 return nil 70 } 71 return errors.Errorf(`protocol "icmp" doesn't support any ports; got "%v"`, pr.fromPort) 72 } 73 if pr.fromPort < 1 || pr.fromPort > 65535 { 74 return errors.Errorf(`fromPort must be in the range [1, 65535]; got "%v"`, pr.fromPort) 75 } 76 if pr.toPort < 1 || pr.toPort > 65535 { 77 return errors.Errorf(`toPort must be in the range [1, 65535]; got "%v"`, pr.toPort) 78 } 79 if proto != "tcp" && proto != "udp" && proto != "icmp" { 80 return errors.Errorf(`protocol must be "tcp", "udp", or "icmp"; got %q`, pr.protocol) 81 } 82 return nil 83 } 84 85 func parseArguments(args []string) (portRange, error) { 86 arg := strings.ToLower(args[0]) 87 if !validPortOrRange.MatchString(arg) { 88 return portRange{}, errors.Errorf("expected %s; got %q", portFormat, args[0]) 89 } 90 portOrRange := validPortOrRange.FindString(arg) 91 parts := strings.SplitN(portOrRange, "/", 2) 92 93 protocol := "tcp" 94 if len(parts) > 1 { 95 protocol = parts[1] 96 } 97 ports := parts[0] 98 portParts := strings.SplitN(ports, "-", 2) 99 fromPort, toPort := 0, 0 100 if len(portParts) >= 1 { 101 if portParts[0] == "icmp" { 102 protocol = "icmp" 103 fromPort, toPort = -1, -1 104 } else { 105 port, err := strconv.Atoi(portParts[0]) 106 if err != nil { 107 return portRange{}, errors.Annotatef(err, "expected port number; got %q", portParts[0]) 108 } 109 fromPort = port 110 } 111 } 112 if len(portParts) == 2 { 113 port, err := strconv.Atoi(portParts[1]) 114 if err != nil { 115 return portRange{}, errors.Annotatef(err, "expected port number; got %q", portParts[1]) 116 } 117 toPort = port 118 } else { 119 toPort = fromPort 120 } 121 pr := portRange{fromPort, toPort, protocol} 122 return pr, pr.validate() 123 } 124 125 // portCommand implements the open-port and close-port commands. 126 type portCommand struct { 127 cmd.CommandBase 128 info *cmd.Info 129 action func(*portCommand) error 130 Protocol string 131 FromPort int 132 ToPort int 133 formatFlag string // deprecated 134 } 135 136 func (c *portCommand) Info() *cmd.Info { 137 return jujucmd.Info(c.info) 138 } 139 140 func (c *portCommand) SetFlags(f *gnuflag.FlagSet) { 141 f.StringVar(&c.formatFlag, "format", "", "deprecated format flag") 142 } 143 144 func (c *portCommand) Init(args []string) error { 145 if args == nil { 146 return errors.Errorf("no port or range specified") 147 } 148 149 portRange, err := parseArguments(args) 150 if err != nil { 151 return errors.Trace(err) 152 } 153 154 c.FromPort = portRange.fromPort 155 c.ToPort = portRange.toPort 156 c.Protocol = portRange.protocol 157 return cmd.CheckEmpty(args[1:]) 158 } 159 160 func (c *portCommand) Run(ctx *cmd.Context) error { 161 if c.formatFlag != "" { 162 fmt.Fprintf(ctx.Stderr, "--format flag deprecated for command %q", c.Info().Name) 163 } 164 return c.action(c) 165 } 166 167 var openPortInfo = &cmd.Info{ 168 Name: "open-port", 169 Args: portFormat, 170 Purpose: "register a port or range to open", 171 Doc: "The port range will only be open while the application is exposed.", 172 } 173 174 func NewOpenPortCommand(ctx Context) (cmd.Command, error) { 175 return &portCommand{ 176 info: openPortInfo, 177 action: func(c *portCommand) error { 178 return ctx.OpenPorts(c.Protocol, c.FromPort, c.ToPort) 179 }, 180 }, nil 181 } 182 183 var closePortInfo = &cmd.Info{ 184 Name: "close-port", 185 Args: portFormat, 186 Purpose: "ensure a port or range is always closed", 187 } 188 189 func NewClosePortCommand(ctx Context) (cmd.Command, error) { 190 return &portCommand{ 191 info: closePortInfo, 192 action: func(c *portCommand) error { 193 return ctx.ClosePorts(c.Protocol, c.FromPort, c.ToPort) 194 }, 195 }, nil 196 }