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  }