1package command 2 3import ( 4 "flag" 5 "fmt" 6 7 "github.com/hashicorp/errwrap" 8 sockaddr "github.com/hashicorp/go-sockaddr" 9 "github.com/mitchellh/cli" 10 "github.com/ryanuber/columnize" 11) 12 13type DumpCommand struct { 14 Ui cli.Ui 15 16 // attrNames is a list of attribute names to include in the output 17 attrNames []string 18 19 // flags is a list of options belonging to this command 20 flags *flag.FlagSet 21 22 // machineMode changes the output format to be machine friendly 23 // (i.e. tab-separated values). 24 machineMode bool 25 26 // valueOnly changes the output format to include only values 27 valueOnly bool 28 29 // ifOnly parses the input as an interface name 30 ifOnly bool 31 32 // ipOnly parses the input as an IP address (either IPv4 or IPv6) 33 ipOnly bool 34 35 // v4Only parses the input exclusively as an IPv4 address 36 v4Only bool 37 38 // v6Only parses the input exclusively as an IPv6 address 39 v6Only bool 40 41 // unixOnly parses the input exclusively as a UNIX Socket 42 unixOnly bool 43} 44 45// Description is the long-form command help. 46func (c *DumpCommand) Description() string { 47 return `Parse address(es) or interface and dumps various output.` 48} 49 50// Help returns the full help output expected by `sockaddr -h cmd` 51func (c *DumpCommand) Help() string { 52 return MakeHelp(c) 53} 54 55// InitOpts is responsible for setup of this command's configuration via the 56// command line. InitOpts() does not parse the arguments (see parseOpts()). 57func (c *DumpCommand) InitOpts() { 58 c.flags = flag.NewFlagSet("dump", flag.ContinueOnError) 59 c.flags.Usage = func() { c.Ui.Output(c.Help()) } 60 c.flags.BoolVar(&c.machineMode, "H", false, "Machine readable output") 61 c.flags.BoolVar(&c.valueOnly, "n", false, "Show only the value") 62 c.flags.BoolVar(&c.v4Only, "4", false, "Parse the input as IPv4 only") 63 c.flags.BoolVar(&c.v6Only, "6", false, "Parse the input as IPv6 only") 64 c.flags.BoolVar(&c.ifOnly, "I", false, "Parse the argument as an interface name") 65 c.flags.BoolVar(&c.ipOnly, "i", false, "Parse the input as IP address (either IPv4 or IPv6)") 66 c.flags.BoolVar(&c.unixOnly, "u", false, "Parse the input as a UNIX Socket only") 67 c.flags.Var((*MultiArg)(&c.attrNames), "o", "Name of an attribute to pass through") 68} 69 70// Run executes this command. 71func (c *DumpCommand) Run(args []string) int { 72 if len(args) == 0 { 73 c.Ui.Error(c.Help()) 74 return 1 75 } 76 77 c.InitOpts() 78 addrs, err := c.parseOpts(args) 79 if err != nil { 80 if errwrap.Contains(err, "flag: help requested") { 81 return 0 82 } 83 return 1 84 } 85 for _, addr := range addrs { 86 var sa sockaddr.SockAddr 87 var ifAddrs sockaddr.IfAddrs 88 var err error 89 switch { 90 case c.v4Only: 91 sa, err = sockaddr.NewIPv4Addr(addr) 92 case c.v6Only: 93 sa, err = sockaddr.NewIPv6Addr(addr) 94 case c.unixOnly: 95 sa, err = sockaddr.NewUnixSock(addr) 96 case c.ipOnly: 97 sa, err = sockaddr.NewIPAddr(addr) 98 case c.ifOnly: 99 ifAddrs, err = sockaddr.GetAllInterfaces() 100 if err != nil { 101 break 102 } 103 104 ifAddrs, _, err = sockaddr.IfByName(addr, ifAddrs) 105 default: 106 sa, err = sockaddr.NewSockAddr(addr) 107 } 108 if err != nil { 109 c.Ui.Error(fmt.Sprintf("Unable to parse %+q: %v", addr, err)) 110 return 1 111 } 112 if sa != nil { 113 c.dumpSockAddr(sa) 114 } else if ifAddrs != nil { 115 c.dumpIfAddrs(ifAddrs) 116 } else { 117 panic("bad") 118 } 119 } 120 return 0 121} 122 123// Synopsis returns a terse description used when listing sub-commands. 124func (c *DumpCommand) Synopsis() string { 125 return `Parses input as an IP or interface name(s) and dumps various information` 126} 127 128// Usage is the one-line usage description 129func (c *DumpCommand) Usage() string { 130 return `sockaddr dump [options] input [...]` 131} 132 133// VisitAllFlags forwards the visitor function to the FlagSet 134func (c *DumpCommand) VisitAllFlags(fn func(*flag.Flag)) { 135 c.flags.VisitAll(fn) 136} 137 138func (c *DumpCommand) dumpIfAddrs(ifAddrs sockaddr.IfAddrs) { 139 for _, ifAddr := range ifAddrs { 140 c.dumpSockAddr(ifAddr.SockAddr) 141 } 142} 143 144func (c *DumpCommand) dumpSockAddr(sa sockaddr.SockAddr) { 145 reservedAttrs := []sockaddr.AttrName{"Attribute"} 146 const maxNumAttrs = 32 147 148 output := make([]string, 0, maxNumAttrs+len(reservedAttrs)) 149 allowedAttrs := make(map[sockaddr.AttrName]struct{}, len(c.attrNames)+len(reservedAttrs)) 150 for _, attr := range reservedAttrs { 151 allowedAttrs[attr] = struct{}{} 152 } 153 for _, attr := range c.attrNames { 154 allowedAttrs[sockaddr.AttrName(attr)] = struct{}{} 155 } 156 157 // allowedAttr returns true if the attribute is allowed to be appended 158 // to the output. 159 allowedAttr := func(k sockaddr.AttrName) bool { 160 if len(allowedAttrs) == len(reservedAttrs) { 161 return true 162 } 163 164 _, found := allowedAttrs[k] 165 return found 166 } 167 168 // outFmt is a small helper function to reduce the tedium below. outFmt 169 // returns a new slice and expects the value to already be a string. 170 outFmt := func(o []string, k sockaddr.AttrName, v interface{}) []string { 171 if !allowedAttr(k) { 172 return o 173 } 174 switch { 175 case c.valueOnly: 176 return append(o, fmt.Sprintf("%s", v)) 177 case !c.valueOnly && c.machineMode: 178 return append(o, fmt.Sprintf("%s\t%s", k, v)) 179 case !c.valueOnly && !c.machineMode: 180 fallthrough 181 default: 182 return append(o, fmt.Sprintf("%s | %s", k, v)) 183 } 184 } 185 186 if !c.machineMode { 187 output = outFmt(output, "Attribute", "Value") 188 } 189 190 // Attributes for all SockAddr types 191 for _, attr := range sockaddr.SockAddrAttrs() { 192 output = outFmt(output, attr, sockaddr.SockAddrAttr(sa, attr)) 193 } 194 195 // Attributes for all IP types (both IPv4 and IPv6) 196 if sa.Type()&sockaddr.TypeIP != 0 { 197 ip := *sockaddr.ToIPAddr(sa) 198 for _, attr := range sockaddr.IPAttrs() { 199 output = outFmt(output, attr, sockaddr.IPAddrAttr(ip, attr)) 200 } 201 } 202 203 if sa.Type() == sockaddr.TypeIPv4 { 204 ipv4 := *sockaddr.ToIPv4Addr(sa) 205 for _, attr := range sockaddr.IPv4Attrs() { 206 output = outFmt(output, attr, sockaddr.IPv4AddrAttr(ipv4, attr)) 207 } 208 } 209 210 if sa.Type() == sockaddr.TypeIPv6 { 211 ipv6 := *sockaddr.ToIPv6Addr(sa) 212 for _, attr := range sockaddr.IPv6Attrs() { 213 output = outFmt(output, attr, sockaddr.IPv6AddrAttr(ipv6, attr)) 214 } 215 } 216 217 if sa.Type() == sockaddr.TypeUnix { 218 us := *sockaddr.ToUnixSock(sa) 219 for _, attr := range sockaddr.UnixSockAttrs() { 220 output = outFmt(output, attr, sockaddr.UnixSockAttr(us, attr)) 221 } 222 } 223 224 // Developer-focused arguments 225 { 226 arg1, arg2 := sa.DialPacketArgs() 227 output = outFmt(output, "DialPacket", fmt.Sprintf("%+q %+q", arg1, arg2)) 228 } 229 { 230 arg1, arg2 := sa.DialStreamArgs() 231 output = outFmt(output, "DialStream", fmt.Sprintf("%+q %+q", arg1, arg2)) 232 } 233 { 234 arg1, arg2 := sa.ListenPacketArgs() 235 output = outFmt(output, "ListenPacket", fmt.Sprintf("%+q %+q", arg1, arg2)) 236 } 237 { 238 arg1, arg2 := sa.ListenStreamArgs() 239 output = outFmt(output, "ListenStream", fmt.Sprintf("%+q %+q", arg1, arg2)) 240 } 241 242 result := columnize.SimpleFormat(output) 243 c.Ui.Output(result) 244} 245 246// parseOpts is responsible for parsing the options set in InitOpts(). Returns 247// a list of non-parsed flags. 248func (c *DumpCommand) parseOpts(args []string) ([]string, error) { 249 if err := c.flags.Parse(args); err != nil { 250 return nil, err 251 } 252 253 conflictingOptsCount := 0 254 if c.v4Only { 255 conflictingOptsCount++ 256 } 257 if c.v6Only { 258 conflictingOptsCount++ 259 } 260 if c.unixOnly { 261 conflictingOptsCount++ 262 } 263 if c.ifOnly { 264 conflictingOptsCount++ 265 } 266 if c.ipOnly { 267 conflictingOptsCount++ 268 } 269 if conflictingOptsCount > 1 { 270 return nil, fmt.Errorf("Conflicting options specified, only one parsing mode may be specified at a time") 271 } 272 273 return c.flags.Args(), nil 274} 275