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