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 }