github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/dhclient/iscsiuri.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dhclient
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"net"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  type iscsiURIParser struct {
    16  	rem     []byte
    17  	state   string
    18  	fieldNo iscsiField
    19  	err     error
    20  }
    21  
    22  func (i *iscsiURIParser) tok(toks string, err error) []byte {
    23  	n := bytes.Index(i.rem, []byte(toks))
    24  	if n == -1 {
    25  		i.err = err
    26  		return nil
    27  	}
    28  
    29  	b := i.rem[:n]
    30  	i.rem = i.rem[n+len(toks):]
    31  	return b
    32  }
    33  
    34  func (i *iscsiURIParser) tokAny(toks string, err error) (data []byte, tok byte) {
    35  	n := bytes.IndexAny(i.rem, toks)
    36  	if n == -1 {
    37  		i.err = err
    38  		return nil, 0
    39  	}
    40  
    41  	b := i.rem[:n]
    42  	tok = i.rem[n]
    43  	i.rem = i.rem[n+1:]
    44  	return b, tok
    45  }
    46  
    47  type iscsiField int
    48  
    49  const (
    50  	iscsiMagic  iscsiField = 0
    51  	serverField iscsiField = 1
    52  	protField   iscsiField = 2
    53  	portField   iscsiField = 3
    54  	lunField    iscsiField = 4
    55  	volumeField iscsiField = 5
    56  )
    57  
    58  // Format:
    59  //
    60  // iscsi:@"<servername>":"<protocol>":"<port>":"<LUN>":"<targetname>"
    61  //
    62  // @ for now will be ignored. Eventually we would want complete support.
    63  // iscsi:[<username>:<password>[:<reverse>:<password>]@]"<servername>":"<protocol>":"<port>"[:[<iscsi_iface_name>]:[<netdev_name>]]:"<LUN>":"<targetname>"
    64  // "<servername>" may contain an IPv6 address enclosed with [] with an
    65  // arbitrary but bounded number of colons.
    66  //
    67  // "<targetname>" may contain an arbitrary string with an arbitrary number of
    68  // colons.
    69  func ParseISCSIURI(s string) (*net.TCPAddr, string, error) {
    70  	var (
    71  		// port has a default value according to RFC 4173.
    72  		port   = 3260
    73  		ip     net.IP
    74  		volume string
    75  		magic  string
    76  	)
    77  	i := &iscsiURIParser{
    78  		state:   "normal",
    79  		fieldNo: iscsiMagic,
    80  		rem:     []byte(s),
    81  	}
    82  	for i.fieldNo <= volumeField && i.err == nil {
    83  		fno, tok := i.next()
    84  		switch fno {
    85  		case iscsiMagic:
    86  			magic = tok
    87  		case serverField:
    88  			tok = strings.TrimPrefix(tok, "@") // ignore any leading @
    89  			ip = net.ParseIP(tok)
    90  		case protField, lunField:
    91  			// yeah whatever
    92  			continue
    93  		case portField:
    94  			if len(tok) > 0 {
    95  				pv, err := strconv.Atoi(tok)
    96  				if err != nil {
    97  					return nil, "", fmt.Errorf("iSCSI URI %q has invalid port: %v", s, err)
    98  				}
    99  				port = pv
   100  			}
   101  		case volumeField:
   102  			volume = tok
   103  		}
   104  	}
   105  	if i.err != nil {
   106  		return nil, "", fmt.Errorf("iSCSI URI %q failed to parse: %v", s, i.err)
   107  	}
   108  	if magic != "iscsi" {
   109  		return nil, "", fmt.Errorf("iSCSI URI %q is missing iscsi scheme prefix, have %s", s, magic)
   110  	}
   111  	if len(volume) == 0 {
   112  		return nil, "", fmt.Errorf("iSCSI URI %q is missing a volume name", s)
   113  	}
   114  	return &net.TCPAddr{
   115  		IP:   ip,
   116  		Port: port,
   117  	}, volume, nil
   118  }
   119  
   120  func (i *iscsiURIParser) next() (iscsiField, string) {
   121  	var val []byte
   122  	switch i.state {
   123  	case "normal":
   124  		val = i.tok(":", fmt.Errorf("fields missing"))
   125  		switch i.fieldNo {
   126  		case iscsiMagic:
   127  			// The next field is an IP or hostname, which may contain other colons.
   128  			i.state = "ip"
   129  		case lunField:
   130  			// The next field is the last field, the volume name,
   131  			// is a free-for-all that may contain as many colons as
   132  			// it wants.
   133  			i.state = "remaining"
   134  		}
   135  
   136  	case "ipv6":
   137  		val = i.tok("]:", fmt.Errorf("invalid IPv6 address"))
   138  		i.state = "normal"
   139  
   140  	case "remaining":
   141  		val = i.rem
   142  		i.rem = nil
   143  
   144  	case "ip":
   145  		var tok byte
   146  		val, tok = i.tokAny("[:", fmt.Errorf("fields missing"))
   147  		switch tok {
   148  		case '[':
   149  			i.state = "ipv6"
   150  			return i.next()
   151  		case ':':
   152  			// IPv4 address is in tok, go back to normal next.
   153  			i.state = "normal"
   154  		}
   155  
   156  	default:
   157  		i.err = fmt.Errorf("unrecognized state %s", i.state)
   158  		return -1, ""
   159  	}
   160  
   161  	i.fieldNo++
   162  	return i.fieldNo - 1, string(val)
   163  }