github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/pxeserver/main.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  // pxeserver is a test & lab PXE server that supports TFTP, HTTP, and DHCPv4.
     6  //
     7  // pxeserver can either respond to *all* DHCP requests, or a DHCP request from
     8  // a specific MAC. In either case, it will supply the same IP in all answers.
     9  package main
    10  
    11  import (
    12  	"bytes"
    13  	"flag"
    14  	"fmt"
    15  	"log"
    16  	"math"
    17  	"net"
    18  	"net/http"
    19  	"runtime"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/insomniacslk/dhcp/dhcpv4"
    24  	"github.com/insomniacslk/dhcp/dhcpv4/server4"
    25  	"github.com/insomniacslk/dhcp/dhcpv6"
    26  	"github.com/insomniacslk/dhcp/dhcpv6/server6"
    27  	"pack.ag/tftp"
    28  )
    29  
    30  var (
    31  	mac = flag.String("mac", "", "MAC address to respond to. Responds to all requests if unspecified.")
    32  
    33  	// DHCPv4-specific
    34  	ipv4         = flag.Bool("4", true, "IPv4 DHCP server")
    35  	selfIP       = flag.String("ip", "192.168.0.1", "DHCPv4 IP of self")
    36  	yourIP       = flag.String("your-ip", "192.168.0.2/24", "The one and only CIDR to give to all DHCPv4 clients")
    37  	rootpath     = flag.String("rootpath", "", "RootPath option to serve via DHCPv4")
    38  	bootfilename = flag.String("bootfilename", "pxelinux.0", "Boot file to serve via DHCPv4")
    39  	inf          = flag.String("interface", "eth0", "Interface to serve DHCPv4 on")
    40  
    41  	// DHCPv6-specific
    42  	ipv6           = flag.Bool("6", false, "DHCPv6 server")
    43  	yourIP6        = flag.String("your-ip6", "fec0::3", "IPv6 to hand to all clients")
    44  	v6Bootfilename = flag.String("v6-bootfilename", "", "Boot file to serve via DHCPv6")
    45  
    46  	// File serving
    47  	tftpDir  = flag.String("tftp-dir", "", "Directory to serve over TFTP")
    48  	tftpPort = flag.Int("tftp-port", 69, "Port to serve TFTP on")
    49  	httpDir  = flag.String("http-dir", "", "Directory to serve over HTTP")
    50  	httpPort = flag.Int("http-port", 80, "Port to serve HTTP on")
    51  )
    52  
    53  type dserver4 struct {
    54  	mac          net.HardwareAddr
    55  	yourIP       net.IP
    56  	submask      net.IPMask
    57  	self         net.IP
    58  	bootfilename string
    59  	rootpath     string
    60  }
    61  
    62  func (s *dserver4) dhcpHandler(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) {
    63  	log.Printf("Handling request %v for peer %v", m, peer)
    64  
    65  	var replyType dhcpv4.MessageType
    66  	switch mt := m.MessageType(); mt {
    67  	case dhcpv4.MessageTypeDiscover:
    68  		replyType = dhcpv4.MessageTypeOffer
    69  	case dhcpv4.MessageTypeRequest:
    70  		replyType = dhcpv4.MessageTypeAck
    71  	default:
    72  		log.Printf("Can't handle type %v", mt)
    73  		return
    74  	}
    75  	if s.mac != nil && !bytes.Equal(m.ClientHWAddr, s.mac) {
    76  		log.Printf("Not responding to DHCP request for mac %s, which does not match %s", m.ClientHWAddr, s.mac)
    77  		return
    78  	}
    79  
    80  	reply, err := dhcpv4.NewReplyFromRequest(m,
    81  		dhcpv4.WithMessageType(replyType),
    82  		dhcpv4.WithServerIP(s.self),
    83  		dhcpv4.WithRouter(s.self),
    84  		dhcpv4.WithNetmask(s.submask),
    85  		dhcpv4.WithYourIP(s.yourIP),
    86  		// RFC 2131, Section 4.3.1. Server Identifier: MUST
    87  		dhcpv4.WithOption(dhcpv4.OptServerIdentifier(s.self)),
    88  		// RFC 2131, Section 4.3.1. IP lease time: MUST
    89  		dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(dhcpv4.MaxLeaseTime)),
    90  	)
    91  	// RFC 6842, MUST include Client Identifier if client specified one.
    92  	if val := m.Options.Get(dhcpv4.OptionClientIdentifier); len(val) > 0 {
    93  		reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientIdentifier, val))
    94  	}
    95  	if len(s.bootfilename) > 0 {
    96  		reply.BootFileName = s.bootfilename
    97  	}
    98  	if len(s.rootpath) > 0 {
    99  		reply.UpdateOption(dhcpv4.OptRootPath(s.rootpath))
   100  	}
   101  	if err != nil {
   102  		log.Printf("Could not create reply for %v: %v", m, err)
   103  		return
   104  	}
   105  
   106  	// Experimentally determined. You can't just blindly send a broadcast packet
   107  	// with the broadcast address. You can, however, send a broadcast packet
   108  	// to a subnet for an interface. That actually makes some sense.
   109  	// This fixes the observed problem that OSX just swallows these
   110  	// packets if the peer is 255.255.255.255.
   111  	// I chose this way of doing it instead of files with build constraints
   112  	// because this is not that expensive and it's just a tiny bit easier to
   113  	// follow IMHO.
   114  	if runtime.GOOS == "darwin" {
   115  		p := &net.UDPAddr{IP: s.yourIP.Mask(s.submask), Port: 68}
   116  		log.Printf("Changing %v to %v", peer, p)
   117  		peer = p
   118  	}
   119  
   120  	log.Printf("Sending %v to %v", reply.Summary(), peer)
   121  	if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil {
   122  		log.Printf("Could not write %v: %v", reply, err)
   123  	}
   124  }
   125  
   126  type dserver6 struct {
   127  	mac         net.HardwareAddr
   128  	yourIP      net.IP
   129  	bootfileurl string
   130  }
   131  
   132  func (s *dserver6) dhcpHandler(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) {
   133  	log.Printf("Handling DHCPv6 request %v sent by %v", m.Summary(), peer.String())
   134  
   135  	msg, err := m.GetInnerMessage()
   136  	if err != nil {
   137  		log.Printf("Could not find unpacked message: %v", err)
   138  		return
   139  	}
   140  
   141  	if msg.MessageType != dhcpv6.MessageTypeSolicit {
   142  		log.Printf("Only accept SOLICIT message type, this is a %s", msg.MessageType)
   143  		return
   144  	}
   145  	if msg.GetOneOption(dhcpv6.OptionRapidCommit) == nil {
   146  		log.Printf("Only accept requests with rapid commit option.")
   147  		return
   148  	}
   149  	if mac, err := dhcpv6.ExtractMAC(msg); err != nil {
   150  		log.Printf("No MAC address in request: %v", err)
   151  		return
   152  	} else if s.mac != nil && !bytes.Equal(s.mac, mac) {
   153  		log.Printf("MAC address %s doesn't match expected MAC %s", mac, s.mac)
   154  		return
   155  	}
   156  
   157  	// From RFC 3315, section 17.1.4, If the client includes a Rapid Commit
   158  	// option in the Solicit message, it will expect a Reply message that
   159  	// includes a Rapid Commit option in response.
   160  	reply, err := dhcpv6.NewReplyFromMessage(msg)
   161  	if err != nil {
   162  		log.Printf("Failed to create reply for %v: %v", m, err)
   163  		return
   164  	}
   165  
   166  	iana := msg.Options.OneIANA()
   167  	if iana != nil {
   168  		iana.Options.Update(&dhcpv6.OptIAAddress{
   169  			IPv6Addr:          s.yourIP,
   170  			PreferredLifetime: math.MaxUint32 * time.Second,
   171  			ValidLifetime:     math.MaxUint32 * time.Second,
   172  		})
   173  		reply.AddOption(iana)
   174  	}
   175  	if len(s.bootfileurl) > 0 {
   176  		reply.Options.Add(dhcpv6.OptBootFileURL(s.bootfileurl))
   177  	}
   178  
   179  	if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil {
   180  		log.Printf("Failed to send response %v: %v", reply, err)
   181  		return
   182  	}
   183  
   184  	log.Printf("DHCPv6 request successfully handled, reply: %v", reply.Summary())
   185  }
   186  
   187  func main() {
   188  	flag.Parse()
   189  
   190  	var maca net.HardwareAddr
   191  	if len(*mac) > 0 {
   192  		var err error
   193  		maca, err = net.ParseMAC(*mac)
   194  		if err != nil {
   195  			log.Fatal(err)
   196  		}
   197  	}
   198  	yourIP, yourNet, err := net.ParseCIDR(*yourIP)
   199  	if err != nil {
   200  		log.Fatal(err)
   201  	}
   202  
   203  	var wg sync.WaitGroup
   204  	if len(*tftpDir) != 0 {
   205  		wg.Add(1)
   206  		go func() {
   207  			defer wg.Done()
   208  			server, err := tftp.NewServer(fmt.Sprintf(":%d", *tftpPort))
   209  			if err != nil {
   210  				log.Fatalf("Could not start TFTP server: %v", err)
   211  			}
   212  
   213  			log.Println("starting file server")
   214  			server.ReadHandler(tftp.FileServer(*tftpDir))
   215  			log.Fatal(server.ListenAndServe())
   216  		}()
   217  	}
   218  	if len(*httpDir) != 0 {
   219  		wg.Add(1)
   220  		go func() {
   221  			defer wg.Done()
   222  			http.Handle("/", http.FileServer(http.Dir(*httpDir)))
   223  			log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil))
   224  		}()
   225  	}
   226  
   227  	if *ipv4 {
   228  		wg.Add(1)
   229  		go func() {
   230  			defer wg.Done()
   231  			s := &dserver4{
   232  				mac:          maca,
   233  				self:         net.ParseIP(*selfIP),
   234  				yourIP:       yourIP,
   235  				submask:      yourNet.Mask,
   236  				bootfilename: *bootfilename,
   237  				rootpath:     *rootpath,
   238  			}
   239  
   240  			laddr := &net.UDPAddr{Port: dhcpv4.ServerPort}
   241  			server, err := server4.NewServer(*inf, laddr, s.dhcpHandler)
   242  			if err != nil {
   243  				log.Fatal(err)
   244  			}
   245  			if err := server.Serve(); err != nil {
   246  				log.Fatal(err)
   247  			}
   248  		}()
   249  	}
   250  
   251  	if *ipv6 {
   252  		wg.Add(1)
   253  		go func() {
   254  			defer wg.Done()
   255  
   256  			s := &dserver6{
   257  				mac:         maca,
   258  				yourIP:      net.ParseIP(*yourIP6),
   259  				bootfileurl: *v6Bootfilename,
   260  			}
   261  			laddr := &net.UDPAddr{
   262  				IP:   net.IPv6unspecified,
   263  				Port: dhcpv6.DefaultServerPort,
   264  			}
   265  			server, err := server6.NewServer("eth0", laddr, s.dhcpHandler)
   266  			if err != nil {
   267  				log.Fatal(err)
   268  			}
   269  
   270  			log.Println("starting dhcpv6 server")
   271  			if err := server.Serve(); err != nil {
   272  				log.Fatal(err)
   273  			}
   274  		}()
   275  	}
   276  
   277  	wg.Wait()
   278  }