-
Notifications
You must be signed in to change notification settings - Fork 315
Open
Description
It would be a huge burden lift if one was able to parse stdlib net.* and net/netip.* types as arguments without needing to either create a wrapper flags.Unmarshaler type every single time or by setting them as string arguments and explicitly parsing them.
I'd imagine they're quite common arguments.
PoC/test case under the fold.
main.go
package main
import (
"errors"
"fmt"
"log"
"math/bits"
"net"
"net/netip"
"strconv"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/jessevdk/go-flags"
)
type (
Args struct {
IP net.IP `short:"i" long:"ip" default:"127.0.0.1" description:"net.IP"`
Addr netip.Addr `short:"a" long:"addr" default:"127.0.0.1" description:"net/netip.Addr"`
// This needs some special syntax logic. By default, CIDR -- as show below as the default value.
// However, an alternate form `<IP>_<MASK>` should be handled, where `<MASK>` is equivalent to the
// Args.Mask syntax rules; e.g. `127.0.0.0_255.255.255.0`
// See parseNet for an example.
IPNet net.IPNet `short:"n" long:"ipnet" default:"127.0.0.0/8" description:"net.IPNet"`
// Same syntax rules as Args.IPNet.
// See parsePrefix for an example.
Pfx netip.Prefix `short:"p" long:"prefix" default:"127.0.0.0/8" description:"net/netip.Prefix"`
IPAddr *net.IPAddr `short:"A" long:"ipaddr" default:"fe80::5c9a:9aff:fec3:e2f%eth0" description:"net.IPAddr"`
AddrPort netip.AddrPort `short:"P" long:"addrport" default:"[::1]:80" description:"net/netip.AddrPort"`
// This needs some special syntax logic. These forms should be supported (where N is a number):
// `N.N.N.N` - This would be parsed as an IPv4 netmask in traditional "dotted-quad" form. Exclusive to IPv4.
// `4:N` - This would be an explicit mask *integer*. Exclusive to IPv4.
/* (There is no "uint128", so it's impossible to represent IPv6 CIDR as a mask integer
without a math.BigInt or some other custom encapsulation type.)
e.g. an IPv4 /24 would be represented as `4:4294967040` because `uint32(0xffffffff) << uint32(32 - uint8(<CIDR>))`
(0xffffffff == 4294967295 == (1<<32 - 1) == math.MaxUint32, take your pick.)
*/
// `/N` - This would be parsed as an IPv6 CIDR.
// `6/N` - This would also be parsed as an IPv6 CIDR.
// `4/N` - This would be parsed as an IPv4 CIDR.
// See parseMask for an example.
Mask net.IPMask `short:"m" long:"mask" default:"255.255.255.0" description:"net.IPMask"`
// This needs some special syntax logic. By default, interface name -- as shown below as the default value.
// However, an alternate form `idx:<IDX>` should be handled, where `<IDX>` is an interface index.
// e.g. `idx:1`
// See parseIface for an example.
Iface *net.Interface `short:"I" long:"device" default:"lo" description:"net.Interface"`
}
)
var (
ErrBadAddr error = errors.New("invalid IP address")
ErrBadIface error = errors.New("invalid interface")
ErrBadMask error = errors.New("invalid netmask")
)
// parseIface is a quick example of parsing a passed Args.Iface value.
func parseIface(s string) (iface *net.Interface, err error) {
var ifaceIdx int
var sl []string
sl = strings.Split(s, ":")
switch len(sl) {
case 2:
// idx:<IDX>... maybe.
if sl[0] != "idx" {
err = ErrBadIface
return
}
// OK it is.
if ifaceIdx, err = strconv.Atoi(sl[1]); err != nil {
log.Panicln(err)
}
if iface, err = net.InterfaceByIndex(ifaceIdx); err != nil {
return
}
case 1:
// <interface name>
if iface, err = net.InterfaceByName(s); err != nil {
return
}
default:
err = ErrBadIface
return
}
return
}
// parseMask is a quick example of parsing a passed Args.Mask value.
func parseMask(s string) (mask net.IPMask, err error) {
var b []byte
var u64 uint64
var bitLen int
var sl []string
sl = strings.Split(s, ".")
switch len(sl) {
case 4:
// "Dotted-quad". Conveniently, e.g. 255.255.255.0 is a net.IPMask([]byte{255, 255, 255, 0})
b = make([]byte, 4)
for idx, oct := range sl {
if u64, err = strconv.ParseUint(oct, 10, 8); err != nil {
return
}
b[idx] = uint8(u64)
}
mask = net.IPMask(b)
// Alternatively:
// mask = net.IPv4Mask(b[0], b[1], b[2], b[3])
case 1:
// It's not dotted-quad, so it's another form.
sl = strings.Split(s, ":")
switch len(sl) {
case 2:
// Explicit mask integer... maybe.
if sl[0] != "4" {
err = ErrBadMask
return
}
// OK it is.
if u64, err = strconv.ParseUint(sl[1], 10, 32); err != nil {
return
}
mask = net.CIDRMask(bits.LeadingZeros32(uint32(u64)), 32)
case 1:
// It's not the explicit mask integer either.
// Try the CIDR format.
sl = strings.Split(s, "/")
switch len(sl) {
case 2:
switch sl[0] {
case "", "6":
// IPv6
bitLen = 128
case "4":
// IPv4
bitLen = 32
default:
err = ErrBadMask
return
}
if u64, err = strconv.ParseUint(sl[1], 10, 8); err != nil {
return
}
mask = net.CIDRMask(int(u64), bitLen)
default:
// All syntaxes have been exhausted.
err = ErrBadMask
return
}
default:
err = ErrBadMask
return
}
default:
err = ErrBadMask
return
}
return
}
// parseNet is a quick example of passing a parsed Args.IPNet value.
func parseNet(s string) (ipnet *net.IPNet, err error) {
var sl []string
var ip net.IP
var mask net.IPMask
sl = strings.SplitN(s, "_", 2)
switch len(sl) {
case 2:
// <IP>_<MASK>
if ip = net.ParseIP(sl[0]); ip == nil {
err = ErrBadAddr
return
}
if mask, err = parseMask(sl[1]); err != nil {
return
}
ipnet = &net.IPNet{
IP: ip,
Mask: mask,
}
case 1:
// <IP>/<CIDR>
if _, ipnet, err = net.ParseCIDR(s); err != nil {
return
}
}
return
}
// parsePrefix is a quick example of parsing a passed Args.Pfx value.
func parsePrefix(s string) (pfx netip.Prefix, err error) {
var cidr int
var sl []string
var addr netip.Addr
var mask net.IPMask
sl = strings.SplitN(s, "_", 2)
switch len(sl) {
case 2:
// <IP>_<MASK>
if addr, err = netip.ParseAddr(sl[0]); err != nil {
return
}
if mask, err = parseMask(sl[1]); err != nil {
return
}
cidr, _ = mask.Size()
pfx = netip.PrefixFrom(addr, cidr)
case 1:
// <IP>/<CIDR>
if pfx, err = netip.ParsePrefix(s); err != nil {
return
}
}
return
}
func main() {
var err error
var args *Args = new(Args)
var flagsErr *flags.Error = new(flags.Error)
var parser *flags.Parser = flags.NewParser(args, flags.Default)
_, err = parser.Parse()
// spew.Dump(parser)
spew.Dump(args)
if err != nil {
switch {
case errors.As(err, &flagsErr):
switch {
case errors.Is(flagsErr.Type, flags.ErrHelp),
errors.Is(flagsErr.Type, flags.ErrCommandRequired),
errors.Is(flagsErr.Type, flags.ErrRequired):
return
default:
log.Panicln(err)
}
default:
log.Panicln(err)
}
}
fmt.Println("Done!")
}Metadata
Metadata
Assignees
Labels
No labels