This commit is contained in:
parent
576c78e6dd
commit
0b6bbce7d5
9 changed files with 246 additions and 84 deletions
|
@ -3,6 +3,9 @@
|
|||
build:
|
||||
go build -o build/
|
||||
|
||||
buildarm:
|
||||
env GOOS=linux GOARCH=arm64 go build -o build/bootserver_arm
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package bootoption
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type EFIApp struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
|
@ -7,6 +9,7 @@ type EFIApp struct {
|
|||
}
|
||||
|
||||
type Client struct {
|
||||
ID uuid.UUID
|
||||
IP string `json:"ip"`
|
||||
Name string `json:"name"`
|
||||
Options []EFIApp `json:"options"`
|
||||
|
|
|
@ -64,6 +64,7 @@ type Message interface {
|
|||
encoding.BinaryMarshaler
|
||||
Action() Action
|
||||
ID() uuid.UUID
|
||||
String() string
|
||||
}
|
||||
|
||||
type requestMessage struct {
|
||||
|
@ -103,6 +104,10 @@ func (rm *requestMessage) ID() uuid.UUID {
|
|||
return rm.id
|
||||
}
|
||||
|
||||
func (rm *requestMessage) String() string {
|
||||
return fmt.Sprintf("%s from %s", ActionRequest.String(), rm.ID().String())
|
||||
}
|
||||
|
||||
type acceptMessage struct {
|
||||
id uuid.UUID
|
||||
efiApp string
|
||||
|
@ -151,6 +156,10 @@ func (am *acceptMessage) ID() uuid.UUID {
|
|||
return am.id
|
||||
}
|
||||
|
||||
func (am *acceptMessage) String() string {
|
||||
return fmt.Sprintf("%s from %s, app %s", ActionAccept.String(), am.ID().String(), am.efiApp)
|
||||
}
|
||||
|
||||
type denyMessage struct {
|
||||
id uuid.UUID
|
||||
reason string
|
||||
|
@ -199,6 +208,10 @@ func (dm *denyMessage) ID() uuid.UUID {
|
|||
return dm.id
|
||||
}
|
||||
|
||||
func (dm *denyMessage) String() string {
|
||||
return fmt.Sprintf("%s from %s, reason %q", ActionDeny.String(), dm.ID().String(), dm.reason)
|
||||
}
|
||||
|
||||
func MessageFromBytes(dat []byte) (Message, error) {
|
||||
rawAction, content, found := bytes.Cut(dat, spaceByte)
|
||||
if !found {
|
||||
|
|
|
@ -48,14 +48,22 @@ type jsonConf struct {
|
|||
Mode string `json:"mode"`
|
||||
SockPath string `json:"sock"`
|
||||
} `json:"server"`
|
||||
BootProvider struct {
|
||||
Iface string `json:"interface"`
|
||||
Port int `json:"port"`
|
||||
McastGroup string `json:"multicast_group"`
|
||||
} `json:"boot_provider"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
LogLevel logrus.Level
|
||||
ServerMode ListeningMode
|
||||
Host string
|
||||
Port int
|
||||
SockPath string
|
||||
LogLevel logrus.Level
|
||||
ServerMode ListeningMode
|
||||
Host string
|
||||
Port int
|
||||
SockPath string
|
||||
UPDMcastGroup string
|
||||
UDPPort int
|
||||
UDPIface string
|
||||
}
|
||||
|
||||
func parseLevel(lvlStr string) logrus.Level {
|
||||
|
@ -82,14 +90,20 @@ func (ac *AppConfig) UnmarshalJSON(data []byte) error {
|
|||
ac.SockPath = jsonConf.Server.SockPath
|
||||
ac.Host = jsonConf.Server.Host
|
||||
ac.Port = jsonConf.Server.Port
|
||||
ac.UPDMcastGroup = jsonConf.BootProvider.McastGroup
|
||||
ac.UDPIface = jsonConf.BootProvider.Iface
|
||||
ac.UDPPort = jsonConf.BootProvider.Port
|
||||
return nil
|
||||
}
|
||||
|
||||
var defaultConfig AppConfig = AppConfig{
|
||||
LogLevel: logrus.InfoLevel,
|
||||
ServerMode: ModeNet,
|
||||
Host: "0.0.0.0",
|
||||
Port: 5000,
|
||||
LogLevel: logrus.InfoLevel,
|
||||
ServerMode: ModeNet,
|
||||
Host: "0.0.0.0",
|
||||
Port: 5000,
|
||||
UPDMcastGroup: "ff02::abcd:1234",
|
||||
UDPPort: 42,
|
||||
UDPIface: "eth0",
|
||||
}
|
||||
|
||||
func New(filepath string) (*AppConfig, error) {
|
||||
|
|
|
@ -1,85 +1,85 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "net"
|
||||
// "net/http"
|
||||
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/helpers"
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/services"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
// "git.faercol.me/faercol/http-boot-server/bootserver/helpers"
|
||||
// "git.faercol.me/faercol/http-boot-server/bootserver/services"
|
||||
// "github.com/sirupsen/logrus"
|
||||
// )
|
||||
|
||||
const BootRoute = "/boot"
|
||||
// const BootRoute = "/boot"
|
||||
|
||||
type BootController struct {
|
||||
clientService *services.ClientHandlerService
|
||||
l *logrus.Logger
|
||||
}
|
||||
// type BootController struct {
|
||||
// clientService *services.ClientHandlerService
|
||||
// l *logrus.Logger
|
||||
// }
|
||||
|
||||
func NewBootController(logger *logrus.Logger, service *services.ClientHandlerService) *BootController {
|
||||
return &BootController{
|
||||
clientService: service,
|
||||
l: logger,
|
||||
}
|
||||
}
|
||||
// func NewBootController(logger *logrus.Logger, service *services.ClientHandlerService) *BootController {
|
||||
// return &BootController{
|
||||
// clientService: service,
|
||||
// l: logger,
|
||||
// }
|
||||
// }
|
||||
|
||||
func (bc *BootController) getBootOption(clientIP string, w http.ResponseWriter, r *http.Request) (int, []byte, error) {
|
||||
bootOption, err := bc.clientService.GetClientSelectedBootOption(clientIP)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, nil, fmt.Errorf("failed to get boot option: %w", err)
|
||||
}
|
||||
// func (bc *BootController) getBootOption(clientIP string, w http.ResponseWriter, r *http.Request) (int, []byte, error) {
|
||||
// bootOption, err := bc.clientService.GetClientSelectedBootOption(clientIP)
|
||||
// if err != nil {
|
||||
// return http.StatusInternalServerError, nil, fmt.Errorf("failed to get boot option: %w", err)
|
||||
// }
|
||||
|
||||
dat, err := json.Marshal(bootOption)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, nil, fmt.Errorf("failed to serialize body")
|
||||
}
|
||||
// dat, err := json.Marshal(bootOption)
|
||||
// if err != nil {
|
||||
// return http.StatusInternalServerError, nil, fmt.Errorf("failed to serialize body")
|
||||
// }
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
return http.StatusOK, dat, nil
|
||||
}
|
||||
// w.Header().Add("Content-Type", "application/json")
|
||||
// return http.StatusOK, dat, nil
|
||||
// }
|
||||
|
||||
func (bc *BootController) setBootOption(clientIP string, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
dat, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
var option string
|
||||
if err := json.Unmarshal(dat, &option); err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to parse body: %w", err)
|
||||
}
|
||||
// func (bc *BootController) setBootOption(clientIP string, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// dat, err := io.ReadAll(r.Body)
|
||||
// if err != nil {
|
||||
// return http.StatusInternalServerError, fmt.Errorf("failed to read body: %w", err)
|
||||
// }
|
||||
// var option string
|
||||
// if err := json.Unmarshal(dat, &option); err != nil {
|
||||
// return http.StatusInternalServerError, fmt.Errorf("failed to parse body: %w", err)
|
||||
// }
|
||||
|
||||
if err := bc.clientService.SetClientBootOption(clientIP, option); err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to set boot option for client: %w", err)
|
||||
}
|
||||
// if err := bc.clientService.SetClientBootOption(clientIP, option); err != nil {
|
||||
// return http.StatusInternalServerError, fmt.Errorf("failed to set boot option for client: %w", err)
|
||||
// }
|
||||
|
||||
return http.StatusAccepted, nil
|
||||
}
|
||||
// return http.StatusAccepted, nil
|
||||
// }
|
||||
|
||||
func (bc *BootController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
bc.l.Errorf("Failed to read remote IP: %s", err.Error())
|
||||
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, bc.l)
|
||||
return
|
||||
}
|
||||
// func (bc *BootController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
// if err != nil {
|
||||
// bc.l.Errorf("Failed to read remote IP: %s", err.Error())
|
||||
// helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, bc.l)
|
||||
// return
|
||||
// }
|
||||
|
||||
var returncode int
|
||||
var content []byte
|
||||
// var returncode int
|
||||
// var content []byte
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
returncode, content, err = bc.getBootOption(clientIP, w, r)
|
||||
case http.MethodPut:
|
||||
returncode, err = bc.setBootOption(clientIP, w, r)
|
||||
default:
|
||||
returncode = http.StatusMethodNotAllowed
|
||||
}
|
||||
// switch r.Method {
|
||||
// case http.MethodGet:
|
||||
// returncode, content, err = bc.getBootOption(clientIP, w, r)
|
||||
// case http.MethodPut:
|
||||
// returncode, err = bc.setBootOption(clientIP, w, r)
|
||||
// default:
|
||||
// returncode = http.StatusMethodNotAllowed
|
||||
// }
|
||||
|
||||
if err != nil {
|
||||
bc.l.Errorf("An error occured while handling boot request: %q", err.Error())
|
||||
}
|
||||
helpers.HandleResponse(w, r, returncode, content, bc.l)
|
||||
}
|
||||
// if err != nil {
|
||||
// bc.l.Errorf("An error occured while handling boot request: %q", err.Error())
|
||||
// }
|
||||
// helpers.HandleResponse(w, r, returncode, content, bc.l)
|
||||
// }
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"git.faercol.me/faercol/http-boot-server/bootserver/config"
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/logger"
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/server"
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/udplistener"
|
||||
)
|
||||
|
||||
const stopTimeout = 10 * time.Second
|
||||
|
@ -47,7 +48,17 @@ func main() {
|
|||
logger.L.Fatalf("Failed to initialize server: %s", err.Error())
|
||||
}
|
||||
|
||||
logger.L.Info("Initializing UDP listener")
|
||||
listener, err := udplistener.New(conf.UDPIface, conf.UPDMcastGroup, conf.UDPPort, logger.L)
|
||||
if err != nil {
|
||||
logger.L.Fatalf("Failed to initialize UDP listener: %s", err.Error())
|
||||
}
|
||||
if err := listener.Init(); err != nil {
|
||||
logger.L.Fatalf("Failed to start UDP listener: %s", err.Error())
|
||||
}
|
||||
|
||||
go s.Run(mainCtx)
|
||||
go listener.Run(mainCtx)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
|
|
@ -66,7 +66,7 @@ func New(appConf *config.AppConfig, logger *logrus.Logger) (*Server, error) {
|
|||
}
|
||||
service := services.NewClientHandlerService()
|
||||
controllers := map[string]http.Handler{
|
||||
client.BootRoute: middlewares.WithLogger(client.NewBootController(logger, service), logger),
|
||||
// client.BootRoute: middlewares.WithLogger(client.NewBootController(logger, service), logger),
|
||||
client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service), logger),
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/bootoption"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var ErrUnknownClient = errors.New("unknown client")
|
||||
|
@ -11,20 +12,20 @@ var ErrUnselectedBootOption = errors.New("unselected boot option")
|
|||
var ErrUnknownBootOption = errors.New("unknown boot option")
|
||||
|
||||
type ClientHandlerService struct {
|
||||
clients map[string]*bootoption.Client
|
||||
clients map[uuid.UUID]*bootoption.Client
|
||||
}
|
||||
|
||||
func NewClientHandlerService() *ClientHandlerService {
|
||||
return &ClientHandlerService{
|
||||
clients: make(map[string]*bootoption.Client),
|
||||
clients: make(map[uuid.UUID]*bootoption.Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (chs *ClientHandlerService) AddClient(client *bootoption.Client) {
|
||||
chs.clients[client.IP] = client
|
||||
chs.clients[client.ID] = client
|
||||
}
|
||||
|
||||
func (chs *ClientHandlerService) GetClientSelectedBootOption(client string) (*bootoption.EFIApp, error) {
|
||||
func (chs *ClientHandlerService) GetClientSelectedBootOption(client uuid.UUID) (*bootoption.EFIApp, error) {
|
||||
clientDetails, ok := chs.clients[client]
|
||||
if !ok {
|
||||
return nil, ErrUnknownClient
|
||||
|
@ -42,7 +43,7 @@ func (chs *ClientHandlerService) GetClientSelectedBootOption(client string) (*bo
|
|||
return nil, ErrUnknownBootOption
|
||||
}
|
||||
|
||||
func (chs *ClientHandlerService) SetClientBootOption(client, option string) error {
|
||||
func (chs *ClientHandlerService) SetClientBootOption(client uuid.UUID, option string) error {
|
||||
clientDetails, ok := chs.clients[client]
|
||||
if !ok {
|
||||
return ErrUnknownClient
|
||||
|
|
117
bootserver/udplistener/udplistener.go
Normal file
117
bootserver/udplistener/udplistener.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package udplistener
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/bootprotocol"
|
||||
"git.faercol.me/faercol/http-boot-server/bootserver/services"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const bufferLength = 2048
|
||||
|
||||
type udpMessage struct {
|
||||
sourceAddr *net.UDPAddr
|
||||
message bootprotocol.Message
|
||||
}
|
||||
|
||||
type UDPListener struct {
|
||||
addr *net.UDPAddr
|
||||
iface *net.Interface
|
||||
l *net.UDPConn
|
||||
log *logrus.Logger
|
||||
ctx context.Context
|
||||
service *services.ClientHandlerService
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func New(ifaceName, multicastGroup string, port int, log *logrus.Logger) (*UDPListener, error) {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("[%s]:%d", multicastGroup, port))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve UDP address: %w", err)
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve interface name %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
return &UDPListener{
|
||||
addr: addr,
|
||||
iface: iface,
|
||||
ctx: context.TODO(),
|
||||
log: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *UDPListener) Init() error {
|
||||
l.log.Debugf("Creating listener on address %s, iface %s", l.addr.String(), l.iface.Name)
|
||||
listener, err := net.ListenMulticastUDP("udp", l.iface, l.addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to init listener: %w", err)
|
||||
}
|
||||
l.l = listener
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *UDPListener) listen() (*udpMessage, error) {
|
||||
buffer := make([]byte, bufferLength)
|
||||
n, source, err := l.l.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read UDP packet: %w", err)
|
||||
}
|
||||
if n > bufferLength {
|
||||
return nil, fmt.Errorf("UDP packet too big (%d/%d)", n, bufferLength)
|
||||
}
|
||||
|
||||
parsedMsg, err := bootprotocol.MessageFromBytes(bytes.Trim(buffer, "\x00"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse message: %w", err)
|
||||
}
|
||||
|
||||
return &udpMessage{sourceAddr: source, message: parsedMsg}, nil
|
||||
}
|
||||
|
||||
func (l *UDPListener) mainLoop() {
|
||||
msgChan := make(chan *udpMessage, 10)
|
||||
errChan := make(chan error, 10)
|
||||
|
||||
for {
|
||||
|
||||
go func() {
|
||||
msg, err := l.listen()
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("error while listening to UDP packets: %w", err)
|
||||
} else {
|
||||
msgChan <- msg
|
||||
}
|
||||
}()
|
||||
|
||||
l.log.Debug("Waiting for packets")
|
||||
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
if err := l.l.Close(); err != nil {
|
||||
l.log.Errorf("Error closing UDP listener: %s", err.Error())
|
||||
}
|
||||
return
|
||||
case err := <-errChan:
|
||||
l.log.Error(err)
|
||||
case msg := <-msgChan:
|
||||
l.log.Infof("Request from %s: %q", msg.sourceAddr.String(), msg.message.String())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UDPListener) Run(ctx context.Context) {
|
||||
l.ctx, l.cancel = context.WithCancel(ctx)
|
||||
l.mainLoop()
|
||||
}
|
||||
|
||||
func (l *UDPListener) Cancel() {
|
||||
l.cancel()
|
||||
}
|
Loading…
Reference in a new issue