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