Compare commits

...

5 Commits

Author SHA1 Message Date
Jian Qiu
aed4db1454 fix: guest ipv6 vip (#23186)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2025-08-28 02:33:33 +08:00
Jian Qiu
5ba5d78344 fix: dhcpv6 should nack confirm if addr changed (#23170)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2025-08-26 15:35:15 +08:00
wanyaoqi
d4840e0293 fix(region,host): ipv6 change addr (#23172) 2025-08-26 15:34:42 +08:00
Jian Qiu
dd72ff2c4b fix: deploy gai.conf if both v4 and v6 present (#23163)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2025-08-25 18:19:55 +08:00
屈轩
84b0f1075a fix(region): optimized cached image list (#23155) 2025-08-22 15:44:44 +08:00
17 changed files with 347 additions and 131 deletions

View File

@@ -1202,9 +1202,11 @@ type ServerQgaGuestInfoTaskInput struct {
type ServerQgaSetNetworkInput struct {
ServerQgaTimeoutInput
Device string
Ipmask string
Gateway string
Device string
Ipmask string
Gateway string
Ip6mask string
Gateway6 string
}
type ServerQgaGetNetworkInput struct {

View File

@@ -901,47 +901,45 @@ func (manager *SCachedimageManager) ListItemFilter(
}
{
var idFilter bool
storagecachedImages := StoragecachedimageManager.Query("cachedimage_id").Equals("status", api.CACHED_IMAGE_STATUS_ACTIVE).SubQuery()
storageCaches := StoragecacheManager.Query().SubQuery()
storagecachedImages := StoragecachedimageManager.Query("cachedimage_id").Equals("status", api.CACHED_IMAGE_STATUS_ACTIVE) // .SubQuery()
storagecachedImages = storagecachedImages.Snapshot()
storagesQ := StorageManager.Query()
storagesQ := StorageManager.Query("storagecache_id")
storagesQ = storagesQ.Snapshot()
if query.Valid {
idFilter = true
storagesQ = storagesQ.In("status", []string{api.STORAGE_ENABLED, api.STORAGE_ONLINE}).IsTrue("enabled")
}
if len(query.CloudproviderId) > 0 {
idFilter = true
storagesQ = storagesQ.In("manager_id", query.CloudproviderId)
}
storages := storagesQ.SubQuery()
zonesQ := ZoneManager.Query()
if len(query.HostSchedtagId) > 0 {
hostschedtags := HostschedtagManager.Query("host_id").Equals("schedtag_id", query.HostSchedtagId)
hoststorages := HoststorageManager.Query("storage_id").In("host_id", hostschedtags.Distinct().SubQuery())
storagesQ = storagesQ.In("id", hoststorages.Distinct().SubQuery())
}
zonesQ := ZoneManager.Query("id")
zonesQ = zonesQ.Snapshot()
if len(query.ZoneId) > 0 {
idFilter = true
zonesQ = zonesQ.Equals("id", query.ZoneId)
}
if len(query.CloudregionId) > 0 {
idFilter = true
zonesQ = zonesQ.In("cloudregion_id", query.CloudregionId)
}
zones := zonesQ.SubQuery()
subq := storagecachedImages.Query(storagecachedImages.Field("cachedimage_id"))
subq = subq.Join(storageCaches, sqlchemy.Equals(storagecachedImages.Field("storagecache_id"), storageCaches.Field("id")))
subq = subq.Join(storages, sqlchemy.Equals(storageCaches.Field("id"), storages.Field("storagecache_id")))
subq = subq.Join(zones, sqlchemy.Equals(storages.Field("zone_id"), zones.Field("id")))
if len(query.HostSchedtagId) > 0 {
idFilter = true
hoststorages := HoststorageManager.Query("host_id", "storage_id").SubQuery()
hostschedtags := HostschedtagManager.Query().Equals("schedtag_id", query.HostSchedtagId).SubQuery()
subq = subq.Join(hoststorages, sqlchemy.Equals(hoststorages.Field("storage_id"), storages.Field("id")))
subq = subq.Join(hostschedtags, sqlchemy.Equals(hostschedtags.Field("host_id"), hoststorages.Field("host_id")))
if zonesQ.IsAltered() {
storagesQ = storagesQ.Filter(
sqlchemy.In(storagesQ.Field("zone_id"), zonesQ.Distinct().SubQuery()),
)
}
if idFilter {
subQ := subq.Distinct().SubQuery()
q = q.Join(subQ, sqlchemy.Equals(q.Field("id"), subQ.Field("cachedimage_id")))
if storagesQ.IsAltered() {
storagecachedImages = storagecachedImages.Filter(
sqlchemy.In(storagecachedImages.Field("storagecache_id"), storagesQ.Distinct().SubQuery()),
)
}
if storagecachedImages.IsAltered() {
q = q.In("id", storagecachedImages.Distinct().SubQuery())
}
}

View File

@@ -1031,13 +1031,18 @@ func (self *SGuest) StartRestartNetworkTask(ctx context.Context, userCred mcclie
return nil
}
func (self *SGuest) StartQgaRestartNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, device string, ipMask string, gateway string, prevIp string, inBlockStream bool) error {
func (self *SGuest) StartQgaRestartNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId, device, ipMask, gateway, ip6Mask, gateway6 string) error {
data := jsonutils.NewDict()
data.Set("device", jsonutils.NewString(device))
data.Set("ip_mask", jsonutils.NewString(ipMask))
data.Set("gateway", jsonutils.NewString(gateway))
data.Set("prev_ip", jsonutils.NewString(prevIp))
data.Set("in_block_stream", jsonutils.NewBool(inBlockStream))
if len(ipMask) > 0 {
data.Set("ip_mask", jsonutils.NewString(ipMask))
data.Set("gateway", jsonutils.NewString(gateway))
}
if len(ip6Mask) > 0 {
data.Set("ip6_mask", jsonutils.NewString(ip6Mask))
data.Set("gateway6", jsonutils.NewString(gateway6))
}
if task, err := taskman.TaskManager.NewTask(ctx, "GuestQgaRestartNetworkTask", self, userCred, data, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
@@ -2755,9 +2760,13 @@ func (self *SGuest) PerformChangeIpaddr(
newMacAddr := networkJsonDesc.Mac
newMaskLen := networkJsonDesc.Masklen
newGateway := networkJsonDesc.Gateway
ipMask := fmt.Sprintf("%s/%d", newIpAddr, newMaskLen)
if conf.StrictIPv6 {
ipMask = fmt.Sprintf("%s/%d", networkJsonDesc.Ip6, networkJsonDesc.Masklen6)
ipMask := ""
if networkJsonDesc.Ip != "" {
ipMask = fmt.Sprintf("%s/%d", newIpAddr, newMaskLen)
}
ip6Mask := ""
if networkJsonDesc.Ip6 != "" {
ip6Mask = fmt.Sprintf("%s/%d", networkJsonDesc.Ip6, networkJsonDesc.Masklen6)
}
notes := gn.GetShortDesc(ctx)
@@ -2781,8 +2790,15 @@ func (self *SGuest) PerformChangeIpaddr(
return nil, errors.Wrapf(err, "GetNetwork")
}
taskData.Set("is_vpc_network", jsonutils.NewBool(net.isOneCloudVpcNetwork()))
taskData.Set("ip_mask", jsonutils.NewString(ipMask))
taskData.Set("gateway", jsonutils.NewString(newGateway))
if len(ipMask) > 0 {
taskData.Set("ip_mask", jsonutils.NewString(ipMask))
taskData.Set("gateway", jsonutils.NewString(newGateway))
}
if len(ip6Mask) > 0 {
taskData.Set("ip6_mask", jsonutils.NewString(ip6Mask))
taskData.Set("gateway6", jsonutils.NewString(networkJsonDesc.Gateway6))
}
if self.Status == api.VM_BLOCK_STREAM {
taskData.Set("in_block_stream", jsonutils.JSONTrue)
}

View File

@@ -502,11 +502,12 @@ func fetchGuestIPs(guestIds []string, virtual tristate.TriState) map[string][]st
func fetchGuestVips(guestIds []string) map[string][]string {
groupguests := GroupguestManager.Query().SubQuery()
groupnetworks := GroupnetworkManager.Query().SubQuery()
q := groupnetworks.Query(groupnetworks.Field("ip_addr"), groupguests.Field("guest_id"))
q := groupnetworks.Query(groupnetworks.Field("ip_addr"), groupnetworks.Field("ip6_addr"), groupguests.Field("guest_id"))
q = q.Join(groupguests, sqlchemy.Equals(q.Field("group_id"), groupguests.Field("group_id")))
q = q.In("guest_id", guestIds)
type sGuestVip struct {
IpAddr string
Ip6Addr string
GuestId string
}
gvips := make([]sGuestVip, 0)
@@ -519,7 +520,12 @@ func fetchGuestVips(guestIds []string) map[string][]string {
if _, ok := ret[gvips[i].GuestId]; !ok {
ret[gvips[i].GuestId] = make([]string, 0)
}
ret[gvips[i].GuestId] = append(ret[gvips[i].GuestId], gvips[i].IpAddr)
if len(gvips[i].IpAddr) > 0 {
ret[gvips[i].GuestId] = append(ret[gvips[i].GuestId], gvips[i].IpAddr)
}
if len(gvips[i].Ip6Addr) > 0 {
ret[gvips[i].GuestId] = append(ret[gvips[i].GuestId], gvips[i].Ip6Addr)
}
}
return ret
}

View File

@@ -434,6 +434,10 @@ func (manager *SGuestnetworkManager) newGuestNetwork(
gn.IsDefault = isDefault
}
if len(gn.Ip6Addr) > 0 && len(gn.IpAddr) == 0 {
gn.NumQueues = 1
}
err = manager.TableSpec().Insert(ctx, &gn)
if err != nil {
return nil, err

View File

@@ -2953,9 +2953,11 @@ func (self *SGuest) GetRealIPs() []string {
func (self *SGuest) IsExitOnly() bool {
for _, ip := range self.GetRealIPs() {
addr, _ := netutils.NewIPV4Addr(ip)
if !netutils.IsExitAddress(addr) {
return false
if regutils.MatchIP4Addr(ip) {
addr, _ := netutils.NewIPV4Addr(ip)
if !netutils.IsExitAddress(addr) {
return false
}
}
}
return true
@@ -2970,7 +2972,12 @@ func (self *SGuest) getVirtualIPs() []string {
continue
}
for _, groupnetwork := range groupnets {
ips = append(ips, groupnetwork.IpAddr)
if len(groupnetwork.IpAddr) > 0 {
ips = append(ips, groupnetwork.IpAddr)
}
if len(groupnetwork.Ip6Addr) > 0 {
ips = append(ips, groupnetwork.Ip6Addr)
}
}
}
return ips
@@ -2979,13 +2986,15 @@ func (self *SGuest) getVirtualIPs() []string {
func (self *SGuest) GetPrivateIPs() []string {
ips := self.GetRealIPs()
for i := len(ips) - 1; i >= 0; i-- {
ipAddr, err := netutils.NewIPV4Addr(ips[i])
if err != nil {
log.Errorf("guest %s(%s) has bad ipv4 address (%s): %v", self.Name, self.Id, ips[i], err)
continue
}
if !netutils.IsPrivate(ipAddr) {
ips = append(ips[:i], ips[i+1:]...)
if regutils.MatchIP4Addr(ips[i]) {
ipAddr, err := netutils.NewIPV4Addr(ips[i])
if err != nil {
log.Errorf("guest %s(%s) has bad ipv4 address (%s): %v", self.Name, self.Id, ips[i], err)
continue
}
if !netutils.IsPrivate(ipAddr) {
ips = append(ips[:i], ips[i+1:]...)
}
}
}
return ips

View File

@@ -45,26 +45,28 @@ func (self *GuestQgaRestartNetworkTask) OnRestartNetwork(ctx context.Context, gu
device, _ := self.Params.GetString("device")
ipMask, _ := self.Params.GetString("ip_mask")
gateway, _ := self.Params.GetString("gateway")
prevIp, _ := self.Params.GetString("prev_ip")
inBlockStream, _ := self.Params.Bool("in_block_stream")
ip6Mask, _ := self.Params.GetString("ip6_mask")
gateway6, _ := self.Params.GetString("gateway6")
_, err := self.requestSetNetwork(ctx, guest, device, ipMask, gateway)
_, err := self.requestSetNetwork(ctx, guest, device, ipMask, gateway, ip6Mask, gateway6)
if err != nil {
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_RESTART_NETWORK, err, self.UserCred, false)
self.taskFailed(ctx, guest, prevIp, inBlockStream, err)
self.taskFailed(ctx, guest, err)
}
}
func (self *GuestQgaRestartNetworkTask) requestSetNetwork(ctx context.Context, guest *models.SGuest, device string, ipMask string, gateway string) (jsonutils.JSONObject, error) {
func (self *GuestQgaRestartNetworkTask) requestSetNetwork(ctx context.Context, guest *models.SGuest, device, ipMask, gateway, ip6Mask, gateway6 string) (jsonutils.JSONObject, error) {
host, err := guest.GetHost()
if err != nil {
self.taskFailed(ctx, guest, "", false, err)
self.taskFailed(ctx, guest, err)
return nil, err
}
inputQgaNet := &api.ServerQgaSetNetworkInput{
Device: device,
Ipmask: ipMask,
Gateway: gateway,
Device: device,
Ipmask: ipMask,
Gateway: gateway,
Ip6mask: ip6Mask,
Gateway6: gateway6,
}
// if success, log network related information
@@ -85,12 +87,10 @@ func (self *GuestQgaRestartNetworkTask) OnSetNetwork(ctx context.Context, guest
}
func (self *GuestQgaRestartNetworkTask) OnSetNetworkFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
prevIp, _ := self.Params.GetString("prev_ip")
inBlockStream, _ := self.Params.Bool("in_block_stream")
self.taskFailed(ctx, guest, prevIp, inBlockStream, errors.Errorf(data.String()))
self.taskFailed(ctx, guest, errors.Errorf(data.String()))
}
func (self *GuestQgaRestartNetworkTask) taskFailed(ctx context.Context, guest *models.SGuest, prevIp string, inBlockStream bool, err error) {
func (self *GuestQgaRestartNetworkTask) taskFailed(ctx context.Context, guest *models.SGuest, err error) {
guest.SetStatus(ctx, self.GetUserCred(), api.VM_QGA_SET_NETWORK_FAILED, err.Error())
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_RESTART_NETWORK, jsonutils.NewString(err.Error()), self.UserCred, false)
self.SetStageFailed(ctx, nil)

View File

@@ -75,24 +75,20 @@ func (self *GuestSyncConfTask) OnSyncComplete(ctx context.Context, obj db.IStand
func (self *GuestSyncConfTask) StartRestartNetworkTask(ctx context.Context, guest *models.SGuest) {
defer self.SetStageComplete(ctx, guest.GetShortDesc(ctx))
prevIp, err := self.Params.GetString("prev_ip")
if err != nil {
log.Errorf("unable to get prev_ip when restart_network is true when sync guest")
return
}
inBlockStream := jsonutils.QueryBoolean(self.Params, "in_block_stream", false)
preMac, err := self.Params.GetString("prev_mac")
if err != nil {
log.Errorf("unable to get prev_mac when restart_network is true when sync guest")
return
}
ipMask, err := self.Params.GetString("ip_mask")
if err != nil {
gateway, err := self.Params.GetString("gateway")
ip6Mask, err := self.Params.GetString("ip6_mask")
gateway6, err := self.Params.GetString("gateway6")
if ipMask == "" && ip6Mask == "" {
log.Errorf("unable to get ip_mask when restart_network is true when sync guest")
return
}
gateway, err := self.Params.GetString("gateway")
if err != nil {
if gateway == "" && gateway6 == "" {
log.Errorf("unable to get gateway when restart_network is true when sync guest")
return
}
@@ -128,8 +124,7 @@ func (self *GuestSyncConfTask) StartRestartNetworkTask(ctx context.Context, gues
// wait for vpcagent sync network topo
time.Sleep(10 * time.Second)
}
return guest.StartQgaRestartNetworkTask(
ctx, self.UserCred, "", ifnameDevice, ipMask, gateway, prevIp, inBlockStream)
return guest.StartQgaRestartNetworkTask(ctx, self.UserCred, "", ifnameDevice, ipMask, gateway, ip6Mask, gateway6)
}()
if err != nil {
log.Errorf("guest %s failed start qga restart network task: %s", guest.GetName(), err)

View File

@@ -53,7 +53,7 @@ func (self *SecurityGroupCreateTask) OnInit(ctx context.Context, obj db.IStandal
}
rules := api.SSecgroupRuleResourceSet{}
self.GetParams().Unmarshal(&rules)
self.GetParams().Unmarshal(&rules, "rules")
driver := region.GetDriver()
err = driver.RequestCreateSecurityGroup(ctx, self.GetUserCred(), secgroup, rules)

View File

@@ -458,6 +458,26 @@ func (l *sLinuxRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []*ty
log.Errorf("rootFs.GenerateSshHostKeys fail %s", err)
}
}
{
// deploy /etc/gai.conf if both IPv4 and IPv6 are enabled
v4Enabled := false
v6Enabled := false
for _, nic := range nics {
if nic.Ip != "" {
v4Enabled = true
}
if nic.Ip6 != "" {
v6Enabled = true
}
if v4Enabled && v6Enabled {
// prefer IPv4 over IPv6 by default of /etc/gai.conf not present
if !rootFs.Exists("/etc/gai.conf", false) {
rootFs.FilePutContents("/etc/gai.conf", "precedence ::ffff:0:0/96 100\n", false, false)
}
break
}
}
}
return nil
}

View File

@@ -103,9 +103,11 @@ func (m *SGuestManager) QgaGuestInfoTask(sid string) (string, error) {
func (m *SGuestManager) QgaSetNetwork(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input := params.(*SQgaGuestSetNetwork)
netmod := &monitor.NetworkModify{
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
Ip6mask: input.Ip6mask,
Gateway6: input.Gateway6,
}
guest, err := m.checkAndInitGuestQga(input.Sid)

View File

@@ -995,18 +995,20 @@ func qgaSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid s
if input.Device == "" {
return nil, httperrors.NewMissingParameterError("device")
}
if input.Ipmask == "" {
if input.Ipmask == "" && input.Ip6mask == "" {
return nil, httperrors.NewMissingParameterError("ipmask")
}
if input.Gateway == "" {
if input.Gateway == "" && input.Gateway6 == "" {
return nil, httperrors.NewMissingParameterError("gateway")
}
hostutils.DelayTask(ctx, guestman.GetGuestManager().QgaSetNetwork, &guestman.SQgaGuestSetNetwork{
Sid: sid,
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
Sid: sid,
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
Ip6mask: input.Ip6mask,
Gateway6: input.Gateway6,
})
return nil, nil

View File

@@ -214,11 +214,13 @@ type SQgaGuestSetPassword struct {
}
type SQgaGuestSetNetwork struct {
Timeout int
Sid string
Device string
Ipmask string
Gateway string
Timeout int
Sid string
Device string
Ipmask string
Gateway string
Ip6mask string
Gateway6 string
}
type CpuSetCounter struct {

View File

@@ -634,10 +634,15 @@ func (n *SGuestNetworkSyncTask) Start(callback func(...error)) {
addNicConfs := make([]*monitor.NetworkModify, 0)
for i := range n.addNics {
addNicMacs = append(addNicMacs, n.addNics[i].Mac)
addNicConfs = append(addNicConfs, &monitor.NetworkModify{
Ipmask: fmt.Sprintf("%s/%d", n.addNics[i].Ip, n.addNics[i].Masklen),
Gateway: n.addNics[i].Gateway,
})
netMod := &monitor.NetworkModify{}
if len(n.addNics[i].Ip) > 0 {
netMod.Ipmask = fmt.Sprintf("%s/%d", n.addNics[i].Ip, n.addNics[i].Masklen)
netMod.Gateway = n.addNics[i].Gateway
}
if len(n.addNics[i].Ip6) > 0 {
netMod.Ip6mask = fmt.Sprintf("%s/%d", n.addNics[i].Ip6, n.addNics[i].Masklen6)
}
addNicConfs = append(addNicConfs, netMod)
}
n.addNicMacs = addNicMacs
n.addNicConfs = addNicConfs

View File

@@ -523,6 +523,29 @@ func ParseIPAndSubnet(input string) (string, string, error) {
return ip, subnetMask, nil
}
func ParseIP6AndSubnet(input string) (string, string, error) {
//Converting IP/MASK format to IP and MASK
parts := strings.Split(input, "/")
if len(parts) != 2 {
return "", "", fmt.Errorf("Invalid input format")
}
ip := parts[0]
subnetSizeStr := parts[1]
subnetSize := 0
for _, c := range subnetSizeStr {
if c < '0' || c > '9' {
return "", "", fmt.Errorf("Invalid subnet size")
}
subnetSize = subnetSize*10 + int(c-'0')
}
mask := net.CIDRMask(subnetSize, 32)
subnetMask := net.IP(mask).To16().String()
return ip, subnetMask, nil
}
func (qga *QemuGuestAgent) QgaAddFileExec(filePath string) error {
//Adding execution permissions to file
shellAddAuth := "chmod +x " + filePath
@@ -545,15 +568,35 @@ func (qga *QemuGuestAgent) QgaAddFileExec(filePath string) error {
}
func (qga *QemuGuestAgent) QgaSetWindowsNetwork(qgaNetMod *monitor.NetworkModify) error {
ip, subnetMask, err := ParseIPAndSubnet(qgaNetMod.Ipmask)
if err != nil {
return err
var ip, ip6, mask, mask6 string
var err error
var networkCmd string
if len(qgaNetMod.Ipmask) > 0 {
ip, mask, err = ParseIPAndSubnet(qgaNetMod.Ipmask)
if err != nil {
return err
}
networkCmd += fmt.Sprintf(
"netsh interface ip set address name=\"%s\" source=static addr=%s mask=%s gateway=%s & "+
"netsh interface ip set address name=\"%s\" dhcp",
qgaNetMod.Device, ip, mask, qgaNetMod.Gateway, qgaNetMod.Device,
)
}
if len(qgaNetMod.Ip6mask) > 0 {
ip6, mask6, err = ParseIP6AndSubnet(qgaNetMod.Ip6mask)
if err != nil {
return err
}
if networkCmd != "" {
networkCmd += " & "
}
networkCmd += fmt.Sprintf("netsh interface ipv6 set address interface=\"%s\" address=%s/%s & "+
"netsh interface ipv6 add route ::/0 interface=\"%s\" %s & "+
"netsh interface ipv6 set address interface=\"%s\" source=dhcp",
qgaNetMod.Device, ip6, mask6, qgaNetMod.Device, qgaNetMod.Gateway6, qgaNetMod.Device,
)
}
networkCmd := fmt.Sprintf(
"netsh interface ip set address name=\"%s\" source=static addr=%s mask=%s gateway=%s & "+
"netsh interface ip set address name=\"%s\" dhcp",
qgaNetMod.Device, ip, subnetMask, qgaNetMod.Gateway, qgaNetMod.Device,
)
log.Infof("networkCmd: %s", networkCmd)
arg := []string{"/C", networkCmd}

View File

@@ -82,9 +82,11 @@ type Command struct {
}
type NetworkModify struct {
Device string `json:"device"`
Ipmask string `json:"ipmask"`
Gateway string `json:"gateway"`
Device string `json:"device"`
Ipmask string `json:"ipmask"`
Gateway string `json:"gateway"`
Ip6mask string `json:"ip6mask"`
Gateway6 string `json:"gateway6"`
}
type Version struct {

View File

@@ -313,11 +313,15 @@ func optionsToBytes(opts []Option6) []byte {
}
func (p Packet) GetOption6s() []Option6 {
options := make([]Option6, 0)
offset := 4
if p.IsRelayMsg() {
offset = 16*2 + 2
}
return decodeDHCP6Options(p, offset)
}
func decodeDHCP6Options(p []byte, offset int) []Option6 {
options := make([]Option6, 0)
i := offset
for i < len(p) {
code := binary.BigEndian.Uint16(p[i : i+2])
@@ -378,28 +382,137 @@ func makeIAAddr(ip net.IP, preferLT, validLT uint32, opts []Option6) []byte {
return buf
}
func responseIANA(buf []byte, opts []Option6) []byte {
func decodeIAAddr(buf []byte) (net.IP, uint32, uint32, []Option6) {
ipBuf := make([]byte, 16)
copy(ipBuf, buf[0:16])
ip := net.IP(ipBuf)
preferLT := binary.BigEndian.Uint32(buf[16:20])
validLT := binary.BigEndian.Uint32(buf[20:24])
opts := decodeDHCP6Options(buf, 24)
return ip, preferLT, validLT, opts
}
/*
+---------------+------+--------------------------------------------+
| Name | Code | Description |
+---------------+------+--------------------------------------------+
| Success | 0 | Success. |
| | | |
| UnspecFail | 1 | Failure, reason unspecified; this status |
| | | code is sent by either a client or a |
| | | server to indicate a failure not |
| | | explicitly specified in this document. |
| | | |
| NoAddrsAvail | 2 | The server has no addresses available to |
| | | assign to the IA(s). |
| | | |
| NoBinding | 3 | Client record (binding) unavailable. |
| | | |
| NotOnLink | 4 | The prefix for the address is not |
| | | appropriate for the link to which the |
| | | client is attached. |
| | | |
| UseMulticast | 5 | Sent by a server to a client to force the |
| | | client to send messages to the server |
| | | using the |
| | | All_DHCP_Relay_Agents_and_Servers |
| | | multicast address. |
| | | |
| NoPrefixAvail | 6 | The server has no prefixes available to |
| | | assign to the IA_PD(s). |
+---------------+------+--------------------------------------------+
*/
type DHCP6StatusCode uint16
const (
Dhcp6StatusSuccess DHCP6StatusCode = 0
Dhcp6StatusUnspecFail DHCP6StatusCode = 1
Dhcp6StatusNoAddrsAvail DHCP6StatusCode = 2
Dhcp6StatusNoBinding DHCP6StatusCode = 3
Dhcp6StatusNotOnLink DHCP6StatusCode = 4
Dhcp6StatusUseMulticast DHCP6StatusCode = 5
Dhcp6StatusNoPrefixAvail DHCP6StatusCode = 6
)
func (code DHCP6StatusCode) Encode() []byte {
var msg string
switch code {
case Dhcp6StatusSuccess:
msg = "Success"
case Dhcp6StatusUnspecFail:
msg = "UnspecFail"
case Dhcp6StatusNoAddrsAvail:
msg = "NoAddrsAvail"
case Dhcp6StatusNoBinding:
msg = "NoBinding"
case Dhcp6StatusNotOnLink:
msg = "NotOnLink"
case Dhcp6StatusUseMulticast:
msg = "UseMulticast"
case Dhcp6StatusNoPrefixAvail:
msg = "NoPrefixAvail"
}
buf := make([]byte, 2)
binary.BigEndian.PutUint16(buf, uint16(code))
buf = append(buf, []byte(msg)...)
return buf
}
func responseIANA(buf []byte, ip net.IP, preferLT, validLT uint32) ([]byte, DHCP6StatusCode) {
// IA_NA structure: IAID (4 bytes) + T1 (4 bytes) + T2 (4 bytes)
// Preserve the original IAID, T1, and T2 values from the client request
opts := make([]Option6, 0)
status := Dhcp6StatusSuccess
resp := make([]byte, 12)
if len(buf) < 12 {
copy(resp, buf)
// If buffer is too short, pad with zeros
padding := make([]byte, 12-len(buf))
buf = append(buf, padding...)
} else if len(buf) > 12 {
// If buffer is too long, truncate to 12 bytes (IAID + T1 + T2)
buf = buf[:12]
}
// Log the IA_NA parameters for debugging
if len(buf) >= 12 {
// padding := make([]byte, 12-len(buf))
// copy(resp[len(buf):], padding)
} else {
// Log the IA_NA parameters for debugging
iaID := binary.BigEndian.Uint32(buf[0:4])
t1 := binary.BigEndian.Uint32(buf[4:8])
t2 := binary.BigEndian.Uint32(buf[8:12])
log.Debugf("responseIANA IA_NA IAID %d t1 %d t2 %d", iaID, t1, t2)
if len(buf) > 12 {
iaOpts := decodeDHCP6Options(buf, 12)
for i := range iaOpts {
if iaOpts[i].Code == DHCPV6_OPTION_IAADDR {
oldIp, oldPreferLT, oldValidLT, _ := decodeIAAddr(iaOpts[i].Value)
if !oldIp.Equal(ip) {
// send NotOnLink
opts = append(opts, Option6{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(oldIp, oldPreferLT, oldValidLT, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: Dhcp6StatusNotOnLink.Encode(),
},
}),
})
if status == Dhcp6StatusSuccess {
status = Dhcp6StatusNotOnLink
}
}
}
}
}
copy(resp, buf[:12])
}
buf = append(buf, optionsToBytes(opts)...)
return buf
opts = append(opts, Option6{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(ip, preferLT, validLT, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: Dhcp6StatusSuccess.Encode(),
},
}),
})
resp = append(resp, optionsToBytes(opts)...)
return resp, status
}
func makeIPv6s(ips []net.IP) []byte {
@@ -473,19 +586,11 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType)
validLifetime := uint32(conf.LeaseTime.Seconds()) // Valid lifetime should be longer than preferred
preferredLifetime := validLifetime / 2
ianaResp, status := responseIANA(ianaOpt.Value, conf.ClientIP6, preferredLifetime, validLifetime)
options = append(options, Option6{
Code: DHCPV6_OPTION_IA_NA,
Value: responseIANA(ianaOpt.Value, []Option6{
{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(conf.ClientIP6, preferredLifetime, validLifetime, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: []byte{0, 0, 'S', 'u', 'c', 'c', 'e', 's', 's'},
},
}),
},
}),
Code: DHCPV6_OPTION_IA_NA,
Value: ianaResp,
})
if len(conf.DNSServers6) > 0 {
@@ -514,6 +619,11 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType)
}
}
options = append(options, Option6{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: status.Encode(),
})
resp := NewPacket6(msgType, tid)
resp = append(resp, optionsToBytes(options)...)