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 }