// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hostdhcp import ( "fmt" "net" "time" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/netutils" "yunion.io/x/onecloud/pkg/hostman/options" "yunion.io/x/onecloud/pkg/util/icmp6" "yunion.io/x/onecloud/pkg/util/netutils2" ) type sRARequest struct { vmMac net.HardwareAddr attempts int succAttempts int nextAttempt time.Time } func (s *SGuestDHCP6Server) requestRA(vmMac net.HardwareAddr) { s.raReqCh <- vmMac } func (s *SGuestDHCP6Server) handleRouterSolicitation(msg *icmp6.SRouterSolicitation) error { // solicitation request from guest s.requestRA(msg.SrcMac) return nil } func (s *SGuestDHCP6Server) handleRouterAdvertisement(msg *icmp6.SRouterAdvertisement) error { // periodic router advertisement from local router return nil } func (s *SGuestDHCP6Server) handleNeighborSolicitation(msg *icmp6.SNeighborSolicitation) error { return nil } func (s *SGuestDHCP6Server) handleNeighborAdvertisement(msg *icmp6.SNeighborAdvertisement) error { if msg.IsSolicited { //log.Infof("Save mac %s for gw IP %s", msg.SrcMac, msg.TargetAddr.String()) s.gwMacCache.Set(msg.TargetAddr.String(), msg.SrcMac) } return nil } func (s *SGuestDHCP6Server) requestGatewayMac(gwIP net.IP, vlanId uint16) { // Solicited-Node Multicast Address, FF02::1:FF00:0/104, 33:33:ff:00:00:00 destIP := netutils2.IP2SolicitMcastIP(gwIP) destMac := netutils2.IP2SolicitMcastMac(gwIP) ns := &icmp6.SNeighborSolicitation{ SBaseICMP6Message: icmp6.SBaseICMP6Message{ SrcMac: s.ifaceDev.GetHardwareAddr(), SrcIP: net.ParseIP(s.ifaceDev.Addr6LinkLocal), DstMac: destMac, DstIP: destIP, Vlan: vlanId, }, TargetAddr: gwIP, } bytes, err := icmp6.EncodePacket(ns) if err != nil { log.Errorf("Encode NeighborSolicitation error: %v", err) return } err = s.conn.SendRaw(bytes, destMac) if err != nil { log.Errorf("Send RouterAdvertisement error: %v", err) return } } func (s *SGuestDHCP6Server) sendRouterAdvertisement(vmMac net.HardwareAddr) (bool, error) { var conf = s.getConfig(vmMac) if conf == nil { return false, errors.Wrapf(errors.ErrNotFound, "getConfig %s", vmMac.String()) } if conf != nil && conf.ClientIP6 != nil && conf.Gateway6 != nil { gwMacObj := s.gwMacCache.AtomicGet(conf.Gateway6.String()) if gwMacObj == nil { log.Debugf("No mac for gw IP %s, request it", conf.Gateway6.String()) s.requestGatewayMac(conf.Gateway6, conf.VlanId) return false, nil } gwMac := gwMacObj.(net.HardwareAddr) pref := icmp6.PreferenceMedium if conf.IsDefaultGW { pref = icmp6.PreferenceHigh } _, ipnet, err := net.ParseCIDR(fmt.Sprintf("%s/%d", conf.ClientIP6, conf.PrefixLen6)) if err != nil { return false, errors.Wrapf(err, "ParseCIDR %s/%d", conf.ClientIP6, conf.PrefixLen6) } srcIpAddr, _ := netutils.Mac2LinkLocal(gwMac.String()) gwLinkLocalIP := srcIpAddr.ToIP() ra := &icmp6.SRouterAdvertisement{ SBaseICMP6Message: icmp6.SBaseICMP6Message{ SrcMac: gwMac, // https://www.rfc-editor.org/rfc/rfc4861.html#page-18 // !!! MUST be the link-local address assigned to the interface from which this message is sent. SrcIP: gwLinkLocalIP, DstMac: vmMac, DstIP: net.ParseIP("ff02::1"), }, CurHopLimit: 64, IsManaged: true, IsOther: true, IsHomeAgent: false, Preference: pref, RouterLifetime: uint16(options.HostOptions.Dhcp6RouterLifetimeSeconds), ReachableTime: 0, RetransTimer: 0, MTU: uint32(conf.MTU), PrefixInfo: []icmp6.SPrefixInfoOption{ { IsOnlink: true, IsAutoconf: false, Prefix: ipnet.IP, PrefixLen: conf.PrefixLen6, ValidLifetime: uint32(options.HostOptions.Dhcp6RouterLifetimeSeconds / 2), PreferredLifetime: uint32(options.HostOptions.Dhcp6RouterLifetimeSeconds / 4), }, }, } for i := range conf.Routes6 { route := conf.Routes6[i] if route.Gateway.String() == "::" { // on-link routes ra.PrefixInfo = append(ra.PrefixInfo, icmp6.SPrefixInfoOption{ IsOnlink: true, IsAutoconf: false, Prefix: route.Prefix, PrefixLen: route.PrefixLen, ValidLifetime: uint32(options.HostOptions.Dhcp6RouterLifetimeSeconds / 2), PreferredLifetime: uint32(options.HostOptions.Dhcp6RouterLifetimeSeconds / 4), }) } else if route.Gateway.String() != conf.Gateway6.String() { // routes forwarded by router ra.RouteInfo = append(ra.RouteInfo, icmp6.SRouteInfoOption{ RouteLifetime: uint32(options.HostOptions.Dhcp6RouterLifetimeSeconds), Prefix: route.Prefix, PrefixLen: route.PrefixLen, Preference: pref, }) } } bytes, err := icmp6.EncodePacket(ra) if err != nil { log.Errorf("Encode RouterAdvertisement error: %v", err) return false, errors.Wrapf(err, "EncodePacket") } err = s.conn.SendRaw(bytes, vmMac) if err != nil { log.Errorf("Send RouterAdvertisement error: %v", err) } } return true, nil } func (s *SGuestDHCP6Server) stopRAServer() { close(s.raExitCh) close(s.raReqCh) } func (s *SGuestDHCP6Server) handleRARequest(vmMac net.HardwareAddr) { vmMacStr := vmMac.String() raReq, ok := s.raReqQueue[vmMacStr] if !ok { raReq = &sRARequest{ vmMac: vmMac, } s.raReqQueue[vmMacStr] = raReq } else { raReq.nextAttempt = time.Time{} } // send RA immediately raReq.attempts++ succ, err := s.sendRouterAdvertisement(vmMac) if err != nil { if errors.Cause(err) == errors.ErrNotFound { // no such vm, giveup delete(s.raReqQueue, vmMacStr) return } log.Errorf("sendRouterAdvertisement error: %v", err) } if succ { raReq.succAttempts++ } if raReq.succAttempts < options.HostOptions.Dhcp6RouterAdvertisementAttempts { if raReq.attempts > 2*options.HostOptions.Dhcp6RouterAdvertisementAttempts { // giveup delete(s.raReqQueue, vmMacStr) } else { // retry next round } } else { // schedule RA when prefix router lifetime expires raReq.attempts = 0 raReq.succAttempts = 0 raReq.nextAttempt = time.Now().Add(time.Second * time.Duration(options.HostOptions.Dhcp6RouterLifetimeSeconds/4)) } } func (s *SGuestDHCP6Server) startRAServer() { // a tiny RA server stop := false for !stop { select { case <-s.raExitCh: stop = true case raReq := <-s.raReqCh: // handle RA request s.handleRARequest(raReq) case <-time.After(time.Second * time.Duration(options.HostOptions.Dhcp6RouterAdvertisementIntervalSecs)): // send RA for _, raReq := range s.raReqQueue { if !raReq.nextAttempt.IsZero() && raReq.nextAttempt.After(time.Now()) { continue } s.handleRARequest(raReq.vmMac) } } } }