http-boot-server/bootserver/udplistener/udplistener.go

181 lines
5 KiB
Go
Raw Normal View History

2023-07-29 19:23:36 +00:00
package udplistener
import (
"bytes"
"context"
2023-07-30 13:56:31 +00:00
"errors"
2023-07-29 19:23:36 +00:00
"fmt"
"net"
"git.faercol.me/faercol/http-boot-server/bootserver/bootprotocol"
2023-07-30 13:56:31 +00:00
"git.faercol.me/faercol/http-boot-server/bootserver/config"
2023-07-29 19:23:36 +00:00
"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
2023-09-16 11:52:05 +00:00
laddr *net.UDPAddr
2023-07-29 19:23:36 +00:00
iface *net.Interface
l *net.UDPConn
log *logrus.Logger
ctx context.Context
service *services.ClientHandlerService
cancel context.CancelFunc
}
2023-07-30 13:56:31 +00:00
func New(conf *config.AppConfig, log *logrus.Logger) (*UDPListener, error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("[%s]:%d", conf.UPDMcastGroup, conf.UDPPort))
2023-07-29 19:23:36 +00:00
if err != nil {
return nil, fmt.Errorf("failed to resolve UDP address: %w", err)
}
2023-07-30 13:56:31 +00:00
iface, err := net.InterfaceByName(conf.UDPIface)
2023-07-29 19:23:36 +00:00
if err != nil {
2023-07-30 13:56:31 +00:00
return nil, fmt.Errorf("failed to resolve interface name %s: %w", conf.UDPIface, err)
2023-07-29 19:23:36 +00:00
}
2023-09-16 11:52:05 +00:00
laddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("[%s%%%s]:0", conf.UDPSrcAddr, conf.UDPIface))
if err != nil {
return nil, fmt.Errorf("failed to resolve UDP source address: %w", err)
}
2023-07-29 19:23:36 +00:00
return &UDPListener{
2023-07-30 13:56:31 +00:00
addr: addr,
iface: iface,
2023-09-16 11:52:05 +00:00
laddr: laddr,
2023-07-30 13:56:31 +00:00
ctx: context.TODO(),
service: services.NewClientHandlerService(conf.DataFilepath, log),
log: log,
2023-07-29 19:23:36 +00:00
}, 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
}
2023-07-30 13:56:31 +00:00
func (l *UDPListener) handleBootRequest(msg bootprotocol.Message, subLogger logrus.FieldLogger) bootprotocol.Message {
subLogger.Debugf("Processing message %q", msg.String())
requestLogger := subLogger.WithField("clientID", msg.ID().String())
requestLogger.Debug("Getting boot option for client")
bootOption, err := l.service.GetClientSelectedBootOption(msg.ID())
if err != nil {
if errors.Is(err, services.ErrUnknownClient) || errors.Is(err, services.ErrUnselectedBootOption) {
requestLogger.Warnf("Client is not configured, returning an error (original error is %q)", err.Error())
return bootprotocol.Deny(msg.ID(), "client not configured")
}
if errors.Is(err, services.ErrUnknownBootOption) {
requestLogger.Errorf("Invalid config for client: %s", err.Error())
return bootprotocol.Deny(msg.ID(), "invalid client config")
}
requestLogger.Errorf("Failed to get config for client: %s", err.Error())
return bootprotocol.Deny(msg.ID(), "unknown server error")
}
2023-09-16 11:51:01 +00:00
return bootprotocol.Accept(msg.ID(), bootOption.DevicePath)
2023-07-30 13:56:31 +00:00
}
func (l *UDPListener) handleClient(msg *udpMessage) error {
clientLogger := l.log.WithField("clientIP", msg.sourceAddr.IP)
clientLogger.Debug("Handling request for client")
response := l.handleBootRequest(msg.message, clientLogger)
clientLogger.Debug("Dialing client for answer")
2023-09-16 11:52:05 +00:00
con, err := net.DialUDP("udp", l.laddr, msg.sourceAddr)
2023-07-30 13:56:31 +00:00
if err != nil {
return fmt.Errorf("failed to dialed client: %w", err)
}
defer con.Close()
clientLogger.Debug("Sending response to client")
dat, err := response.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal response to bytes, %w", err)
}
n, err := con.Write(dat)
if err != nil {
return fmt.Errorf("failed to send response to client, %w", err)
}
if n != len(dat) {
return fmt.Errorf("failed to send the entire response to client (%d/%d)", n, len(dat))
}
return nil
}
2023-07-29 19:23:36 +00:00
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)
}
2023-08-13 20:33:57 +00:00
l.log.Debugf("Parsing UDP message %q", bytes.Trim(buffer, "\x00"))
2023-07-29 19:23:36 +00:00
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:
2023-07-30 13:56:31 +00:00
if err := l.handleClient(msg); err != nil {
l.log.Errorf("Failed to handle message from client: %q", err.Error())
}
2023-07-29 19:23:36 +00:00
}
}
}
func (l *UDPListener) Run(ctx context.Context) {
l.ctx, l.cancel = context.WithCancel(ctx)
l.mainLoop()
}
func (l *UDPListener) Cancel() {
l.cancel()
}