Esempio n. 1
0
func (b *HTTPBooter) fileHandler(w http.ResponseWriter, r *http.Request) {
	splitPath := strings.SplitN(r.URL.Path, "/", 4)
	version := splitPath[2]
	id := splitPath[3]

	logging.Debug("HTTPBOOTER", "Got request for %s", r.URL.Path)

	var (
		f   io.ReadCloser
		err error
	)

	f, err = b.coreOS(version, id)

	if err != nil {
		logging.Log("HTTPBOOTER", "Couldn't get byte stream for %q from %s: %s", r.URL, r.RemoteAddr, err)
		http.Error(w, "Couldn't get byte stream", http.StatusInternalServerError)
		return
	}
	defer f.Close()

	w.Header().Set("Content-Type", "application/octet-stream")
	written, err := io.Copy(w, f)
	if err != nil {
		logging.Log("HTTPBOOTER", "Error serving %s to %s: %s", id, r.RemoteAddr, err)
		return
	}
	logging.Log("HTTPBOOTER", "Sent %s to %s (%d bytes)", id, r.RemoteAddr, written)
}
Esempio n. 2
0
func executeTemplate(rootTemplte *template.Template, templateName string, machine datasource.Machine, hostAddr string) (string, error) {
	template := rootTemplte.Lookup(templateName)

	if template == nil {
		return "", fmt.Errorf("template with name=%s wasn't found for root=%s",
			templateName, rootTemplte)
	}

	buf := new(bytes.Buffer)
	template.Funcs(map[string]interface{}{
		"V": func(key string) string {
			flag, err := machine.GetFlag(key)
			if err != nil { // TODO excepts Not-Found
				logging.Log(templatesDebugTag,
					"Error while getting flag key=%s for machine=%s: %s",
					key, machine.Name(), err)
				return ""
			}
			return flag
		},
		"b64": func(text string) string {
			return base64.StdEncoding.EncodeToString([]byte(text))
		},
		"b64template": func(templateName string) string {
			text, err := executeTemplate(rootTemplte, templateName, machine, hostAddr)
			if err != nil {
				logging.Log(templatesDebugTag,
					"Error while b64template for templateName=%s machine=%s: %s",
					templateName, machine.Name(), err)
				return ""
			}
			return base64.StdEncoding.EncodeToString([]byte(text))
		},
	})
	ip, _ := machine.IP()
	data := struct {
		Mac      string
		IP       string
		Hostname string
		Domain   string
		HostAddr string
	}{
		machine.Mac().String(),
		ip.String(),
		machine.Name(),
		machine.Domain(),
		hostAddr,
	}
	err := template.ExecuteTemplate(buf, templateName, &data)
	if err != nil {
		return "", err
	}
	str := buf.String()
	str = strings.Trim(str, "\n")
	return str, nil
}
Esempio n. 3
0
func (c *CloudConfig) handler(w http.ResponseWriter, r *http.Request) {
	req := strings.Split(r.URL.Path, "/")[1:]

	queryMap, _ := extractQueries(r.URL.RawQuery)

	if len(req) != 2 {
		logging.Log("CLOUDCONFIG", "Received request - request not found")
		http.NotFound(w, r)
		return
	}
	var selectedRepo *Repo
	switch req[0] {
	case "cloud":
		selectedRepo = c.cloudRepo
	case "ignition":
		selectedRepo = c.ignitionRepo
	default:
		http.NotFound(w, r)
		return
	}

	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		http.Error(w, "internal server error - parsing host and port", 500)
		logging.Log("CLOUDCONFIG", "Error - %s with mac %s - %s", req[0], req[1], err.Error())
		return
	}

	configCtx := &ConfigContext{
		MacAddr: req[1],
		IP:      ip,
	}
	config, err := selectedRepo.GenerateConfig(configCtx)
	if err != nil {
		http.Error(w, "internal server error - error in generating config", 500)
		logging.Log("CLOUDCONFIG", "Error when generating config - %s with mac %s - %s", req[0], req[1], err.Error())
		return
	}
	w.Header().Set("Content-Type", "application/x-yaml")

	//always validate the cloudconfig. Don't if explicitly stated.
	if value, exists := queryMap["validate"]; !exists || value != "false" {
		config += validateCloudConfig(config)
	}

	w.Write([]byte(config))
	logging.Log("CLOUDCONFIG", "Received request - %s with mac %s", req[0], req[1])
}
Esempio n. 4
0
// Assign assigns an ip to the node with the specified nic
// Will use etcd machines records as LeasePool
// part of DHCPDataSource interface implementation
func (ds *EtcdDataSource) Assign(nic string) (net.IP, error) {
	ds.lockDHCPAssign()
	defer ds.unlockdhcpAssign()

	// TODO: first try to retrieve the machine, if exists (for performance)

	assignedIPs := make(map[string]bool)
	//find by Mac
	machines, _ := ds.Machines()
	for _, node := range machines {
		if node.Mac().String() == nic {
			ip, _ := node.IP()
			ds.store(node, ip)
			return ip, nil
		}
		nodeIP, _ := node.IP()
		assignedIPs[nodeIP.String()] = true
	}

	//find an unused ip
	for i := 0; i < ds.LeaseRange(); i++ {
		ip := dhcp4.IPAdd(ds.LeaseStart(), i)
		if _, exists := assignedIPs[ip.String()]; !exists {
			macAddress, _ := net.ParseMAC(nic)
			ds.CreateMachine(macAddress, ip)
			return ip, nil
		}
	}

	//use an expired ip
	//not implemented
	logging.Log(debugTag, "DHCP pool is full")

	return nil, nil
}
Esempio n. 5
0
func ServeHTTPBooter(listenAddr net.TCPAddr, runtimeConfig *datasource.RuntimeConfiguration, bootParamsRepo *cloudconfig.Repo) error {
	logging.Log("HTTPBOOTER", "Listening on %s", listenAddr.String())
	mux, err := HTTPBooterMux(listenAddr, runtimeConfig, bootParamsRepo)
	if err != nil {
		return err
	}
	return http.ListenAndServe(listenAddr.String(), mux)
}
Esempio n. 6
0
func ServeHTTPBooter(listenAddr net.TCPAddr, ds datasource.DataSource, webPort int) error {
	logging.Log("HTTPBOOTER", "Listening on %s", listenAddr.String())
	mux, err := HTTPBooterMux(listenAddr, ds, webPort)
	if err != nil {
		return err
	}
	return http.ListenAndServe(listenAddr.String(), mux)
}
Esempio n. 7
0
func logln(level int, s string) {
	if level == 2 {
		log.Fatalf(s)
	} else if level == 1 {
		logging.Log(debugTag, s)
	} else {
		logging.Debug(debugTag, s)
	}
}
Esempio n. 8
0
func Serve(listenAddr net.TCPAddr, datasource *datasource.DataSource) error {
	logging.Log(debugTag, "Serving Rest API on %s", listenAddr)
	restApi := newRestServerAPI(datasource)
	handler, err := restApi.MakeHandler()
	if err != nil {
		return err
	}
	return http.ListenAndServe(listenAddr.String(), handler)
}
Esempio n. 9
0
func (ds *DataSource) ConfigByteArray(name string) []byte {
	base64Value := ds.ConfigString(name)
	value, err := base64.StdEncoding.DecodeString(base64Value)
	if err != nil {
		logging.Log(debugTag, "Error while decoding config value %s: %s", name, err)
		return nil
	}
	return value
}
Esempio n. 10
0
func newRestServerAPI(datasource *datasource.DataSource) *restServerAPI {
	rest := grest.NewApi()
	rest.Use(grest.DefaultDevStack...)

	rest.Use(&grest.CorsMiddleware{
		RejectNonCorsRequests: false,
		OriginValidator: func(origin string, request *grest.Request) bool {
			// TODO Origin check
			return true
		},
		AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
		AllowedHeaders: []string{
			"Accept", "Content-Type", "X-Custom-Header", "Origin", "Authorization"},
		AccessControlAllowCredentials: true,
		AccessControlMaxAge:           3600,
	})

	var bearerAuthMiddleware = &AuthBearerMiddleware{
		Realm: "RestAuthentication",
		Authenticator: func(token string) string {
			parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
				}
				return datasource.ConfigByteArray("TOKEN_SIGN_KEY"), nil
			})

			if err == nil && parsedToken.Valid {
				return parsedToken.Claims["email"].(string)
			} else {
				return ""
			}
		},
		Authorizer: func(request *grest.Request, userID string) bool {
			user, err := datasource.UserByEmail(userID)
			if err != nil {
				logging.Log(debugTag, "Couldn't fetch user for userID=%s", userID)
				return false
			}
			request.Env["REMOTE_USER_OBJECT"] = user

			return true
		},
	}
	rest.Use(&grest.IfMiddleware{
		Condition: func(request *grest.Request) bool {
			return request.URL.Path != "/login"
		},
		IfTrue: bearerAuthMiddleware,
	})

	return &restServerAPI{
		rest: rest,
		ds:   datasource,
	}
}
Esempio n. 11
0
func ServeCloudConfig(listenAddr net.TCPAddr, workspacePath string, datasources map[string]DataSource) error {
	logging.Log("CLOUDCONFIG", "Listening on %s", listenAddr.String())
	cloudRepo, err := FromPath(datasources, path.Join(workspacePath, "config/cloudconfig"))
	if err != nil {
		return err
	}
	ignitionRepo, err := FromPath(datasources, path.Join(workspacePath, "config/ignition"))
	if err != nil {
		return err
	}
	cloudConfig := NewCloudConfig(cloudRepo, ignitionRepo)
	return http.ListenAndServe(listenAddr.String(), cloudConfig.Mux())
}
Esempio n. 12
0
func ServePXE(listenAddr net.UDPAddr, serverIP net.IP, httpAddr net.TCPAddr) error {
	conn, err := net.ListenPacket("udp4", listenAddr.String())
	if err != nil {
		return err
	}
	defer conn.Close()
	l := ipv4.NewPacketConn(conn)
	if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil {
		return err
	}

	logging.Log("PXE", "Listening on %s", listenAddr.String())
	buf := make([]byte, 1024)
	for {
		n, msg, addr, err := l.ReadFrom(buf)
		if err != nil {
			logging.Log("PXE", "Error reading from socket: %s", err)
			continue
		}

		req, err := ParsePXE(buf[:n])
		if err != nil {
			logging.Debug("PXE", "ParsePXE: %s", err)
			continue
		}

		req.ServerIP = serverIP
		req.HTTPServer = fmt.Sprintf("http://%s/", httpAddr.String())

		logging.Log("PXE", "Chainloading %s (%s) to pxelinux (via %s)", req.MAC, req.ClientIP, req.ServerIP)

		if _, err := l.WriteTo(ReplyPXE(req), &ipv4.ControlMessage{
			IfIndex: msg.IfIndex,
		}, addr); err != nil {
			logging.Log("PXE", "Responding to %s: %s", req.MAC, err)
			continue
		}
	}
}
Esempio n. 13
0
// IsMaster checks for being master, and makes a heartbeat
func (ds *EtcdDataSource) IsMaster() bool {
	var err error
	if ds.instancesEtcdDir == invalidEtcdKey {
		err = ds.registerOnEtcd()
		if err != nil {
			logging.Log(debugTag, "error while registerOnEtcd: %s", err)
			return false
		}
	} else {
		err = ds.etcdHeartbeat()
		if err != nil {
			ds.instancesEtcdDir = invalidEtcdKey
			logging.Log(debugTag, "error while updateOnEtcd: %s", err)
			return false
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), etcdTimeout)
	defer cancel()
	masterGetOptions := etcd.GetOptions{
		Recursive: true,
		Quorum:    true,
		Sort:      true,
	}
	resp, err := ds.keysAPI.Get(ctx, ds.prefixify(instancesEtcdDir), &masterGetOptions)
	if err != nil {
		logging.Log(debugTag, "error while getting the dir list from etcd: %s", err)
		return false
	}
	if len(resp.Node.Nodes) < 1 {
		logging.Log(debugTag, "empty list while getting the dir list from etcd")
		return false
	}
	if resp.Node.Nodes[0].Key == ds.instancesEtcdDir {
		return true
	}
	return false
}
Esempio n. 14
0
func ServeTFTP(listenAddr net.UDPAddr) error {
	pxelinuxDir := FS(false)
	pxelinux, err := pxelinuxDir.Open("/pxelinux/lpxelinux.0")
	if err != nil {
		return err
	}
	tftp.Log = func(msg string, args ...interface{}) { logging.Log("TFTP", msg, args...) }
	tftp.Debug = func(msg string, args ...interface{}) { logging.Debug("TFTP", msg, args...) }

	handler := func(string, net.Addr) (io.ReadCloser, error) {
		return pxelinux, nil
	}

	return tftp.ListenAndServe("udp4", listenAddr.String(), handler)
}
Esempio n. 15
0
func ServeDHCP(settings *DHCPSetting, leasePool *LeasePool) error {
	handler, err := newDHCPHandler(settings, leasePool)
	if err != nil {
		logging.Debug("DHCP", "Error in connecting etcd - %s", err.Error())
		return err
	}
	logging.Log("DHCP", "Listening on :67 - with server IP %s", settings.ServerIP.String())
	if settings.IFName != "" {
		err = dhcp4.ListenAndServeIf(settings.IFName, handler)
	} else {
		err = dhcp4.ListenAndServe(handler)
	}
	if err != nil {
		logging.Debug("DHCP", "Error in server - %s", err.Error())
	}
	return err
}
Esempio n. 16
0
func (r *restServerAPI) Login(w grest.ResponseWriter, req *grest.Request) {
	up := userPass{}
	err := req.DecodeJsonPayload(&up)
	if err != nil {
		grest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if up.Email == "" || up.Password == "" {
		grest.Error(w, "user/password missing", http.StatusBadRequest)
	}

	user, err := r.ds.UserByEmail(up.Email)
	if err != nil {
		grest.Error(w, "user/password failed", http.StatusBadRequest)
		return
	}
	if !user.AcceptsPassword(up.Password, r.ds.ConfigByteArray("PASSWORD_SALT")) {
		grest.Error(w, "user/password failed", http.StatusBadRequest)
		return
	}

	if !user.Active {
		grest.Error(w, "user isn't activated", http.StatusForbidden)
		return
	}

	token := jwt.New(jwt.GetSigningMethod("HS512"))
	token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
	token.Claims["email"] = user.Email
	token.Claims["isAdmin"] = user.Admin
	tokenString, err := token.SignedString(r.ds.ConfigByteArray("TOKEN_SIGN_KEY"))
	if err != nil {
		logging.Log(debugTag, "Signing failed: %s", err)
		grest.Error(w, "Authentication failed", http.StatusInternalServerError)
		return
	}
	w.WriteJson(map[string]string{
		"token": tokenString,
	})
}
Esempio n. 17
0
func ServeDHCP(settings *DHCPSetting, datasource datasource.DataSource) error {
	handler, err := newDHCPHandler(settings, datasource)
	if err != nil {
		logging.Debug("DHCP", "Error in connecting etcd - %s", err.Error())
		return err
	}
	logging.Log("DHCP", "Listening on %s:67 (interface: %s)",
		settings.ServerIP.String(), settings.IFName)
	if settings.IFName != "" {
		err = dhcp4.ListenAndServeIf(settings.IFName, handler)
	} else {
		err = dhcp4.ListenAndServe(handler)
	}
	if err != nil {
		logging.Debug("DHCP", "Error in server - %s", err.Error())
	}

	rand.Seed(time.Now().UTC().UnixNano())

	return err
}
Esempio n. 18
0
func (b *HTTPBooter) pxelinuxConfig(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")

	macStr := filepath.Base(r.URL.Path)
	errStr := fmt.Sprintf("%s requested a pxelinux config from URL %q, which does not include a correct MAC address", r.RemoteAddr, r.URL)
	if !strings.HasPrefix(macStr, "01-") {
		logging.Debug("HTTPBOOTER", errStr)
		http.Error(w, "Missing MAC address in request", http.StatusBadRequest)
		return
	}
	mac, err := net.ParseMAC(macStr[3:])
	if err != nil {
		logging.Debug("HTTPBOOTER", errStr)
		http.Error(w, "Malformed MAC address in request", http.StatusBadRequest)
		return
	}

	machine, exist := b.datasource.GetMachine(mac)
	if !exist {
		logging.Debug("HTTPBOOTER", "Machine not found. mac=%s", mac)
		http.Error(w, "Machine not found", http.StatusNotFound)
		return
	}

	if _, _, err := net.SplitHostPort(r.Host); err != nil {
		r.Host = fmt.Sprintf("%s:%d", r.Host, b.listenAddr.Port)
	}

	coreOSVersion, _ := b.datasource.CoreOSVersion()

	KernelURL := "http://" + r.Host + "/f/" + coreOSVersion + "/kernel"
	InitrdURL := "http://" + r.Host + "/f/" + coreOSVersion + "/initrd"

	host, _, err := net.SplitHostPort(r.Host)
	if err != nil {
		logging.Log("HTTPBOOTER", "error in parsing host and port")
		http.Error(w, "error in parsing host and port", 500)
		return
	}

	params, err := templating.ExecuteTemplateFolder(
		path.Join(b.datasource.WorkspacePath(), "config", "bootparams"), machine, r.Host)
	if err != nil {
		logging.Log("HTTPBOOTER", `Error while executing the template: %q`, err)
		http.Error(w, fmt.Sprintf(`Error while executing the template: %q`, err),
			http.StatusInternalServerError)
		return
	}

	if err != nil {
		logging.Log("HTTPBOOTER", "error in bootparam template - %s", err.Error())
		http.Error(w, "error in bootparam template", 500)
		return
	}
	params = strings.Replace(params, "\n", " ", -1)

	Cmdline := fmt.Sprintf(
		"cloud-config-url=http://%s:%d/t/cc/%s "+
			"coreos.config.url=http://%s:%d/t/ig/%s %s",
		host, b.webPort, mac.String(),
		host, b.webPort, mac.String(), params)
	bootMessage := strings.Replace(b.bootMessageTemplate, "$MAC", macStr, -1)
	cfg := fmt.Sprintf(`
SAY %s
DEFAULT linux
LABEL linux
LINUX %s
APPEND initrd=%s %s
`, strings.Replace(bootMessage, "\n", "\nSAY ", -1), KernelURL, InitrdURL, Cmdline)
	w.Write([]byte(cfg))
	logging.Log("HTTPBOOTER", "Sent pxelinux config to %s (%s)", mac, r.RemoteAddr)
}
Esempio n. 19
0
func (b *HTTPBooter) pxelinuxConfig(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")

	macStr := filepath.Base(r.URL.Path)
	errStr := fmt.Sprintf("%s requested a pxelinux config from URL %q, which does not include a MAC address", r.RemoteAddr, r.URL)
	if !strings.HasPrefix(macStr, "01-") {
		logging.Debug("HTTPBOOTER", errStr)
		http.Error(w, "Missing MAC address in request", http.StatusBadRequest)
		return
	}
	mac, err := net.ParseMAC(macStr[3:])
	if err != nil {
		logging.Debug("HTTPBOOTER", errStr)
		http.Error(w, "Malformed MAC address in request", http.StatusBadRequest)
		return
	}

	if _, _, err := net.SplitHostPort(r.Host); err != nil {
		r.Host = fmt.Sprintf("%s:%d", r.Host, b.listenAddr.Port)
	}

	// TODO: Ask dataSource about the mac
	// We have a machine sitting in pxelinux, but the Booter says
	// we shouldn't be netbooting. So, give it a config that tells
	// pxelinux to shut down PXE booting and continue with the
	// next local boot method.

	coreOSVersion, _ := b.runtimeConfig.GetCoreOSVersion()

	KernelURL := "http://" + r.Host + "/f/" + coreOSVersion + "/kernel"
	InitrdURL := "http://" + r.Host + "/f/" + coreOSVersion + "/initrd"

	host, _, err := net.SplitHostPort(r.Host)
	if err != nil {
		logging.Log("HTTPBOOTER", "error in parsing host and port")
		http.Error(w, "error in parsing host and port", 500)
		return
	}

	// generate bootparams config
	params, err := b.bootParamsRepo.GenerateConfig(&cloudconfig.ConfigContext{
		MacAddr: strings.Replace(mac.String(), ":", "", -1),
		IP:      "",
	})
	if err != nil {
		logging.Log("HTTPBOOTER", "error in bootparam template - %s", err.Error())
		http.Error(w, "error in bootparam template", 500)
		return
	}
	params = strings.Replace(params, "\n", " ", -1)

	// FIXME: 8001 is hardcoded
	Cmdline := fmt.Sprintf(
		"cloud-config-url=http://%s:8001/cloud/%s "+
			"coreos.config.url=http://%s:8001/ignition/%s %s",
		host, strings.Replace(mac.String(), ":", "", -1),
		host, strings.Replace(mac.String(), ":", "", -1), params)
	bootMessage := strings.Replace(bootMessageTemplate, "$MAC", macStr, -1)
	cfg := fmt.Sprintf(`
SAY %s
DEFAULT linux
LABEL linux
LINUX %s
APPEND initrd=%s %s
`, strings.Replace(bootMessage, "\n", "\nSAY ", -1), KernelURL, InitrdURL, Cmdline)
	w.Write([]byte(cfg))
	logging.Log("HTTPBOOTER", "Sent pxelinux config to %s (%s)", mac, r.RemoteAddr)
}
Esempio n. 20
0
func (h *DHCPHandler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) (d dhcp4.Packet) {
	var macAddress string = strings.Join(strings.Split(p.CHAddr().String(), ":"), "")
	switch msgType {
	case dhcp4.Discover:
		ip, err := h.leasePool.Assign(p.CHAddr().String())
		if err != nil {
			logging.Debug("DHCP", "err in lease pool - %s", err.Error())
			return nil // pool is full
		}
		replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])
		packet := dhcp4.ReplyPacket(p, dhcp4.Offer, h.settings.ServerIP, ip, h.settings.LeaseDuration, replyOptions)
		// this is a pxe request
		guidVal, is_pxe := options[97]
		if is_pxe {
			logging.Log("DHCP", "dhcp discover with PXE - CHADDR %s - IP %s - our ip %s", p.CHAddr().String(), ip.String(), h.settings.ServerIP.String())
			guid := guidVal[1:]
			packet.AddOption(60, []byte("PXEClient"))
			packet.AddOption(97, guid)
			packet.AddOption(43, h.fillPXE())
		} else {
			logging.Log("DHCP", "dhcp discover - CHADDR %s - IP %s", p.CHAddr().String(), ip.String())
		}
		return packet
	case dhcp4.Request:
		if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.settings.ServerIP) {
			return nil // this message is not ours
		}
		requestedIP := net.IP(options[dhcp4.OptionRequestedIPAddress])
		if requestedIP == nil {
			requestedIP = net.IP(p.CIAddr())
		}
		if len(requestedIP) != 4 || requestedIP.Equal(net.IPv4zero) {
			logging.Debug("DHCP", "dhcp request - CHADDR %s - bad request", p.CHAddr().String())
			return nil
		}
		_, err := h.leasePool.Request(p.CHAddr().String(), requestedIP)
		if err != nil {
			logging.Debug("DHCP", "dhcp request - CHADDR %s - Requested IP %s - NO MATCH", p.CHAddr().String(), requestedIP.String())
			return dhcp4.ReplyPacket(p, dhcp4.NAK, h.settings.ServerIP, nil, 0, nil)
		}

		replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])
		packet := dhcp4.ReplyPacket(p, dhcp4.ACK, h.settings.ServerIP, requestedIP, h.settings.LeaseDuration, replyOptions)
		// this is a pxe request
		guidVal, is_pxe := options[97]
		if is_pxe {
			logging.Log("DHCP", "dhcp request with PXE - CHADDR %s - Requested IP %s - our ip %s - ACCEPTED", p.CHAddr().String(), requestedIP.String(), h.settings.ServerIP.String())
			guid := guidVal[1:]
			packet.AddOption(60, []byte("PXEClient"))
			packet.AddOption(97, guid)
			packet.AddOption(43, h.fillPXE())
		} else {
			logging.Log("DHCP", "dhcp request - CHADDR %s - Requested IP %s - ACCEPTED", p.CHAddr().String(), requestedIP.String())
		}
		packet.AddOption(12, []byte("node"+macAddress)) // host name option
		return packet
	case dhcp4.Release, dhcp4.Decline:
		return nil
	}
	return nil
}