added support for generation of "dns servers" option

This commit is contained in:
Dmitri Dolguikh 2017-10-30 16:28:33 -07:00 committed by Dave Anderson
parent b78d515ee1
commit 6c11f1e722
8 changed files with 102 additions and 38 deletions

View File

@ -20,10 +20,6 @@ import (
"go.universe.tf/netboot/third_party/ipxe"
)
// qemu-system-x86_64 -L . --bios /usr/share/edk2-firmware/ipv6/OVMF.fd -netdev bridge,br=br1,id=net0 -device virtio-net-pci,netdev=net0
// sudo ./pixiecore bootipv6 --listen-addr=2001:db8:f00f:cafe::4/64 --httpboot-url=http://[2001:db8:f00f:cafe::4]/bootx64.efi --ipxe-url=http://[2001:db8:f00f:cafe::4]/script.ipxe
// sudo ./pixiecore ipv6api --listen-addr=2001:db8:f00f:cafe::4 --api-request-url=http://[2001:db8:f00f:cafe::4]:8888
func main() {
cli.Ipxe[pixiecore.FirmwareX86PC] = ipxe.MustAsset("undionly.kpxe")
cli.Ipxe[pixiecore.FirmwareEFI32] = ipxe.MustAsset("ipxe-i386.efi")

View File

@ -7,28 +7,31 @@ import (
"fmt"
"net/url"
"bytes"
"net"
)
type BootConfiguration interface {
GetBootUrl(id []byte, clientArchType uint16) ([]byte, error)
GetPreference() []byte
GetRecursiveDns() []byte
GetRecursiveDns() []net.IP
}
type StaticBootConfiguration struct {
HttpBootUrl []byte
IPxeBootUrl []byte
RecursiveDns []byte
RecursiveDns []net.IP
Preference []byte
UsePreference bool
}
func MakeStaticBootConfiguration(httpBootUrl, ipxeBootUrl string, preference uint8, usePreference bool) *StaticBootConfiguration {
func MakeStaticBootConfiguration(httpBootUrl, ipxeBootUrl string, preference uint8, usePreference bool,
dnsServerAddresses []net.IP) *StaticBootConfiguration {
ret := &StaticBootConfiguration{HttpBootUrl: []byte(httpBootUrl), IPxeBootUrl: []byte(ipxeBootUrl), UsePreference: usePreference}
if usePreference {
ret.Preference = make([]byte, 1)
ret.Preference[0] = byte(preference)
}
ret.RecursiveDns = dnsServerAddresses
return ret
}
@ -43,19 +46,20 @@ func (bc *StaticBootConfiguration) GetPreference() []byte {
return bc.Preference
}
func (bc *StaticBootConfiguration) GetRecursiveDns() []byte {
func (bc *StaticBootConfiguration) GetRecursiveDns() []net.IP {
return bc.RecursiveDns
}
type ApiBootConfiguration struct {
client *http.Client
urlPrefix string
RecursiveDns []byte
RecursiveDns []net.IP
Preference []byte
UsePreference bool
}
func MakeApiBootConfiguration(url string, timeout time.Duration, preference uint8, usePreference bool) *ApiBootConfiguration {
func MakeApiBootConfiguration(url string, timeout time.Duration, preference uint8, usePreference bool,
dnsServerAddresses []net.IP) *ApiBootConfiguration {
if !strings.HasSuffix(url, "/") {
url += "/"
}
@ -68,6 +72,7 @@ func MakeApiBootConfiguration(url string, timeout time.Duration, preference uint
ret.Preference = make([]byte, 1)
ret.Preference[0] = byte(preference)
}
ret.RecursiveDns = dnsServerAddresses
return ret
}
@ -110,6 +115,6 @@ func (bc *ApiBootConfiguration) GetPreference() []byte {
return bc.Preference
}
func (bc *ApiBootConfiguration) GetRecursiveDns() []byte {
func (bc *ApiBootConfiguration) GetRecursiveDns() []net.IP {
return bc.RecursiveDns
}

View File

@ -158,6 +158,14 @@ func MakeStatusOption(statusCode uint16, message string) *Option {
return MakeOption(OptStatusCode, value)
}
func MakeDNSServersOption(addresses []net.IP) *Option {
value := make([]byte, 16*len(addresses))
for i, dnsAddress := range addresses {
copy(value[i*16:], dnsAddress)
}
return MakeOption(OptRecursiveDns, value)
}
func (o Options) Marshal() ([]byte, error) {
buffer := bytes.NewBuffer(make([]byte, 0, 1446))
for _, multipleOptions := range(o) {

View File

@ -102,3 +102,22 @@ func TestUnmarshalFailsIfOROLengthIsOdd(t *testing.T) {
t.Fatalf("Parsing options should fail: option request for options has odd length.")
}
}
func TestMakeDNSServersOption(t *testing.T) {
expectedAddress1 := net.ParseIP("2001:db8:f00f:cafe::99")
expectedAddress2 := net.ParseIP("2001:db8:f00f:cafe::9A")
dnsServersOption := MakeDNSServersOption([]net.IP{expectedAddress1, expectedAddress2})
if dnsServersOption.Id != OptRecursiveDns {
t.Fatalf("Expected option id %d, got %d", OptRecursiveDns, dnsServersOption.Id)
}
if dnsServersOption.Length != 32 {
t.Fatalf("Expected length 32 bytes, got %d", dnsServersOption.Length)
}
if string(dnsServersOption.Value[0:16]) != string(expectedAddress1) {
t.Fatalf("Expected dns server address %v, got %v", expectedAddress1, net.IP(dnsServersOption.Value[0:16]))
}
if string(dnsServersOption.Value[16:]) != string(expectedAddress2) {
t.Fatalf("Expected dns server address %v, got %v", expectedAddress2, net.IP(dnsServersOption.Value[16:]))
}
}

View File

@ -3,6 +3,7 @@ package dhcp6
import (
"hash/fnv"
"encoding/binary"
"net"
)
type PacketBuilder struct {
@ -27,7 +28,7 @@ func (b *PacketBuilder) BuildResponse(in *Packet, configuration BootConfiguratio
return b.MakeMsgAdvertiseWithNoAddrsAvailable(in.TransactionID, in.Options.ClientId(), err), err
}
return b.MakeMsgAdvertise(in.TransactionID, in.Options.ClientId(),
in.Options.ClientArchType(), associations, bootFileUrl, configuration.GetPreference()), nil
in.Options.ClientArchType(), associations, bootFileUrl, configuration.GetPreference(), configuration.GetRecursiveDns()), nil
case MsgRequest:
bootFileUrl, err := configuration.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType())
if err != nil {
@ -35,14 +36,15 @@ func (b *PacketBuilder) BuildResponse(in *Packet, configuration BootConfiguratio
}
associations, err := addresses.ReserveAddresses(in.Options.ClientId(), in.Options.IaNaIds())
return b.MakeMsgReply(in.TransactionID, in.Options.ClientId(),
in.Options.ClientArchType(), associations, iasWithoutAddesses(associations, in.Options.IaNaIds()), bootFileUrl, err), err
in.Options.ClientArchType(), associations, iasWithoutAddesses(associations, in.Options.IaNaIds()), bootFileUrl,
configuration.GetRecursiveDns(), err), err
case MsgInformationRequest:
bootFileUrl, err := configuration.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType())
if err != nil {
return nil, err
}
return b.MakeMsgInformationRequestReply(in.TransactionID, in.Options.ClientId(),
in.Options.ClientArchType(), bootFileUrl), nil
in.Options.ClientArchType(), bootFileUrl, configuration.GetRecursiveDns()), nil
case MsgRelease:
addresses.ReleaseAddresses(in.Options.ClientId(), in.Options.IaNaIds())
return b.MakeMsgReleaseReply(in.TransactionID, in.Options.ClientId()), nil
@ -52,7 +54,7 @@ func (b *PacketBuilder) BuildResponse(in *Packet, configuration BootConfiguratio
}
func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId []byte, clientArchType uint16,
associations []*IdentityAssociation, bootFileUrl, preference []byte) *Packet {
associations []*IdentityAssociation, bootFileUrl, preference []byte, dnsServers []net.IP) *Packet {
ret_options := make(Options)
ret_options.AddOption(MakeOption(OptClientId, clientId))
for _, association := range(associations) {
@ -65,15 +67,13 @@ func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId []byte,
}
ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl))
if preference != nil {ret_options.AddOption(MakeOption(OptPreference, preference))}
//ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1"))
//ret_options.AddOption(OptBootfileParam, []byte("http://")
ret_options.AddOption(MakeDNSServersOption(dnsServers))
return &Packet{Type: MsgAdvertise, TransactionID: transactionId, Options: ret_options}
}
func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId []byte, clientArchType uint16,
associations []*IdentityAssociation, iasWithoutAddresses [][]byte, bootFileUrl []byte, err error) *Packet {
associations []*IdentityAssociation, iasWithoutAddresses [][]byte, bootFileUrl []byte, dnsServers []net.IP, err error) *Packet {
ret_options := make(Options)
ret_options.AddOption(MakeOption(OptClientId, clientId))
for _, association := range(associations) {
@ -89,12 +89,13 @@ func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId []byte, cli
ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient
}
ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl))
ret_options.AddOption(MakeDNSServersOption(dnsServers))
return &Packet{Type: MsgReply, TransactionID: transactionId, Options: ret_options}
}
func (b *PacketBuilder) MakeMsgInformationRequestReply(transactionId [3]byte, clientId []byte, clientArchType uint16,
bootFileUrl []byte) *Packet {
bootFileUrl []byte, dnsServers []net.IP) *Packet {
ret_options := make(Options)
ret_options.AddOption(MakeOption(OptClientId, clientId))
ret_options.AddOption(MakeOption(OptServerId, b.ServerDuid))
@ -102,6 +103,7 @@ func (b *PacketBuilder) MakeMsgInformationRequestReply(transactionId [3]byte, cl
ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient
}
ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl))
ret_options.AddOption(MakeDNSServersOption(dnsServers))
return &Packet{Type: MsgReply, TransactionID: transactionId, Options: ret_options}
}

