From 78bf0bcb2fe11b0a59f94bd463f00c6aa206a044 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Sat, 12 Jul 2025 12:10:18 +0800 Subject: [PATCH] fix: dhcpv6 support phase 3 (#22880) Co-authored-by: Qiu Jian --- cmd/dhcprelay/main.go | 65 +++-- pkg/baremetal/nic.go | 6 + pkg/hostman/guestfs/fsdriver/linux.go | 2 +- pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go | 126 +++++++--- pkg/hostman/hostinfo/hostdhcp/dhcpserver.go | 58 +++-- pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go | 51 +++- .../hostinfo/hostdhcp/icmp6handlers.go | 232 ++++++++++++++++++ pkg/hostman/options/options.go | 3 + pkg/util/dhcp/conn.go | 12 +- pkg/util/dhcp/conn_linux.go | 1 - pkg/util/dhcp/conn_linux6.go | 15 +- pkg/util/dhcp/helpers.go | 36 ++- pkg/util/dhcp/interface.go | 2 +- pkg/util/dhcp/packet.go | 3 + pkg/util/dhcp/packet6.go | 177 +++++++++++-- pkg/util/dhcp/server.go | 2 +- pkg/util/icmp6/messages.go | 112 +++++++-- pkg/util/netplan/netplan.go | 2 + pkg/util/netplan/netplan_test.go | 2 +- 19 files changed, 745 insertions(+), 162 deletions(-) create mode 100644 pkg/hostman/hostinfo/hostdhcp/icmp6handlers.go diff --git a/cmd/dhcprelay/main.go b/cmd/dhcprelay/main.go index e24e434041..c98e9523a8 100644 --- a/cmd/dhcprelay/main.go +++ b/cmd/dhcprelay/main.go @@ -27,11 +27,17 @@ import ( ) type Options struct { - Help bool `help:"Show help"` + Help bool `help:"Show help"` + Interface string `help:"Listening interface, e.g. eth0"` - Ip string `help:"Listening interface IP, e.g. 192.168.22.2"` - Port int `help:"listening port" default:"67"` - Relay string `help:"Relay server address, e.g. 192.168.22.23"` + + Ip string `help:"Listening interface IP, e.g. 192.168.22.2"` + Port int `help:"listening port" default:"67"` + Relay string `help:"Relay server address, e.g. 192.168.22.23"` + + Ip6 string `help:"Listening interface IP, e.g. 2001:db8::1"` + Port6 int `help:"listening port" default:"547"` + Relay6 string `help:"Relay server address, e.g. 2001:db8::23"` } func relayMain() error { @@ -45,6 +51,10 @@ func relayMain() error { } err = parse.ParseArgs(os.Args[1:], false) + if err != nil { + return err + } + options := parse.Options().(*Options) if options.Help { @@ -55,24 +65,45 @@ func relayMain() error { if len(options.Interface) == 0 { return errors.Error("Missing interface") } - if len(options.Ip) == 0 { - return errors.Error("Missing interface IP") - } - if len(options.Relay) == 0 { - return errors.Error("Missing DHCP relay server") + if len(options.Ip) == 0 && len(options.Ip6) == 0 { + return errors.Error("Missing interface IP or IP6") } - relayConfig := &hostdhcp.SDHCPRelayUpstream{} - relayConfig.IP = options.Relay - relayConfig.Port = 67 - srv, err := hostdhcp.NewGuestDHCPServer(options.Interface, options.Port, relayConfig) - if err != nil { - return errors.Wrap(err, "NewGuestDHCPServer") + if len(options.Ip) > 0 { + if len(options.Relay) == 0 { + return errors.Error("Missing DHCP relay server or relay server6") + } + + relayConfig := &hostdhcp.SDHCPRelayUpstream{} + relayConfig.IP = options.Relay + relayConfig.Port = 67 + srv, err := hostdhcp.NewGuestDHCPServer(options.Interface, options.Port, relayConfig) + if err != nil { + return errors.Wrap(err, "NewGuestDHCPServer") + } + + srv.Start(false) + + srv.RelaySetup(options.Ip) } - srv.Start(false) + if len(options.Ip6) > 0 { + if len(options.Relay6) == 0 { + return errors.Error("Missing DHCP relay server or relay server6") + } - srv.RelaySetup(options.Ip) + relayConfig6 := &hostdhcp.SDHCPRelayUpstream{} + relayConfig6.IP = options.Relay6 + relayConfig6.Port = options.Port6 + srv6, err := hostdhcp.NewGuestDHCP6Server(options.Interface, options.Port6, relayConfig6) + if err != nil { + return errors.Wrap(err, "NewGuestDHCP6Server") + } + + srv6.Start(false) + + srv6.RelaySetup(options.Ip6) + } for { time.Sleep(time.Hour) diff --git a/pkg/baremetal/nic.go b/pkg/baremetal/nic.go index d0fe121daa..38f08ed1eb 100644 --- a/pkg/baremetal/nic.go +++ b/pkg/baremetal/nic.go @@ -61,7 +61,10 @@ func GetNicDHCPConfig( } } + isDefaultGW := false if n.IsDefault == nil || *n.IsDefault { + isDefaultGW = true + if n.Gateway != "" && !strings.HasPrefix(strings.ToLower(osName), "win") { routes4 = append(routes4, netutils2.SRouteInfo{ SPrefixInfo: netutils2.SPrefixInfo{ @@ -84,6 +87,7 @@ func GetNicDHCPConfig( conf := &dhcp.ResponseConfig{ InterfaceMac: serverMac, + VlanId: uint16(n.VlanId), ServerIP: serverIP, Domain: n.Domain, @@ -93,6 +97,8 @@ func GetNicDHCPConfig( Routes6: routes6, LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, + + IsDefaultGW: isDefaultGW, } if n.IpAddr != "" { diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go index 2ea7e3970d..f583ca43d4 100644 --- a/pkg/hostman/guestfs/fsdriver/linux.go +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -1528,7 +1528,7 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics // TODO cmds.WriteString("IPV6INIT=yes\n") cmds.WriteString("DHCPV6C=yes\n") - cmds.WriteString("IPV6_AUTOCONF=no\n") + cmds.WriteString("IPV6_AUTOCONF=yes\n") // cmds.WriteString(fmt.Sprintf("IPV6ADDR=%s/%d\n", nicDesc.Ip6, nicDesc.Masklen6)) // if len(nicDesc.Gateway6) > 0 { // cmds.WriteString("IPV6_DEFROUTE=yes\n", nicDesc.Gateway6)) diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go index 016358101c..e245f0d8fa 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go @@ -21,10 +21,21 @@ import ( "time" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/util/dhcp" ) +type SRelayCache6 struct { + peerMac net.HardwareAddr + peerUdp *net.UDPAddr + + linkAddr net.IP + peerAddr net.IP + + timer time.Time +} + type SDHCP6Relay struct { server *dhcp.DHCP6Server OnRecv recvFunc @@ -53,13 +64,13 @@ func NewDHCP6Relay(guestDHCPConn *dhcp.Conn, config *SDHCPRelayUpstream) (*SDHCP func (r *SDHCP6Relay) Setup(addr string) error { r.ipv6srcAddr = net.ParseIP(addr) if len(r.ipv6srcAddr) == 0 { - return fmt.Errorf("Wrong ip address %s", addr) + return fmt.Errorf("wrong ip address %s", addr) } log.Infof("DHCP6 Relay Setup on %s %d", addr, DEFAULT_DHCP6_RELAY_PORT) var err error r.server, err = dhcp.NewDHCP6Server3(addr, DEFAULT_DHCP6_RELAY_PORT) if err != nil { - return err + return errors.Wrapf(err, "NewDHCP6Server3") } go r.server.ListenAndServe(r) return nil @@ -70,52 +81,101 @@ func (r *SDHCP6Relay) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr * return pkg, nil, err } -func (r *SDHCP6Relay) OnRecvICMP6(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) error { +func (r *SDHCP6Relay) OnRecvICMP6(pkt dhcp.Packet) error { + // null operation return nil } +func getSessionKey(tid uint32, clientID []byte) string { + return fmt.Sprintf("%x-%x", tid, clientID) +} + func (r *SDHCP6Relay) serveDHCPInternal(pkt dhcp.Packet, _ *net.UDPAddr) (dhcp.Packet, error) { - log.Infof("DHCP Relay Reply TO %s", pkt.CHAddr()) - v, ok := r.cache.Load(pkt.TransactionID()) + if pkt.Type6() != dhcp.DHCPV6_RELAY_REPL { + return nil, errors.Wrapf(errors.ErrInvalidFormat, "not a valid relay reply message") + } + + hopCount := pkt.HopCount() + decapPkt, err := dhcp.DecapDHCP6RelayMsg(pkt) + if err != nil { + return nil, errors.Wrapf(err, "DecapDHCP6RelayMsg") + } + tid, err := decapPkt.TID6() + if err != nil { + return nil, errors.Wrapf(err, "TID6") + } + cliID, err := decapPkt.ClientID() + if err != nil { + return nil, errors.Wrapf(err, "ClientID") + } + + key := getSessionKey(tid, cliID) + v, ok := r.cache.Load(key) if ok { - r.cache.Delete(pkt.TransactionID()) - val := v.(*SRelayCache) - udpAddr := &net.UDPAddr{ - IP: pkt.CIAddr(), - Port: val.srcPort, - } - if err := r.guestDHCPConn.SendDHCP(pkt, udpAddr, pkt.CHAddr()); err != nil { - log.Errorln(err) + r.cache.Delete(key) + val := v.(*SRelayCache6) + + if hopCount > 1 { + pkt.SetHopCount(hopCount - 1) + pkt.SetLinkAddr(val.linkAddr) + pkt.SetPeerAddr(val.peerAddr) + if err := r.server.GetConn().SendDHCP(pkt, val.peerUdp, val.peerMac); err != nil { + log.Errorf("send relay packet to client %s %s failed: %s", val.peerUdp, val.peerMac, err) + } + } else { + pkt = decapPkt + if err := r.guestDHCPConn.SendDHCP(pkt, val.peerUdp, val.peerMac); err != nil { + log.Errorf("last hop send dhcp packet to client %s %s failed: %s", val.peerUdp, val.peerMac, err) + } } } return nil, nil } -func (r *SDHCP6Relay) Relay(pkt dhcp.Packet, addr *net.UDPAddr) (dhcp.Packet, error) { - if addr.IP.Equal(r.ipv6srcAddr) { +func (r *SDHCP6Relay) Relay(pkt dhcp.Packet, cliMac net.HardwareAddr, cliAddr *net.UDPAddr) (dhcp.Packet, error) { + if cliAddr.IP.Equal(r.ipv6srcAddr) { + // come from local? ignore it return nil, nil } - log.Infof("Receive DHCP Relay Rquest FROM %s %s", addr.IP, pkt.CHAddr()) - // clean cache first - var now = time.Now().Add(time.Second * -30) - r.cache.Range(func(key, value interface{}) bool { - v := value.(*SRelayCache) - if v.timer.Before(now) { - r.cache.Delete(key) - } - return true - }) + log.Infof("Receive IPv6 DHCPRequest FROM %s, relay to upstream %s:%d", cliAddr.IP, r.destaddr, r.destport) - // cache pkt info - r.cache.Store(pkt.TransactionID(), &SRelayCache{ - mac: pkt.CHAddr(), - srcPort: addr.Port, + if pkt.Type6() == dhcp.DHCPV6_RELAY_REPL { + return nil, errors.Wrapf(errors.ErrInvalidFormat, "cannot relay a reply message") + } + + session := &SRelayCache6{ + peerMac: cliMac, + peerUdp: cliAddr, timer: time.Now(), - }) + } + hopCount := uint8(0) + if pkt.Type6() == dhcp.DHCPV6_RELAY_FORW { + hopCount = pkt.HopCount() + session.linkAddr = pkt.LinkAddr() + session.peerAddr = pkt.PeerAddr() + } else { + pkt = dhcp.EncapDHCP6RelayMsg(pkt) + } + pkt.SetHopCount(hopCount + 1) + pkt.SetLinkAddr(r.ipv6srcAddr) + pkt.SetPeerAddr(cliAddr.IP) - pkt.SetGIAddr(r.ipv6srcAddr) + tid, err := pkt.TID6() + if err != nil { + return nil, errors.Wrapf(err, "TID6") + } + cliID, err := pkt.ClientID() + if err != nil { + return nil, errors.Wrapf(err, "ClientID") + } + sessionKey := getSessionKey(tid, cliID) + r.cache.Store(sessionKey, session) - err := r.server.GetConn().SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, nil) - return nil, err + err = r.server.GetConn().SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, nil) + if err != nil { + return nil, errors.Wrapf(err, "SendDHCP to upstream %s:%d", r.destaddr, r.destport) + } + + return nil, nil } diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go index 701be2f663..d691a96a07 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go @@ -186,15 +186,23 @@ func getGuestConfig( var conf = new(dhcp.ResponseConfig) conf.InterfaceMac = serverMac + conf.VlanId = uint16(nicdesc.Vlan) - nicIp := nicdesc.Ip - v4Ip, _ := netutils.NewIPV4Addr(nicIp) - conf.ClientIP = net.ParseIP(nicdesc.Ip) + if len(nicdesc.Ip) > 0 { + nicIp := nicdesc.Ip + v4Ip, _ := netutils.NewIPV4Addr(nicIp) + conf.ClientIP = net.ParseIP(nicdesc.Ip) + + masklen := nicdesc.Masklen + conf.ServerIP = net.ParseIP(v4Ip.NetAddr(int8(masklen)).String()) + conf.SubnetMask = net.ParseIP(netutils2.Netlen2Mask(int(masklen))) + conf.BroadcastAddr = v4Ip.BroadcastAddr(int8(masklen)).ToBytes() + + if nicdesc.Gateway != "" { + conf.Gateway = net.ParseIP(nicdesc.Gateway) + } + } - masklen := nicdesc.Masklen - conf.ServerIP = net.ParseIP(v4Ip.NetAddr(int8(masklen)).String()) - conf.SubnetMask = net.ParseIP(netutils2.Netlen2Mask(int(masklen))) - conf.BroadcastAddr = v4Ip.BroadcastAddr(int8(masklen)).ToBytes() if len(guestDesc.Hostname) > 0 { conf.Hostname = guestDesc.Hostname } else { @@ -206,6 +214,10 @@ func getGuestConfig( if len(nicdesc.Ip6) > 0 { // ipv6 conf.ClientIP6 = net.ParseIP(nicdesc.Ip6) + conf.PrefixLen6 = uint8(nicdesc.Masklen6) + if nicdesc.Gateway6 != "" { + conf.Gateway6 = net.ParseIP(nicdesc.Gateway6) + } } // get main ip @@ -224,36 +236,34 @@ func getGuestConfig( route4 := make([]netutils2.SRouteInfo, 0) route6 := make([]netutils2.SRouteInfo, 0) if nicdesc.IsDefault { + conf.IsDefaultGW = true + osName := guestDesc.OsName if len(osName) == 0 { osName = "Linux" } - if nicdesc.Gateway != "" { - conf.Gateway = net.ParseIP(nicdesc.Gateway) - + if conf.Gateway != nil { if !strings.HasPrefix(strings.ToLower(osName), "win") { route4 = append(route4, netutils2.SRouteInfo{ SPrefixInfo: netutils2.SPrefixInfo{ Prefix: net.ParseIP("0.0.0.0"), PrefixLen: 0, }, - Gateway: net.ParseIP(nicdesc.Gateway), + Gateway: conf.Gateway, }) } } - if len(nicdesc.Gateway6) > 0 { - conf.Gateway6 = net.ParseIP(nicdesc.Gateway6) - conf.PrefixLen6 = uint8(nicdesc.Masklen6) - route6 = append(route6, netutils2.SRouteInfo{ - SPrefixInfo: netutils2.SPrefixInfo{ - Prefix: net.ParseIP("::"), - PrefixLen: 0, - }, - Gateway: net.ParseIP(nicdesc.Gateway6), - }) - } + //if conf.Gateway6 != nil { + /*route6 = append(route6, netutils2.SRouteInfo{ + SPrefixInfo: netutils2.SPrefixInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + }, + Gateway: conf.Gateway6, + })*/ + //} } route4, route6 = netutils2.AddNicRoutes(route4, route6, nicdesc, mainIp, mainIp6, len(guestNics)) @@ -317,7 +327,7 @@ func (s *SGuestDHCPServer) getConfig(pkt dhcp.Packet) *dhcp.ResponseConfig { if guestNic == nil { guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.ifaceDev.String(), !isCandidate) } - if guestNic != nil && !guestNic.Virtual && len(guestNic.Ip6) > 0 { + if guestNic != nil && !guestNic.Virtual { return getGuestConfig(guestDesc, guestNic, s.ifaceDev.GetHardwareAddr()) } return nil @@ -338,7 +348,7 @@ func (s *SGuestDHCPServer) serveDHCPInternal(pkt dhcp.Packet, addr *net.UDPAddr) } var conf = s.getConfig(pkt) if conf != nil { - log.Infof("Make DHCP Reply %s TO %s", conf.ClientIP, pkt.CHAddr()) + log.Infof("Make DHCP Reply %s TO %s %s", conf.ClientIP, pkt.CHAddr(), addr.String()) // Guest request ip return dhcp.MakeReplyPacket(pkt, conf) } else if s.relay != nil && s.relay.server != nil { diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go index 8ec7b94b32..d98830a9c3 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go @@ -16,12 +16,17 @@ package hostdhcp import ( "net" + "time" + + "github.com/google/gopacket/layers" "yunion.io/x/log" "yunion.io/x/pkg/errors" guestman "yunion.io/x/onecloud/pkg/hostman/guestman/types" "yunion.io/x/onecloud/pkg/util/dhcp" + "yunion.io/x/onecloud/pkg/util/hashcache" + "yunion.io/x/onecloud/pkg/util/icmp6" "yunion.io/x/onecloud/pkg/util/netutils2" ) @@ -37,6 +42,12 @@ type SGuestDHCP6Server struct { conn *dhcp.Conn ifaceDev *netutils2.SNetInterface + + raExitCh chan struct{} + raReqCh chan *sRARequest + raReqQueue []*sRARequest + + gwMacCache *hashcache.Cache } func NewGuestDHCP6Server(iface string, port int, relay *SDHCPRelayUpstream) (*SGuestDHCP6Server, error) { @@ -64,12 +75,20 @@ func NewGuestDHCP6Server(iface string, port int, relay *SDHCPRelayUpstream) (*SG } } + guestdhcp.raExitCh = make(chan struct{}) + guestdhcp.raReqCh = make(chan *sRARequest, 100) + guestdhcp.raReqQueue = make([]*sRARequest, 0) + guestdhcp.gwMacCache = hashcache.NewCache(1024, 5*time.Minute) + return guestdhcp, nil } func (s *SGuestDHCP6Server) Start(blocking bool) { log.Infof("SGuestDHCP6Server starting ...") serve := func() { + defer s.stopRAServer() + + go s.startRAServer() err := s.server.ListenAndServe(s) if err != nil { log.Errorf("DHCP serve error: %s", err) @@ -89,7 +108,7 @@ func (s *SGuestDHCP6Server) RelaySetup(addr string) error { return nil } -func (s *SGuestDHCP6Server) getConfig(cliMac net.HardwareAddr, _ dhcp.Packet) *dhcp.ResponseConfig { +func (s *SGuestDHCP6Server) getConfig(cliMac net.HardwareAddr) *dhcp.ResponseConfig { if guestman.GuestDescGetter == nil { return nil } @@ -113,20 +132,40 @@ func (s *SGuestDHCP6Server) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, return pkg, nil, err } -func (s *SGuestDHCP6Server) OnRecvICMP6(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) error { - log.Infof("SGuestDHCP6Server ServeRA from %s", cliMac.String()) - return nil +func (s *SGuestDHCP6Server) OnRecvICMP6(pkt dhcp.Packet) error { + msg, err := icmp6.DecodePacket(pkt) + if err != nil { + return errors.Wrap(err, "icmp6.DecodePacket") + } + // log.Infof("SGuestDHCP6Server recv ICMP6 message %s", msg.String()) + switch msg.ICMP6TypeCode().Type() { + case layers.ICMPv6TypeRouterSolicitation: + // Router Solicitation + return s.handleRouterSolicitation(msg.(*icmp6.SRouterSolicitation)) + case layers.ICMPv6TypeRouterAdvertisement: + // Router Advertisement + return s.handleRouterAdvertisement(msg.(*icmp6.SRouterAdvertisement)) + case layers.ICMPv6TypeNeighborSolicitation: + // Neighbor Solicitation + return s.handleNeighborSolicitation(msg.(*icmp6.SNeighborSolicitation)) + case layers.ICMPv6TypeNeighborAdvertisement: + // Neighbor Advertisement + return s.handleNeighborAdvertisement(msg.(*icmp6.SNeighborAdvertisement)) + default: + log.Errorf("SGuestDHCP6Server recv unknown ICMP6 message %s", msg.String()) + return errors.Wrapf(errors.ErrNotSupported, "unknown ICMP6 message %s", msg.String()) + } } func (s *SGuestDHCP6Server) serveDHCPInternal(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, error) { - var conf = s.getConfig(cliMac, pkt) + var conf = s.getConfig(cliMac) if conf != nil { log.Infof("Make DHCPv6 Reply %s TO %s %s", conf.ClientIP6, cliMac.String(), addr.String()) // Guest request ip return dhcp.MakeDHCP6Reply(pkt, conf) } else if s.relay != nil && s.relay.server != nil { // Host agent as dhcp relay, relay to baremetal - return s.relay.Relay(pkt, addr) + return s.relay.Relay(pkt, cliMac, addr) } return nil, nil } diff --git a/pkg/hostman/hostinfo/hostdhcp/icmp6handlers.go b/pkg/hostman/hostinfo/hostdhcp/icmp6handlers.go new file mode 100644 index 0000000000..ac055dc1c1 --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/icmp6handlers.go @@ -0,0 +1,232 @@ +// 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" +) + +type sRARequest struct { + solicitation *icmp6.SRouterSolicitation + attempts int + succAttempts int +} + +func (s *SGuestDHCP6Server) handleRouterSolicitation(msg *icmp6.SRouterSolicitation) error { + // solicitation request from guest + var conf = s.getConfig(msg.SrcMac) + if conf != nil && conf.ClientIP6 != nil && conf.Gateway6 != nil { + req := &sRARequest{ + solicitation: msg, + attempts: 1, + } + succ, err := s.sendRouterAdvertisement(msg) + if err != nil { + return errors.Wrapf(err, "sendRouterAdvertisement") + } + if succ { + req.succAttempts++ + } + s.raReqCh <- req + } + 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 := net.IP{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, gwIP[13], gwIP[14], gwIP[15]} + destMac := net.HardwareAddr{0x33, 0x33, 0xff, gwIP[13], gwIP[14], gwIP[15]} + + 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(solicitation *icmp6.SRouterSolicitation) (bool, error) { + var conf = s.getConfig(solicitation.SrcMac) + 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: solicitation.SrcMac, + DstIP: net.ParseIP("ff02::1"), + }, + CurHopLimit: 64, + IsManaged: true, + IsOther: true, + IsHomeAgent: false, + Preference: pref, + RouterLifetime: 9000, + ReachableTime: 0, + RetransTimer: 0, + MTU: uint32(conf.MTU), + PrefixInfo: []icmp6.SPrefixInfoOption{ + { + IsOnlink: true, + IsAutoconf: true, + Prefix: ipnet.IP, + PrefixLen: conf.PrefixLen6, + ValidLifetime: 4500, + PreferredLifetime: 2250, + }, + }, + } + 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: 4500, + PreferredLifetime: 2250, + }) + } else if route.Gateway.String() != conf.Gateway6.String() { + // routes forwarded by router + ra.RouteInfo = append(ra.RouteInfo, icmp6.SRouteInfoOption{ + RouteLifetime: 9000, + Prefix: route.Prefix, + PrefixLen: route.PrefixLen, + Preference: icmp6.PreferenceMedium, + }) + } + } + + 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, solicitation.SrcMac) + 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) startRAServer() { + // a tiny RA server + stop := false + for !stop { + select { + case <-s.raExitCh: + stop = true + case raReq := <-s.raReqCh: + // handle RA request + s.raReqQueue = append(s.raReqQueue, raReq) + case <-time.After(time.Second * time.Duration(options.HostOptions.Dhcp6RouterAdvertisementIntervalSecs)): + // send RA + // log.Infof("timeout, to announce RA %d requests", len(s.raReqQueue)) + if len(s.raReqQueue) > 0 { + raReqQueue := s.raReqQueue + s.raReqQueue = make([]*sRARequest, 0) + + for i := range raReqQueue { + raReq := raReqQueue[i] + raReq.attempts++ + succ, err := s.sendRouterAdvertisement(raReq.solicitation) + if err != nil { + log.Errorf("sendRouterAdvertisement error: %v", err) + continue + } + if succ { + raReq.succAttempts++ + } + if raReq.succAttempts < options.HostOptions.Dhcp6RouterAdvertisementAttempts && raReq.attempts < 2*options.HostOptions.Dhcp6RouterAdvertisementAttempts { + s.raReqCh <- raReq + } + } + } + } + } +} diff --git a/pkg/hostman/options/options.go b/pkg/hostman/options/options.go index 6b291f05dd..ca7f04f055 100644 --- a/pkg/hostman/options/options.go +++ b/pkg/hostman/options/options.go @@ -48,6 +48,9 @@ type SHostBaseOptions struct { DhcpLeaseTime int `default:"100663296" help:"DHCP lease time in seconds"` DhcpRenewalTime int `default:"67108864" help:"DHCP renewal time in seconds"` + Dhcp6RouterAdvertisementIntervalSecs int `default:"3" help:"DHCPv6 router advertisement interval in seconds, default 3 seconds"` + Dhcp6RouterAdvertisementAttempts int `default:"3" help:"DHCPv6 router advertisement attempts, default 3 attempts"` + Ext4LargefileSizeGb int `default:"4096" help:"Use largefile options when the ext4 fs greater than this size"` Ext4HugefileSizeGb int `default:"512" help:"Use huge options when the ext4 fs greater than this size"` diff --git a/pkg/util/dhcp/conn.go b/pkg/util/dhcp/conn.go index d9a117de8e..c909f188f0 100644 --- a/pkg/util/dhcp/conn.go +++ b/pkg/util/dhcp/conn.go @@ -196,16 +196,8 @@ func (c *Conn) RecvDHCP6() (Packet, *net.UDPAddr, net.HardwareAddr, error) { // which configuration is desired if acting as a client. func (c *Conn) SendDHCP(pkt Packet, addr *net.UDPAddr, mac net.HardwareAddr) error { b := pkt.Marshal() - - switch len(addr.IP) { - case net.IPv4len: - if addr.IP.Equal(net.IPv4zero) || pkt.txType() == txBroadcast { - addr = &net.UDPAddr{IP: net.IPv4bcast, Port: addr.Port} - } - case net.IPv6len: - default: - return errors.Wrapf(errors.ErrNotSupported, "unsupported length of IP address length %d", len(addr.IP)) - + if addr.IP.Equal(net.IPv4zero) || pkt.txType() == txBroadcast { + addr = &net.UDPAddr{IP: net.IPv4bcast, Port: addr.Port} } return c.conn.Send(b, addr, mac) } diff --git a/pkg/util/dhcp/conn_linux.go b/pkg/util/dhcp/conn_linux.go index 54b6e59843..1b13d7953d 100644 --- a/pkg/util/dhcp/conn_linux.go +++ b/pkg/util/dhcp/conn_linux.go @@ -93,7 +93,6 @@ func (s *rawSocketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, if err != nil { return nil, nil, nil, errors.Wrap(err, "Read from errror") } - log.Debugf("rawSocketConn Recv %d bytes", n) b = b[:n] diff --git a/pkg/util/dhcp/conn_linux6.go b/pkg/util/dhcp/conn_linux6.go index 92b21ea7ce..d260a3ce5c 100644 --- a/pkg/util/dhcp/conn_linux6.go +++ b/pkg/util/dhcp/conn_linux6.go @@ -78,7 +78,6 @@ func (s *rawSocketConn) Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, if err != nil { return nil, nil, nil, errors.Wrap(err, "Read from errror") } - log.Debugf("rawSocketConn Recv6 %d bytes from %s", n, addr.String()) b = b[:n] @@ -106,19 +105,7 @@ func (s *rawSocketConn) Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, icmpLayer := p.Layer(layers.LayerTypeICMPv6) if icmpLayer != nil { - log.Infof("rawSocketConn Recv6 icmpLayer %s", icmpLayer.LayerType()) - /*raLayer := p.Layer(layers.LayerTypeICMPv6RouterSolicitation) - if raLayer != nil { - // icmp6, ra solitation - raPkt := raLayer.(*layers.ICMPv6RouterSolicitation) - sbf := gopacket.NewSerializeBuffer() - if err := raPkt.SerializeTo(sbf, gopacket.SerializeOptions{}); err != nil { - return nil, nil, nil, 0, errors.Wrap(err, "Serialize ICMPv6 ra solitation packet error") - } - return sbf.Bytes(), &net.UDPAddr{IP: srcIp, Port: icmpRAFakePort}, srcMac, 0, nil - } else { - return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "expect an ICMPv6 RA solitation packet") - }*/ + // receive icmp packet return b, &net.UDPAddr{IP: srcIp, Port: icmpRAFakePort}, srcMac, nil } diff --git a/pkg/util/dhcp/helpers.go b/pkg/util/dhcp/helpers.go index f68ec32c2d..4f5d7c20de 100644 --- a/pkg/util/dhcp/helpers.go +++ b/pkg/util/dhcp/helpers.go @@ -36,6 +36,7 @@ const ( // http://www.networksorcery.com/enp/rfc/rfc2132.txt type ResponseConfig struct { InterfaceMac net.HardwareAddr + VlanId uint16 OsName string ServerIP net.IP // OptServerIdentifier 54 @@ -59,6 +60,8 @@ type ResponseConfig struct { NTPServers6 []net.IP Routes6 []netutils2.SRouteInfo + IsDefaultGW bool + // Relay Info https://datatracker.ietf.org/doc/html/rfc3046 RelayInfo []byte @@ -78,6 +81,12 @@ func GetOptUint16(val uint16) []byte { return opts } +func GetOptUint32(val uint32) []byte { + opts := []byte{0, 0, 0, 0} + binary.BigEndian.PutUint32(opts, val) + return opts +} + func GetOptIP(ip net.IP) []byte { return []byte(ip.To4()) } @@ -149,33 +158,40 @@ func makeDHCPReplyPacket(req Packet, conf *ResponseConfig, msgType MessageType) opts := make([]Option, 0) if conf.SubnetMask != nil { - opts = append(opts, Option{OptionSubnetMask, GetOptIP(conf.SubnetMask)}) + opts = append(opts, Option{Code: OptionSubnetMask, Value: GetOptIP(conf.SubnetMask)}) } if conf.Gateway != nil { - opts = append(opts, Option{OptionRouter, GetOptIP(conf.Gateway)}) + opts = append(opts, Option{Code: OptionRouter, Value: GetOptIP(conf.Gateway)}) } if conf.Domain != "" { - opts = append(opts, Option{OptionDomainName, []byte(conf.Domain)}) + opts = append(opts, Option{Code: OptionDomainName, Value: []byte(conf.Domain)}) } if conf.BroadcastAddr != nil { - opts = append(opts, Option{OptionBroadcastAddress, GetOptIP(conf.BroadcastAddr)}) + opts = append(opts, Option{Code: OptionBroadcastAddress, Value: GetOptIP(conf.BroadcastAddr)}) } if conf.Hostname != "" { - opts = append(opts, Option{OptionHostName, []byte(conf.GetHostname())}) + opts = append(opts, Option{Code: OptionHostName, Value: []byte(conf.GetHostname())}) } if len(conf.DNSServers) > 0 { - opts = append(opts, Option{OptionDomainNameServer, GetOptIPs(conf.DNSServers)}) + opts = append(opts, Option{Code: OptionDomainNameServer, Value: GetOptIPs(conf.DNSServers)}) } if len(conf.NTPServers) > 0 { - opts = append(opts, Option{OptionNetworkTimeProtocolServers, GetOptIPs(conf.NTPServers)}) + opts = append(opts, Option{Code: OptionNetworkTimeProtocolServers, Value: GetOptIPs(conf.NTPServers)}) } if conf.MTU > 0 { - opts = append(opts, Option{OptionInterfaceMTU, GetOptUint16(conf.MTU)}) + opts = append(opts, Option{Code: OptionInterfaceMTU, Value: GetOptUint16(conf.MTU)}) } if conf.RelayInfo != nil { - opts = append(opts, Option{OptionRelayAgentInformation, conf.RelayInfo}) + opts = append(opts, Option{Code: OptionRelayAgentInformation, Value: conf.RelayInfo}) } - resp := ReplyPacket(req, msgType, conf.ServerIP, conf.ClientIP, conf.LeaseTime, opts) + var clientIP net.IP + if conf.ClientIP != nil { + clientIP = conf.ClientIP + } else { + clientIP = net.ParseIP("0.0.0.0") + opts = append(opts, Option{Code: OptionIPv6Only, Value: GetOptUint32(60)}) + } + resp := ReplyPacket(req, msgType, conf.ServerIP, clientIP, conf.LeaseTime, opts) if conf.BootServer != "" { //resp.Options[OptOverload] = []byte{3} resp.SetSIAddr(net.ParseIP(conf.BootServer)) diff --git a/pkg/util/dhcp/interface.go b/pkg/util/dhcp/interface.go index 1678a1b8b5..63bd32a3a2 100644 --- a/pkg/util/dhcp/interface.go +++ b/pkg/util/dhcp/interface.go @@ -30,5 +30,5 @@ type SendPacket struct { type DHCP6Handler interface { DHCPHandler - OnRecvICMP6(pkt Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) error + OnRecvICMP6(pkt Packet) error } diff --git a/pkg/util/dhcp/packet.go b/pkg/util/dhcp/packet.go index 074ebc8b0a..7fc2cfb760 100644 --- a/pkg/util/dhcp/packet.go +++ b/pkg/util/dhcp/packet.go @@ -469,6 +469,9 @@ const ( OptionTZPOSIXString OptionCode = 100 OptionTZDatabaseString OptionCode = 101 + // https://datatracker.ietf.org/doc/html/rfc8925 + OptionIPv6Only OptionCode = 108 + OptionDomainSearch OptionCode = 119 OptionClasslessRouteFormat OptionCode = 121 diff --git a/pkg/util/dhcp/packet6.go b/pkg/util/dhcp/packet6.go index 450511b026..1f29b881ab 100644 --- a/pkg/util/dhcp/packet6.go +++ b/pkg/util/dhcp/packet6.go @@ -17,6 +17,7 @@ package dhcp import ( "encoding/binary" "net" + "strings" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -116,14 +117,111 @@ const ( OPTION_NEW_TZDB_TIMEZONE OptionCode6 = 42 ) +func (opt OptionCode6) String() string { + switch opt { + case DHCPV6_OPTION_CLIENTID: + return "DHCPV6_OPTION_CLIENTID" + case DHCPV6_OPTION_SERVERID: + return "DHCPV6_OPTION_SERVERID" + case DHCPV6_OPTION_IA_NA: + return "DHCPV6_OPTION_IA_NA" + case DHCPV6_OPTION_IA_TA: + return "DHCPV6_OPTION_IA_TA" + case DHCPV6_OPTION_IAADDR: + return "DHCPV6_OPTION_IAADDR" + case DHCPV6_OPTION_ORO: + return "DHCPV6_OPTION_ORO" + case DHCPV6_OPTION_PREFERENCE: + return "DHCPV6_OPTION_PREFERENCE" + case DHCPV6_OPTION_ELAPSED_TIME: + return "DHCPV6_OPTION_ELAPSED_TIME" + case DHCPV6_OPTION_RELAY_MSG: + return "DHCPV6_OPTION_RELAY_MSG" + case DHCPV6_OPTION_AUTH: + return "DHCPV6_OPTION_AUTH" + case DHCPV6_OPTION_UNICAST: + return "DHCPV6_OPTION_UNICAST" + case DHCPV6_OPTION_STATUS_CODE: + return "DHCPV6_OPTION_STATUS_CODE" + case DHCPV6_OPTION_RAPID_COMMIT: + return "DHCPV6_OPTION_RAPID_COMMIT" + case DHCPV6_OPTION_USER_CLASS: + return "DHCPV6_OPTION_USER_CLASS" + case DHCPV6_OPTION_VENDOR_CLASS: + return "DHCPV6_OPTION_VENDOR_CLASS" + case DHCPV6_OPTION_VENDOR_OPTS: + return "DHCPV6_OPTION_VENDOR_OPTS" + case DHCPV6_OPTION_INTERFACE_ID: + return "DHCPV6_OPTION_INTERFACE_ID" + case DHCPV6_OPTION_RECONF_MSG: + return "DHCPV6_OPTION_RECONF_MSG" + case DHCPV6_OPTION_RECONF_ACCEPT: + return "DHCPV6_OPTION_RECONF_ACCEPT" + case DHCPV6_OPTION_IA_PD: + return "DHCPV6_OPTION_IA_PD" + case DHCPV6_OPTION_IAPREFIX: + return "DHCPV6_OPTION_IAPREFIX" + case DHCPV6_OPTION_INFORMATION_REFRESH_TIME: + return "DHCPV6_OPTION_INFORMATION_REFRESH_TIME" + case DHCPV6_OPTION_SOL_MAX_RT: + return "DHCPV6_OPTION_SOL_MAX_RT" + case DHCPV6_OPTION_INF_MAX_RT: + return "DHCPV6_OPTION_INF_MAX_RT" + case OPTION_DNS_SERVERS: + return "OPTION_DNS_SERVERS" + case OPTION_DOMAIN_LIST: + return "OPTION_DOMAIN_LIST" + case OPTION_SNTP_SERVERS: + return "OPTION_SNTP_SERVERS" + case OPTION_NTP_SERVERS6: + return "OPTION_NTP_SERVERS6" + case OPTION_NEW_POSIX_TIMEZONE: + return "OPTION_NEW_POSIX_TIMEZONE" + case OPTION_NEW_TZDB_TIMEZONE: + return "OPTION_NEW_TZDB_TIMEZONE" + } + return "DHCPV6_OPTION_UNKNOWN" +} + // DHCPv6 message type func (p Packet) Type6() MessageType { return MessageType(p[0]) } // DHCPv6 transaction ID -func (p Packet) TID() uint32 { - return binary.BigEndian.Uint32([]byte{0, p[1], p[2], p[3]}) +func (p Packet) TID6() (uint32, error) { + if !p.IsRelayMsg() { + if len(p) < 4 { + return 0, errors.Wrapf(errors.ErrInvalidFormat, "packet too short") + } + return binary.BigEndian.Uint32([]byte{0, p[1], p[2], p[3]}), nil + } + options := p.GetOption6s() + for _, o := range options { + if o.Code == DHCPV6_OPTION_RELAY_MSG { + return Packet(o.Value).TID6() + } + } + return 0, errors.Wrapf(errors.ErrInvalidFormat, "not a valid relay message") +} + +func (p Packet) ClientID() ([]byte, error) { + if !p.IsRelayMsg() { + options := p.GetOption6s() + for _, o := range options { + if o.Code == DHCPV6_OPTION_CLIENTID { + return o.Value, nil + } + } + return nil, errors.Wrapf(errors.ErrInvalidFormat, "clientID option not found") + } + options := p.GetOption6s() + for _, o := range options { + if o.Code == DHCPV6_OPTION_RELAY_MSG { + return Packet(o.Value).ClientID() + } + } + return nil, errors.Wrapf(errors.ErrInvalidFormat, "not a valid relay message") } // DHCPv6 hop Count for relay message @@ -176,6 +274,12 @@ func NewPacket6(opCode MessageType, tid uint32) Packet { return p } +func NewRelayPacket6() Packet { + p := make(Packet, 34) + p.SetType6(DHCPV6_RELAY_FORW) + return p +} + type Option6 struct { Code OptionCode6 Value []byte @@ -227,15 +331,6 @@ func (p Packet) GetOption6s() []Option6 { return options } -// Creates a request packet that a Client would send to a server. -/*func RequestPacket6(mt MessageType, tid uint32, options []Option6) Packet { - p := NewPacket6(mt, tid) - for _, o := range options { - p.AddOption6(o) - } - return p -}*/ - func MakeDHCP6Reply(pkt Packet, conf *ResponseConfig) (Packet, error) { var msgType MessageType pktType := pkt.Type6() @@ -256,7 +351,7 @@ func MakeDHCP6Reply(pkt Packet, conf *ResponseConfig) (Packet, error) { return nil, errors.Wrapf(errors.ErrNotSupported, "unsupported message type %d", pktType) } - return makeDHCPReplyPacket6(pkt, conf, msgType), nil + return makeDHCPReplyPacket6(pkt, conf, msgType) } const ( @@ -282,14 +377,13 @@ func makeIAAddr(ip net.IP, preferLT, validLT uint32, opts []Option6) []byte { } func responseIANA(buf []byte, opts []Option6) []byte { - log.Debugf("responseIANA buf %d", len(buf)) if len(buf) > 12 { buf = buf[:12] } - 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) + // 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) buf = append(buf, optionsToBytes(opts)...) return buf } @@ -306,10 +400,21 @@ func makeIPv6s(ips []net.IP) []byte { return buf } -func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType) Packet { - log.Debugf("makeDHCPReplyPacket6 msgType %d tid %x", msgType, pkt.TID()) +func decodeRequestOptions(value []byte) []OptionCode6 { + var optCodes []OptionCode6 + for i := 0; i < len(value); i += 2 { + optCodes = append(optCodes, OptionCode6(binary.BigEndian.Uint16(value[i:i+2]))) + } + return optCodes +} - resp := NewPacket6(msgType, pkt.TID()) +func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType) (Packet, error) { + tid, err := pkt.TID6() + if err != nil { + return nil, errors.Wrapf(err, "TID6") + } + + resp := NewPacket6(msgType, tid) originOpts := pkt.GetOption6s() getOption := func(code OptionCode6) Option6 { for _, o := range originOpts { @@ -320,6 +425,16 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType) return Option6{} } + reqInfo := getOption(DHCPV6_OPTION_ORO) + if len(reqInfo.Value) > 0 { + reqOpts := decodeRequestOptions(reqInfo.Value) + reqOptsStr := make([]string, len(reqOpts)) + for i, opt := range reqOpts { + reqOptsStr[i] = opt.String() + } + log.Debugf("request options: %s", strings.Join(reqOptsStr, ",")) + } + options := make([]Option6, 0) // copy clientID @@ -362,5 +477,25 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType) resp = append(resp, optionsToBytes(options)...) - return resp + return resp, nil +} + +func EncapDHCP6RelayMsg(pkt Packet) Packet { + relayMsg := NewRelayPacket6() + relayOpt := Option6{ + Code: DHCPV6_OPTION_RELAY_MSG, + Value: pkt, + } + relayMsg = append(relayMsg, optionsToBytes([]Option6{relayOpt})...) + return relayMsg +} + +func DecapDHCP6RelayMsg(pkt Packet) (Packet, error) { + options := pkt.GetOption6s() + for _, o := range options { + if o.Code == DHCPV6_OPTION_RELAY_MSG { + return Packet(o.Value), nil + } + } + return nil, errors.Wrapf(errors.ErrInvalidFormat, "relay message not found") } diff --git a/pkg/util/dhcp/server.go b/pkg/util/dhcp/server.go index 1544279a5b..2b6bf64638 100644 --- a/pkg/util/dhcp/server.go +++ b/pkg/util/dhcp/server.go @@ -286,7 +286,7 @@ func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error { if addr.Port == icmpRAFakePort { // receive a RA solication - handler.OnRecvICMP6(pkt, mac, addr) + handler.OnRecvICMP6(pkt) return } diff --git a/pkg/util/icmp6/messages.go b/pkg/util/icmp6/messages.go index 7e3c589074..054ced49b6 100644 --- a/pkg/util/icmp6/messages.go +++ b/pkg/util/icmp6/messages.go @@ -49,13 +49,20 @@ type SBaseICMP6Message struct { SrcIP net.IP DstMac net.HardwareAddr DstIP net.IP + Vlan uint16 } func (msg SBaseICMP6Message) String() string { - return fmt.Sprintf("%s[%s]->%s[%s]", msg.SrcIP.String(), msg.SrcMac.String(), msg.DstIP.String(), msg.DstMac.String()) + var vlanStr string + if msg.Vlan > 1 { + vlanStr += fmt.Sprintf("(vlan %d)", msg.Vlan) + } + return fmt.Sprintf("%s[%s]->%s[%s]%s", msg.SrcIP.String(), msg.SrcMac.String(), msg.DstIP.String(), msg.DstMac.String(), vlanStr) } type IICMP6Message interface { + fmt.Stringer + Payload() gopacket.SerializableLayer ICMP6TypeCode() layers.ICMPv6TypeCode @@ -63,6 +70,8 @@ type IICMP6Message interface { SourceIP() net.IP DestinationMac() net.HardwareAddr DestinationIP() net.IP + + VlanId() uint16 } func (msg SBaseICMP6Message) SourceIP() net.IP { @@ -81,6 +90,10 @@ func (msg SBaseICMP6Message) DestinationMac() net.HardwareAddr { return msg.DstMac } +func (msg SBaseICMP6Message) VlanId() uint16 { + return msg.Vlan +} + type SRouterSolicitation struct { SBaseICMP6Message } @@ -179,6 +192,10 @@ func (msg *SNeighborSolicitation) Unmarshal(data *layers.ICMPv6NeighborSolicitat return nil } +func (msg SNeighborSolicitation) String() string { + return fmt.Sprintf("NeighborSolicitation %s target %s", msg.SBaseICMP6Message.String(), msg.TargetAddr.String()) +} + func (msg SNeighborAdvertisement) ICMP6TypeCode() layers.ICMPv6TypeCode { return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeNeighborAdvertisement, 0) } @@ -211,6 +228,10 @@ func (msg *SNeighborAdvertisement) Unmarshal(data *layers.ICMPv6NeighborAdvertis return nil } +func (msg SNeighborAdvertisement) String() string { + return fmt.Sprintf("NeighborAdvertisement %s target %s", msg.SBaseICMP6Message.String(), msg.TargetAddr.String()) +} + func (msg SRouterSolicitation) ICMP6TypeCode() layers.ICMPv6TypeCode { return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterSolicitation, 0) } @@ -232,6 +253,10 @@ func (msg *SRouterSolicitation) Unmarshal(data *layers.ICMPv6RouterSolicitation) return nil } +func (msg SRouterSolicitation) String() string { + return fmt.Sprintf("RouterSolicitation %s", msg.SBaseICMP6Message.String()) +} + func (msg SRouterAdvertisement) ICMP6TypeCode() layers.ICMPv6TypeCode { return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterAdvertisement, 0) } @@ -265,16 +290,8 @@ func (msg SRouterAdvertisement) Payload() gopacket.SerializableLayer { pkt.ReachableTime = msg.ReachableTime pkt.RetransTimer = msg.RetransTimer - pkt.Options = layers.ICMPv6Options{ - layers.ICMPv6Option{ - Type: layers.ICMPv6OptSourceAddress, - Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(), - }, - layers.ICMPv6Option{ - Type: layers.ICMPv6OptMTU, - Data: NewIcmpV6OptMtu(msg.MTU).Bytes(), - }, - } + pkt.Options = layers.ICMPv6Options{} + for i := range msg.PrefixInfo { pref := msg.PrefixInfo[i] pkt.Options = append(pkt.Options, layers.ICMPv6Option{ @@ -289,6 +306,16 @@ func (msg SRouterAdvertisement) Payload() gopacket.SerializableLayer { Data: NewIcmpv6OptRouteInfo(rt).Bytes(), }) } + if msg.MTU > 0 { + pkt.Options = append(pkt.Options, layers.ICMPv6Option{ + Type: layers.ICMPv6OptMTU, + Data: NewIcmpV6OptMtu(msg.MTU).Bytes(), + }) + } + pkt.Options = append(pkt.Options, layers.ICMPv6Option{ + Type: layers.ICMPv6OptSourceAddress, + Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(), + }) return &pkt } @@ -327,42 +354,58 @@ func (msg *SRouterAdvertisement) Unmarshal(data *layers.ICMPv6RouterAdvertisemen func (msg SRouterAdvertisement) String() string { return fmt.Sprintf(`RouterAdvertisement %s -CurHopLimit: %d, IsManaged: %t, IsOther: %t, IsHomeAgent: %t, Preference: %s, RouterLifetime: %d, ReachableTime: %d, RetransTimer: %d -MTU: %d -PrefixInfo: %v -RouteInfo: %v)`, + CurHopLimit: %d, IsManaged: %t, IsOther: %t, IsHomeAgent: %t, Preference: %s, RouterLifetime: %d, ReachableTime: %d, RetransTimer: %d + MTU: %d + PrefixInfo: %v + RouteInfo: %v)`, msg.SBaseICMP6Message.String(), msg.CurHopLimit, msg.IsManaged, msg.IsOther, msg.IsHomeAgent, msg.Preference.String(), msg.RouterLifetime, msg.ReachableTime, msg.RetransTimer, msg.MTU, msg.PrefixInfo, msg.RouteInfo) } func EncodePacket(msg IICMP6Message) ([]byte, error) { + var pktLayers []gopacket.SerializableLayer + var eth = &layers.Ethernet{ EthernetType: layers.EthernetTypeIPv6, SrcMAC: msg.SourceMac(), DstMAC: msg.DestinationMac(), } + pktLayers = append(pktLayers, eth) + + if msg.VlanId() > 1 { + eth.EthernetType = layers.EthernetTypeDot1Q + dot1Q := &layers.Dot1Q{ + VLANIdentifier: msg.VlanId(), + Priority: 6, + Type: layers.EthernetTypeIPv6, + } + pktLayers = append(pktLayers, dot1Q) + } var ip = &layers.IPv6{ - Version: 6, - HopLimit: 64, - SrcIP: msg.SourceIP(), - DstIP: msg.DestinationIP(), - NextHeader: layers.IPProtocolICMPv6, + Version: 6, + HopLimit: 0xff, + TrafficClass: 0xc0, + SrcIP: msg.SourceIP(), + DstIP: msg.DestinationIP(), + NextHeader: layers.IPProtocolICMPv6, } + pktLayers = append(pktLayers, ip) var icmp6 = &layers.ICMPv6{ TypeCode: msg.ICMP6TypeCode(), } icmp6.SetNetworkLayerForChecksum(ip) + pktLayers = append(pktLayers, icmp6) - var payload = msg.Payload() + pktLayers = append(pktLayers, msg.Payload()) var ( buf = gopacket.NewSerializeBuffer() opts = gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true} ) - if err := gopacket.SerializeLayers(buf, opts, eth, ip, icmp6, payload); err != nil { + if err := gopacket.SerializeLayers(buf, opts, pktLayers...); err != nil { return nil, errors.Wrap(err, "SerializeLayers error") } @@ -388,6 +431,15 @@ func DecodePacket(data []byte) (IICMP6Message, error) { } } + { + // optional vlan layer + dot1qLayer := packet.Layer(layers.LayerTypeDot1Q) + if dot1qLayer != nil { + dot1q := dot1qLayer.(*layers.Dot1Q) + baseMsg.Vlan = dot1q.VLANIdentifier + } + } + { ipLayer := packet.Layer(layers.LayerTypeIPv6) if ipLayer != nil { @@ -415,6 +467,10 @@ func DecodePacket(data []byte) (IICMP6Message, error) { msg := &SRouterSolicitation{ SBaseICMP6Message: baseMsg, } + err := msg.Unmarshal(payload.(*layers.ICMPv6RouterSolicitation)) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal Router Solicitation packet") + } return msg, nil } else { return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Router Solicitation packet") @@ -425,6 +481,10 @@ func DecodePacket(data []byte) (IICMP6Message, error) { msg := &SRouterAdvertisement{ SBaseICMP6Message: baseMsg, } + err := msg.Unmarshal(payload.(*layers.ICMPv6RouterAdvertisement)) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal Router Advertisement packet") + } return msg, nil } else { return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Router Advertisement packet") @@ -435,6 +495,10 @@ func DecodePacket(data []byte) (IICMP6Message, error) { msg := &SNeighborSolicitation{ SBaseICMP6Message: baseMsg, } + err := msg.Unmarshal(payload.(*layers.ICMPv6NeighborSolicitation)) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal Neighbor Solicitation packet") + } return msg, nil } else { return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Neighbor Solicitation packet") @@ -445,6 +509,10 @@ func DecodePacket(data []byte) (IICMP6Message, error) { msg := &SNeighborAdvertisement{ SBaseICMP6Message: baseMsg, } + err := msg.Unmarshal(payload.(*layers.ICMPv6NeighborAdvertisement)) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal Neighbor Advertisement packet") + } return msg, nil } else { return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Neighbor Advertisement packet") diff --git a/pkg/util/netplan/netplan.go b/pkg/util/netplan/netplan.go index 32e57af3f1..8d489decd3 100644 --- a/pkg/util/netplan/netplan.go +++ b/pkg/util/netplan/netplan.go @@ -65,6 +65,7 @@ func NewEthernetConfigMatchMac(macAddr string) *EthernetConfigMatch { type EthernetConfig struct { DHCP4 bool `json:"dhcp4,omitfalse"` DHCP6 bool `json:"dhcp6,omitfalse"` + AcceptRa bool `json:"accept-ra,omitfalse"` Addresses []string `json:"addresses"` Match *EthernetConfigMatch `json:"match"` MacAddress string `json:"macaddress"` @@ -206,6 +207,7 @@ func NewDHCP4EthernetConfig() *EthernetConfig { func (c *EthernetConfig) EnableDHCP6() { c.DHCP6 = true + c.AcceptRa = true } func NewStaticEthernetConfig( diff --git a/pkg/util/netplan/netplan_test.go b/pkg/util/netplan/netplan_test.go index c352c45724..57a13879e7 100644 --- a/pkg/util/netplan/netplan_test.go +++ b/pkg/util/netplan/netplan_test.go @@ -27,7 +27,7 @@ func TestNewEthernetConfig(t *testing.T) { assert.YAMLEq("dhcp4: true", c.YAMLString()) c.EnableDHCP6() - assert.YAMLEq("dhcp4: true\ndhcp6: true", c.YAMLString()) + assert.YAMLEq("dhcp4: true\ndhcp6: true\naccept-ra: true", c.YAMLString()) } func TestNewBondMode4(t *testing.T) {