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