View File

@ -14,12 +14,13 @@ func TestMakeMsgAdvertise(t *testing.T) {
transactionId := [3]byte{'1', '2', '3'}
expectedIp := net.ParseIP("2001:db8:f00f:cafe::1")
expectedBootFileUrl := []byte("http://bootfileurl")
expectedDnsServerIp := net.ParseIP("2001:db8:f00f:cafe::99")
identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: expectedInterfaceId}
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, 0x11, []*IdentityAssociation{identityAssociation},
expectedBootFileUrl, nil)
expectedBootFileUrl, nil, []net.IP{expectedDnsServerIp})
if msg.Type != MsgAdvertise {
t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type)
@ -70,7 +71,7 @@ func TestShouldSetPreferenceOptionWhenSpecified(t *testing.T) {
expectedPreference := []byte{128}
msg := builder.MakeMsgAdvertise([3]byte{'t', 'i', 'd'}, []byte("clientid"), 0x11,
[]*IdentityAssociation{identityAssociation}, []byte("http://bootfileurl"), expectedPreference)
[]*IdentityAssociation{identityAssociation}, []byte("http://bootfileurl"), expectedPreference, []net.IP{})
preferenceOption := msg.Options[OptPreference]
if preferenceOption == nil {
@ -92,7 +93,7 @@ func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) {
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, 0x10, []*IdentityAssociation{identityAssociation},
expectedBootFileUrl, nil)
expectedBootFileUrl, nil, []net.IP{})
vendorClassOption := msg.Options[OptVendorClass]
if vendorClassOption == nil {
@ -158,12 +159,13 @@ func TestMakeMsgReply(t *testing.T) {
transactionId := [3]byte{'1', '2', '3'}
expectedIp := net.ParseIP("2001:db8:f00f:cafe::1")
expectedBootFileUrl := []byte("http://bootfileurl")
expectedDnsServerIp := net.ParseIP("2001:db8:f00f:cafe::99")
identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("id-1")}
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgReply(transactionId, expectedClientId, 0x11, []*IdentityAssociation{identityAssociation},
make([][]byte, 0), expectedBootFileUrl, nil)
make([][]byte, 0), expectedBootFileUrl, []net.IP{expectedDnsServerIp}, nil)
if msg.Type != MsgReply {
t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type)
@ -220,7 +222,7 @@ func TestMakeMsgReplyWithHttpClientArch(t *testing.T) {
msg := builder.MakeMsgReply(transactionId, expectedClientId, 0x10,
[]*IdentityAssociation{identityAssociation}, make([][]byte, 0),
expectedBootFileUrl, nil)
expectedBootFileUrl, []net.IP{}, nil)
vendorClassOption := msg.Options[OptVendorClass]
if vendorClassOption == nil {
@ -248,7 +250,7 @@ func TestMakeMsgReplyWithNoAddrsAvailable(t *testing.T) {
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgReply(transactionId, expectedClientId, 0x10,
[]*IdentityAssociation{identityAssociation}, [][]byte{[]byte("id-2")}, expectedBootFileUrl,
[]*IdentityAssociation{identityAssociation}, [][]byte{[]byte("id-2")}, expectedBootFileUrl, []net.IP{},
fmt.Errorf(expectedErrorMessage))
iaNaOption := msg.Options[OptIaNa]
@ -295,10 +297,12 @@ func TestMakeMsgInformationRequestReply(t *testing.T) {
expectedServerId := []byte("serverid")
transactionId := [3]byte{'1', '2', '3'}
expectedBootFileUrl := []byte("http://bootfileurl")
expectedDnsServerIp := net.ParseIP("2001:db8:f00f:cafe::99")
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x11, expectedBootFileUrl)
msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x11,
expectedBootFileUrl, []net.IP{expectedDnsServerIp})
if msg.Type != MsgReply {
t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type)
@ -346,7 +350,8 @@ func TestMakeMsgInformationRequestReplyWithHttpClientArch(t *testing.T) {
builder := MakePacketBuilder(expectedServerId, 90, 100)
msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x10, expectedBootFileUrl)
msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x10,
expectedBootFileUrl, []net.IP{})
vendorClassOption := msg.Options[OptVendorClass]
if vendorClassOption == nil {

View File

@ -6,8 +6,10 @@ import (
"go.universe.tf/netboot/pixiecorev6"
"go.universe.tf/netboot/dhcp6"
"net"
"strings"
)
// pixiecore bootipv6 --listen-addr=2001:db8:f00f:cafe::4/64 --httpboot-url=http://[2001:db8:f00f:cafe::4]/bootx64.efi --ipxe-url=http://[2001:db8:f00f:cafe::4]/script.ipxe
var bootIPv6Cmd = &cobra.Command{
Use: "bootipv6",
Short: "Boot a kernel and optional init ramdisks over IPv6",
@ -50,7 +52,18 @@ var bootIPv6Cmd = &cobra.Command{
if err != nil {
fatalf("Error reading flag: %s", err)
}
s.BootConfig = dhcp6.MakeStaticBootConfiguration(httpBootUrl, ipxeUrl, preference, cmd.Flags().Changed("preference"))
dnsServers, err := cmd.Flags().GetString("dns-servers")
if err != nil {
fatalf("Error reading flag: %s", err)
}
dnsServerAddresses := make([]net.IP, 0)
if cmd.Flags().Changed("dns-servers") {
for _, dnsServerAddress := range strings.Split(dnsServers, ",") {
dnsServerAddresses = append(dnsServerAddresses, net.ParseIP(dnsServerAddress))
}
}
s.BootConfig = dhcp6.MakeStaticBootConfiguration(httpBootUrl, ipxeUrl, preference,
cmd.Flags().Changed("preference"), dnsServerAddresses)
addressPoolStart, err := cmd.Flags().GetString("address-pool-start")
if err != nil {
@ -80,6 +93,7 @@ func serverv6ConfigFlags(cmd *cobra.Command) {
cmd.Flags().StringP("address-pool-start", "", "2001:db8:f00f:cafe:ffff::100", "Starting ip of the address pool, e.g. 2001:db8:f00f:cafe:ffff::100")
cmd.Flags().Uint64("address-pool-size", 50, "Address pool size")
cmd.Flags().Uint32("address-pool-lifetime", 1850, "Address pool ip valid lifetime in seconds")
cmd.Flags().StringP("dns-servers", "", "", "Comma separated list of one or more dns server addresses")
}
func init() {

View File

@ -7,8 +7,11 @@ import (
"go.universe.tf/netboot/dhcp6"
"time"
"net"
"strings"
)
// pixiecore ipv6api --listen-addr=2001:db8:f00f:cafe::4 --api-request-url=http://[2001:db8:f00f:cafe::4]:8888
var ipv6ApiCmd = &cobra.Command{
Use: "ipv6api",
Short: "Boot a kernel and optional init ramdisks over IPv6 using api",
@ -46,7 +49,18 @@ var ipv6ApiCmd = &cobra.Command{
if err != nil {
fatalf("Error reading flag: %s", err)
}
s.BootConfig = dhcp6.MakeApiBootConfiguration(apiUrl, apiTimeout, preference, cmd.Flags().Changed("preference"))
dnsServers, err := cmd.Flags().GetString("dns-servers")
if err != nil {
fatalf("Error reading flag: %s", err)
}
dnsServerAddresses := make([]net.IP, 0)
if cmd.Flags().Changed("dns-servers") {
for _, dnsServerAddress := range strings.Split(dnsServers, ",") {
dnsServerAddresses = append(dnsServerAddresses, net.ParseIP(dnsServerAddress))
}
}
s.BootConfig = dhcp6.MakeApiBootConfiguration(apiUrl, apiTimeout, preference,
cmd.Flags().Changed("preference"), dnsServerAddresses)
addressPoolStart, err := cmd.Flags().GetString("address-pool-start")
if err != nil {
@ -76,6 +90,7 @@ func serverv6ApiConfigFlags(cmd *cobra.Command) {
cmd.Flags().StringP("address-pool-start", "", "2001:db8:f00f:cafe:ffff::100", "Starting ip of the address pool, e.g. 2001:db8:f00f:cafe:ffff::100")
cmd.Flags().Uint64("address-pool-size", 50, "Address pool size")
cmd.Flags().Uint32("address-pool-lifetime", 1850, "Address pool ip address valid lifetime in seconds")
cmd.Flags().StringP("dns-servers", "", "", "Comma separated list of one or more dns server addresses")
}
func init() {