f-46: dhcpd: build dhcp replies over insomniacslk/dhcp #46
internal/dhcpd décide quelles options partent vers quelle MAC, sans I/O ni état. L'encodage RFC 3442 de l'option 121 est délégué à la bibliothèque, qui le teste elle-même. L'option 3 est omise plutôt qu'envoyée vide : la dhcp-option=3 nue n'existait que pour empêcher dnsmasq d'annoncer sa propre adresse. La route par défaut est conditionnée au subnet (DefaultGateway) et à l'interface (Host.DefaultRoute), ce qui remplace le mécanisme de tags. 24 tests plus un fuzz sur BuildReply. Les assertions d'encodage sont validées par mutation. Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
19750975c2
commit
5918feed83
6 changed files with 570 additions and 22 deletions
97
internal/dhcpd/reply.go
Normal file
97
internal/dhcpd/reply.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/metadata"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoInterfaceIP = errors.New("interface ip is required: guests would have no route to the metadata server")
|
||||
ErrNoNetwork = errors.New("subnet network is required")
|
||||
ErrNoHostIP = errors.New("host ip is required")
|
||||
ErrNoRequest = errors.New("request is nil")
|
||||
)
|
||||
|
||||
func metadataRoute() *net.IPNet {
|
||||
return &net.IPNet{
|
||||
IP: net.ParseIP(metadata.ServiceIP).To4(),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRoute() *net.IPNet {
|
||||
return &net.IPNet{
|
||||
IP: net.IPv4zero.To4(),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func Routes(c SubnetConfig, h Host) (dhcpv4.Routes, error) {
|
||||
if c.InterfaceIP == nil {
|
||||
return nil, ErrNoInterfaceIP
|
||||
}
|
||||
|
||||
routes := dhcpv4.Routes{{Dest: metadataRoute(), Router: c.InterfaceIP}}
|
||||
|
||||
if c.VPCRoute != nil {
|
||||
routes = append(routes, &dhcpv4.Route{Dest: c.VPCRoute, Router: c.InterfaceIP})
|
||||
}
|
||||
if h.DefaultRoute && c.DefaultGateway != nil {
|
||||
routes = append(routes, &dhcpv4.Route{Dest: defaultRoute(), Router: c.DefaultGateway})
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func replyType(req *dhcpv4.DHCPv4) (dhcpv4.MessageType, error) {
|
||||
switch req.MessageType() {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
return dhcpv4.MessageTypeOffer, nil
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
return dhcpv4.MessageTypeAck, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("no reply built for message type %s", req.MessageType())
|
||||
}
|
||||
}
|
||||
|
||||
func BuildReply(c SubnetConfig, h Host, req *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, error) {
|
||||
if req == nil {
|
||||
return nil, ErrNoRequest
|
||||
}
|
||||
if c.Network == nil {
|
||||
return nil, ErrNoNetwork
|
||||
}
|
||||
if h.IP == nil {
|
||||
return nil, ErrNoHostIP
|
||||
}
|
||||
|
||||
kind, err := replyType(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := Routes(c, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mods := []dhcpv4.Modifier{
|
||||
dhcpv4.WithMessageType(kind),
|
||||
dhcpv4.WithServerIP(c.InterfaceIP),
|
||||
dhcpv4.WithYourIP(h.IP),
|
||||
dhcpv4.WithNetmask(c.Network.Mask),
|
||||
dhcpv4.WithLeaseTime(uint32(LeaseTime.Seconds())),
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(c.InterfaceIP)),
|
||||
dhcpv4.WithOption(dhcpv4.OptDNS(DNSServers()...)),
|
||||
dhcpv4.WithOption(dhcpv4.OptClasslessStaticRoute(routes...)),
|
||||
}
|
||||
if h.DefaultRoute && c.DefaultGateway != nil {
|
||||
mods = append(mods, dhcpv4.WithOption(dhcpv4.OptRouter(c.DefaultGateway)))
|
||||
}
|
||||
|
||||
return dhcpv4.NewReplyFromRequest(req, mods...)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue