gopkg.in/hugelgupf/u-root.v2@v2.0.0-20180831055005-3f8fdb0ce09d/pkg/wifi/iwl.go (about) 1 // Copyright 2018 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 wifi 6 7 import ( 8 "context" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "regexp" 14 "strings" 15 "time" 16 17 "github.com/u-root/u-root/pkg/wpa/passphrase" 18 ) 19 20 const ( 21 nopassphrase = `network={ 22 ssid="%s" 23 proto=RSN 24 key_mgmt=NONE 25 }` 26 eap = `network={ 27 ssid="%s" 28 key_mgmt=WPA-EAP 29 identity="%s" 30 password="%s" 31 }` 32 ) 33 34 var ( 35 // RegEx for parsing iwlist output 36 cellRE = regexp.MustCompile("(?m)^\\s*Cell") 37 essidRE = regexp.MustCompile("(?m)^\\s*ESSID.*") 38 encKeyOptRE = regexp.MustCompile("(?m)^\\s*Encryption key:(on|off)$") 39 wpa2RE = regexp.MustCompile("(?m)^\\s*IE: IEEE 802.11i/WPA2 Version 1$") 40 authSuitesRE = regexp.MustCompile("(?m)^\\s*Authentication Suites .*$") 41 ) 42 43 type SecProto int 44 45 const ( 46 NoEnc SecProto = iota 47 WpaPsk 48 WpaEap 49 NotSupportedProto 50 ) 51 52 // IWLWorker implements the WiFi interface using the Intel Wireless LAN commands 53 type IWLWorker struct { 54 Interface string 55 } 56 57 func NewIWLWorker(i string) (WiFi, error) { 58 if o, err := exec.Command("ip", "link", "set", "dev", i, "up").CombinedOutput(); err != nil { 59 return &IWLWorker{""}, fmt.Errorf("ip link set dev %v up: %v (%v)", i, string(o), err) 60 } 61 return &IWLWorker{i}, nil 62 } 63 64 func (w *IWLWorker) Scan() ([]Option, error) { 65 o, err := exec.Command("iwlist", w.Interface, "scanning").CombinedOutput() 66 if err != nil { 67 return nil, fmt.Errorf("iwlist: %v (%v)", string(o), err) 68 } 69 return parseIwlistOut(o), nil 70 } 71 72 /* 73 * Assumptions: 74 * 1) Cell, essid, and encryption key option are 1:1 match 75 * 2) We only support IEEE 802.11i/WPA2 Version 1 76 * 3) Each Wifi only support (1) authentication suites (based on observations) 77 */ 78 79 func parseIwlistOut(o []byte) []Option { 80 cells := cellRE.FindAllIndex(o, -1) 81 essids := essidRE.FindAll(o, -1) 82 encKeyOpts := encKeyOptRE.FindAll(o, -1) 83 84 if cells == nil { 85 return nil 86 } 87 88 var res []Option 89 knownEssids := make(map[string]bool) 90 91 // Assemble all the Wifi options 92 for i := 0; i < len(cells); i++ { 93 essid := strings.Trim(strings.Split(string(essids[i]), ":")[1], "\"\n") 94 if knownEssids[essid] { 95 continue 96 } 97 knownEssids[essid] = true 98 encKeyOpt := strings.Trim(strings.Split(string(encKeyOpts[i]), ":")[1], "\n") 99 if encKeyOpt == "off" { 100 res = append(res, Option{essid, NoEnc}) 101 continue 102 } 103 // Find the proper Authentication Suites 104 start, end := cells[i][0], len(o) 105 if i != len(cells)-1 { 106 end = cells[i+1][0] 107 } 108 // Narrow down the scope when looking for WPA Tag 109 wpa2SearchArea := o[start:end] 110 l := wpa2RE.FindIndex(wpa2SearchArea) 111 if l == nil { 112 res = append(res, Option{essid, NotSupportedProto}) 113 continue 114 } 115 // Narrow down the scope when looking for Authorization Suites 116 authSearchArea := wpa2SearchArea[l[0]:] 117 authSuites := strings.Trim(strings.Split(string(authSuitesRE.Find(authSearchArea)), ":")[1], "\n ") 118 switch authSuites { 119 case "PSK": 120 res = append(res, Option{essid, WpaPsk}) 121 case "802.1x": 122 res = append(res, Option{essid, WpaEap}) 123 default: 124 res = append(res, Option{essid, NotSupportedProto}) 125 } 126 } 127 return res 128 } 129 130 func (w *IWLWorker) GetID() (string, error) { 131 o, err := exec.Command("iwgetid", "-r").CombinedOutput() 132 if err != nil { 133 return "", err 134 } 135 return strings.Trim(string(o), " \n"), nil 136 } 137 138 func (w *IWLWorker) Connect(a ...string) error { 139 // format of a: [essid, pass, id] 140 conf, err := generateConfig(a...) 141 if err != nil { 142 return err 143 } 144 145 if err := ioutil.WriteFile("/tmp/wifi.conf", conf, 0444); err != nil { 146 return fmt.Errorf("/tmp/wifi.conf: %v", err) 147 } 148 149 // Each request has a 30 second window to make a connection 150 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 151 defer cancel() 152 c := make(chan error, 1) 153 154 // There's no telling how long the supplicant will take, but on the other hand, 155 // it's been almost instantaneous. But, further, it needs to keep running. 156 go func() { 157 cmd := exec.CommandContext(ctx, "wpa_supplicant", "-i"+w.Interface, "-c/tmp/wifi.conf") 158 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr //For an easier time debugging 159 cmd.Run() 160 }() 161 162 // dhclient might never return on incorrect passwords or identity 163 go func() { 164 cmd := exec.CommandContext(ctx, "dhclient", "-ipv4=true", "-ipv6=false", "-verbose", w.Interface) 165 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr //For an easier time debugging 166 if err := cmd.Run(); err != nil { 167 c <- err 168 } else { 169 c <- nil 170 } 171 }() 172 173 select { 174 case err := <-c: 175 return err 176 case <-ctx.Done(): 177 return fmt.Errorf("Connection timeout") 178 } 179 } 180 181 func generateConfig(a ...string) (conf []byte, err error) { 182 // format of a: [essid, pass, id] 183 switch { 184 case len(a) == 3: 185 conf = []byte(fmt.Sprintf(eap, a[0], a[2], a[1])) 186 case len(a) == 2: 187 conf, err = passphrase.Run(a[0], a[1]) 188 if err != nil { 189 return nil, fmt.Errorf("essid: %v, pass: %v : %v", a[0], a[1], err) 190 } 191 case len(a) == 1: 192 conf = []byte(fmt.Sprintf(nopassphrase, a[0])) 193 default: 194 return nil, fmt.Errorf("generateConfig needs 1, 2, or 3 args") 195 } 196 return 197 }