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 }