diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index 5f77b4460e..b24cd33bd1 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -1040,20 +1040,22 @@ func (b *SBaremetalInstance) GetIPMINicIPAddr() string { func (b *SBaremetalInstance) GetDHCPConfig(cliMac net.HardwareAddr) (*dhcp.ResponseConfig, error) { var nic *types.SNic var hostname string + var osName string if b.GetServer() != nil && (b.GetTask() == nil || !b.GetTask().NeedPXEBoot()) { nic = b.GetServer().GetNicByMac(cliMac) - hostname = b.GetServer().GetName() + hostname = b.GetServer().GetHostName() + osName = b.GetServer().GetOsName() } else { nic = b.GetNicByMac(cliMac) } if nic == nil { return nil, fmt.Errorf("GetNicDHCPConfig no nic found by mac: %s", cliMac) } - return b.getDHCPConfig(nic, hostname, false, 0) + return b.getDHCPConfig(nic, hostname, false, 0, osName) } func (b *SBaremetalInstance) GetPXEDHCPConfig(arch uint16) (*dhcp.ResponseConfig, error) { - return b.getDHCPConfig(b.GetAdminNic(), "", true, arch) + return b.getDHCPConfig(b.GetAdminNic(), "", true, arch, "Linux") } func (b *SBaremetalInstance) getDHCPConfig( @@ -1061,10 +1063,14 @@ func (b *SBaremetalInstance) getDHCPConfig( hostName string, isPxe bool, arch uint16, + osName string, ) (*dhcp.ResponseConfig, error) { if hostName == "" { hostName = b.GetName() } + if osName == "" { + osName = "Linux" + } serverIP, err := b.manager.Agent.GetDHCPServerIP() if err != nil { return nil, err @@ -1078,7 +1084,7 @@ func (b *SBaremetalInstance) getDHCPConfig( return nil, errors.Errorf("Baremetal %s not need UEFI PXE boot", b.GetName()) } } - return GetNicDHCPConfig(nic, serverIP.String(), hostName, isPxe, arch) + return GetNicDHCPConfig(nic, serverIP, b.manager.Agent.ListenInterface.HardwareAddr, hostName, isPxe, arch, osName) } func (b *SBaremetalInstance) GetNotifyUrl() string { @@ -2550,6 +2556,22 @@ func (server *SBaremetalServer) GetName() string { return id } +func (server *SBaremetalServer) GetHostName() string { + hostname, err := server.desc.GetString("hostname") + if err != nil { + log.Errorf("Get hostname from desc %s error: %v", server.desc.String(), err) + } + return hostname +} + +func (server *SBaremetalServer) GetOsName() string { + osName, err := server.desc.GetString("os_name") + if err != nil { + log.Errorf("Get os name from desc %s error: %v", server.desc.String(), err) + } + return osName +} + func (server *SBaremetalServer) SaveDesc(desc jsonutils.JSONObject) error { if desc != nil { server.desc = desc.(*jsonutils.JSONDict) diff --git a/pkg/baremetal/nic.go b/pkg/baremetal/nic.go index fc6a5f732c..9dd7af2534 100644 --- a/pkg/baremetal/nic.go +++ b/pkg/baremetal/nic.go @@ -15,14 +15,15 @@ package baremetal import ( - "fmt" "net" "os" "path/filepath" "strings" "time" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/util/regutils" o "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/cloudcommon/types" @@ -31,68 +32,127 @@ import ( func GetNicDHCPConfig( n *types.SNic, - serverIP string, + serverIP net.IP, + serverMac net.HardwareAddr, hostName string, isPxe bool, arch uint16, + osName string, ) (*dhcp.ResponseConfig, error) { if n == nil { - return nil, fmt.Errorf("Nic is nil") + return nil, errors.Wrap(errors.ErrEmpty, "Nic is nil") } - if n.IpAddr == "" { - return nil, fmt.Errorf("Nic no ip address") - } - ipAddr, err := netutils.NewIPV4Addr(n.IpAddr) - if err != nil { - return nil, fmt.Errorf("Parse IP address error: %q", n.IpAddr) + if n.IpAddr == "" && n.Ip6Addr == "" { + return nil, errors.Wrap(errors.ErrEmpty, "Nic no ip or ip6 address") } - subnetMask := net.ParseIP(netutils.Masklen2Mask(n.Masklen).String()) - - routes := make([][]string, 0) + routes4 := make([]dhcp.SRouteInfo, 0) + routes6 := make([]dhcp.SRouteInfo, 0) for _, route := range n.Routes { - routes = append(routes, []string{route[0], route[1]}) + routeInfo, err := dhcp.ParseRouteInfo([]string{route[0], route[1]}) + if err != nil { + return nil, errors.Wrapf(err, "Parse route %s error: %q", route, err) + } + if regutils.MatchCIDR6(route[0]) { + routes6 = append(routes6, *routeInfo) + } else { + routes4 = append(routes4, *routeInfo) + } } - parseIPs := func(ips string) []net.IP { - ret := make([]net.IP, 0) - iplist := strings.Split(ips, ",") - for _, ip := range iplist { - ret = append(ret, net.ParseIP(ip)) + if n.IsDefault == nil || *n.IsDefault { + if n.Gateway != "" && !strings.HasPrefix(strings.ToLower(osName), "win") { + routes4 = append(routes4, dhcp.SRouteInfo{ + Prefix: net.ParseIP("0.0.0.0"), + PrefixLen: 0, + Gateway: net.ParseIP(n.Gateway), + }) } - return ret - } - - parseDomains := func(domains string) []net.IP { - ret := make([]net.IP, 0) - domainlist := strings.Split(domains, ",") - for _, domain := range domainlist { - addrs, _ := net.LookupHost(domain) - for _, addr := range addrs { - ret = append(ret, net.ParseIP(addr)) - } + if n.Gateway6 != "" { + routes6 = append(routes6, dhcp.SRouteInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + Gateway: net.ParseIP(n.Gateway6), + }) } - return ret } conf := &dhcp.ResponseConfig{ - ServerIP: net.ParseIP(serverIP), - ClientIP: net.ParseIP(ipAddr.String()), - Gateway: net.ParseIP(n.Gateway), - SubnetMask: subnetMask, - BroadcastAddr: net.ParseIP(ipAddr.BroadcastAddr(n.Masklen).String()), - DNSServers: parseIPs(n.Dns), - NTPServers: parseDomains(n.Ntp), - Domain: n.Domain, - OsName: "Linux", - Hostname: hostName, - Routes: routes, - LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, - RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, + InterfaceMac: serverMac, + + ServerIP: serverIP, + Domain: n.Domain, + OsName: osName, + Hostname: strings.ToLower(hostName), + Routes: routes4, + Routes6: routes6, + LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, + RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, + } + + if n.IpAddr != "" { + ipAddr, err := netutils.NewIPV4Addr(n.IpAddr) + if err != nil { + return nil, errors.Wrapf(err, "Parse IP address error: %q", n.IpAddr) + } + + subnetMask := net.ParseIP(netutils.Masklen2Mask(n.Masklen).String()) + + conf.ClientIP = net.ParseIP(ipAddr.String()) + conf.SubnetMask = subnetMask + conf.BroadcastAddr = net.ParseIP(ipAddr.BroadcastAddr(n.Masklen).String()) + conf.Gateway = net.ParseIP(n.Gateway) + } + if n.Ip6Addr != "" { + ipAddr6, err := netutils.NewIPV6Addr(n.Ip6Addr) + if err != nil { + return nil, errors.Wrapf(err, "Parse IPv6 address error: %q", n.Ip6Addr) + } + + conf.ClientIP6 = net.ParseIP(ipAddr6.String()) + conf.PrefixLen6 = n.Masklen6 + conf.Gateway6 = net.ParseIP(n.Gateway6) + } + + if len(n.Dns) > 0 { + conf.DNSServers = make([]net.IP, 0) + conf.DNSServers6 = make([]net.IP, 0) + for _, dns := range strings.Split(n.Dns, ",") { + if regutils.MatchIP4Addr(dns) { + conf.DNSServers = append(conf.DNSServers, net.ParseIP(dns)) + } else if regutils.MatchIP6Addr(dns) { + conf.DNSServers6 = append(conf.DNSServers6, net.ParseIP(dns)) + } + } + } + + if len(n.Ntp) > 0 { + conf.NTPServers = make([]net.IP, 0) + conf.NTPServers6 = make([]net.IP, 0) + for _, ntp := range strings.Split(n.Ntp, ",") { + if regutils.MatchIP4Addr(ntp) { + conf.NTPServers = append(conf.NTPServers, net.ParseIP(ntp)) + } else if regutils.MatchIP6Addr(ntp) { + conf.NTPServers6 = append(conf.NTPServers6, net.ParseIP(ntp)) + } else if regutils.MatchDomainName(ntp) { + ntpAddrs, _ := net.LookupHost(ntp) + for _, ntpAddr := range ntpAddrs { + if regutils.MatchIP4Addr(ntpAddr) { + conf.NTPServers = append(conf.NTPServers, net.ParseIP(ntpAddr)) + } else if regutils.MatchIP6Addr(ntpAddr) { + conf.NTPServers6 = append(conf.NTPServers6, net.ParseIP(ntpAddr)) + } + } + } + } + } + + if n.Mtu > 0 { + conf.MTU = uint16(n.Mtu) } if isPxe { - conf.BootServer = serverIP + conf.BootServer = serverIP.String() if len(o.Options.TftpBootServer) > 0 { conf.BootServer = o.Options.TftpBootServer } diff --git a/pkg/baremetal/types/types.go b/pkg/baremetal/types/types.go index b56de06bcd..b7701d8e1e 100644 --- a/pkg/baremetal/types/types.go +++ b/pkg/baremetal/types/types.go @@ -41,4 +41,7 @@ type IBaremetalServer interface { GetNicByMac(mac net.HardwareAddr) *types.SNic GetRootTemplateId() string + + GetHostName() string + GetOsName() string } diff --git a/pkg/cloudcommon/types/nic.go b/pkg/cloudcommon/types/nic.go index 8b766789dd..74dbbdc214 100644 --- a/pkg/cloudcommon/types/nic.go +++ b/pkg/cloudcommon/types/nic.go @@ -52,6 +52,8 @@ type SNic struct { Bandwidth int `json:"bandwidth"` Index int `json:"index"` + + IsDefault *bool `json:"is_default"` } type SRoute []string @@ -132,5 +134,7 @@ func (n SServerNic) ToNic() SNic { Ip6Addr: n.Ip6, Masklen6: uint8(n.Masklen6), Gateway6: n.Gateway6, + + IsDefault: &n.IsDefault, } } diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go index 05baed0932..38e4187b06 100644 --- a/pkg/hostman/guestfs/fsdriver/linux.go +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -38,6 +38,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/types" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" "yunion.io/x/onecloud/pkg/util/coreosutils" + "yunion.io/x/onecloud/pkg/util/dhcp" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/fstabutils" "yunion.io/x/onecloud/pkg/util/netutils2" @@ -930,13 +931,18 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics if mainNic != nil { mainIp = mainNic.Ip } + mainNic6 := getMainNic6(allNics) + var mainIp6 string + if mainNic6 != nil { + mainIp6 = mainNic6.Ip6 + } netplanDir := "/etc/netplan" if rootFs.Exists(netplanDir, false) { for _, f := range rootFs.ListDir(netplanDir, false) { rootFs.Remove(filepath.Join(netplanDir, f), false) } - netplanConfig := NewNetplanConfig(allNics, bondNics, mainIp) + netplanConfig := NewNetplanConfig(allNics, bondNics, mainIp, mainIp6) log.Debugf("netplanConfig:\n %s", netplanConfig.YAMLString()) if err := rootFs.FilePutContents(path.Join(netplanDir, "config.yaml"), netplanConfig.YAMLString(), false, false); err != nil { return errors.Wrap(err, "Put netplan config") @@ -978,11 +984,16 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics if nicDesc.Mtu > 0 { cmds.WriteString(fmt.Sprintf(" mtu %d\n", nicDesc.Mtu)) } - var routes = make([][]string, 0) - routes = netutils2.AddNicRoutes(routes, nicDesc, mainIp, nicCnt) - for _, r := range routes { - cmds.WriteString(fmt.Sprintf(" up route add -net %s gw %s || true\n", r[0], r[1])) - cmds.WriteString(fmt.Sprintf(" down route del -net %s gw %s || true\n", r[0], r[1])) + routes4 := make([]dhcp.SRouteInfo, 0) + routes6 := make([]dhcp.SRouteInfo, 0) + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, nicDesc, mainIp, mainIp6, nicCnt) + for _, r := range routes4 { + cmds.WriteString(fmt.Sprintf(" up ip route add %s/%d via %s || true\n", r.Prefix, r.PrefixLen, r.Gateway)) + cmds.WriteString(fmt.Sprintf(" down ip route del %s/%d via %s || true\n", r.Prefix, r.PrefixLen, r.Gateway)) + } + for _, r := range routes6 { + cmds.WriteString(fmt.Sprintf(" up ip -6 route add %s/%d via %s || true\n", r.Prefix, r.PrefixLen, r.Gateway)) + cmds.WriteString(fmt.Sprintf(" down ip -6 route del %s/%d via %s || true\n", r.Prefix, r.PrefixLen, r.Gateway)) } dnslist := netutils2.GetNicDns(nicDesc) if len(dnslist) > 0 { @@ -1326,7 +1337,21 @@ func getMainNic(nics []*types.SServerNic) *types.SServerNic { } } for i := range nics { - if len(nics[i].Gateway) > 0 { + if len(nics[i].Ip) > 0 && len(nics[i].Gateway) > 0 { + return nics[i] + } + } + return nil +} + +func getMainNic6(nics []*types.SServerNic) *types.SServerNic { + for i := range nics { + if nics[i].IsDefault { + return nics[i] + } + } + for i := range nics { + if len(nics[i].Ip6) > 0 && len(nics[i].Gateway6) > 0 { return nics[i] } } @@ -1386,6 +1411,12 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics if mainNic != nil { mainIp = mainNic.Ip } + mainNic6 := getMainNic6(allNics) + var mainIp6 string + if mainNic6 != nil { + mainIp6 = mainNic6.Ip6 + } + for i := range allNics { nicDesc := allNics[i] var cmds strings.Builder @@ -1436,7 +1467,7 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics } else if nicDesc.Manual { cmds.WriteString("BOOTPROTO=none\n") if nicDesc.VlanInterface { - if err := r.deployVlanNetworkingScripts(rootFs, scriptPath, mainIp, nicCnt, nicDesc); err != nil { + if err := r.deployVlanNetworkingScripts(rootFs, scriptPath, mainIp, mainIp6, nicCnt, nicDesc); err != nil { return errors.Wrap(err, "deployVlanNetworkingScripts") } } else { @@ -1452,13 +1483,14 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics cmds.WriteString(nicDesc.Gateway) cmds.WriteString("\n") } - var routes = make([][]string, 0) - routes = netutils2.AddNicRoutes(routes, nicDesc, mainIp, nicCnt) + routes4 := make([]dhcp.SRouteInfo, 0) + routes6 := make([]dhcp.SRouteInfo, 0) + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, nicDesc, mainIp, mainIp6, nicCnt) var rtbl strings.Builder - for _, r := range routes { - rtbl.WriteString(r[0]) + for _, r := range routes4 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) rtbl.WriteString(" via ") - rtbl.WriteString(r[1]) + rtbl.WriteString(r.Gateway.String()) rtbl.WriteString(" dev ") rtbl.WriteString(nicDesc.Name) rtbl.WriteString("\n") @@ -1496,12 +1528,12 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics // IPv6 support static temporarily // TODO cmds.WriteString("IPV6INIT=yes\n") - cmds.WriteString("DHCPV6C=no\n") + cmds.WriteString("DHCPV6C=yes\n") cmds.WriteString("IPV6_AUTOCONF=no\n") - cmds.WriteString(fmt.Sprintf("IPV6ADDR=%s/%d\n", nicDesc.Ip6, nicDesc.Masklen6)) - if len(nicDesc.Gateway6) > 0 { - cmds.WriteString(fmt.Sprintf("IPV6_DEFAULTGW=%s\n", nicDesc.Gateway6)) - } + // 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)) + // } } } var fn = fmt.Sprintf("%s/ifcfg-%s", scriptPath, nicDesc.Name) @@ -1513,7 +1545,7 @@ func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics return nil } -func (r *sRedhatLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, scriptPath, mainIp string, nicCnt int, nicDesc *types.SServerNic) error { +func (r *sRedhatLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, scriptPath, mainIp, mainIp6 string, nicCnt int, nicDesc *types.SServerNic) error { if nicDesc.Vlan <= 1 { return nil } @@ -1544,13 +1576,14 @@ func (r *sRedhatLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, s cmds.WriteString(nicDesc.Gateway) cmds.WriteString("\n") } - var routes = make([][]string, 0) - routes = netutils2.AddNicRoutes(routes, nicDesc, mainIp, nicCnt) + routes4 := make([]dhcp.SRouteInfo, 0) + routes6 := make([]dhcp.SRouteInfo, 0) + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, nicDesc, mainIp, mainIp6, nicCnt) var rtbl strings.Builder - for _, r := range routes { - rtbl.WriteString(r[0]) + for _, r := range routes4 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) rtbl.WriteString(" via ") - rtbl.WriteString(r[1]) + rtbl.WriteString(r.Gateway.String()) rtbl.WriteString(" dev ") rtbl.WriteString(fmt.Sprintf("%s.%d", nicDesc.Name, nicDesc.Vlan)) rtbl.WriteString("\n") diff --git a/pkg/hostman/guestfs/fsdriver/netplan.go b/pkg/hostman/guestfs/fsdriver/netplan.go index 8ea5086488..6b2ccd8f58 100644 --- a/pkg/hostman/guestfs/fsdriver/netplan.go +++ b/pkg/hostman/guestfs/fsdriver/netplan.go @@ -20,21 +20,22 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/dhcp" "yunion.io/x/onecloud/pkg/util/netplan" "yunion.io/x/onecloud/pkg/util/netutils2" ) -func NewNetplanConfig(allNics []*types.SServerNic, bondNics []*types.SServerNic, mainIp string) *netplan.Configuration { - network := newNetplanNetwork(allNics, bondNics, mainIp) +func NewNetplanConfig(allNics []*types.SServerNic, bondNics []*types.SServerNic, mainIp, mainIp6 string) *netplan.Configuration { + network := newNetplanNetwork(allNics, bondNics, mainIp, mainIp6) return netplan.NewConfiguration(network) } -func newNetplanNetwork(allNics []*types.SServerNic, bondNics []*types.SServerNic, mainIp string) *netplan.Network { +func newNetplanNetwork(allNics []*types.SServerNic, bondNics []*types.SServerNic, mainIp, mainIp6 string) *netplan.Network { network := netplan.NewNetwork() nicCnt := len(allNics) - len(bondNics) for _, nic := range allNics { - nicConf := getNetplanEthernetConfig(nic, false, mainIp, nicCnt) + nicConf := getNetplanEthernetConfig(nic, false, mainIp, mainIp6, nicCnt) if nicConf == nil { continue } @@ -87,7 +88,7 @@ func newNetplanNetwork(allNics []*types.SServerNic, bondNics []*types.SServerNic network.AddEthernet(sn.Name, nicConf) } - netConf := getNetplanEthernetConfig(bondNic, true, mainIp, nicCnt) + netConf := getNetplanEthernetConfig(bondNic, true, mainIp, mainIp6, nicCnt) if netConf.Mtu == 0 { netConf.Mtu = defaultMtu @@ -102,7 +103,7 @@ func newNetplanNetwork(allNics []*types.SServerNic, bondNics []*types.SServerNic return network } -func getNetplanEthernetConfig(nic *types.SServerNic, isBond bool, mainIp string, nicCnt int) *netplan.EthernetConfig { +func getNetplanEthernetConfig(nic *types.SServerNic, isBond bool, mainIp, mainIp6 string, nicCnt int) *netplan.EthernetConfig { var nicConf *netplan.EthernetConfig if !isBond && (nic.TeamingMaster != nil || nic.TeamingSlaves != nil) { @@ -120,20 +121,27 @@ func getNetplanEthernetConfig(nic *types.SServerNic, isBond bool, mainIp string, gateway6 := "" if len(nic.Ip6) > 0 { addr6 = fmt.Sprintf("%s/%d", nic.Ip6, nic.Masklen6) - if nic.Ip == mainIp && len(mainIp) > 0 { + if nic.Ip6 == mainIp6 && len(mainIp6) > 0 { gateway6 = nic.Gateway6 } } - var routeArrs = make([][]string, 0) - routeArrs = netutils2.AddNicRoutes(routeArrs, nic, mainIp, nicCnt) + routeArrs4 := make([]dhcp.SRouteInfo, 0) + routeArrs6 := make([]dhcp.SRouteInfo, 0) + routeArrs4, routeArrs6 = netutils2.AddNicRoutes(routeArrs4, routeArrs6, nic, mainIp, mainIp6, nicCnt) var routes []*netplan.Route - for _, route := range routeArrs { + for _, route := range routeArrs4 { routes = append(routes, &netplan.Route{ - To: route[0], - Via: route[1], + To: fmt.Sprintf("%s/%d", route.Prefix, route.PrefixLen), + Via: route.Gateway.String(), + }) + } + for _, route := range routeArrs6 { + routes = append(routes, &netplan.Route{ + To: fmt.Sprintf("%s/%d", route.Prefix, route.PrefixLen), + Via: route.Gateway.String(), }) } diff --git a/pkg/hostman/guestfs/fsdriver/netplan_test.go b/pkg/hostman/guestfs/fsdriver/netplan_test.go index 681ae1c517..ff0f78d83e 100644 --- a/pkg/hostman/guestfs/fsdriver/netplan_test.go +++ b/pkg/hostman/guestfs/fsdriver/netplan_test.go @@ -28,6 +28,8 @@ func TestNewNetplanConfig(t *testing.T) { mainIp string nics []*types.SServerNic want *netplan.Configuration + + mainIp6 string }{ { mainIp: "10.168.222.175", @@ -254,10 +256,138 @@ func TestNewNetplanConfig(t *testing.T) { }, }, }, + { + mainIp: "10.168.22.175", + mainIp6: "2001:db8::23", + nics: []*types.SServerNic{ + &types.SServerNic{ + Name: "eth0", + Index: 0, + Bridge: "br0", + Domain: "cloud.local", + Ip: "10.168.222.175", + Vlan: 1, + Driver: "virtio", + Masklen: 24, + Virtual: false, + Manual: true, + WireId: "399a06f3-7925-46c1-8b9f-d5a8580a74df", + NetId: "22c93412-5882-4de0-8357-45ce647ceada", + Mac: "00:24:c7:16:80:f2", + BandWidth: 1000, + Mtu: 1500, + Dns: "8.8.8.8", + Ntp: "", + Net: "vnet222", + Interface: "ens5", + Gateway: "10.168.222.1", + Ifname: "vnet222-175", + Routes: nil, + + LinkUp: true, + + Ip6: "2001:db8::23", + Masklen6: 64, + Gateway6: "2001:db8::1", + }, + &types.SServerNic{ + Name: "eth1", + Index: 1, + Bridge: "br0", + Domain: "cloud.local", + Ip: "", + Vlan: 1, + Driver: "virtio", + Masklen: 0, + Virtual: true, + Manual: true, + WireId: "399a06f3-7925-46c1-8b9f-d5a8580a74df", + NetId: "22c93412-5882-4de0-8357-45ce647ceada", + Mac: "00:24:c7:16:80:f3", + BandWidth: 1000, + Mtu: 1500, + Dns: "8.8.8.8", + Ntp: "", + Net: "vnet222", + Interface: "ens5", + Gateway: "", + Ifname: "vnet222-bpg", + Routes: nil, + + LinkUp: true, + TeamWith: "00:24:c7:16:80:f2", + }, + }, + want: &netplan.Configuration{ + Network: &netplan.Network{ + Version: 2, + Renderer: netplan.NetworkRendererNetworkd, + Ethernets: map[string]*netplan.EthernetConfig{ + "eth0": &netplan.EthernetConfig{ + MacAddress: "00:24:c7:16:80:f2", + Match: &netplan.EthernetConfigMatch{ + MacAddress: "00:24:c7:16:80:f2", + }, + Mtu: 1500, + }, + "eth1": &netplan.EthernetConfig{ + MacAddress: "00:24:c7:16:80:f3", + Match: &netplan.EthernetConfigMatch{ + MacAddress: "00:24:c7:16:80:f3", + }, + Mtu: 1500, + }, + }, + Bonds: map[string]*netplan.Bond{ + "bond0": &netplan.Bond{ + EthernetConfig: netplan.EthernetConfig{ + Addresses: []string{ + "10.168.222.175/24", + "2001:db8::23/64", + }, + MacAddress: "00:24:c7:16:80:f2", + Gateway6: "2001:db8::1", + Nameservers: &netplan.Nameservers{ + Search: []string{ + "cloud.local", + }, + Addresses: []string{ + "8.8.8.8", + }, + }, + Mtu: 1500, + Routes: []*netplan.Route{ + { + Metric: 0, + To: "fd00:ec2::254/128", + Via: "::", + }, + { + Metric: 0, + To: "fe80::a9fe:a9fe/128", + Via: "::", + }, + }, + }, + Interfaces: []string{ + "eth0", + "eth1", + }, + Parameters: &netplan.BondMode4Params{ + BondModeBaseParams: &netplan.BondModeBaseParams{ + Mode: "802.3ad", + MiiMonitorInterval: 100, + }, + }, + }, + }, + }, + }, + }, } for i, c := range cases { allNics, bondNics := convertNicConfigs(c.nics) - netplanConfig := NewNetplanConfig(allNics, bondNics, c.mainIp) + netplanConfig := NewNetplanConfig(allNics, bondNics, c.mainIp, c.mainIp6) if jsonutils.Marshal(netplanConfig).String() != jsonutils.Marshal(c.want).String() { t.Errorf("nics %d: %s want: %s got: %s", i, jsonutils.Marshal(c.nics), jsonutils.Marshal(c.want).PrettyString(), jsonutils.Marshal(netplanConfig).PrettyString()) } diff --git a/pkg/hostman/guestfs/fsdriver/suse.go b/pkg/hostman/guestfs/fsdriver/suse.go index 31eb319a45..8deb6944f0 100644 --- a/pkg/hostman/guestfs/fsdriver/suse.go +++ b/pkg/hostman/guestfs/fsdriver/suse.go @@ -16,6 +16,7 @@ package fsdriver import ( "fmt" + "net" "path" "strings" @@ -26,6 +27,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/types" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" + "yunion.io/x/onecloud/pkg/util/dhcp" "yunion.io/x/onecloud/pkg/util/netutils2" ) @@ -85,7 +87,7 @@ func (r *sSuseLikeRootFs) enableBondingModule(rootFs IDiskPartition, bondNics [] return rootFs.FilePutContents("/etc/modprobe.d/bonding.conf", content.String(), false, false) } -func (r *sSuseLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, scriptPath, mainIp string, nicCnt int, nicDesc *types.SServerNic) error { +func (r *sSuseLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, scriptPath, mainIp, mainIp6 string, nicCnt int, nicDesc *types.SServerNic) error { var cmds strings.Builder var ifname = fmt.Sprintf("%s.%d", nicDesc.Name, nicDesc.Vlan) cmds.WriteString("STARTMODE=auto\n") @@ -104,26 +106,37 @@ func (r *sSuseLikeRootFs) deployVlanNetworkingScripts(rootFs IDiskPartition, scr cmds.WriteString(fmt.Sprintf("VLAN_ID=%d\n", nicDesc.Vlan)) cmds.WriteString(fmt.Sprintf("ETHERDEVICE=%s\n", nicDesc.Name)) - var routes = make([][]string, 0) + var routes4 = make([]dhcp.SRouteInfo, 0) + var routes6 = make([]dhcp.SRouteInfo, 0) var dnsSrv []string - routes = netutils2.AddNicRoutes(routes, nicDesc, mainIp, nicCnt) + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, nicDesc, mainIp, mainIp6, nicCnt) if len(nicDesc.Gateway) > 0 && nicDesc.Ip == mainIp { - routes = append(routes, []string{ - "default", - nicDesc.Gateway, + routes4 = append(routes4, dhcp.SRouteInfo{ + Prefix: net.ParseIP("0.0.0.0"), + PrefixLen: 0, + Gateway: net.ParseIP(nicDesc.Gateway), }) } - if len(nicDesc.Gateway6) > 0 && nicDesc.Ip == mainIp { - routes = append(routes, []string{ - "default", - nicDesc.Gateway6, + if len(nicDesc.Gateway6) > 0 && nicDesc.Ip6 == mainIp6 { + routes6 = append(routes6, dhcp.SRouteInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + Gateway: net.ParseIP(nicDesc.Gateway6), }) } var rtbl strings.Builder - for _, r := range routes { - rtbl.WriteString(r[0]) + for _, r := range routes4 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) rtbl.WriteString(" ") - rtbl.WriteString(r[1]) + rtbl.WriteString(r.Gateway.String()) + rtbl.WriteString(" - ") + rtbl.WriteString(nicDesc.Name) + rtbl.WriteString("\n") + } + for _, r := range routes6 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) + rtbl.WriteString(" ") + rtbl.WriteString(r.Gateway.String()) rtbl.WriteString(" - ") rtbl.WriteString(nicDesc.Name) rtbl.WriteString("\n") @@ -169,11 +182,18 @@ func (r *sSuseLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics [] nicCnt := len(allNics) - len(bondNics) var dnsSrv []string + mainNic := getMainNic(allNics) var mainIp string if mainNic != nil { mainIp = mainNic.Ip } + mainNic6 := getMainNic6(allNics) + var mainIp6 string + if mainNic6 != nil { + mainIp6 = mainNic6.Ip6 + } + for i := range allNics { nicDesc := allNics[i] var cmds strings.Builder @@ -212,25 +232,36 @@ func (r *sSuseLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics [] cmds.WriteString(fmt.Sprintf("IPADDR_V6=%s/%d\n", nicDesc.Ip6, nicDesc.Masklen6)) } - var routes = make([][]string, 0) - routes = netutils2.AddNicRoutes(routes, nicDesc, mainIp, nicCnt) + var routes4 = make([]dhcp.SRouteInfo, 0) + var routes6 = make([]dhcp.SRouteInfo, 0) + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, nicDesc, mainIp, mainIp6, nicCnt) if len(nicDesc.Gateway) > 0 && nicDesc.Ip == mainIp { - routes = append(routes, []string{ - "default", - nicDesc.Gateway, + routes4 = append(routes4, dhcp.SRouteInfo{ + Prefix: net.ParseIP("0.0.0.0"), + PrefixLen: 0, + Gateway: net.ParseIP(nicDesc.Gateway), }) } - if len(nicDesc.Gateway6) > 0 && nicDesc.Ip == mainIp { - routes = append(routes, []string{ - "default", - nicDesc.Gateway6, + if len(nicDesc.Gateway6) > 0 && nicDesc.Ip6 == mainIp6 { + routes6 = append(routes6, dhcp.SRouteInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + Gateway: net.ParseIP(nicDesc.Gateway6), }) } var rtbl strings.Builder - for _, r := range routes { - rtbl.WriteString(r[0]) + for _, r := range routes4 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) rtbl.WriteString(" ") - rtbl.WriteString(r[1]) + rtbl.WriteString(r.Gateway.String()) + rtbl.WriteString(" - ") + rtbl.WriteString(nicDesc.Name) + rtbl.WriteString("\n") + } + for _, r := range routes6 { + rtbl.WriteString(fmt.Sprintf("%s/%d", r.Prefix, r.PrefixLen)) + rtbl.WriteString(" ") + rtbl.WriteString(r.Gateway.String()) rtbl.WriteString(" - ") rtbl.WriteString(nicDesc.Name) rtbl.WriteString("\n") diff --git a/pkg/hostman/guestfs/fsdriver/windows.go b/pkg/hostman/guestfs/fsdriver/windows.go index 973e4e1037..48d0c12dce 100644 --- a/pkg/hostman/guestfs/fsdriver/windows.go +++ b/pkg/hostman/guestfs/fsdriver/windows.go @@ -31,6 +31,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/types" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" + "yunion.io/x/onecloud/pkg/util/dhcp" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/netutils2" "yunion.io/x/onecloud/pkg/util/procutils" @@ -288,6 +289,10 @@ func (w *SWindowsRootFs) DeployNetworkingScripts(rootfs IDiskPartition, nics []* if mainNic != nil { mainIp = mainNic.Ip } + mainIp6 := "" + if mainNic != nil { + mainIp6 = mainNic.Ip6 + } bootScript := strings.Join([]string{ `set NETCFG_SCRIPT=%SystemRoot%\netcfg.bat`, `if exist %NETCFG_SCRIPT% (`, @@ -322,15 +327,19 @@ func (w *SWindowsRootFs) DeployNetworkingScripts(rootfs IDiskPartition, nics []* if len(snic.Ip6) > 0 { cfg := fmt.Sprintf(` netsh interface ipv6 add address "%%%%b" %s/%d store=persistent`, snic.Ip6, snic.Masklen6) lines = append(lines, cfg) - if len(snic.Gateway) > 0 && snic.Ip == mainIp { + if len(snic.Gateway6) > 0 && snic.Ip6 == mainIp6 { cfg := fmt.Sprintf(` netsh interface ipv6 add route ::/0 "%%%%b" %s`, snic.Gateway6) lines = append(lines, cfg) } } - routes := [][]string{} - routes = netutils2.AddNicRoutes(routes, snic, mainIp, len(nics)) - for _, r := range routes { - lines = append(lines, fmt.Sprintf(` netsh interface ip add route %s "%%%%b" %s`, r[0], r[1])) + routes4 := []dhcp.SRouteInfo{} + routes6 := []dhcp.SRouteInfo{} + routes4, routes6 = netutils2.AddNicRoutes(routes4, routes6, snic, mainIp, mainIp6, len(nics)) + for _, r := range routes4 { + lines = append(lines, fmt.Sprintf(` netsh interface ip add route %s/%d %s "%%%%b"`, r.Prefix, r.PrefixLen, r.Gateway.String())) + } + for _, r := range routes6 { + lines = append(lines, fmt.Sprintf(` netsh interface ipv6 add route %s/%d %s "%%%%b"`, r.Prefix, r.PrefixLen, r.Gateway.String())) } dnslist := netutils2.GetNicDns(snic) if len(dnslist) > 0 { diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go index 5e4da3aaa8..c34791b959 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go @@ -70,9 +70,9 @@ func (r *SDHCP6Relay) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr * return pkg, nil, err } -func (r *SDHCP6Relay) ServeRA(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, error) { +func (r *SDHCP6Relay) ServeRA(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, net.IP, net.HardwareAddr, error) { pkg, err := r.serveDHCPInternal(pkt, addr) - return pkg, err + return pkg, nil, nil, err } func (r *SDHCP6Relay) serveDHCPInternal(pkt dhcp.Packet, _ *net.UDPAddr) (dhcp.Packet, error) { diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go index 41ceae386b..34cfbdf5ce 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go @@ -20,7 +20,9 @@ import ( "time" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/util/regutils" "yunion.io/x/onecloud/pkg/apis" "yunion.io/x/onecloud/pkg/cloudcommon/types" @@ -42,7 +44,7 @@ type SGuestDHCPServer struct { relay *SDHCPRelay conn *dhcp.Conn - iface string + ifaceDev *netutils2.SNetInterface } type SDHCPRelayUpstream struct { @@ -56,19 +58,25 @@ func NewGuestDHCPServer(iface string, port int, relay *SDHCPRelayUpstream) (*SGu guestdhcp = new(SGuestDHCPServer) ) + dev := netutils2.NewNetInterface(iface) + if dev.GetHardwareAddr() == nil { + return nil, errors.Wrapf(errors.ErrInvalidStatus, "iface %s no mac", iface) + } + + guestdhcp.ifaceDev = dev + guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCPServer2(iface, DEFAULT_DHCP_SERVER_PORT) if err != nil { - return nil, err + return nil, errors.Wrap(err, "dhcp.NewDHCPServer2") } if relay != nil { guestdhcp.relay, err = NewDHCPRelay(guestdhcp.conn, relay) if err != nil { - return nil, err + return nil, errors.Wrap(err, "NewDHCPRelay") } } - guestdhcp.iface = iface return guestdhcp, nil } @@ -143,11 +151,31 @@ func GetMainNic(nics []*desc.SGuestNetwork) *desc.SGuestNetwork { return n } } + for _, n := range nics { + if n.Ip != "" && n.Gateway != "" { + return n + } + } + return nil +} + +func GetMainNic6(nics []*desc.SGuestNetwork) *desc.SGuestNetwork { + for _, n := range nics { + if n.IsDefault { + return n + } + } + for _, n := range nics { + if n.Ip6 != "" && n.Gateway6 != "" { + return n + } + } return nil } func getGuestConfig( guestDesc *desc.SGuestDesc, guestNic *desc.SGuestNetwork, + serverMac net.HardwareAddr, ) *dhcp.ResponseConfig { var nicdesc = new(types.SServerNic) if err := gusetnetworkJsonDescToServerNic(nicdesc, guestNic); err != nil { @@ -156,6 +184,9 @@ func getGuestConfig( } var conf = new(dhcp.ResponseConfig) + + conf.InterfaceMac = serverMac + nicIp := nicdesc.Ip v4Ip, _ := netutils.NewIPV4Addr(nicIp) conf.ClientIP = net.ParseIP(nicdesc.Ip) @@ -174,46 +205,87 @@ func getGuestConfig( if len(nicdesc.Ip6) > 0 { // ipv6 - conf.Gateway6 = net.ParseIP(nicdesc.Gateway6) - conf.PrefixLen6 = uint8(nicdesc.Masklen6) conf.ClientIP6 = net.ParseIP(nicdesc.Ip6) } // get main ip guestNics := guestDesc.Nics - manNic := GetMainNic(guestNics) + mainNic := GetMainNic(guestNics) var mainIp string - if manNic != nil { - mainIp = manNic.Ip + if mainNic != nil { + mainIp = mainNic.Ip + } + mainNic6 := GetMainNic6(guestNics) + var mainIp6 string + if mainNic6 != nil { + mainIp6 = mainNic6.Ip6 } - var route = [][]string{} + route4 := make([]dhcp.SRouteInfo, 0) + route6 := make([]dhcp.SRouteInfo, 0) if nicdesc.IsDefault { - conf.Gateway = net.ParseIP(nicdesc.Gateway) - osName := guestDesc.OsName if len(osName) == 0 { osName = "Linux" } - if !strings.HasPrefix(strings.ToLower(osName), "win") { - route = append(route, []string{"0.0.0.0/0", nicdesc.Gateway}) + + if nicdesc.Gateway != "" { + conf.Gateway = net.ParseIP(nicdesc.Gateway) + + if !strings.HasPrefix(strings.ToLower(osName), "win") { + route4 = append(route4, dhcp.SRouteInfo{ + Prefix: net.ParseIP("0.0.0.0"), + PrefixLen: 0, + Gateway: net.ParseIP(nicdesc.Gateway), + }) + } + } + + if len(nicdesc.Gateway6) > 0 { + conf.Gateway6 = net.ParseIP(nicdesc.Gateway6) + conf.PrefixLen6 = uint8(nicdesc.Masklen6) + route6 = append(route6, dhcp.SRouteInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + Gateway: net.ParseIP(nicdesc.Gateway6), + }) } - route = append(route, []string{"169.254.169.254/32", "0.0.0.0"}) } - route = netutils2.AddNicRoutes(route, nicdesc, mainIp, len(guestNics)) - conf.Routes = route + route4, route6 = netutils2.AddNicRoutes(route4, route6, nicdesc, mainIp, mainIp6, len(guestNics)) + + conf.Routes = route4 + conf.Routes6 = route6 if len(nicdesc.Dns) > 0 { conf.DNSServers = make([]net.IP, 0) + conf.DNSServers6 = make([]net.IP, 0) for _, dns := range strings.Split(nicdesc.Dns, ",") { - conf.DNSServers = append(conf.DNSServers, net.ParseIP(dns)) + if regutils.MatchIP4Addr(dns) { + conf.DNSServers = append(conf.DNSServers, net.ParseIP(dns)) + } else if regutils.MatchIP6Addr(dns) { + conf.DNSServers6 = append(conf.DNSServers6, net.ParseIP(dns)) + } } } if len(nicdesc.Ntp) > 0 { conf.NTPServers = make([]net.IP, 0) + conf.NTPServers6 = make([]net.IP, 0) for _, ntp := range strings.Split(nicdesc.Ntp, ",") { - conf.NTPServers = append(conf.NTPServers, net.ParseIP(ntp)) + if regutils.MatchIP4Addr(ntp) { + conf.NTPServers = append(conf.NTPServers, net.ParseIP(ntp)) + } else if regutils.MatchIP6Addr(ntp) { + conf.NTPServers6 = append(conf.NTPServers6, net.ParseIP(ntp)) + } else if regutils.MatchDomainName(ntp) { + ntpAddrs, _ := net.LookupHost(ntp) + for _, ntpAddr := range ntpAddrs { + if regutils.MatchIP4Addr(ntpAddr) { + conf.NTPServers = append(conf.NTPServers, net.ParseIP(ntpAddr)) + } else if regutils.MatchIP6Addr(ntpAddr) { + conf.NTPServers6 = append(conf.NTPServers6, net.ParseIP(ntpAddr)) + } + } + } } } @@ -237,12 +309,12 @@ func (s *SGuestDHCPServer) getConfig(pkt dhcp.Packet) *dhcp.ResponseConfig { ip, port = "", "" isCandidate = false ) - guestDesc, guestNic := guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.iface, isCandidate) + guestDesc, guestNic := guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.ifaceDev.String(), isCandidate) if guestNic == nil { - guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.iface, !isCandidate) + guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.ifaceDev.String(), !isCandidate) } - if guestNic != nil && !guestNic.Virtual { - return getGuestConfig(guestDesc, guestNic) + if guestNic != nil && !guestNic.Virtual && len(guestNic.Ip6) > 0 { + return getGuestConfig(guestDesc, guestNic, s.ifaceDev.GetHardwareAddr()) } return nil } diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go index ad1da44043..fd4ed79a8f 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go @@ -18,9 +18,11 @@ import ( "net" "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/netutils2" ) const ( @@ -34,7 +36,7 @@ type SGuestDHCP6Server struct { relay *SDHCP6Relay conn *dhcp.Conn - iface string + ifaceDev *netutils2.SNetInterface } func NewGuestDHCP6Server(iface string, port int, relay *SDHCPRelayUpstream) (*SGuestDHCP6Server, error) { @@ -43,19 +45,25 @@ func NewGuestDHCP6Server(iface string, port int, relay *SDHCPRelayUpstream) (*SG guestdhcp = new(SGuestDHCP6Server) ) + dev := netutils2.NewNetInterface(iface) + if dev.GetHardwareAddr() == nil { + return nil, errors.Wrapf(errors.ErrInvalidStatus, "iface %s no mac", iface) + } + + guestdhcp.ifaceDev = dev + guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCP6Server2(iface, DEFAULT_DHCP6_SERVER_PORT) if err != nil { - return nil, err + return nil, errors.Wrap(err, "dhcp.NewDHCP6Server2") } if relay != nil { guestdhcp.relay, err = NewDHCP6Relay(guestdhcp.conn, relay) if err != nil { - return nil, err + return nil, errors.Wrap(err, "NewDHCP6Relay") } } - guestdhcp.iface = iface return guestdhcp, nil } @@ -90,12 +98,12 @@ func (s *SGuestDHCP6Server) getConfig(cliMac net.HardwareAddr, _ dhcp.Packet) *d ip, port = "", "" isCandidate = false ) - guestDesc, guestNic := guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.iface, isCandidate) + guestDesc, guestNic := guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.ifaceDev.String(), isCandidate) if guestNic == nil { - guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.iface, !isCandidate) + guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.ifaceDev.String(), !isCandidate) } if guestNic != nil && !guestNic.Virtual && len(guestNic.Ip6) > 0 { - return getGuestConfig(guestDesc, guestNic) + return getGuestConfig(guestDesc, guestNic, s.ifaceDev.GetHardwareAddr()) } return nil } @@ -105,20 +113,21 @@ func (s *SGuestDHCP6Server) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, return pkg, nil, err } -func (s *SGuestDHCP6Server) ServeRA(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, error) { - var conf = s.getConfig(cliMac, pkt) +func (s *SGuestDHCP6Server) ServeRA(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, net.IP, net.HardwareAddr, error) { + log.Infof("SGuestDHCP6Server ServeRA from %s", cliMac.String()) + var conf = s.getConfig(cliMac, nil) if conf != nil { - return dhcp.MakeRouterAdverPacket(conf.Gateway6, conf.PrefixLen6, uint32(conf.MTU)) + return dhcp.MakeRouterAdverPacket(conf.Gateway6, conf.PrefixLen6, conf.Routes6, uint32(conf.MTU)) } - return nil, nil + return nil, nil, nil, nil } func (s *SGuestDHCP6Server) serveDHCPInternal(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, error) { var conf = s.getConfig(cliMac, pkt) if conf != nil { - log.Infof("Make DHCPv6 Reply %s TO %s", conf.ClientIP6, pkt.CHAddr()) + log.Infof("Make DHCPv6 Reply %s TO %s %s", conf.ClientIP6, cliMac.String(), addr.String()) // Guest request ip - return dhcp.MakeReplyPacket(pkt, conf) + 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) diff --git a/pkg/util/dhcp/conn.go b/pkg/util/dhcp/conn.go index a074517c80..40d3e5b2c3 100644 --- a/pkg/util/dhcp/conn.go +++ b/pkg/util/dhcp/conn.go @@ -73,6 +73,7 @@ type conn interface { Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int, error) Send6(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error + SendICMP6(b []byte, srcIp net.IP, srcMac, destMac net.HardwareAddr) error Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int, error) SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error @@ -208,6 +209,16 @@ func (c *Conn) SendDHCP(pkt Packet, addr *net.UDPAddr, mac net.HardwareAddr) err return c.conn.Send(b, addr, mac) } +func (c *Conn) SendDHCP6(pkt Packet, addr *net.UDPAddr, mac net.HardwareAddr) error { + b := pkt.Marshal() + return c.conn.Send6(b, addr, mac) +} + +func (c *Conn) SendICMP6(pkt Packet, srcIp net.IP, srcmac, dstmac net.HardwareAddr) error { + b := pkt.Marshal() + return c.conn.SendICMP6(b, srcIp, srcmac, dstmac) +} + // SetReadDeadline sets the deadline for future Read calls. If the // deadline is reached, Read will fail with a timeout (see net.Error) // instead of blocking. A zero value for t means Read will not time diff --git a/pkg/util/dhcp/conn6.go b/pkg/util/dhcp/conn6.go index b23620cf56..2d344b7f64 100644 --- a/pkg/util/dhcp/conn6.go +++ b/pkg/util/dhcp/conn6.go @@ -70,11 +70,11 @@ func interfaceToIPv6Addr(ifi *net.Interface) (net.IP, error) { for _, ifa := range ifat { switch v := ifa.(type) { case *net.IPAddr: - if len(v.IP) == net.IPv6len { + if v.IP.To4() == nil && v.IP.To16() != nil { return v.IP, nil } case *net.IPNet: - if len(v.IP) == net.IPv6len { + if v.IP.To4() == nil && v.IP.To16() != nil { return v.IP, nil } } @@ -146,3 +146,7 @@ func (s *socketConn) Send6(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr } return syscall.Sendto(s.sock, b, 0, destAddr) } + +func (s *socketConn) SendICMP6(b []byte, srcIp net.IP, srcMac, destMac net.HardwareAddr) error { + return errors.Wrap(errors.ErrNotImplemented, "SendICMP6 not implemented") +} diff --git a/pkg/util/dhcp/conn_linux6.go b/pkg/util/dhcp/conn_linux6.go index e1666ebd40..3fe871bba4 100644 --- a/pkg/util/dhcp/conn_linux6.go +++ b/pkg/util/dhcp/conn_linux6.go @@ -106,6 +106,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 @@ -143,14 +144,56 @@ func (s *rawSocketConn) Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, } } +func (s *rawSocketConn) SendICMP6(b []byte, srcIP net.IP, srcMac, destMac net.HardwareAddr) error { + var icmpRouterAdvMsg = new(layers.ICMPv6RouterAdvertisement) + if err := icmpRouterAdvMsg.DecodeFromBytes(b, gopacket.NilDecodeFeedback); err != nil { + return errors.Wrap(err, "Decode icmp router advertisement bytes error") + } + + var eth = &layers.Ethernet{ + EthernetType: layers.EthernetTypeIPv6, + SrcMAC: s.iface.HardwareAddr, + DstMAC: destMac, + } + + dstIP := net.ParseIP("ff02::1") + + var ip = &layers.IPv6{ + Version: 6, + HopLimit: 64, + SrcIP: srcIP, + DstIP: dstIP, + NextHeader: layers.IPProtocolICMPv6, + } + + var icmp6 = &layers.ICMPv6{ + TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterAdvertisement, 0), + } + icmp6.SetNetworkLayerForChecksum(ip) + + var ( + buf = gopacket.NewSerializeBuffer() + opts = gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true} + ) + if err := gopacket.SerializeLayers(buf, opts, eth, ip, icmp6, icmpRouterAdvMsg); err != nil { + return errors.Wrap(err, "SerializeLayers error") + } + + // s.conn.SetWriteDeadline(time.Now().Add(DefaultWriteTimeout)) // 2 second + if _, err := s.conn.WriteTo(buf.Bytes(), &packet.Addr{HardwareAddr: destMac}); err != nil { + return errors.Wrap(err, "Send icmp packet error") + } + return nil +} + func (s *rawSocketConn) Send6(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error { - var dhcp = new(layers.DHCPv4) + var dhcp = new(layers.DHCPv6) if err := dhcp.DecodeFromBytes(b, gopacket.NilDecodeFeedback); err != nil { return errors.Wrap(err, "Decode dhcp bytes error") } var eth = &layers.Ethernet{ - EthernetType: layers.EthernetTypeIPv4, + EthernetType: layers.EthernetTypeIPv6, SrcMAC: s.iface.HardwareAddr, DstMAC: destMac, } diff --git a/pkg/util/dhcp/helpers.go b/pkg/util/dhcp/helpers.go index ad6726dfd9..48245c79c5 100644 --- a/pkg/util/dhcp/helpers.go +++ b/pkg/util/dhcp/helpers.go @@ -18,11 +18,11 @@ import ( "encoding/binary" "fmt" "net" - "strconv" "strings" "time" "yunion.io/x/log" + "yunion.io/x/pkg/errors" ) const ( @@ -34,6 +34,8 @@ const ( // http://www.networksorcery.com/enp/rfc/rfc2132.txt type ResponseConfig struct { + InterfaceMac net.HardwareAddr + OsName string ServerIP net.IP // OptServerIdentifier 54 ClientIP net.IP @@ -45,13 +47,16 @@ type ResponseConfig struct { Hostname string // OptHostname 12 SubnetMask net.IP // OptSubnetMask 1 DNSServers []net.IP // OptDNSServers - Routes [][]string // TODO: 249 for windows, 121 for linux + Routes []SRouteInfo // TODO: 249 for windows, 121 for linux NTPServers []net.IP // OptNTPServers 42 MTU uint16 // OptMTU 26 - ClientIP6 net.IP - Gateway6 net.IP - PrefixLen6 uint8 + ClientIP6 net.IP + Gateway6 net.IP + PrefixLen6 uint8 + DNSServers6 []net.IP + NTPServers6 []net.IP + Routes6 []SRouteInfo // Relay Info https://datatracker.ietf.org/doc/html/rfc3046 RelayInfo []byte @@ -90,14 +95,40 @@ func GetOptTime(d time.Duration) []byte { return timeBytes } -func getClasslessRoutePack(route []string) []byte { - var snet, gw = route[0], route[1] - tmp := strings.Split(snet, "/") - netaddr := net.ParseIP(tmp[0]) +type SRouteInfo struct { + Prefix net.IP + PrefixLen uint8 + Gateway net.IP +} + +func (r SRouteInfo) String() string { + return fmt.Sprintf("%s/%d via %s", r.Prefix.String(), r.PrefixLen, r.Gateway.String()) +} + +func ParseRouteInfo(route []string) (*SRouteInfo, error) { + if len(route) < 2 { + return nil, errors.Wrapf(errors.ErrInvalidStatus, "invalid route %#v", route) + } + _, prefixLen, err := net.ParseCIDR(route[0]) + if err != nil { + return nil, errors.Wrapf(err, "net.ParseCIDR %s", route[0]) + } + ones, _ := prefixLen.Mask.Size() + return &SRouteInfo{ + Prefix: prefixLen.IP, + PrefixLen: uint8(ones), + Gateway: net.ParseIP(route[1]), + }, nil +} + +func getClasslessRoutePack(route SRouteInfo) []byte { + // var snet, gw = route[0], route[1] + // tmp := strings.Split(snet, "/") + netaddr := route.Prefix if netaddr != nil { netaddr = netaddr.To4() } - masklen, _ := strconv.Atoi(tmp[1]) + masklen := route.PrefixLen netlen := masklen / 8 if masklen%8 > 0 { netlen += 1 @@ -105,11 +136,10 @@ func getClasslessRoutePack(route []string) []byte { if netlen < 4 { netaddr = netaddr[0:netlen] } - gwaddr := net.ParseIP(gw) + gwaddr := route.Gateway if gwaddr != nil { gwaddr = gwaddr.To4() } - res := []byte{byte(masklen)} res = append(res, []byte(netaddr)...) return append(res, []byte(gwaddr)...) diff --git a/pkg/util/dhcp/helpers_test.go b/pkg/util/dhcp/helpers_test.go index 555486cb13..294d8c3f1f 100644 --- a/pkg/util/dhcp/helpers_test.go +++ b/pkg/util/dhcp/helpers_test.go @@ -16,6 +16,7 @@ package dhcp import ( "fmt" + "net" "testing" ) @@ -60,9 +61,69 @@ func TestGetClasslessRoutePack(t *testing.T) { }, } for _, c := range cases { - got := getClasslessRoutePack([]string{c.net, c.gw}) + routeInfo, err := ParseRouteInfo([]string{c.net, c.gw}) + if err != nil { + t.Errorf("parse route %s error: %v", c.net, err) + continue + } + got := getClasslessRoutePack(*routeInfo) if err := compareBytes(c.want, got); err != nil { t.Errorf("net: %s gw: %s want: %#v got: %#v", c.net, c.gw, c.want, got) } } } + +func TestParseRouteInfo(t *testing.T) { + cases := []struct { + net string + gw string + want *SRouteInfo + }{ + { + net: "10.0.0.0/8", + gw: "10.168.120.1", + want: &SRouteInfo{ + Prefix: net.ParseIP("10.0.0.0"), + PrefixLen: 8, + Gateway: net.ParseIP("10.168.120.1"), + }, + }, + { + net: "::/0", + gw: "3ffe:3200:fe::1", + want: &SRouteInfo{ + Prefix: net.ParseIP("::"), + PrefixLen: 0, + Gateway: net.ParseIP("3ffe:3200:fe::1"), + }, + }, + { + net: "fd00:ec2::254/128", + gw: "::", + want: &SRouteInfo{ + Prefix: net.ParseIP("fd00:ec2::254"), + PrefixLen: 128, + Gateway: net.ParseIP("::"), + }, + }, + { + net: "fe80::a9fe:a9fe/128", + gw: "::", + want: &SRouteInfo{ + Prefix: net.ParseIP("fe80::a9fe:a9fe"), + PrefixLen: 128, + Gateway: net.ParseIP("::"), + }, + }, + } + for _, c := range cases { + routeInfo, err := ParseRouteInfo([]string{c.net, c.gw}) + if err != nil { + t.Errorf("parse route %s error: %v", c.net, err) + continue + } + if routeInfo.String() != c.want.String() { + t.Errorf("parse route expect %s got: %s", c.want.String(), routeInfo.String()) + } + } +} diff --git a/pkg/util/dhcp/interface.go b/pkg/util/dhcp/interface.go index 8ff9d6c389..8695000b53 100644 --- a/pkg/util/dhcp/interface.go +++ b/pkg/util/dhcp/interface.go @@ -25,5 +25,5 @@ type DHCPHandler interface { type DHCP6Handler interface { DHCPHandler - ServeRA(pkt Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (Packet, error) + ServeRA(pkt Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (Packet, net.IP, net.HardwareAddr, error) } diff --git a/pkg/util/dhcp/packet6.go b/pkg/util/dhcp/packet6.go index c6f2049649..67c8fa7fa5 100644 --- a/pkg/util/dhcp/packet6.go +++ b/pkg/util/dhcp/packet6.go @@ -16,15 +16,26 @@ package dhcp import ( "encoding/binary" + "fmt" "net" "github.com/google/gopacket" "github.com/google/gopacket/layers" + "yunion.io/x/log" "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/netutils" ) +/* +https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.4 +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type | Length | Reserved | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| MTU | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ type icmpV6OptMtu struct { MTU uint32 } @@ -42,6 +53,7 @@ func NewIcmpV6OptMtu(mtu uint32) icmpV6OptMtu { } /* +https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.2 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -68,7 +80,7 @@ type icmpv6OptPrefixInfo struct { ValidLifetime uint32 PreferredLifetime uint32 Reserved2 uint32 - Prefix [16]byte + Prefix []byte } func (opt icmpv6OptPrefixInfo) Bytes() []byte { @@ -83,52 +95,126 @@ func (opt icmpv6OptPrefixInfo) Bytes() []byte { } func NewIcmpv6OptPrefixInfo(gw net.IP, preflen uint8) icmpv6OptPrefixInfo { + _, ipnet, _ := net.ParseCIDR(fmt.Sprintf("%s/%d", gw.String(), preflen)) return icmpv6OptPrefixInfo{ PrefixLen: preflen, Flag: 0x80, ValidLifetime: 0xffffffff, PreferredLifetime: 0xffffffff, Reserved2: 0, - Prefix: [16]byte(gw), + Prefix: ipnet.IP, } } +/* +https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.1 +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type | Length | Link-Layer Address ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ type icmpv6OptSourceTargetAddress struct { - Mac netutils.SMacAddr + Mac net.HardwareAddr } func (opt icmpv6OptSourceTargetAddress) Bytes() []byte { - return opt.Mac[:] + return opt.Mac[0:6] } -func NewIcmpv6OptSourceTargetAddress(macAddr string) icmpv6OptSourceTargetAddress { - mac, _ := netutils.ParseMac(macAddr) +func NewIcmpv6OptSourceTargetAddress(macAddr net.HardwareAddr) icmpv6OptSourceTargetAddress { return icmpv6OptSourceTargetAddress{ - Mac: mac, + Mac: macAddr, } } -func MakeRouterAdverPacket(gwIP net.IP, preflen uint8, mtu uint32) (Packet, error) { +/* +https://datatracker.ietf.org/doc/html/rfc4191#section-2.3 +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type | Length | Prefix Length |Resvd|Prf|Resvd| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Route Lifetime | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Prefix (Variable Length) | +. . +. . ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +type icmpv6OptRouteInfo struct { + PrefixLen uint8 + Reserved1 uint8 + RouteLifetime uint32 + Prefix []byte +} + +func (opt icmpv6OptRouteInfo) Bytes() []byte { + buf := make([]byte, 6) + buf[0] = opt.PrefixLen + buf[1] = opt.Reserved1 + binary.BigEndian.PutUint32(buf[2:6], opt.RouteLifetime) + buf = append(buf, opt.Prefix...) + return buf +} + +func NewIcmpv6OptRouteInfo(routeLifetime uint32, prefix []byte, prefixLen uint8) icmpv6OptRouteInfo { + if prefixLen <= 0 { + prefix = prefix[0:0] + } else if prefixLen <= 64 { + prefix = prefix[0:8] + } else { + prefix = prefix[0:16] + } + return icmpv6OptRouteInfo{ + PrefixLen: prefixLen, + Reserved1: 0, + RouteLifetime: routeLifetime, + Prefix: prefix, + } +} + +func MakeRouterAdverPacket(gwIP net.IP, prefixLen uint8, routes6 []SRouteInfo, mtu uint32) (Packet, net.IP, net.HardwareAddr, error) { pkt := layers.ICMPv6RouterAdvertisement{} - pkt.Flags = 0x80 // M=1, O=0 stateful DHCPv6 + // https://datatracker.ietf.org/doc/html/rfc4861#section-4.2 + // https://datatracker.ietf.org/doc/html/rfc4191 + pkt.Flags = 0xc0 // M=1, O=1, stateful DHCPv6, pref=00 + pkt.HopLimit = 64 + pkt.RouterLifetime = 9000 + pkt.ReachableTime = 0 + pkt.RetransTimer = 0 + + // gwMac, _ := net.ParseMAC("14:09:dc:cf:16:d5") pkt.Options = layers.ICMPv6Options{ + /*layers.ICMPv6Option{ + Type: layers.ICMPv6OptSourceAddress, + Data: NewIcmpv6OptSourceTargetAddress(gwMac).Bytes(), + },*/ layers.ICMPv6Option{ Type: layers.ICMPv6OptMTU, Data: NewIcmpV6OptMtu(mtu).Bytes(), }, layers.ICMPv6Option{ Type: layers.ICMPv6OptPrefixInfo, - Data: NewIcmpv6OptPrefixInfo(gwIP, preflen).Bytes(), + Data: NewIcmpv6OptPrefixInfo(gwIP, prefixLen).Bytes(), }, } + for i := range routes6 { + rt := routes6[i] + pkt.Options = append(pkt.Options, layers.ICMPv6Option{ + Type: 24, + Data: NewIcmpv6OptRouteInfo(9000, rt.Prefix.To16(), rt.PrefixLen).Bytes(), + }) + } sbf := gopacket.NewSerializeBuffer() err := pkt.SerializeTo(sbf, gopacket.SerializeOptions{}) if err != nil { - return nil, errors.Wrap(err, "SerializeTo") + return nil, nil, nil, errors.Wrap(err, "SerializeTo") } - return sbf.Bytes(), nil + return sbf.Bytes(), gwIP, nil, nil } // DHCPv6 https://datatracker.ietf.org/doc/html/rfc8415 @@ -212,6 +298,17 @@ const ( DHCPV6_OPTION_INFORMATION_REFRESH_TIME OptionCode6 = 32 DHCPV6_OPTION_SOL_MAX_RT OptionCode6 = 82 DHCPV6_OPTION_INF_MAX_RT OptionCode6 = 83 + + // https://www.rfc-editor.org/rfc/rfc3646 + OPTION_DNS_SERVERS OptionCode6 = 23 + OPTION_DOMAIN_LIST OptionCode6 = 24 + // https://www.rfc-editor.org/rfc/rfc4075 + OPTION_SNTP_SERVERS OptionCode6 = 31 + //https://www.rfc-editor.org/rfc/rfc5908 + OPTION_NTP_SERVERS6 OptionCode6 = 56 + // https://www.rfc-editor.org/rfc/rfc4833 + OPTION_NEW_POSIX_TIMEZONE OptionCode6 = 41 + OPTION_NEW_TZDB_TIMEZONE OptionCode6 = 42 ) // DHCPv6 message type @@ -239,26 +336,32 @@ func (p Packet) PeerAddr() net.IP { return net.IP(p[18:34]) } -func (p Packet) SetType6(hType MessageType) { p[0] = byte(hType) } +func (p Packet) IsRelayMsg() bool { + return p.Type6() == DHCPV6_RELAY_FORW || p.Type6() == DHCPV6_RELAY_REPL +} -func (p Packet) SetTID(tid uint32) { +func (p *Packet) SetType6(hType MessageType) { + (*p)[0] = byte(hType) +} + +func (p *Packet) SetTID(tid uint32) { tidBytes := make([]byte, 4) binary.BigEndian.PutUint32(tidBytes, tid) - p[1] = tidBytes[1] - p[2] = tidBytes[2] - p[3] = tidBytes[3] + (*p)[1] = tidBytes[1] + (*p)[2] = tidBytes[2] + (*p)[3] = tidBytes[3] } -func (p Packet) SetHopCount(hops byte) { - p[1] = hops +func (p *Packet) SetHopCount(hops byte) { + (*p)[1] = hops } -func (p Packet) SetLinkAddr(linkAddr net.IP) { - copy(p[2:18], linkAddr) +func (p *Packet) SetLinkAddr(linkAddr net.IP) { + copy((*p)[2:18], linkAddr) } -func (p Packet) SetPeerAddr(peerAddr net.IP) { - copy(p[18:34], peerAddr) +func (p *Packet) SetPeerAddr(peerAddr net.IP) { + copy((*p)[18:34], peerAddr) } func NewPacket6(opCode MessageType, tid uint32) Packet { @@ -284,20 +387,175 @@ type Option6 struct { | (option-len octets) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ -func (p *Packet) AddOption6(o Option6) { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf, uint16(o.Code)) - *p = append(*p, buf...) - binary.BigEndian.PutUint16(buf, uint16(len(o.Value))) - *p = append(*p, buf...) - *p = append(*p, o.Value...) +func optionToBytes(o Option6) []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(o.Code)) + binary.BigEndian.PutUint16(buf[2:4], uint16(len(o.Value))) + buf = append(buf, o.Value...) + return buf +} + +func optionsToBytes(opts []Option6) []byte { + buf := make([]byte, 0) + for i := range opts { + buf = append(buf, optionToBytes(opts[i])...) + } + return buf +} + +func (p Packet) GetOption6s() []Option6 { + options := make([]Option6, 0) + offset := 4 + if p.IsRelayMsg() { + offset = 18 + } + i := offset + for i < len(p) { + code := binary.BigEndian.Uint16(p[i : i+2]) + length := binary.BigEndian.Uint16(p[i+2 : i+4]) + options = append(options, Option6{ + Code: OptionCode6(code), + Value: p[i+4 : i+4+int(length)], + }) + i += 4 + int(length) + } + return options } // Creates a request packet that a Client would send to a server. -func RequestPacket6(mt MessageType, tid uint32, options []Option6) Packet { +/*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() + switch pktType { + case DHCPV6_SOLICIT: + msgType = DHCPV6_ADVERTISE + case DHCPV6_REQUEST: + msgType = DHCPV6_REPLY + case DHCPV6_CONFIRM: + msgType = DHCPV6_REPLY + case DHCPV6_RENEW: + msgType = DHCPV6_REPLY + case DHCPV6_REBIND: + msgType = DHCPV6_REPLY + case DHCPV6_INFORMATION_REQUEST: + msgType = DHCPV6_REPLY + default: + return nil, errors.Wrapf(errors.ErrNotSupported, "unsupported message type %d", pktType) + } + + return makeDHCPReplyPacket6(pkt, conf, msgType), nil +} + +const ( + DUID_TYPE_LINK_LAYER_ADDRESS = 3 + DUID_HARDWARE_TYPE_ETHERNET = 1 +) + +func makeServerId(serverMac net.HardwareAddr) []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(DUID_TYPE_LINK_LAYER_ADDRESS)) + binary.BigEndian.PutUint16(buf[2:4], uint16(DUID_HARDWARE_TYPE_ETHERNET)) + buf = append(buf, serverMac[:6]...) + return buf +} + +func makeIAAddr(ip net.IP, preferLT, validLT uint32, opts []Option6) []byte { + buf := make([]byte, 24) + copy(buf[0:16], ip.To16()) + binary.BigEndian.PutUint32(buf[16:20], preferLT) + binary.BigEndian.PutUint32(buf[20:24], validLT) + buf = append(buf, optionsToBytes(opts)...) + return buf +} + +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) + buf = append(buf, optionsToBytes(opts)...) + return buf +} + +func makeIPv6s(ips []net.IP) []byte { + buf := make([]byte, 0) + for _, ip := range ips { + ip6 := ip.To16() + if ip6 == nil { + continue + } + buf = append(buf, ip6...) + } + return buf +} + +func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType) Packet { + log.Debugf("makeDHCPReplyPacket6 msgType %d tid %x", msgType, pkt.TID()) + + resp := NewPacket6(msgType, pkt.TID()) + originOpts := pkt.GetOption6s() + getOption := func(code OptionCode6) Option6 { + for _, o := range originOpts { + if o.Code == code { + return o + } + } + return Option6{} + } + + options := make([]Option6, 0) + + // copy clientID + options = append(options, getOption(DHCPV6_OPTION_CLIENTID)) + // serverID + options = append(options, Option6{ + Code: DHCPV6_OPTION_SERVERID, + Value: makeServerId(conf.InterfaceMac), + }) + // Identity Association for Non-temporary Addresses Option + ianaOpt := getOption(DHCPV6_OPTION_IA_NA) + options = append(options, Option6{ + Code: DHCPV6_OPTION_IA_NA, + Value: responseIANA(ianaOpt.Value, []Option6{ + { + Code: DHCPV6_OPTION_IAADDR, + Value: makeIAAddr(conf.ClientIP6, uint32(conf.LeaseTime.Seconds()), uint32(conf.LeaseTime.Seconds()), []Option6{ + { + Code: DHCPV6_OPTION_STATUS_CODE, + Value: []byte{0, 0, 'S', 'u', 'c', 'c', 'e', 's', 's'}, + }, + }), + }, + }), + }) + + if len(conf.DNSServers6) > 0 { + options = append(options, Option6{ + Code: OPTION_DNS_SERVERS, + Value: makeIPv6s(conf.DNSServers6), + }) + } + + if len(conf.NTPServers6) > 0 { + options = append(options, Option6{ + Code: OPTION_SNTP_SERVERS, + Value: makeIPv6s(conf.NTPServers6), + }) + } + + resp = append(resp, optionsToBytes(options)...) + + return resp } diff --git a/pkg/util/dhcp/server.go b/pkg/util/dhcp/server.go index 8075885423..bff000c883 100644 --- a/pkg/util/dhcp/server.go +++ b/pkg/util/dhcp/server.go @@ -68,8 +68,8 @@ const ( bpfProcDhcpResp6 uint8 = 8 bpfProcICMPv6 uint8 = 11 - bpfProcReadPkt6 uint8 = 14 - bpfProcEnd6 uint8 = 15 + bpfProcReadPkt6 uint8 = 17 + bpfProcEnd6 uint8 = 18 bpfProcReadPkt4 uint8 = 14 bpfProcEnd4 uint8 = 15 @@ -128,29 +128,35 @@ var ( &bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: udpProtocol, SkipTrue: bpfGoto(4, bpfProcICMPv6), SkipFalse: bpfContinue}, /* 4 load 14 + 40 + 0 (UDP src port), 2 bytes */ bpf.LoadAbsolute{Off: 54, Size: 2}, - /* 5 if udp_src_port != 547 then jump to ipv6_resp/21(19+2) else continue */ - bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 547, SkipTrue: bpfGoto(6, bpfProcDhcpResp), SkipFalse: bpfContinue}, + /* 5 if udp_src_port != 546 then jump to ipv6_resp/21(19+2) else continue */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 546, SkipTrue: bpfGoto(6, bpfProcDhcpResp), SkipFalse: bpfContinue}, /* 6 load 14 + 40 + 2 (udp dst port), 2 bytes */ bpf.LoadAbsolute{Off: 56, Size: 2}, - /* 7 if udp_dst_port == 546 then jump to read/24(21+3) else jump to end/25(21+4) */ - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 546, SkipTrue: bpfGoto(8, bpfProcReadPkt6), SkipFalse: bpfGoto(8, bpfProcEnd6)}, - /* 8 [dhcpv6_resp] if udp_src_port != 546 then jump to end/25(22+3) else continue, which means a UDP response */ - bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 546, SkipTrue: bpfGoto(9, bpfProcEnd6), SkipFalse: bpfContinue}, + /* 7 if udp_dst_port == 547 then jump to readPkt6 else jump to end */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 547, SkipTrue: bpfGoto(8, bpfProcReadPkt6), SkipFalse: bpfGoto(8, bpfProcEnd6)}, + /* 8 [dhcpv6_resp] if udp_src_port != 547 then jump to end else continue, which means a DHCPv6 response */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 547, SkipTrue: bpfGoto(9, bpfProcEnd6), SkipFalse: bpfContinue}, /* 9 load 14 + 40 + 2 (udp dst port), 2 bytes */ bpf.LoadAbsolute{Off: 56, Size: 2}, - /* 10 if udp_dst_port != 547 then jump to end/25(24+1) else jump to read/ */ - bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 547, SkipTrue: bpfGoto(11, bpfProcEnd6), SkipFalse: bpfGoto(11, bpfProcReadPkt6)}, + /* 10 if udp_dst_port != 546 then jump to end else jump to readPkt6 */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 546, SkipTrue: bpfGoto(11, bpfProcEnd6), SkipFalse: bpfGoto(11, bpfProcReadPkt6)}, - /* 11 [icmp6] if ip_proto != ICMP then jump to [end]/24(17 + 7) else continue */ + /* 11 [icmp6] if ip_proto != ICMP then jump to [end] else continue */ &bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: icmpProtocol, SkipTrue: bpfGoto(12, bpfProcEnd6), SkipFalse: bpfContinue}, /* 12 [icmp6 type] load 14 + 40 + 0 (ICMPv6 type), 1 bytes */ bpf.LoadAbsolute{Off: 54, Size: 1}, - /* 13 [Router Solicitation] if icmp6_type == 133 then jump to [read] else [end] */ - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 133, SkipTrue: bpfGoto(14, bpfProcReadPkt6), SkipFalse: bpfGoto(14, bpfProcEnd6)}, + /* 13 [Router Solicitation] if icmp6_type == 133 then jump to [read] else [continue] */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 133, SkipTrue: bpfGoto(14, bpfProcReadPkt6), SkipFalse: bpfContinue}, + /* 14 [Router Advertisement] if icmp6_type == 134 then jump to [read] else [continue] */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 134, SkipTrue: bpfGoto(15, bpfProcReadPkt6), SkipFalse: bpfContinue}, + /* 15 [Neightbor Solicitation] if icmp6_type == 135 then jump to [read] else [continue] */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 135, SkipTrue: bpfGoto(16, bpfProcReadPkt6), SkipFalse: bpfContinue}, + /* 16 [Neighbor Advertisement] if icmp6_type == 136 then jump to [read] else [end] */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 136, SkipTrue: bpfGoto(17, bpfProcReadPkt6), SkipFalse: bpfGoto(17, bpfProcEnd6)}, - /* 14 [read] return the whole packet */ + /* 17 [read] return the whole packet */ bpf.RetConstant{Val: 0x40000}, - /* 15 [end] return none */ + /* 18 [end] return none */ bpf.RetConstant{Val: 0x0}, } ) @@ -285,7 +291,7 @@ func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error { if addr.Port == icmpRAFakePort { // receive a RA solication - resp, err := handler.ServeRA(pkt, mac, addr) + resp, gwIp, gwMac, err := handler.ServeRA(pkt, mac, addr) if err != nil { log.Warningf("[DHCP6] handler ServeRA error: %s", err) return @@ -295,20 +301,21 @@ func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error { return } - //log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf) - err = s.conn.SendDHCP(resp, addr, mac) + // RA should be sent to FF02::1 from gwIP + err = s.conn.SendICMP6(resp, gwIp, gwMac, mac) if err != nil { - log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err) + log.Errorf("[DHCP6] failed to response packet for ICMP6 message: %s", err) } return } + resp, targets, err := handler.ServeDHCP(pkt, mac, addr) if err != nil { - log.Warningf("[DHCP] handler serve error: %s", err) + log.Warningf("[DHCP6] handler serve error: %s", err) return } if resp == nil { - // log.Warningf("[DHCP] hander response null packet") + log.Warningf("[DHCP6] hander response null packet") return } if len(targets) > 0 { @@ -316,15 +323,14 @@ func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error { for _, target := range targets { log.Debugf("[DHCP6] Send packet back to %s", target) targetUdpAddr.IP = net.ParseIP(target) - resp.SetGIAddr(targetUdpAddr.IP) - if err = s.conn.SendDHCP(resp, &targetUdpAddr, mac); err != nil { + if err = s.conn.SendDHCP6(resp, &targetUdpAddr, mac); err != nil { log.Errorf("[DHCP6] failed to response packet for %s: %v", pkt.CHAddr(), err) } } return } //log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf) - if err = s.conn.SendDHCP(resp, addr, mac); err != nil { + if err = s.conn.SendDHCP6(resp, addr, mac); err != nil { log.Errorf("[DHCP6] failed to response packet for %s: %v", pkt.CHAddr(), err) return } diff --git a/pkg/util/netutils2/netutils.go b/pkg/util/netutils2/netutils.go index 6c3f69b20d..d680046649 100644 --- a/pkg/util/netutils2/netutils.go +++ b/pkg/util/netutils2/netutils.go @@ -30,6 +30,7 @@ import ( "yunion.io/x/pkg/util/regutils" "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/dhcp" "yunion.io/x/onecloud/pkg/util/procutils" ) @@ -131,22 +132,31 @@ func Netlen2Mask(netmasklen int) string { return netutils.Netlen2Mask(netmasklen) } -func addRoute(routes [][]string, net, gw string) [][]string { - for _, rt := range routes { - if rt[0] == net { - return routes +func addRoute(routes []dhcp.SRouteInfo, net, gw string) []dhcp.SRouteInfo { + route, _ := dhcp.ParseRouteInfo([]string{net, gw}) + if route != nil { + for _, rt := range routes { + if rt.Prefix.String() == route.Prefix.String() && rt.PrefixLen == route.PrefixLen { + return routes + } } - } - return append(routes, []string{net, gw}) -} - -func extendRoutes(routes [][]string, nicRoutes []types.SRoute) [][]string { - for i := 0; i < len(nicRoutes); i++ { - routes = addRoute(routes, nicRoutes[i][0], nicRoutes[i][1]) + // not found + routes = append(routes, *route) } return routes } +func extendRoutes(routes4, routes6 []dhcp.SRouteInfo, nicRoutes []types.SRoute) ([]dhcp.SRouteInfo, []dhcp.SRouteInfo) { + for i := 0; i < len(nicRoutes); i++ { + if regutils.MatchCIDR6(nicRoutes[i][0]) { + routes6 = addRoute(routes6, nicRoutes[i][0], nicRoutes[i][1]) + } else { + routes4 = addRoute(routes4, nicRoutes[i][0], nicRoutes[i][1]) + } + } + return routes4, routes6 +} + func isExitAddress(ip string) bool { ipv4, err := netutils.NewIPV4Addr(ip) if err != nil { @@ -156,28 +166,33 @@ func isExitAddress(ip string) bool { return netutils.IsExitAddress(ipv4) } -func AddNicRoutes(routes [][]string, nicDesc *types.SServerNic, mainIp string, nicCnt int) [][]string { +func AddNicRoutes(routes4 []dhcp.SRouteInfo, routes6 []dhcp.SRouteInfo, nicDesc *types.SServerNic, mainIp string, mainIp6 string, nicCnt int) ([]dhcp.SRouteInfo, []dhcp.SRouteInfo) { // always add static routes, even if this is the default NIC // if mainIp == nicDesc.Ip { // return routes // } if len(nicDesc.Routes) > 0 { - routes = extendRoutes(routes, nicDesc.Routes) + routes4, routes6 = extendRoutes(routes4, routes6, nicDesc.Routes) } else if len(nicDesc.Gateway) > 0 && !isExitAddress(nicDesc.Ip) && nicCnt == 2 && nicDesc.Ip != mainIp && isExitAddress(mainIp) { for _, pref := range netutils.GetPrivateIPRanges() { prefs := pref.ToPrefixes() for _, p := range prefs { - routes = addRoute(routes, p.String(), nicDesc.Gateway) + routes4 = addRoute(routes4, p.String(), nicDesc.Gateway) } } } - if nicDesc.Ip == mainIp { + if len(mainIp) > 0 && nicDesc.Ip == mainIp { // always add 169.254.169.254 for default NIC - routes = addRoute(routes, "169.254.169.254/32", "0.0.0.0") + routes4 = addRoute(routes4, "169.254.169.254/32", "0.0.0.0") } - return routes + if len(mainIp6) > 0 && nicDesc.Ip6 == mainIp6 { + routes6 = addRoute(routes6, "fd00:ec2::254/128", "::") + routes6 = addRoute(routes6, "fe80::a9fe:a9fe/128", "::") + } + + return routes4, routes6 } func GetNicDns(nicdesc *types.SServerNic) []string { @@ -324,6 +339,14 @@ func (n *SNetInterface) GetMac() string { return n.mac } +func (n *SNetInterface) GetHardwareAddr() net.HardwareAddr { + mac, err := net.ParseMAC(n.mac) + if err != nil { + return nil + } + return mac +} + func (n *SNetInterface) GetAllMacs() []string { macs := make([]string, 0, len(n.BondingSlaves)+1) find := false