github.com/craftyguy/u-root@v1.0.0/pkg/pxe/pxe_test.go (about)

     1  // Copyright 2017-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 pxe
     6  
     7  import (
     8  	"fmt"
     9  	"net"
    10  	"net/url"
    11  	"reflect"
    12  	"testing"
    13  
    14  	"github.com/u-root/u-root/pkg/uio"
    15  )
    16  
    17  func TestProbeFiles(t *testing.T) {
    18  	// Anyone got some ideas for other test cases?
    19  	for _, tt := range []struct {
    20  		mac   net.HardwareAddr
    21  		ip    net.IP
    22  		files []string
    23  	}{
    24  		{
    25  			mac: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
    26  			ip:  []byte{192, 168, 0, 1},
    27  			files: []string{
    28  				"01-aa-bb-cc-dd-ee-ff",
    29  				"C0A80001",
    30  				"C0A8000",
    31  				"C0A800",
    32  				"C0A80",
    33  				"C0A8",
    34  				"C0A",
    35  				"C0",
    36  				"C",
    37  				"default",
    38  			},
    39  		},
    40  		{
    41  			mac: []byte{0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd},
    42  			ip:  []byte{192, 168, 2, 91},
    43  			files: []string{
    44  				"01-88-99-aa-bb-cc-dd",
    45  				"C0A8025B",
    46  				"C0A8025",
    47  				"C0A802",
    48  				"C0A80",
    49  				"C0A8",
    50  				"C0A",
    51  				"C0",
    52  				"C",
    53  				"default",
    54  			},
    55  		},
    56  	} {
    57  		got := probeFiles(tt.mac, tt.ip)
    58  		if !reflect.DeepEqual(got, tt.files) {
    59  			t.Errorf("probeFiles(%s, %s) = %v, want %v", tt.mac, tt.ip, got, tt.files)
    60  		}
    61  	}
    62  }
    63  
    64  func TestAppendFile(t *testing.T) {
    65  	content1 := "1111"
    66  	content2 := "2222"
    67  	content3 := "3333"
    68  	content4 := "4444"
    69  
    70  	type label struct {
    71  		kernel    string
    72  		kernelErr error
    73  		initrd    string
    74  		initrdErr error
    75  		cmdline   string
    76  	}
    77  	type config struct {
    78  		defaultEntry string
    79  		labels       map[string]label
    80  	}
    81  
    82  	for i, tt := range []struct {
    83  		desc          string
    84  		configFileURI string
    85  		schemeFunc    func() Schemes
    86  		wd            *url.URL
    87  		config        *Config
    88  		want          config
    89  		err           error
    90  	}{
    91  		{
    92  			desc:          "all files exist, simple config with cmdline initrd",
    93  			configFileURI: "pxelinux.cfg/default",
    94  			schemeFunc: func() Schemes {
    95  				s := make(Schemes)
    96  				fs := NewMockScheme("tftp")
    97  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
    98  				conf := `default foo
    99  				label foo
   100  				kernel ./pxefiles/kernel
   101  				append initrd=./pxefiles/initrd`
   102  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   103  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   104  				fs.Add("1.2.3.4", "/foobar/pxefiles/initrd", content2)
   105  				s.Register(fs.scheme, fs)
   106  				return s
   107  			},
   108  			wd: &url.URL{
   109  				Scheme: "tftp",
   110  				Host:   "1.2.3.4",
   111  				Path:   "/foobar",
   112  			},
   113  			want: config{
   114  				defaultEntry: "foo",
   115  				labels: map[string]label{
   116  					"foo": {
   117  						kernel:  content1,
   118  						initrd:  content2,
   119  						cmdline: "initrd=./pxefiles/initrd",
   120  					},
   121  				},
   122  			},
   123  		},
   124  		{
   125  			desc:          "all files exist, simple config with directive initrd",
   126  			configFileURI: "pxelinux.cfg/default",
   127  			schemeFunc: func() Schemes {
   128  				s := make(Schemes)
   129  				fs := NewMockScheme("tftp")
   130  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   131  				conf := `default foo
   132  				label foo
   133  				kernel ./pxefiles/kernel
   134  				initrd ./pxefiles/initrd
   135  				append foo=bar`
   136  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   137  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   138  				fs.Add("1.2.3.4", "/foobar/pxefiles/initrd", content2)
   139  				s.Register(fs.scheme, fs)
   140  				return s
   141  			},
   142  			wd: &url.URL{
   143  				Scheme: "tftp",
   144  				Host:   "1.2.3.4",
   145  				Path:   "/foobar",
   146  			},
   147  			want: config{
   148  				defaultEntry: "foo",
   149  				labels: map[string]label{
   150  					"foo": {
   151  						kernel:  content1,
   152  						initrd:  content2,
   153  						cmdline: "foo=bar",
   154  					},
   155  				},
   156  			},
   157  		},
   158  		{
   159  			desc:          "all files exist, simple config, no initrd",
   160  			configFileURI: "pxelinux.cfg/default",
   161  			schemeFunc: func() Schemes {
   162  				s := make(Schemes)
   163  				fs := NewMockScheme("tftp")
   164  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   165  				conf := `default foo
   166  				label foo
   167  				kernel ./pxefiles/kernel`
   168  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   169  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   170  				s.Register(fs.scheme, fs)
   171  				return s
   172  			},
   173  			wd: &url.URL{
   174  				Scheme: "tftp",
   175  				Host:   "1.2.3.4",
   176  				Path:   "/foobar",
   177  			},
   178  			want: config{
   179  				defaultEntry: "foo",
   180  				labels: map[string]label{
   181  					"foo": {
   182  						kernel:  content1,
   183  						initrd:  "",
   184  						cmdline: "",
   185  					},
   186  				},
   187  			},
   188  		},
   189  		{
   190  			desc:          "kernel does not exist, simple config",
   191  			configFileURI: "pxelinux.cfg/default",
   192  			schemeFunc: func() Schemes {
   193  				s := make(Schemes)
   194  				fs := NewMockScheme("tftp")
   195  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   196  				conf := `default foo
   197  				label foo
   198  				kernel ./pxefiles/kernel`
   199  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   200  				s.Register(fs.scheme, fs)
   201  				return s
   202  			},
   203  			wd: &url.URL{
   204  				Scheme: "tftp",
   205  				Host:   "1.2.3.4",
   206  				Path:   "/foobar",
   207  			},
   208  			want: config{
   209  				defaultEntry: "foo",
   210  				labels: map[string]label{
   211  					"foo": {
   212  						kernelErr: &URLError{
   213  							URL: &url.URL{
   214  								Scheme: "tftp",
   215  								Host:   "1.2.3.4",
   216  								Path:   "/foobar/pxefiles/kernel",
   217  							},
   218  							Err: errNoSuchFile,
   219  						},
   220  						initrd:  "",
   221  						cmdline: "",
   222  					},
   223  				},
   224  			},
   225  		},
   226  		{
   227  			desc:          "config file does not exist",
   228  			configFileURI: "pxelinux.cfg/default",
   229  			schemeFunc: func() Schemes {
   230  				s := make(Schemes)
   231  				fs := NewMockScheme("tftp")
   232  				s.Register(fs.scheme, fs)
   233  				return s
   234  			},
   235  			wd: &url.URL{
   236  				Scheme: "tftp",
   237  				Host:   "1.2.3.4",
   238  				Path:   "/foobar",
   239  			},
   240  			err: &URLError{
   241  				URL: &url.URL{
   242  					Scheme: "tftp",
   243  					Host:   "1.2.3.4",
   244  					Path:   "/foobar/pxelinux.cfg/default",
   245  				},
   246  				Err: errNoSuchHost,
   247  			},
   248  		},
   249  		{
   250  			desc:          "empty config",
   251  			configFileURI: "pxelinux.cfg/default",
   252  			schemeFunc: func() Schemes {
   253  				s := make(Schemes)
   254  				fs := NewMockScheme("tftp")
   255  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", "")
   256  				s.Register(fs.scheme, fs)
   257  				return s
   258  			},
   259  			wd: &url.URL{
   260  				Scheme: "tftp",
   261  				Host:   "1.2.3.4",
   262  				Path:   "/foobar",
   263  			},
   264  			want: config{
   265  				defaultEntry: "",
   266  			},
   267  		},
   268  		{
   269  			desc:          "valid config with two labels",
   270  			configFileURI: "pxelinux.cfg/default",
   271  			schemeFunc: func() Schemes {
   272  				s := make(Schemes)
   273  				fs := NewMockScheme("tftp")
   274  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   275  				conf := `default foo
   276  
   277  				label foo
   278  				kernel ./pxefiles/fookernel
   279  				append earlyprintk=ttyS0 printk=ttyS0
   280  
   281  				label bar
   282  				kernel ./pxefiles/barkernel
   283  				append console=ttyS0`
   284  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   285  				fs.Add("1.2.3.4", "/foobar/pxefiles/fookernel", content1)
   286  				fs.Add("1.2.3.4", "/foobar/pxefiles/barkernel", content2)
   287  				s.Register(fs.scheme, fs)
   288  				return s
   289  			},
   290  			wd: &url.URL{
   291  				Scheme: "tftp",
   292  				Host:   "1.2.3.4",
   293  				Path:   "/foobar",
   294  			},
   295  			want: config{
   296  				defaultEntry: "foo",
   297  				labels: map[string]label{
   298  					"foo": {
   299  						kernel:  content1,
   300  						cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   301  					},
   302  					"bar": {
   303  						kernel:  content2,
   304  						cmdline: "console=ttyS0",
   305  					},
   306  				},
   307  			},
   308  		},
   309  		{
   310  			desc:          "valid config with global APPEND directive",
   311  			configFileURI: "pxelinux.cfg/default",
   312  			schemeFunc: func() Schemes {
   313  				s := make(Schemes)
   314  				fs := NewMockScheme("tftp")
   315  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   316  				conf := `default foo
   317  				append foo=bar
   318  
   319  				label foo
   320  				kernel ./pxefiles/fookernel
   321  				append earlyprintk=ttyS0 printk=ttyS0
   322  
   323  				label bar
   324  				kernel ./pxefiles/barkernel
   325  
   326  				label baz
   327  				kernel ./pxefiles/barkernel
   328  				append -`
   329  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   330  				fs.Add("1.2.3.4", "/foobar/pxefiles/fookernel", content1)
   331  				fs.Add("1.2.3.4", "/foobar/pxefiles/barkernel", content2)
   332  				s.Register(fs.scheme, fs)
   333  				return s
   334  			},
   335  			wd: &url.URL{
   336  				Scheme: "tftp",
   337  				Host:   "1.2.3.4",
   338  				Path:   "/foobar",
   339  			},
   340  			want: config{
   341  				defaultEntry: "foo",
   342  				labels: map[string]label{
   343  					"foo": {
   344  						kernel: content1,
   345  						// Does not contain global APPEND.
   346  						cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   347  					},
   348  					"bar": {
   349  						kernel: content2,
   350  						// Contains only global APPEND.
   351  						cmdline: "foo=bar",
   352  					},
   353  					"baz": {
   354  						kernel: content2,
   355  						// "APPEND -" means ignore global APPEND.
   356  						cmdline: "",
   357  					},
   358  				},
   359  			},
   360  		},
   361  		{
   362  			desc:          "valid config with global APPEND with initrd",
   363  			configFileURI: "pxelinux.cfg/default",
   364  			schemeFunc: func() Schemes {
   365  				s := make(Schemes)
   366  				fs := NewMockScheme("tftp")
   367  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   368  				conf := `default mcnulty
   369  				append initrd=./pxefiles/normal_person
   370  
   371  				label mcnulty
   372  				kernel ./pxefiles/copkernel
   373  				append earlyprintk=ttyS0 printk=ttyS0
   374  
   375  				label lester
   376  				kernel ./pxefiles/copkernel
   377  
   378  				label omar
   379  				kernel ./pxefiles/drugkernel
   380  				append -
   381  				
   382  				label stringer
   383  				kernel ./pxefiles/drugkernel
   384  				initrd ./pxefiles/criminal
   385  				`
   386  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   387  				fs.Add("1.2.3.4", "/foobar/pxefiles/copkernel", content1)
   388  				fs.Add("1.2.3.4", "/foobar/pxefiles/drugkernel", content2)
   389  				fs.Add("1.2.3.4", "/foobar/pxefiles/normal_person", content3)
   390  				fs.Add("1.2.3.4", "/foobar/pxefiles/criminal", content4)
   391  				s.Register(fs.scheme, fs)
   392  				return s
   393  			},
   394  			wd: &url.URL{
   395  				Scheme: "tftp",
   396  				Host:   "1.2.3.4",
   397  				Path:   "/foobar",
   398  			},
   399  			want: config{
   400  				defaultEntry: "mcnulty",
   401  				labels: map[string]label{
   402  					"mcnulty": {
   403  						kernel: content1,
   404  						// Does not contain global APPEND.
   405  						cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   406  					},
   407  					"lester": {
   408  						kernel: content1,
   409  						initrd: content3,
   410  						// Contains only global APPEND.
   411  						cmdline: "initrd=./pxefiles/normal_person",
   412  					},
   413  					"omar": {
   414  						kernel: content2,
   415  						// "APPEND -" means ignore global APPEND.
   416  						cmdline: "",
   417  					},
   418  					"stringer": {
   419  						kernel: content2,
   420  						// See TODO in pxe.go initrd handling.
   421  						initrd:  content4,
   422  						cmdline: "initrd=./pxefiles/normal_person",
   423  					},
   424  				},
   425  			},
   426  		},
   427  		{
   428  			desc:          "default label does not exist",
   429  			configFileURI: "pxelinux.cfg/default",
   430  			schemeFunc: func() Schemes {
   431  				s := make(Schemes)
   432  				fs := NewMockScheme("tftp")
   433  				conf := `default avon`
   434  
   435  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   436  				s.Register(fs.scheme, fs)
   437  				return s
   438  			},
   439  			wd: &url.URL{
   440  				Scheme: "tftp",
   441  				Host:   "1.2.3.4",
   442  				Path:   "/foobar",
   443  			},
   444  			err: ErrDefaultEntryNotFound,
   445  			want: config{
   446  				defaultEntry: "avon",
   447  			},
   448  		},
   449  		{
   450  			desc:          "multi-scheme valid config",
   451  			configFileURI: "pxelinux.cfg/default",
   452  			schemeFunc: func() Schemes {
   453  				conf := `default sheeeit
   454  
   455  				label sheeeit
   456  				kernel ./pxefiles/kernel
   457  				initrd http://someplace.com/someinitrd.gz`
   458  
   459  				tftp := NewMockScheme("tftp")
   460  				tftp.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   461  				tftp.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   462  				tftp.Add("1.2.3.4", "/foobar/pxefiles/kernel", content2)
   463  
   464  				http := NewMockScheme("http")
   465  				http.Add("someplace.com", "/someinitrd.gz", content3)
   466  
   467  				s := make(Schemes)
   468  				s.Register(tftp.scheme, tftp)
   469  				s.Register(http.scheme, http)
   470  				return s
   471  			},
   472  			wd: &url.URL{
   473  				Scheme: "tftp",
   474  				Host:   "1.2.3.4",
   475  				Path:   "/foobar",
   476  			},
   477  			want: config{
   478  				defaultEntry: "sheeeit",
   479  				labels: map[string]label{
   480  					"sheeeit": {
   481  						kernel: content2,
   482  						initrd: content3,
   483  					},
   484  				},
   485  			},
   486  		},
   487  		{
   488  			desc:          "valid config with three includes",
   489  			configFileURI: "pxelinux.cfg/default",
   490  			schemeFunc: func() Schemes {
   491  				s := make(Schemes)
   492  				fs := NewMockScheme("tftp")
   493  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   494  				conf := `default mcnulty
   495  
   496  				include installer/txt.cfg
   497  				include installer/stdmenu.cfg
   498  
   499  				menu begin advanced
   500  				  menu title Advanced Options
   501  				  include installer/stdmenu.cfg
   502  				menu end
   503  				`
   504  
   505  				txt := `
   506  				label mcnulty
   507  				kernel ./pxefiles/copkernel
   508  				append earlyprintk=ttyS0 printk=ttyS0
   509  				`
   510  
   511  				stdmenu := `
   512  				label omar
   513  				kernel ./pxefiles/drugkernel
   514  				`
   515  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   516  				fs.Add("1.2.3.4", "/foobar/installer/stdmenu.cfg", stdmenu)
   517  				fs.Add("1.2.3.4", "/foobar/installer/txt.cfg", txt)
   518  				fs.Add("1.2.3.4", "/foobar/pxefiles/copkernel", content1)
   519  				fs.Add("1.2.3.4", "/foobar/pxefiles/drugkernel", content2)
   520  				s.Register(fs.scheme, fs)
   521  				return s
   522  			},
   523  			wd: &url.URL{
   524  				Scheme: "tftp",
   525  				Host:   "1.2.3.4",
   526  				Path:   "/foobar",
   527  			},
   528  			want: config{
   529  				defaultEntry: "mcnulty",
   530  				labels: map[string]label{
   531  					"mcnulty": {
   532  						kernel:  content1,
   533  						cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   534  					},
   535  					"omar": {
   536  						kernel: content2,
   537  					},
   538  				},
   539  			},
   540  		},
   541  	} {
   542  		t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) {
   543  			s := tt.schemeFunc()
   544  			c := NewConfig(tt.wd)
   545  			c.schemes = s
   546  
   547  			if err := c.AppendFile(tt.configFileURI); !reflect.DeepEqual(err, tt.err) {
   548  				t.Errorf("AppendFile() got %v, want %v", err, tt.err)
   549  			} else if err != nil {
   550  				return
   551  			}
   552  
   553  			if got, want := c.DefaultEntry, tt.want.defaultEntry; got != want {
   554  				t.Errorf("DefaultEntry got %v, want %v", got, want)
   555  			}
   556  
   557  			for labelName, want := range tt.want.labels {
   558  				t.Run(fmt.Sprintf("label %s", labelName), func(t *testing.T) {
   559  					label, ok := c.Entries[labelName]
   560  					if !ok {
   561  						t.Errorf("Config label %v does not exist", labelName)
   562  						return
   563  					}
   564  
   565  					// Same kernel?
   566  					if label.Kernel == nil && (len(want.kernel) > 0 || want.kernelErr != nil) {
   567  						t.Errorf("want kernel, got none")
   568  					}
   569  					if label.Kernel != nil {
   570  						k, err := uio.ReadAll(label.Kernel)
   571  						if !reflect.DeepEqual(err, want.kernelErr) {
   572  							t.Errorf("could not read kernel of label %q: %v, want %v", labelName, err, want.kernelErr)
   573  						}
   574  						if got, want := string(k), want.kernel; got != want {
   575  							t.Errorf("got kernel %s, want %s", got, want)
   576  						}
   577  					}
   578  
   579  					// Same initrd?
   580  					if label.Initrd == nil && (len(want.initrd) > 0 || want.initrdErr != nil) {
   581  						t.Errorf("want initrd, got none")
   582  					}
   583  					if label.Initrd != nil {
   584  						i, err := uio.ReadAll(label.Initrd)
   585  						if err != want.initrdErr {
   586  							t.Errorf("could not read initrd of label %q: %v, want %v", labelName, err, want.initrdErr)
   587  						}
   588  						if got, want := string(i), want.initrd; got != want {
   589  							t.Errorf("got initrd %s, want %s", got, want)
   590  						}
   591  					}
   592  
   593  					// Same cmdline?
   594  					if got, want := label.Cmdline, want.cmdline; got != want {
   595  						t.Errorf("got cmdline %s, want %s", got, want)
   596  					}
   597  				})
   598  			}
   599  
   600  			// Check that the parser didn't make up labels.
   601  			for labelName := range c.Entries {
   602  				if _, ok := tt.want.labels[labelName]; !ok {
   603  					t.Errorf("config has extra label %s, but not wanted", labelName)
   604  				}
   605  			}
   606  		})
   607  	}
   608  }