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 }