From 36f629e349ea46402c527d1301491370cadfc4f4 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Thu, 3 Jul 2025 14:36:59 +0800 Subject: [PATCH] fix: dhcpv6 support, initial support (#22820) Co-authored-by: Qiu Jian --- cmd/climc/shell/compute/networks.go | 2 +- cmd/climc/shell/compute/secgroups.go | 1 + cmd/dhcprelay/main.go | 8 +- pkg/baremetal/pxe/dhcp.go | 5 +- pkg/hostman/hostinfo/finalize_network.go | 19 +- pkg/hostman/hostinfo/hostbridge/hostbridge.go | 200 ++++++++---- .../hostinfo/hostbridge/linux_bridge.go | 4 +- pkg/hostman/hostinfo/hostbridge/ovs.go | 6 +- pkg/hostman/hostinfo/hostdhcp/dhcprelay.go | 29 +- pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go | 122 +++++++ pkg/hostman/hostinfo/hostdhcp/dhcpserver.go | 42 ++- pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go | 127 ++++++++ pkg/hostman/hostinfo/hostinfo.go | 7 +- pkg/hostman/hostinfo/hostinfohelper.go | 106 +++++- pkg/hostman/options/options.go | 4 +- pkg/mcclient/options/compute/secgroup.go | 10 + pkg/monitor/dbinit/measurements/metrics.csv | 87 +++-- pkg/util/dhcp/conn.go | 152 +++------ pkg/util/dhcp/conn6.go | 148 +++++++++ pkg/util/dhcp/conn_linux.go | 162 ++-------- pkg/util/dhcp/conn_linux6.go | 190 +++++++++++ pkg/util/dhcp/conn_unsupported.go | 4 + pkg/util/dhcp/const.go | 4 + pkg/util/dhcp/helpers.go | 4 + pkg/util/dhcp/interface.go | 29 ++ pkg/util/dhcp/packet6.go | 303 ++++++++++++++++++ pkg/util/dhcp/server.go | 269 +++++++++++++--- pkg/util/netutils2/netutils.go | 40 ++- pkg/util/netutils2/netutils_linux.go | 37 ++- pkg/util/netutils2/netutils_test.go | 11 +- 30 files changed, 1654 insertions(+), 478 deletions(-) create mode 100644 pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go create mode 100644 pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go create mode 100644 pkg/util/dhcp/conn6.go create mode 100644 pkg/util/dhcp/conn_linux6.go create mode 100644 pkg/util/dhcp/interface.go create mode 100644 pkg/util/dhcp/packet6.go diff --git a/cmd/climc/shell/compute/networks.go b/cmd/climc/shell/compute/networks.go index 87ea57d3de..48406990b2 100644 --- a/cmd/climc/shell/compute/networks.go +++ b/cmd/climc/shell/compute/networks.go @@ -234,7 +234,7 @@ func init() { fmt.Println("[Slave Addresses]") for _, addr := range netIf.GetSlaveAddresses() { - fmt.Printf("%s/%s\n", addr[0], addr[1]) + fmt.Printf("%s/%d\n", addr.Addr, addr.MaskLen) } fmt.Println("[Routes]") for _, r := range netIf.GetRouteSpecs() { diff --git a/cmd/climc/shell/compute/secgroups.go b/cmd/climc/shell/compute/secgroups.go index 723736c729..1f8d85012b 100644 --- a/cmd/climc/shell/compute/secgroups.go +++ b/cmd/climc/shell/compute/secgroups.go @@ -32,6 +32,7 @@ func init() { cmd.Perform("syncstatus", &options.SecgroupIdOptions{}) cmd.Perform("private", &options.SecgroupIdOptions{}) cmd.Perform("add-rule", &options.SecgroupsAddRuleOptions{}) + cmd.Perform("clone", &options.SecgroupCloneOptions{}) cmd.Perform("change-owner", &options.SecgroupChangeOwnerOptions{}) cmd.Perform("import-rules", &options.SecgroupImportRulesOptions{}) cmd.PerformClass("clean", &options.SecgroupCleanOptions{}) diff --git a/cmd/dhcprelay/main.go b/cmd/dhcprelay/main.go index 010ab2ef40..e24e434041 100644 --- a/cmd/dhcprelay/main.go +++ b/cmd/dhcprelay/main.go @@ -62,10 +62,10 @@ func relayMain() error { return errors.Error("Missing DHCP relay server") } - srv, err := hostdhcp.NewGuestDHCPServer(options.Interface, options.Port, []string{ - options.Relay, "67", - }) - + 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") } diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go index 53bc00a302..19c97d3b6d 100644 --- a/pkg/baremetal/pxe/dhcp.go +++ b/pkg/baremetal/pxe/dhcp.go @@ -33,6 +33,7 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + "yunion.io/x/onecloud/pkg/util/ctx" "yunion.io/x/onecloud/pkg/util/dhcp" ) @@ -70,7 +71,7 @@ type dhcpRequest struct { netConfig *types.SNetworkConfig } -func (h *DHCPHandler) ServeDHCP(ctx context.Context, pkt dhcp.Packet, _ *net.UDPAddr, _ *net.Interface) (dhcp.Packet, []string, error) { +func (h *DHCPHandler) ServeDHCP(pkt dhcp.Packet, _ net.HardwareAddr, _ *net.UDPAddr) (dhcp.Packet, []string, error) { req, err := h.newRequest(pkt, h.baremetalManager) if err != nil { log.Errorf("[DHCP] new request by packet error: %v", err) @@ -82,7 +83,7 @@ func (h *DHCPHandler) ServeDHCP(ctx context.Context, pkt dhcp.Packet, _ *net.UDP return nil, nil, fmt.Errorf("Request not from a DHCP relay, ignore mac: %s", req.ClientMac) } log.Infof("[DHCP] from relay %s packet, mac: %s", req.RelayAddr, req.ClientMac) - conf, targets, err := req.fetchConfig(ctx, h.baremetalManager.GetClientSession()) + conf, targets, err := req.fetchConfig(ctx.CtxWithTime(), h.baremetalManager.GetClientSession()) if err != nil { return nil, nil, errors.Wrapf(err, "fetchConfig for %s", req.ClientMac.String()) } diff --git a/pkg/hostman/hostinfo/finalize_network.go b/pkg/hostman/hostinfo/finalize_network.go index 12f4400912..6b507223e7 100644 --- a/pkg/hostman/hostinfo/finalize_network.go +++ b/pkg/hostman/hostinfo/finalize_network.go @@ -17,7 +17,6 @@ package hostinfo import ( "context" "fmt" - "strconv" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -88,14 +87,14 @@ func (n *SNIC) setupSlaveIp(ctx context.Context, gatewayIp string, maskLen byte) bridgeIf := netutils2.NewNetInterface(n.Bridge) slaveAddrs := bridgeIf.GetSlaveAddresses() var curGatewayIp string - var curMaskLen string + var curMaskLen int isMaskUpdate := false for i := range slaveAddrs { addr := slaveAddrs[i] - curGatewayIp = addr[0] - curMaskLen = addr[1] + curGatewayIp = addr.Addr + curMaskLen = addr.MaskLen if curGatewayIp == gatewayIp { - if curMaskLen == fmt.Sprintf("%d", maskLen) { + if curMaskLen == int(maskLen) { // already configured, skip return nil } else { @@ -104,8 +103,8 @@ func (n *SNIC) setupSlaveIp(ctx context.Context, gatewayIp string, maskLen byte) } } } - if err := n.BridgeDev.SetupSlaveAddresses([][]string{ - {gatewayIp, fmt.Sprintf("%d", maskLen)}, + if err := n.BridgeDev.SetupSlaveAddresses([]netutils2.SNicAddress{ + {Addr: gatewayIp, MaskLen: int(maskLen)}, }); err != nil { return errors.Wrap(err, "SetupSlaveAddresses") } @@ -114,13 +113,13 @@ func (n *SNIC) setupSlaveIp(ctx context.Context, gatewayIp string, maskLen byte) } if isMaskUpdate { brName := n.BridgeDev.Bridge() - logPrefix := fmt.Sprintf("%s slave address %s mask is update from %s to %d", brName, gatewayIp, curMaskLen, maskLen) - addr := fmt.Sprintf("%s/%s", gatewayIp, curMaskLen) + logPrefix := fmt.Sprintf("%s slave address %s mask is update from %d to %d", brName, gatewayIp, curMaskLen, maskLen) + addr := fmt.Sprintf("%s/%d", gatewayIp, curMaskLen) log.Infof("%s: delete addr %s", logPrefix, addr) if err := iproute2.NewAddress(n.BridgeDev.Bridge(), addr).Del().Err(); err != nil { log.Warningf("%s: delete addr %s: %v", logPrefix, addr, err) } - curMaskLenInt, _ := strconv.Atoi(curMaskLen) + curMaskLenInt := curMaskLen if err := n.deleteMasqueradeRule(ctx, gatewayIp, byte(curMaskLenInt)); err != nil { log.Warningf("%s: delete iptables masqueradeRule: %v", logPrefix, err) } diff --git a/pkg/hostman/hostinfo/hostbridge/hostbridge.go b/pkg/hostman/hostinfo/hostbridge/hostbridge.go index 5fb94b598d..b80f20c3d0 100644 --- a/pkg/hostman/hostinfo/hostbridge/hostbridge.go +++ b/pkg/hostman/hostinfo/hostbridge/hostbridge.go @@ -16,7 +16,6 @@ package hostbridge import ( "fmt" - "net" "os" "strconv" "strings" @@ -45,9 +44,9 @@ type IBridgeDriver interface { GetVlanId() int FetchConfig() Setup(IBridgeDriver) error - SetupAddresses(net.IPMask) error - SetupSlaveAddresses([][]string) error - SetupRoutes(routes []iproute2.RouteSpec, add bool) error + SetupAddresses() error + SetupSlaveAddresses([]netutils2.SNicAddress) error + SetupRoutes(routes []iproute2.RouteSpec, add bool, dev string) error BringupInterface() error Exists() (bool, error) @@ -74,12 +73,16 @@ type IBridgeDriver interface { type SBaseBridgeDriver struct { bridge *netutils2.SNetInterface ip string + ip6 string inter *netutils2.SNetInterface + maskLen int + mask6Len int + drv IBridgeDriver } -func NewBaseBridgeDriver(bridge, inter, ip string) (*SBaseBridgeDriver, error) { +func NewBaseBridgeDriver(bridge, inter, ip string, maskLen int, ip6 string, mask6Len int) (*SBaseBridgeDriver, error) { var bd = new(SBaseBridgeDriver) bd.bridge = netutils2.NewNetInterface(bridge) if len(inter) > 0 { @@ -88,6 +91,9 @@ func NewBaseBridgeDriver(bridge, inter, ip string) (*SBaseBridgeDriver, error) { return nil, fmt.Errorf("%s not exists", inter) } bd.ip = ip + bd.maskLen = maskLen + bd.ip6 = ip6 + bd.mask6Len = mask6Len var enableGso bool if len(options.HostOptions.EthtoolEnableGsoInterfaces) > 0 { if utils.IsInStringArray(bridge, options.HostOptions.EthtoolEnableGsoInterfaces) || @@ -107,14 +113,14 @@ func NewBaseBridgeDriver(bridge, inter, ip string) (*SBaseBridgeDriver, error) { enableGso = options.HostOptions.EthtoolEnableGso } bd.inter.SetupGso(enableGso) - } else if len(ip) > 0 { + } else if len(ip) > 0 || len(ip6) > 0 { return nil, fmt.Errorf("A bridge without interface must have no IP") } return bd, nil } func (d *SBaseBridgeDriver) FetchConfig() { - d.bridge.FetchConfig() + d.bridge.FetchConfig2(d.ip, d.ip6) d.inter.FetchConfig() } @@ -159,7 +165,7 @@ func (d *SBaseBridgeDriver) BringupInterface() error { return nil } -func trySetupSlaveAddressesRoutes(o IBridgeDriver, migrateAddrs [][]string, delRoutes []iproute2.RouteSpec, migrateRoutes []iproute2.RouteSpec) error { +func trySetupSlaveAddressesRoutes(o IBridgeDriver, migrateAddrs []netutils2.SNicAddress, delRoutes []iproute2.RouteSpec, migrateRoutes []iproute2.RouteSpec) error { if len(migrateAddrs) > 0 { tried := 0 const MAX_TRIES = 4 @@ -184,7 +190,7 @@ func trySetupSlaveAddressesRoutes(o IBridgeDriver, migrateAddrs [][]string, delR const MAX_TRIES = 4 errs := make([]error, 0) for { - if err := o.SetupRoutes(delRoutes, false); err != nil { + if err := o.SetupRoutes(delRoutes, false, o.Bridge()); err != nil { errs = append(errs, err) log.Errorf("delRoutes fail: %s", err) tried += 1 @@ -203,7 +209,7 @@ func trySetupSlaveAddressesRoutes(o IBridgeDriver, migrateAddrs [][]string, delR const MAX_TRIES = 4 errs := make([]error, 0) for { - if err := o.SetupRoutes(migrateRoutes, true); err != nil { + if err := o.SetupRoutes(migrateRoutes, true, o.Bridge()); err != nil { errs = append(errs, err) log.Errorf("SetupRoutes fail: %s", err) tried += 1 @@ -222,7 +228,7 @@ func trySetupSlaveAddressesRoutes(o IBridgeDriver, migrateAddrs [][]string, delR func (d *SBaseBridgeDriver) MigrateSlaveConfigs(o IBridgeDriver) error { if d.inter != nil { - migrateAddrs := make([][]string, 0) + migrateAddrs := make([]netutils2.SNicAddress, 0) migrateRoutes := make([]iproute2.RouteSpec, 0) delRoutes := make([]iproute2.RouteSpec, 0) { @@ -234,16 +240,16 @@ func (d *SBaseBridgeDriver) MigrateSlaveConfigs(o IBridgeDriver) error { log.Infof("to migrate routes: %s slaveAddress: %s", jsonutils.Marshal(routes), jsonutils.Marshal(slaveAddrs)) for i := range slaveAddrs { - if strings.HasPrefix(slaveAddrs[i][0], "fe80:") || strings.HasPrefix(slaveAddrs[i][0], "169.254.") { + if strings.HasPrefix(slaveAddrs[i].Addr, "fe80:") || strings.HasPrefix(slaveAddrs[i].Addr, "169.254.") { // skip link local address continue } - if slaveAddrs[i][0] == d.bridge.Addr { + if slaveAddrs[i].Addr == d.bridge.Addr || slaveAddrs[i].Addr == d.bridge.Addr6 { continue } find := false for j := range currentSlaves { - if slaveAddrs[i][0] == currentSlaves[j][0] && slaveAddrs[i][1] == currentSlaves[j][1] { + if slaveAddrs[i].Addr == currentSlaves[j].Addr && slaveAddrs[i].MaskLen == currentSlaves[j].MaskLen { find = true break } @@ -270,7 +276,7 @@ func (d *SBaseBridgeDriver) MigrateSlaveConfigs(o IBridgeDriver) error { } if !find { for j := range slaveAddrs { - if routes[i].Dst.String() == addr2Prefix(slaveAddrs[j][0], slaveAddrs[j][1]) { + if routes[i].Dst.String() == addr2Prefix(slaveAddrs[j].Addr, slaveAddrs[j].MaskLen) { find = true break } @@ -290,7 +296,7 @@ func (d *SBaseBridgeDriver) MigrateSlaveConfigs(o IBridgeDriver) error { } } { - err := d.inter.ClearAddrs() + err := d.inter.Reset() if err != nil { return errors.Wrap(err, "ClearAddrs") } @@ -318,7 +324,7 @@ func (d *SBaseBridgeDriver) ConfirmToConfig() (bool, error) { return false, err } if exist { - d.bridge.FetchConfig() + d.bridge.FetchConfig2(d.ip, d.ip6) if len(d.ip) > 0 { if len(d.bridge.Addr) == 0 { log.Infof("bridge %s has no ip assignment initially", d.bridge) @@ -349,6 +355,36 @@ func (d *SBaseBridgeDriver) ConfirmToConfig() (bool, error) { return false, fmt.Errorf("%s should have address in 169.254.0.0/16", d.bridge) } } + if len(d.ip6) > 0 { + if len(d.bridge.Addr6) == 0 { + log.Infof("bridge %s has no ipv6 assignment initially", d.bridge) + if len(d.inter.Addr6) == 0 { + return false, fmt.Errorf("Neither %s nor %s owner ipv6 address %s", + d.inter, d.bridge, d.ip6) + } + if d.inter.Addr6 != d.ip6 { + return false, fmt.Errorf("%s!=%s, %s not same as config", + d.ip6, d.inter.Addr6, d.inter) + } + log.Infof("Bridge ipv6 address is not configured") + return false, nil + } else { + log.Infof("bridge %s already has ipv6 address %s", d.bridge, d.bridge.Addr6) + } + if d.bridge.Addr6 != d.ip6 { + return false, fmt.Errorf("%s IP %s!=%s, mismatch", d.bridge, d.bridge.Addr6, d.ip6) + } + } else { + if d.inter != nil && len(d.inter.Addr6) > 0 { + return false, fmt.Errorf("%s should have no ipv6 address", d.inter) + } + if len(d.bridge.Addr6) == 0 { + return false, nil + } + if !d.bridge.IsSecretInterface6() { + return false, fmt.Errorf("%s(%s,%s) should have link local address in fe80::/10", d.bridge, d.bridge.Addr6, d.bridge.Addr6LinkLocal) + } + } infs, err := d.drv.Interfaces() if err != nil { return false, err @@ -369,13 +405,36 @@ func (d *SBaseBridgeDriver) ConfirmToConfig() (bool, error) { if len(d.ip) > 0 && (d.inter == nil || len(d.inter.Addr) == 0) { return false, fmt.Errorf("Interface %s not configured", d.inter) } + if len(d.ip6) > 0 && (d.inter == nil || len(d.inter.Addr6) == 0) { + return false, fmt.Errorf("Interface %s ipv6 not configured", d.inter) + } return false, nil } } -func (d *SBaseBridgeDriver) SetupAddresses(mask net.IPMask) error { +func (d *SBaseBridgeDriver) SetupAddresses() error { br := d.bridge.String() + if d.inter != nil { + // first shutdown the origin interface + ifname := d.inter.String() + if err := d.inter.Shutdown(); err != nil { + return errors.Wrapf(err, "shutdown bridge %s slave ifname: %s", br, ifname) + } + for _, cmd := range [][]string{ + {"/sbin/ifdown", ifname}, + {"nmcli", "connection", "down", ifname}, + } { + output, err := procutils.NewRemoteCommandAsFarAsPossible(cmd[0], cmd[1:]...).Output() + if err != nil { + log.Errorf("run cmd: %s, output: %s, error: %s", strings.Join(cmd, " "), string(output), err) + } else { + log.Infof("run cmd: %s, output: %s", strings.Join(cmd, " "), string(output)) + break + } + } + } { + // assign address to bridge interface var ( addr string masklen int @@ -384,14 +443,20 @@ func (d *SBaseBridgeDriver) SetupAddresses(mask net.IPMask) error { addr, masklen = netutils2.GetSecretInterfaceAddress() } else { addr = d.ip - masklen, _ = mask.Size() + masklen = d.maskLen } - addrStr := fmt.Sprintf("%s/%d", addr, masklen) - if err := iproute2.NewAddress(br, addrStr).Exact().Err(); err != nil { + addrStr := []string{ + fmt.Sprintf("%s/%d", addr, masklen), + } + if len(d.ip6) > 0 { + addrStr = append(addrStr, fmt.Sprintf("%s/%d", d.ip6, d.mask6Len)) + } + if err := iproute2.NewAddress(br, addrStr...).Exact().Err(); err != nil { return errors.Wrapf(err, "set bridge %s address", br) } } { + // bring up the bridge interface brLink := iproute2.NewLink(br).Up() if options.HostOptions.TunnelPaddingBytes > 0 { mtu := 1500 + int(options.HostOptions.TunnelPaddingBytes) @@ -400,24 +465,27 @@ func (d *SBaseBridgeDriver) SetupAddresses(mask net.IPMask) error { if err := brLink.Err(); err != nil { return errors.Wrapf(err, "setting bridge %s up", br) } - } - if d.inter != nil { - ifname := d.inter.String() - if err := iproute2.NewAddress(ifname).Exact().Err(); err != nil { - return errors.Wrapf(err, "remove addresses on slave ifname: %s", ifname) - } - if err := iproute2.NewLink(ifname).Up().Err(); err != nil { - return errors.Wrapf(err, "setting bridge %s ifname %s up", br, ifname) + if d.inter != nil { + // bring up the origin interface + ethLink := iproute2.NewLink(d.inter.String()).Up() + if options.HostOptions.TunnelPaddingBytes > 0 { + mtu := 1500 + int(options.HostOptions.TunnelPaddingBytes) + ethLink.MTU(mtu) + } + if err := ethLink.Err(); err != nil { + return errors.Wrapf(err, "setting origin interface %s up", d.inter.String()) + } } } + return nil } -func (d *SBaseBridgeDriver) SetupSlaveAddresses(slaveAddrs [][]string) error { +func (d *SBaseBridgeDriver) SetupSlaveAddresses(slaveAddrs []netutils2.SNicAddress) error { br := d.bridge.String() addrs := make([]string, len(slaveAddrs)) for i, slaveAddr := range slaveAddrs { - addrs[i] = fmt.Sprintf("%s/%s", slaveAddr[0], slaveAddr[1]) + addrs[i] = fmt.Sprintf("%s/%d", slaveAddr.Addr, slaveAddr.MaskLen) } if err := iproute2.NewAddress(br, addrs...).Add().Err(); err != nil { return errors.Wrap(err, "move secondary addresses to bridge interface") @@ -425,20 +493,15 @@ func (d *SBaseBridgeDriver) SetupSlaveAddresses(slaveAddrs [][]string) error { return nil } -func (d *SBaseBridgeDriver) SetupRoutes(routespecs []iproute2.RouteSpec, add bool) error { - bridgeIP := d.inter.Addr - bridgeMask := d.inter.Mask - br := d.bridge.String() +func (d *SBaseBridgeDriver) SetupRoutes(routespecs []iproute2.RouteSpec, add bool, dev string) error { for i := 0; i < len(routespecs); i++ { errs := []error{} routespec := routespecs[i] - if routespec.Dst.Contains(net.ParseIP(bridgeIP)) && bridgeMask.String() == routespec.Dst.Mask.String() { - log.Infof("skip setup route: %s", routespec.String()) - continue - } - cmd := []string{ - "route", + var cmd []string + if regutils.MatchCIDR6(routespec.Dst.String()) { + cmd = append(cmd, "-6") } + cmd = append(cmd, "route") if add { cmd = append(cmd, "add") } else { @@ -448,7 +511,7 @@ func (d *SBaseBridgeDriver) SetupRoutes(routespecs []iproute2.RouteSpec, add boo if routespec.Gw != nil { cmd = append(cmd, "via", routespec.Gw.String()) } - cmd = append(cmd, "dev", br) + cmd = append(cmd, "dev", dev) output, err := procutils.NewRemoteCommandAsFarAsPossible("ip", cmd...).Output() if err != nil { @@ -467,21 +530,19 @@ func (d *SBaseBridgeDriver) SetupRoutes(routespecs []iproute2.RouteSpec, add boo return nil } -func addr2Prefix(addrStr string, maskLenStr string) string { +func addr2Prefix(addrStr string, maskLen int) string { if regutils.MatchIP6Addr(addrStr) { v6Addr, _ := netutils.NewIPV6Addr(addrStr) - maskLen, _ := strconv.ParseInt(maskLenStr, 10, 64) netAddr := v6Addr.NetAddr(uint8(maskLen)) return fmt.Sprintf("%s/%d", netAddr.String(), maskLen) } else { v4Addr, _ := netutils.NewIPV4Addr(addrStr) - maskLen, _ := strconv.ParseInt(maskLenStr, 10, 64) netAddr := v4Addr.NetAddr(int8(maskLen)) return fmt.Sprintf("%s/%d", netAddr.String(), maskLen) } } -func addr2Prefix2(addrStr string, mask net.IPMask) string { +/*func addr2Prefix2(addrStr string, mask net.IPMask) string { if regutils.MatchIP6Addr(addrStr) { v6Addr, _ := netutils.NewIPV6Addr(addrStr) maskLen, _ := mask.Size() @@ -493,11 +554,11 @@ func addr2Prefix2(addrStr string, mask net.IPMask) string { netAddr := v4Addr.NetAddr(int8(maskLen)) return fmt.Sprintf("%s/%d", netAddr.String(), maskLen) } -} +}*/ func (d *SBaseBridgeDriver) Setup(o IBridgeDriver) error { var routes []iproute2.RouteSpec - var slaveAddrs [][]string + var slaveAddrs []netutils2.SNicAddress if d.inter != nil && len(d.inter.Addr) > 0 { routes = d.inter.GetRouteSpecs() slaveAddrs = d.inter.GetSlaveAddresses() @@ -522,24 +583,28 @@ func (d *SBaseBridgeDriver) Setup(o IBridgeDriver) error { return errors.Wrap(err, "SetupInterface") } } - if len(d.bridge.Addr) == 0 { - if len(d.ip) > 0 { - if err := o.SetupAddresses(d.inter.Mask); err != nil { - return errors.Wrap(err, "SetupAddresses") - } - time.Sleep(1 * time.Second) + if len(d.bridge.Addr) == 0 && len(d.bridge.Addr6) == 0 { + // need to do bridge setup + if err := o.SetupAddresses(); err != nil { + return errors.Wrap(err, "SetupAddresses") + } + // sleep 1 second to wait for bridge setup + time.Sleep(1 * time.Second) + // to setup default routes + + if len(d.ip) > 0 || len(d.ip6) > 0 { setupRoutes := make([]iproute2.RouteSpec, 0) { for i := range routes { find := false if !find { - if routes[i].Dst.String() == addr2Prefix2(d.ip, d.inter.Mask) { + if routes[i].Dst.String() == addr2Prefix(d.ip, d.maskLen) || routes[i].Dst.String() == addr2Prefix(d.ip6, d.mask6Len) { find = true } } if !find { for j := range slaveAddrs { - if routes[i].Dst.String() == addr2Prefix(slaveAddrs[j][0], slaveAddrs[j][1]) { + if routes[i].Dst.String() == addr2Prefix(slaveAddrs[j].Addr, slaveAddrs[j].MaskLen) { find = true break } @@ -547,18 +612,23 @@ func (d *SBaseBridgeDriver) Setup(o IBridgeDriver) error { } if !find { // need to migrate route + log.Infof("need to migrate route: %s", routes[i].String()) setupRoutes = append(setupRoutes, routes[i]) } } } - if err := trySetupSlaveAddressesRoutes(o, slaveAddrs, nil, setupRoutes); err != nil { - return errors.Wrap(err, "trySetupSlaveAddressesRoutes") - } - } else { - if err := o.SetupAddresses(nil); err != nil { - return errors.Wrap(err, "SetupAddresses nil") + if len(setupRoutes) > 0 { + if err := o.SetupRoutes(setupRoutes, true, d.bridge.String()); err != nil { + return errors.Wrap(err, "SetupRoutes") + } } + /*if len(setupRoutes) > 0 { + if err := trySetupSlaveAddressesRoutes(o, slaveAddrs, nil, setupRoutes); err != nil { + return errors.Wrap(err, "trySetupSlaveAddressesRoutes") + } + }*/ } + } return o.BringupInterface() @@ -629,11 +699,11 @@ func (d *SBaseBridgeDriver) DisableDHCPClient() (bool, error) { return false, nil } -func NewDriver(bridgeDriver, bridge, inter, ip string) (IBridgeDriver, error) { +func NewDriver(bridgeDriver, bridge, inter, ip string, maskLen int, ip6 string, mask6Len int) (IBridgeDriver, error) { if bridgeDriver == DRV_OPEN_VSWITCH { - return NewOVSBridgeDriver(bridge, inter, ip) + return NewOVSBridgeDriver(bridge, inter, ip, maskLen, ip6, mask6Len) } else if bridgeDriver == DRV_LINUX_BRIDGE { - return NewLinuxBridgeDeriver(bridge, inter, ip) + return NewLinuxBridgeDeriver(bridge, inter, ip, maskLen, ip6, mask6Len) } return nil, fmt.Errorf("Dirver %s not found", bridgeDriver) } diff --git a/pkg/hostman/hostinfo/hostbridge/linux_bridge.go b/pkg/hostman/hostinfo/hostbridge/linux_bridge.go index 72c3e8bf3c..6777247abc 100644 --- a/pkg/hostman/hostinfo/hostbridge/linux_bridge.go +++ b/pkg/hostman/hostinfo/hostbridge/linux_bridge.go @@ -31,8 +31,8 @@ import ( "yunion.io/x/onecloud/pkg/util/procutils" ) -func NewLinuxBridgeDeriver(bridge, inter, ip string) (*SLinuxBridgeDriver, error) { - base, err := NewBaseBridgeDriver(bridge, inter, ip) +func NewLinuxBridgeDeriver(bridge, inter, ip string, maskLen int, ip6 string, mask6Len int) (*SLinuxBridgeDriver, error) { + base, err := NewBaseBridgeDriver(bridge, inter, ip, maskLen, ip6, mask6Len) if err != nil { return nil, err } diff --git a/pkg/hostman/hostinfo/hostbridge/ovs.go b/pkg/hostman/hostinfo/hostbridge/ovs.go index cbae731c93..e9bb30951f 100644 --- a/pkg/hostman/hostinfo/hostbridge/ovs.go +++ b/pkg/hostman/hostinfo/hostbridge/ovs.go @@ -300,8 +300,8 @@ func cleanOvsBridge() { //ovsutils.CleanAllHiddenPorts() } -func NewOVSBridgeDriver(bridge, inter, ip string) (*SOVSBridgeDriver, error) { - base, err := NewBaseBridgeDriver(bridge, inter, ip) +func NewOVSBridgeDriver(bridge, inter, ip string, maskLen int, ip6 string, mask6Len int) (*SOVSBridgeDriver, error) { + base, err := NewBaseBridgeDriver(bridge, inter, ip, maskLen, ip6, mask6Len) if err != nil { return nil, err } @@ -311,5 +311,5 @@ func NewOVSBridgeDriver(bridge, inter, ip string) (*SOVSBridgeDriver, error) { } func NewOVSBridgeDriverByName(bridge string) (*SOVSBridgeDriver, error) { - return NewOVSBridgeDriver(bridge, "", "") + return NewOVSBridgeDriver(bridge, "", "", 0, "", 0) } diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go b/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go index 5b5b913462..cf496cbe3a 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcprelay.go @@ -15,10 +15,8 @@ package hostdhcp import ( - "context" "fmt" "net" - "strconv" "sync" "time" @@ -27,8 +25,6 @@ import ( "yunion.io/x/onecloud/pkg/util/dhcp" ) -const DEFAULT_DHCP_RELAY_PORT = 68 - type recvFunc func(pkt *dhcp.Packet) type SRelayCache struct { @@ -52,18 +48,13 @@ type SDHCPRelay struct { cache sync.Map } -func NewDHCPRelay(guestDHCPConn *dhcp.Conn, addrs []string) (*SDHCPRelay, error) { +func NewDHCPRelay(guestDHCPConn *dhcp.Conn, config *SDHCPRelayUpstream) (*SDHCPRelay, error) { relay := new(SDHCPRelay) relay.guestDHCPConn = guestDHCPConn - addr := addrs[0] - port, err := strconv.Atoi(addrs[1]) - if err != nil { - return nil, fmt.Errorf("Pares dhcp relay addrs error %s", err) - } - log.Infof("Set Relay To Address: %s, %d", addr, port) - relay.destaddr = net.ParseIP(addr) - relay.destport = port + log.Infof("Set Relay To Address: %s, %d", config.IP, config.Port) + relay.destaddr = net.ParseIP(config.IP) + relay.destport = config.Port relay.cache = sync.Map{} return relay, nil @@ -84,12 +75,12 @@ func (r *SDHCPRelay) Setup(addr string) error { return nil } -func (r *SDHCPRelay) ServeDHCP(ctx context.Context, pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, []string, error) { - pkg, err := r.serveDHCPInternal(pkt, addr, intf) +func (r *SDHCPRelay) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, []string, error) { + pkg, err := r.serveDHCPInternal(pkt, addr) return pkg, nil, err } -func (r *SDHCPRelay) serveDHCPInternal(pkt dhcp.Packet, _ *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { +func (r *SDHCPRelay) 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 ok { @@ -99,14 +90,14 @@ func (r *SDHCPRelay) serveDHCPInternal(pkt dhcp.Packet, _ *net.UDPAddr, intf *ne IP: pkt.CIAddr(), Port: val.srcPort, } - if err := r.guestDHCPConn.SendDHCP(pkt, udpAddr, pkt.CHAddr(), intf); err != nil { + if err := r.guestDHCPConn.SendDHCP(pkt, udpAddr, pkt.CHAddr()); err != nil { log.Errorln(err) } } return nil, nil } -func (r *SDHCPRelay) Relay(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { +func (r *SDHCPRelay) Relay(pkt dhcp.Packet, addr *net.UDPAddr) (dhcp.Packet, error) { if addr.IP.Equal(r.ipv4srcAddr) { return nil, nil } @@ -131,6 +122,6 @@ func (r *SDHCPRelay) Relay(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interfa pkt.SetGIAddr(r.ipv4srcAddr) - err := r.server.GetConn().SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, nil, intf) + err := r.server.GetConn().SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, nil) return nil, err } diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go new file mode 100644 index 0000000000..5e4da3aaa8 --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/dhcprelay6.go @@ -0,0 +1,122 @@ +// 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" + "sync" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +type SDHCP6Relay struct { + server *dhcp.DHCP6Server + OnRecv recvFunc + + guestDHCPConn *dhcp.Conn + ipv6srcAddr net.IP + + destaddr net.IP + destport int + + cache sync.Map +} + +func NewDHCP6Relay(guestDHCPConn *dhcp.Conn, config *SDHCPRelayUpstream) (*SDHCP6Relay, error) { + relay := new(SDHCP6Relay) + relay.guestDHCPConn = guestDHCPConn + + log.Infof("Set Relay To Address: %s, %d", config.IP, config.Port) + relay.destaddr = net.ParseIP(config.IP) + relay.destport = config.Port + relay.cache = sync.Map{} + + return relay, nil +} + +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) + } + 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 + } + go r.server.ListenAndServe(r) + return nil +} + +func (r *SDHCP6Relay) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, []string, error) { + pkg, err := r.serveDHCPInternal(pkt, addr) + return pkg, nil, err +} + +func (r *SDHCP6Relay) ServeRA(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, error) { + pkg, err := r.serveDHCPInternal(pkt, addr) + return pkg, err +} + +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 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) + } + } + return nil, nil +} + +func (r *SDHCP6Relay) Relay(pkt dhcp.Packet, addr *net.UDPAddr) (dhcp.Packet, error) { + if addr.IP.Equal(r.ipv6srcAddr) { + 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 + }) + + // cache pkt info + r.cache.Store(pkt.TransactionID(), &SRelayCache{ + mac: pkt.CHAddr(), + srcPort: addr.Port, + timer: time.Now(), + }) + + pkt.SetGIAddr(r.ipv6srcAddr) + + err := r.server.GetConn().SendDHCP(pkt, &net.UDPAddr{IP: r.destaddr, Port: r.destport}, nil) + return nil, err +} diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go index 509e7ea53b..41ceae386b 100644 --- a/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver.go @@ -15,8 +15,6 @@ package hostdhcp import ( - "context" - "fmt" "net" "strings" "time" @@ -33,7 +31,11 @@ import ( "yunion.io/x/onecloud/pkg/util/netutils2" ) -const DEFAULT_DHCP_CLIENT_PORT = 68 +const ( + DEFAULT_DHCP_SERVER_PORT = 67 + // DEFAULT_DHCP_CLIENT_PORT = 68 + DEFAULT_DHCP_RELAY_PORT = 68 +) type SGuestDHCPServer struct { server *dhcp.DHCPServer @@ -43,22 +45,23 @@ type SGuestDHCPServer struct { iface string } -func NewGuestDHCPServer(iface string, port int, relay []string) (*SGuestDHCPServer, error) { +type SDHCPRelayUpstream struct { + IP string + Port int +} + +func NewGuestDHCPServer(iface string, port int, relay *SDHCPRelayUpstream) (*SGuestDHCPServer, error) { var ( err error guestdhcp = new(SGuestDHCPServer) ) - if len(relay) > 0 && len(relay) != 2 { - return nil, fmt.Errorf("Wrong dhcp relay address") - } - - guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCPServer2(iface, uint16(port), DEFAULT_DHCP_CLIENT_PORT) + guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCPServer2(iface, DEFAULT_DHCP_SERVER_PORT) if err != nil { return nil, err } - if len(relay) == 2 { + if relay != nil { guestdhcp.relay, err = NewDHCPRelay(guestdhcp.conn, relay) if err != nil { return nil, err @@ -143,7 +146,7 @@ func GetMainNic(nics []*desc.SGuestNetwork) *desc.SGuestNetwork { return nil } -func (s *SGuestDHCPServer) getGuestConfig( +func getGuestConfig( guestDesc *desc.SGuestDesc, guestNic *desc.SGuestNetwork, ) *dhcp.ResponseConfig { var nicdesc = new(types.SServerNic) @@ -169,6 +172,13 @@ func (s *SGuestDHCPServer) getGuestConfig( conf.Hostname = strings.ToLower(conf.Hostname) conf.Domain = nicdesc.Domain + 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) @@ -232,7 +242,7 @@ func (s *SGuestDHCPServer) getConfig(pkt dhcp.Packet) *dhcp.ResponseConfig { guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(mac, ip, port, s.iface, !isCandidate) } if guestNic != nil && !guestNic.Virtual { - return s.getGuestConfig(guestDesc, guestNic) + return getGuestConfig(guestDesc, guestNic) } return nil } @@ -241,12 +251,12 @@ func (s *SGuestDHCPServer) IsDhcpPacket(pkt dhcp.Packet) bool { return pkt != nil && (pkt.Type() == dhcp.Request || pkt.Type() == dhcp.Discover) } -func (s *SGuestDHCPServer) ServeDHCP(ctx context.Context, pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, []string, error) { - pkg, err := s.serveDHCPInternal(pkt, addr, intf) +func (s *SGuestDHCPServer) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, []string, error) { + pkg, err := s.serveDHCPInternal(pkt, addr) return pkg, nil, err } -func (s *SGuestDHCPServer) serveDHCPInternal(pkt dhcp.Packet, addr *net.UDPAddr, intf *net.Interface) (dhcp.Packet, error) { +func (s *SGuestDHCPServer) serveDHCPInternal(pkt dhcp.Packet, addr *net.UDPAddr) (dhcp.Packet, error) { if !s.IsDhcpPacket(pkt) { return nil, nil } @@ -257,7 +267,7 @@ func (s *SGuestDHCPServer) serveDHCPInternal(pkt dhcp.Packet, addr *net.UDPAddr, return dhcp.MakeReplyPacket(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, intf) + return s.relay.Relay(pkt, addr) } return nil, nil } diff --git a/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go new file mode 100644 index 0000000000..ad1da44043 --- /dev/null +++ b/pkg/hostman/hostinfo/hostdhcp/dhcpserver6.go @@ -0,0 +1,127 @@ +// 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 ( + "net" + + "yunion.io/x/log" + + guestman "yunion.io/x/onecloud/pkg/hostman/guestman/types" + "yunion.io/x/onecloud/pkg/util/dhcp" +) + +const ( + DEFAULT_DHCP6_SERVER_PORT = 547 + // DEFAULT_DHCP_CLIENT_PORT = 68 + DEFAULT_DHCP6_RELAY_PORT = 546 +) + +type SGuestDHCP6Server struct { + server *dhcp.DHCP6Server + relay *SDHCP6Relay + conn *dhcp.Conn + + iface string +} + +func NewGuestDHCP6Server(iface string, port int, relay *SDHCPRelayUpstream) (*SGuestDHCP6Server, error) { + var ( + err error + guestdhcp = new(SGuestDHCP6Server) + ) + + guestdhcp.server, guestdhcp.conn, err = dhcp.NewDHCP6Server2(iface, DEFAULT_DHCP6_SERVER_PORT) + if err != nil { + return nil, err + } + + if relay != nil { + guestdhcp.relay, err = NewDHCP6Relay(guestdhcp.conn, relay) + if err != nil { + return nil, err + } + } + + guestdhcp.iface = iface + return guestdhcp, nil +} + +func (s *SGuestDHCP6Server) Start(blocking bool) { + log.Infof("SGuestDHCP6Server starting ...") + serve := func() { + err := s.server.ListenAndServe(s) + if err != nil { + log.Errorf("DHCP serve error: %s", err) + } + } + if blocking { + serve() + } else { + go serve() + } +} + +func (s *SGuestDHCP6Server) RelaySetup(addr string) error { + if s.relay != nil { + return s.relay.Setup(addr) + } + return nil +} + +func (s *SGuestDHCP6Server) getConfig(cliMac net.HardwareAddr, _ dhcp.Packet) *dhcp.ResponseConfig { + if guestman.GuestDescGetter == nil { + return nil + } + + var ( + ip, port = "", "" + isCandidate = false + ) + guestDesc, guestNic := guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.iface, isCandidate) + if guestNic == nil { + guestDesc, guestNic = guestman.GuestDescGetter.GetGuestNicDesc(cliMac.String(), ip, port, s.iface, !isCandidate) + } + if guestNic != nil && !guestNic.Virtual && len(guestNic.Ip6) > 0 { + return getGuestConfig(guestDesc, guestNic) + } + return nil +} + +func (s *SGuestDHCP6Server) ServeDHCP(pkt dhcp.Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (dhcp.Packet, []string, error) { + pkg, err := s.serveDHCPInternal(pkt, cliMac, addr) + 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) + if conf != nil { + return dhcp.MakeRouterAdverPacket(conf.Gateway6, conf.PrefixLen6, uint32(conf.MTU)) + } + return 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()) + // Guest request ip + return dhcp.MakeReplyPacket(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 nil, nil +} diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go index 0e5cb72103..3b331c5160 100644 --- a/pkg/hostman/hostinfo/hostinfo.go +++ b/pkg/hostman/hostinfo/hostinfo.go @@ -172,7 +172,12 @@ func (h *SHostInfo) GetBridgeDev(bridge string) hostbridge.IBridgeDriver { func (h *SHostInfo) StartDHCPServer() { for _, nic := range h.Nics { - nic.dhcpServer.Start(false) + if nic.dhcpServer != nil { + nic.dhcpServer.Start(false) + } + if nic.dhcpServer6 != nil { + nic.dhcpServer6.Start(false) + } } } diff --git a/pkg/hostman/hostinfo/hostinfohelper.go b/pkg/hostman/hostinfo/hostinfohelper.go index 705162e411..9856b6e3ed 100644 --- a/pkg/hostman/hostinfo/hostinfohelper.go +++ b/pkg/hostman/hostinfo/hostinfohelper.go @@ -171,22 +171,45 @@ type SNIC struct { Inter string Bridge string Ip string + Ip6 string Wire string WireId string Mask int + Mask6 int - Bandwidth int - BridgeDev hostbridge.IBridgeDriver - dhcpServer *hostdhcp.SGuestDHCPServer + Bandwidth int + BridgeDev hostbridge.IBridgeDriver + + dhcpServer *hostdhcp.SGuestDHCPServer + dhcpServer6 *hostdhcp.SGuestDHCP6Server } func (n *SNIC) EnableDHCPRelay() bool { + if len(n.Ip) == 0 { + return false + } v4Ip, err := netutils.NewIPV4Addr(n.Ip) if err != nil { log.Errorf("EnableDHCPRelay netutils.NewIPV4Addr(%s) error: %v", n.Ip, err) return false } - if len(options.HostOptions.DhcpRelay) > 0 && !netutils.IsExitAddress(v4Ip) { + if len(options.HostOptions.DhcpRelay) == 2 && !netutils.IsExitAddress(v4Ip) { + return true + } else { + return false + } +} + +func (n *SNIC) EnableDHCP6Relay() bool { + if len(n.Ip6) == 0 { + return false + } + _, err := netutils.NewIPV6Addr(n.Ip6) + if err != nil { + log.Errorf("EnableDHCP6Relay netutils.NewIPV6Addr(%s) error: %v", n.Ip6, err) + return false + } + if len(options.HostOptions.Dhcp6Relay) == 2 { return true } else { return false @@ -200,7 +223,12 @@ func (n *SNIC) SetupDhcpRelay() error { return errors.Wrapf(err, "setup dhcp relay on ip: %s", n.Ip) } } - log.Infof("Not enable dhcp relay on nic: %#v", n) + if n.EnableDHCP6Relay() { + log.Infof("Enable dhcpv6 relay on nic: %#v", n) + if err := n.dhcpServer6.RelaySetup(n.Ip6); err != nil { + return errors.Wrapf(err, "setup dhcpv6 relay on ip: %s", n.Ip6) + } + } return nil } @@ -231,26 +259,42 @@ func NewNIC(desc string) (*SNIC, error) { } nic.Inter = data[0] nic.Bridge = data[1] + if regutils.MatchIP4Addr(data[2]) { nic.Ip = data[2] + if len(data) > 3 && regutils.MatchIP6Addr(data[3]) { + nic.Ip6 = data[3] + } + } else if regutils.MatchIP6Addr(data[2]) { + nic.Ip6 = data[3] + if len(data) > 3 && regutils.MatchIP4Addr(data[3]) { + nic.Ip = data[3] + } } else { nic.Wire = data[2] } + nic.Bandwidth = 1000 - log.Infof("IP %s/%s/%s", nic.Ip, nic.Bridge, nic.Inter) - if len(nic.Ip) > 0 { + log.Infof("IP %s/%s/%s/%s", nic.Ip, nic.Ip6, nic.Bridge, nic.Inter) + // fetch ip and ip6 netmask from interface and bridge + if len(nic.Ip) > 0 || len(nic.Ip6) > 0 { // waiting for interface assign ip // in case nic bonding is too slow var max, wait = 30, 0 for wait < max { - inf := netutils2.NewNetInterfaceWithExpectIp(nic.Inter, nic.Ip) - if inf.Addr == nic.Ip { + inf := netutils2.NewNetInterfaceWithExpectIp(nic.Inter, nic.Ip, nic.Ip6) + if len(nic.Ip) > 0 && inf.Addr == nic.Ip { mask, _ := inf.Mask.Size() if mask > 0 { nic.Mask = mask } - break + } + if len(nic.Ip6) > 0 && inf.Addr6 == nic.Ip6 { + mask, _ := inf.Mask6.Size() + if mask > 0 { + nic.Mask6 = mask + } } br := netutils2.NewNetInterface(nic.Bridge) if br.Addr == nic.Ip { @@ -258,6 +302,14 @@ func NewNIC(desc string) (*SNIC, error) { if nic.Mask == 0 && mask > 0 { nic.Mask = mask } + } + if br.Addr6 == nic.Ip6 { + mask, _ := br.Mask6.Size() + if nic.Mask6 == 0 && mask > 0 { + nic.Mask6 = mask + } + } + if nic.Mask > 0 || nic.Mask6 > 0 { break } time.Sleep(time.Second * 2) @@ -272,9 +324,9 @@ func NewNIC(desc string) (*SNIC, error) { var err error nic.BridgeDev, err = hostbridge.NewDriver(options.HostOptions.BridgeDriver, - nic.Bridge, nic.Inter, nic.Ip) + nic.Bridge, nic.Inter, nic.Ip, nic.Mask, nic.Ip6, nic.Mask6) if err != nil { - return nil, errors.Wrapf(err, "hostbridge.NewDriver driver: %s, bridge: %s, interface: %s, ip: %s", options.HostOptions.BridgeDriver, nic.Bridge, nic.Inter, nic.Ip) + return nil, errors.Wrapf(err, "hostbridge.NewDriver driver: %s, bridge: %s, interface: %s, ip: %s/%d, ip6: %s/%d", options.HostOptions.BridgeDriver, nic.Bridge, nic.Inter, nic.Ip, nic.Mask, nic.Ip6, nic.Mask6) } confirm, err := nic.BridgeDev.ConfirmToConfig() @@ -303,14 +355,36 @@ func NewNIC(desc string) (*SNIC, error) { Instance().AppendHostError("dhcp client is enabled before host agent start, please disable it") } - var dhcpRelay []string + var relayConf *hostdhcp.SDHCPRelayUpstream if nic.EnableDHCPRelay() { log.Infof("EnableDHCPRelay on nic %#v", nic) - dhcpRelay = options.HostOptions.DhcpRelay + relayConf = &hostdhcp.SDHCPRelayUpstream{} + relayConf.IP = options.HostOptions.DhcpRelay[0] + relayConf.Port, err = strconv.Atoi(options.HostOptions.DhcpRelay[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid relay port %s", options.HostOptions.DhcpRelay[1]) + } } - nic.dhcpServer, err = hostdhcp.NewGuestDHCPServer(nic.Bridge, options.HostOptions.DhcpServerPort, dhcpRelay) + + nic.dhcpServer, err = hostdhcp.NewGuestDHCPServer(nic.Bridge, options.HostOptions.DhcpServerPort, relayConf) if err != nil { - return nil, errors.Wrapf(err, "NewGuestDHCPServer(%s, %d, %#v)", nic.Bridge, options.HostOptions.DhcpServerPort, dhcpRelay) + return nil, errors.Wrapf(err, "NewGuestDHCPServer(%s, %d, %#v)", nic.Bridge, options.HostOptions.DhcpServerPort, relayConf) + } + + var relayConf6 *hostdhcp.SDHCPRelayUpstream + if nic.EnableDHCP6Relay() { + log.Infof("EnableDHCP6Relay on nic %#v", nic) + relayConf6 = &hostdhcp.SDHCPRelayUpstream{} + relayConf6.IP = options.HostOptions.Dhcp6Relay[0] + relayConf6.Port, err = strconv.Atoi(options.HostOptions.Dhcp6Relay[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid relay port %s", options.HostOptions.Dhcp6Relay[1]) + } + } + + nic.dhcpServer6, err = hostdhcp.NewGuestDHCP6Server(nic.Bridge, options.HostOptions.Dhcp6ServerPort, relayConf6) + if err != nil { + return nil, errors.Wrapf(err, "NewGuestDHCP6Server(%s, %d, %#v)", nic.Bridge, options.HostOptions.Dhcp6ServerPort, relayConf6) } // dhcp server start after guest manager init return nic, nil diff --git a/pkg/hostman/options/options.go b/pkg/hostman/options/options.go index 1eae83b503..6b291f05dd 100644 --- a/pkg/hostman/options/options.go +++ b/pkg/hostman/options/options.go @@ -123,13 +123,15 @@ type SHostOptions struct { SharedStorages []string `help:"Path of shared storages"` LVMVolumeGroups []string `help:"LVM Volume Groups(vgs)"` - DhcpRelay []string `help:"DHCP relay upstream"` + DhcpRelay []string `help:"DHCP relay upstream"` + Dhcp6Relay []string `help:"DHCPv6 relay upstream"` TunnelPaddingBytes int64 `help:"Specify tunnel padding bytes" default:"0"` CheckSystemServices bool `help:"Check system services (ntpd, telegraf) on startup" default:"true"` DhcpServerPort int `help:"Host dhcp server bind port" default:"67"` + Dhcp6ServerPort int `help:"Host dhcp6 server bind port" default:"547"` FetcherfsPath string `default:"/opt/yunion/fetchclient/bin/fetcherfs" help:"Fuse fetcherfs path"` FetcherfsBlockSize int `default:"16" help:"Fuse fetcherfs fetch chunk_size MB"` diff --git a/pkg/mcclient/options/compute/secgroup.go b/pkg/mcclient/options/compute/secgroup.go index 3df46e0714..c4eb2cfd2a 100644 --- a/pkg/mcclient/options/compute/secgroup.go +++ b/pkg/mcclient/options/compute/secgroup.go @@ -121,6 +121,16 @@ func (opts *SecgroupsAddRuleOptions) Params() (jsonutils.JSONObject, error) { return params, nil } +type SecgroupCloneOptions struct { + SecgroupIdOptions + NAME string `help:"Name of new secgroup"` + Desc string `help:"Description of new secgroup"` +} + +func (opts *SecgroupCloneOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(map[string]string{"name": opts.NAME, "description": opts.Desc}), nil +} + type SecurityGroupCacheOptions struct { SecgroupIdOptions VPC_ID string `help:"ID or Name of vpc"` diff --git a/pkg/monitor/dbinit/measurements/metrics.csv b/pkg/monitor/dbinit/measurements/metrics.csv index 25662138b8..ac50564a63 100644 --- a/pkg/monitor/dbinit/measurements/metrics.csv +++ b/pkg/monitor/dbinit/measurements/metrics.csv @@ -239,29 +239,57 @@ Measurement,MeasurementNote,ResourceType,Database,Metric,MetricNote,MetricUnit "vasmi","Vasmi GPU metrics","host","telegraf","eclk","eclk, MHz","" "vasmi","Vasmi GPU metrics","host","telegraf","gclk","gclk, MHz","" "vasmi","Vasmi GPU metrics","host","telegraf","aic_power","AIC power","" -"worker","Worker queue","worker","system","active_worker_cnt","Active Worker Count","NULL" -"worker","Worker queue","worker","system","max_worker_count","Max Worker Count","NULL" -"worker","Worker queue","worker","system","detach_worker_cnt","Detach worker Count","NULL" -"worker","Worker queue","worker","system","queue_cnt","Worker Queue Count","NULL" -"http_request","HTTP Request hit","http_request","system","duration.2xx","http code 2xxx duration","NULL" -"http_request","HTTP Request hit","http_request","system","duration.4xx","http code 4xxx duration","NULL" -"http_request","HTTP Request hit","http_request","system","duration.5xx","http code 5xxx duration","NULL" -"http_request","HTTP Request hit","http_request","system","hit.2xx","http code 2xxx hit","NULL" -"http_request","HTTP Request hit","http_request","system","hit.4xx","http code 4xxx hit","NULL" -"http_request","HTTP Request hit","http_request","system","hit.5xx","http code 5xxx hit","NULL" -"process","Service process stats","process","system","cpu_percent","CPU percent","NULL" -"process","Service process stats","process","system","mem_percent","Memory percent","NULL" -"process","Service process stats","process","system","mem_size","Memory size","NULL" -"process","Service process stats","process","system","goroutine_num","Goroutine num","NULL" -"db_stats","Database Stats","db_stats","system","idle","Database Idle","NULL" -"db_stats","Database Stats","db_stats","system","in_use","Database InUse","NULL" -"db_stats","Database Stats","db_stats","system","max_idle_closed","Database max idle closed","NULL" -"db_stats","Database Stats","db_stats","system","max_idle_time_closed","Database max idle time closed","NULL" -"db_stats","Database Stats","db_stats","system","max_lifetime_closed","Database max lifetime closed","NULL" -"db_stats","Database Stats","db_stats","system","max_open_connections","Database max open connections","NULL" -"db_stats","Database Stats","db_stats","system","open_connections","Database open connections","NULL" -"db_stats","Database Stats","db_stats","system","wait_count","Database wait count","NULL" -"db_stats","Database Stats","db_stats","system","wait_duration","Database wait duration","NULL" +"worker","Worker queue","system","system","active_worker_cnt","Active Worker Count","NULL" +"worker","Worker queue","system","system","max_worker_count","Max Worker Count","NULL" +"worker","Worker queue","system","system","detach_worker_cnt","Detach worker Count","NULL" +"worker","Worker queue","system","system","queue_cnt","Worker Queue Count","NULL" +"worker","Worker queue","system","system","total_workload","Total workload","NULL" +"worker","Worker queue","system","system","active_workload","Active workload","NULL" +"http_request","HTTP Request hit","system","system","duration_ms_any","Accumulated request duration in milliseconds","ms" +"http_request","HTTP Request hit","system","system","dura_ms_delta_any","Accumulated request duration in milliseconds in last interval","ms" +"http_request","HTTP Request hit","system","system","hit_any","Accumulated request count","NULL" +"http_request","HTTP Request hit","system","system","hit_delta_any","Accumulated request count in last interval","NULL" +"http_request","HTTP Request hit","system","system","delay_ms_any","Average request delay in miilliseconds","ms" +"http_request","HTTP Request hit","system","system","qps_any","Averatge request per second","NULL" +"http_request","HTTP Request hit","system","system","duration_ms_2xx","Accumulated request duration in milliseconds for 2xx http code","ms" +"http_request","HTTP Request hit","system","system","dura_ms_delta_2xx","Accumulated request duration in milliseconds in last interval for 2xx http code","ms" +"http_request","HTTP Request hit","system","system","hit_2xx","Accumulated request count for 2xx http code","NULL" +"http_request","HTTP Request hit","system","system","hit_delta_2xx","Accumulated request count in last interval for 2xx http code","NULL" +"http_request","HTTP Request hit","system","system","delay_ms_2xx","Average request delay in miilliseconds for 2xx http code","ms" +"http_request","HTTP Request hit","system","system","percent_hit_2xx","Request hit weight in percentage for 2xx http code","%" +"http_request","HTTP Request hit","system","system","percent_duration_2xx","Request duration weight in percentage for 2xx http code","%" +"http_request","HTTP Request hit","system","system","qps_2xx","Averatge request per second for 2xx http code","NULL" +"http_request","HTTP Request hit","system","system","duration_ms_4xx","Accumulated request duration in milliseconds for 4xx http code","ms" +"http_request","HTTP Request hit","system","system","dura_ms_delta_4xx","Accumulated request duration in milliseconds in last interval for 4xx http code","ms" +"http_request","HTTP Request hit","system","system","hit_4xx","Accumulated request count for 4xx http code","NULL" +"http_request","HTTP Request hit","system","system","hit_delta_4xx","Accumulated request count in last interval for 4xx http code","NULL" +"http_request","HTTP Request hit","system","system","delay_ms_4xx","Average request delay in miilliseconds for 4xx http code","ms" +"http_request","HTTP Request hit","system","system","percent_hit_4xx","Request hit weight in percentage for 4xx http code","%" +"http_request","HTTP Request hit","system","system","percent_duration_4xx","Request duration weight in percentage for 4xx http code","%" +"http_request","HTTP Request hit","system","system","qps_4xx","Averatge request per second for 4xx http code","NULL" +"http_request","HTTP Request hit","system","system","duration_ms_5xx","Accumulated request duration in milliseconds for 5xx http code","ms" +"http_request","HTTP Request hit","system","system","dura_ms_delta_5xx","Accumulated request duration in milliseconds in last interval for 5xx http code","ms" +"http_request","HTTP Request hit","system","system","hit_5xx","Accumulated request count for 5xx http code","NULL" +"http_request","HTTP Request hit","system","system","hit_delta_5xx","Accumulated request count in last interval for 5xx http code","NULL" +"http_request","HTTP Request hit","system","system","delay_ms_5xx","Average request delay in miilliseconds for 5xx http code","ms" +"http_request","HTTP Request hit","system","system","percent_hit_5xx","Request hit weight in percentage for 5xx http code","%" +"http_request","HTTP Request hit","system","system","percent_duration_5xx","Request duration weight in percentage for 5xx http code","%" +"http_request","HTTP Request hit","system","system","qps_5xx","Averatge request per second for 5xx http code","NULL" +"process","Service process stats","system","system","cpu_percent","CPU percent","NULL" +"process","Service process stats","system","system","mem_percent","Memory percent","NULL" +"process","Service process stats","system","system","mem_size","Memory size","NULL" +"process","Service process stats","system","system","goroutine_num","Goroutine num","NULL" +"db_stats","Database Stats","system","system","idle","Database Idle","NULL" +"db_stats","Database Stats","system","system","in_use","Database InUse","NULL" +"db_stats","Database Stats","system","system","max_idle_closed","Database max idle closed","NULL" +"db_stats","Database Stats","system","system","max_idle_time_closed","Database max idle time closed","NULL" +"db_stats","Database Stats","system","system","max_lifetime_closed","Database max lifetime closed","NULL" +"db_stats","Database Stats","system","system","max_open_connections","Database max open connections","NULL" +"db_stats","Database Stats","system","system","open_connections","Database open connections","NULL" +"db_stats","Database Stats","system","system","wait_count","Database wait count","NULL" +"db_stats","Database Stats","system","system","wait_duration","Database wait duration","NULL" +"status_probe","Resource status probe results","system","system","count","Resouce count for each status","NULL" +"status_probe","Resource status probe results","system","system","pending_deleted","Pending deleted resource count for each status","NULL" "vm_cpu","Guest CPU usage","guest","telegraf","usage_active","CPU active state utilization rate","%" "vm_cpu","Guest CPU usage","guest","telegraf","cpu_usage_pcore","CPU utilization rate per core","%" "vm_cpu","Guest CPU usage","guest","telegraf","cpu_usage_idle_pcore","CPU idle rate per core","%" @@ -282,10 +310,18 @@ Measurement,MeasurementNote,ResourceType,Database,Metric,MetricNote,MetricUnit "vm_netio","Guest network traffic","guest","telegraf","bps_sent","Send traffic per second","bps" "vm_netio","Guest network traffic","guest","telegraf","pps_recv","Received packets per second","pps" "vm_netio","Guest network traffic","guest","telegraf","pps_sent","Send packets per second","pps" +"vm_netio","Guest network traffic","guest","telegraf","bytes_sent","The total number of bytes sent by the network interface","byte" +"vm_netio","Guest network traffic","guest","telegraf","bytes_recv","The total number of bytes received by the network interface","byte" +"vm_netio","Guest network traffic","guest","telegraf","packets_sent","The total number of packets sent by the network interface","count" +"vm_netio","Guest network traffic","guest","telegraf","packets_recv","The total number of packets received by the network interface","count" "pod_netio","Pod network traffic","container","telegraf","bps_recv","Received traffic per second","bps" "pod_netio","Pod network traffic","container","telegraf","bps_sent","Send traffic per second","bps" "pod_netio","Pod network traffic","container","telegraf","pps_recv","Received packets per second","pps" "pod_netio","Pod network traffic","container","telegraf","pps_sent","Send packets per second","pps" +"pod_netio","Pod network traffic","container","telegraf","bytes_sent","The total number of bytes sent by the network interface","byte" +"pod_netio","Pod network traffic","container","telegraf","bytes_recv","The total number of bytes received by the network interface","byte" +"pod_netio","Pod network traffic","container","telegraf","packets_sent","The total number of packets sent by the network interface","count" +"pod_netio","Pod network traffic","container","telegraf","packets_recv","The total number of packets received by the network interface","count" "cloudaccount_balance","Cloud account balance","cloudaccount","meter_db","balance","balance","NULL" "container_cpu","Container cpu","container","telegraf","usage_rate","Container cpu usage rate","%" "container_mem","Container memory","container","telegraf","usage_rate","Container memory usage rate","%" @@ -493,6 +529,11 @@ Measurement,MeasurementNote,ResourceType,Database,Metric,MetricNote,MetricUnit "oss_netio","Object storage network traffic","oss","telegraf","bps_recv","Receive byte","byte" "oss_netio","Object storage network traffic","oss","telegraf","bps_sent","Send byte","byte" "oss_req","Object store request","oss","telegraf","req_count","request count","count" +"bucket_perf","Object storage bucket performance monitor","oss","telegraf","upload_delay_ms","Bucket upload delay in milliseconds","ms" +"bucket_perf","Object storage bucket performance monitor","oss","telegraf","download_delay_ms","Bucket download delay in milliseconds","ms" +"bucket_perf","Object storage bucket performance monitor","oss","telegraf","delete_delay_ms","Bucket delete delay in milliseconds","ms" +"bucket_perf","Object storage bucket performance monitor","oss","telegraf","upload_rate_mbps","Bucket upload rate in megabits per second","Mbps" +"bucket_perf","Object storage bucket performance monitor","oss","telegraf","download_rate_mbps","Bucket download rate in megabits per second","Mbps" "ping","Ping monitor","host","telegraf","packets_transmitted","used SNAT port count","count" "ping","Ping monitor","host","telegraf","packets_received","SNAT connection count","count" "ping","Ping monitor","host","telegraf","percent_packet_loss","Packet loss rate in percetile","%" diff --git a/pkg/util/dhcp/conn.go b/pkg/util/dhcp/conn.go index cb39314da9..a074517c80 100644 --- a/pkg/util/dhcp/conn.go +++ b/pkg/util/dhcp/conn.go @@ -30,16 +30,15 @@ package dhcp import ( - "errors" "fmt" "io" "net" - "strconv" "syscall" "time" "golang.org/x/net/bpf" - "golang.org/x/net/ipv4" + + "yunion.io/x/pkg/errors" ) // defined as a var so tests can override it. @@ -71,8 +70,10 @@ const ( type conn interface { io.Closer - Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr, ifidx int) error + 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 + Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int, error) SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error } @@ -85,8 +86,8 @@ type Conn struct { ifIndex int } -func NewRawSocketConn(iface string, filter []bpf.RawInstruction, dhcpServerPort uint16) (*Conn, error) { - conn, err := newRawSocketConn(iface, filter, dhcpServerPort) +func NewRawSocketConn(iface string, filter []bpf.RawInstruction, serverPort uint16) (*Conn, error) { + conn, err := newRawSocketConn(iface, filter, serverPort) if err != nil { return nil, err } @@ -102,11 +103,11 @@ func NewSocketConn(addr string, port int) (*Conn, error) { } // NewConn creates a Conn bound to the given UDP ip:port. -func NewConn(addr string, disableBroadcast bool) (*Conn, error) { - return newConn(addr, disableBroadcast, newPortableConn) -} +// func NewConn(addr string, disableBroadcast bool) (*Conn, error) { +// return newConn(addr, disableBroadcast, newPortableConn) +// } -func newConn(addr string, disableBroadcast bool, n func(net.IP, int, bool) (conn, error)) (*Conn, error) { +/*func newConn(addr string, disableBroadcast bool, n func(net.IP, int, bool) (conn, error)) (*Conn, error) { if addr == "" { addr = "0.0.0.0:67" } @@ -136,7 +137,7 @@ func newConn(addr string, disableBroadcast bool, n func(net.IP, int, bool) (conn conn: c, ifIndex: ifIndex, }, nil -} +}*/ func ipToIfindex(ip net.IP) (int, error) { intfs, err := net.Interfaces() @@ -167,69 +168,44 @@ func (c *Conn) Close() error { // RecvDHCP reads a Packet from the connection. It returns the // packet and the interface it was received on. -func (c *Conn) RecvDHCP() (Packet, *net.UDPAddr, net.HardwareAddr, *net.Interface, error) { +func (c *Conn) RecvDHCP() (Packet, *net.UDPAddr, net.HardwareAddr, error) { var buf [1500]byte b, addr, mac, _, err := c.conn.Recv(buf[:]) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, err } - /*if c.ifIndex != 0 && ifidx != c.ifIndex { - log.Errorf("======= ifIndex continue, c.ifIndex: %d, ifidx: %d", c.ifIndex, ifidx) - continue - }*/ pkt := Unmarshal(b) - // intf, err := net.InterfaceByIndex(ifidx) - // if err != nil { - // return nil, nil, nil, err - // } + return pkt, addr, mac, nil +} - // TODO: possibly more validation that the source lines up - // with what the packet says. - return pkt, addr, mac, nil, nil +func (c *Conn) RecvDHCP6() (Packet, *net.UDPAddr, net.HardwareAddr, error) { + var buf [1500]byte + b, addr, mac, _, err := c.conn.Recv6(buf[:]) + if err != nil { + return nil, nil, nil, err + } + pkt := Unmarshal(b) + return pkt, addr, mac, nil } // SendDHCP sends pkt. The precise transmission mechanism depends // on pkt.txType(). intf should be the net.Interface returned by // RecvDHCP if responding to a DHCP client, or the interface for // which configuration is desired if acting as a client. -func (c *Conn) SendDHCP(pkt Packet, addr *net.UDPAddr, mac net.HardwareAddr, intf *net.Interface) error { +func (c *Conn) SendDHCP(pkt Packet, addr *net.UDPAddr, mac net.HardwareAddr) error { b := pkt.Marshal() - ipStr, portStr, err := net.SplitHostPort(addr.String()) - if err != nil { - return err - } + 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 net.ParseIP(ipStr).Equal(net.IPv4zero) || pkt.txType() == txBroadcast { - port, _ := strconv.Atoi(portStr) - addr = &net.UDPAddr{IP: net.IPv4bcast, Port: port} } - return c.conn.Send(b, addr, mac, 0) - - /* - switch pkt.txType() { - case txBroadcast, txHardwareAddr: - addr := net.UDPAddr{ - IP: net.IPv4bcast, - Port: dhcpClientPort, - } - return c.conn.Send(b, &addr, intf.Index) - case txRelayAddr: - addr := net.UDPAddr{ - IP: pkt.RelayAddr(), - Port: dhcpClientPort, - } - log.Errorf("===============relay type pkt, addr: %#v", addr) - return c.conn.Send(b, &addr, 0) - case txClientAddr: - addr := net.UDPAddr{ - IP: pkt.CIAddr(), - Port: dhcpClientPort, - } - return c.conn.Send(b, &addr, 0) - default: - return errors.New("unknown TX type for packet") - }*/ + return c.conn.Send(b, addr, mac) } // SetReadDeadline sets the deadline for future Read calls. If the @@ -248,56 +224,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } -type portableConn struct { - conn *ipv4.PacketConn -} - -func newPortableConn(_ net.IP, port int, _ bool) (conn, error) { - c, err := net.ListenPacket("udp4", fmt.Sprintf(":%d", port)) - if err != nil { - return nil, err - } - - l := ipv4.NewPacketConn(c) - if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil { - l.Close() - return nil, err - } - return &portableConn{l}, nil -} - -func (c *portableConn) Close() error { - return c.conn.Close() -} - -func (c *portableConn) Recv(b []byte) (rb []byte, addr *net.UDPAddr, mac net.HardwareAddr, ifidx int, err error) { - n, cm, a, err := c.conn.ReadFrom(b) - if err != nil { - return nil, nil, nil, 0, err - } - return b[:n], a.(*net.UDPAddr), nil, cm.IfIndex, nil -} - -func (c *portableConn) Send(b []byte, addr *net.UDPAddr, _ net.HardwareAddr, ifidx int) error { - if ifidx <= 0 { - _, err := c.conn.WriteTo(b, nil, addr) - return err - } - cm := ipv4.ControlMessage{ - IfIndex: ifidx, - } - _, err := c.conn.WriteTo(b, &cm, addr) - return err -} - -func (c *portableConn) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -func (c *portableConn) SetWriteDeadline(t time.Time) error { - return c.conn.SetWriteDeadline(t) -} - func interfaceToIPv4Addr(ifi *net.Interface) (net.IP, error) { if ifi == nil { return net.IPv4zero, nil @@ -318,7 +244,7 @@ func interfaceToIPv4Addr(ifi *net.Interface) (net.IP, error) { } } } - return nil, errors.New("no such network interface") + return nil, errors.Wrapf(errors.ErrNotFound, "no such network interface %s", ifi.Name) } type socketConn struct { @@ -372,7 +298,7 @@ func (s *socketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int return nil, nil, nil, 0, err } if addr, ok := a.(*syscall.SockaddrInet4); !ok { - return nil, nil, nil, 0, errors.New("Recvfrom recevice address is not famliy Inet4") + return nil, nil, nil, 0, errors.Wrap(errors.ErrUnsupportedProtocol, "Recvfrom recevice address is not famliy Inet4") } else { ip := net.IP{addr.Addr[0], addr.Addr[1], addr.Addr[2], addr.Addr[3]} udpAddr := &net.UDPAddr{ @@ -384,7 +310,7 @@ func (s *socketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int } } -func (s *socketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr, ifidx int) error { +func (s *socketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error { destIp := [4]byte{} copy(destIp[:], addr.IP.To4()[:4]) destAddr := &syscall.SockaddrInet4{ @@ -395,9 +321,9 @@ func (s *socketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr, } func (s *socketConn) SetReadDeadline(t time.Time) error { - return errors.New("Not Implement") + return errors.ErrNotImplemented } func (s *socketConn) SetWriteDeadline(t time.Time) error { - return errors.New("Not Implement") + return errors.ErrNotImplemented } diff --git a/pkg/util/dhcp/conn6.go b/pkg/util/dhcp/conn6.go new file mode 100644 index 0000000000..b23620cf56 --- /dev/null +++ b/pkg/util/dhcp/conn6.go @@ -0,0 +1,148 @@ +// 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. + +// Copyright 2019 Yunion +// Copyright 2016 Google Inc. +// +// 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 dhcp + +import ( + "net" + "syscall" + + "golang.org/x/net/bpf" + + "yunion.io/x/pkg/errors" +) + +// defined as a var so tests can override it. +var ( + dhcpv6ClientPort = 546 +) + +func NewRawSocketConn6(iface string, filter []bpf.RawInstruction, serverPort uint16) (*Conn, error) { + conn, err := newRawSocketConn6(iface, filter, serverPort) + if err != nil { + return nil, err + } + return &Conn{conn, 0}, nil +} + +func NewSocketConn6(addr string, port int) (*Conn, error) { + conn, err := newSocketConn6(net.ParseIP(addr), port, false) + if err != nil { + return nil, err + } + return &Conn{conn, 0}, nil +} + +func interfaceToIPv6Addr(ifi *net.Interface) (net.IP, error) { + if ifi == nil { + return net.IPv6zero, nil + } + ifat, err := ifi.Addrs() + if err != nil { + return nil, err + } + for _, ifa := range ifat { + switch v := ifa.(type) { + case *net.IPAddr: + if len(v.IP) == net.IPv6len { + return v.IP, nil + } + case *net.IPNet: + if len(v.IP) == net.IPv6len { + return v.IP, nil + } + } + } + return nil, errors.Wrapf(errors.ErrNotFound, "no such network interface %s", ifi.Name) +} + +func newSocketConn6(addr net.IP, port int, disableBroadcast bool) (conn, error) { + var broadcastOpt = 1 + if disableBroadcast { + broadcastOpt = 0 + } + sock, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0) + if err != nil { + return nil, err + } + err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + return nil, err + } + err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_BROADCAST, broadcastOpt) + if err != nil { + return nil, err + } + byteAddr := [16]byte{} + copy(byteAddr[:], addr.To16()) + lsa := &syscall.SockaddrInet6{ + Port: port, + Addr: byteAddr, + } + if err = syscall.Bind(sock, lsa); err != nil { + return nil, err + } + if err = syscall.SetNonblock(sock, false); err != nil { + return nil, err + } + + // Its equal syscall.CloseOnExec + // most file descriptors are getting set to close-on-exec + // apart from syscall open, socket etc. + syscall.Syscall(syscall.SYS_FCNTL, uintptr(sock), syscall.F_SETFD, syscall.FD_CLOEXEC) + return &socketConn{sock}, nil +} + +func (s *socketConn) Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int, error) { + n, a, err := syscall.Recvfrom(s.sock, b, 0) + if err != nil { + return nil, nil, nil, 0, err + } + if addr, ok := a.(*syscall.SockaddrInet6); !ok { + return nil, nil, nil, 0, errors.Wrap(errors.ErrUnsupportedProtocol, "Recvfrom recevice address is not famliy Inet6") + } else { + ip := net.IP(addr.Addr[:]) + udpAddr := &net.UDPAddr{ + IP: ip, + Port: addr.Port, + } + // there is no interface index info + return b[:n], udpAddr, nil, 0, nil + } +} + +func (s *socketConn) Send6(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error { + destIp := [16]byte{} + copy(destIp[:], addr.IP.To16()) + destAddr := &syscall.SockaddrInet6{ + Addr: destIp, + Port: addr.Port, + } + return syscall.Sendto(s.sock, b, 0, destAddr) +} diff --git a/pkg/util/dhcp/conn_linux.go b/pkg/util/dhcp/conn_linux.go index e9172a3f9a..a29e5ff3e3 100644 --- a/pkg/util/dhcp/conn_linux.go +++ b/pkg/util/dhcp/conn_linux.go @@ -33,7 +33,6 @@ package dhcp import ( - "encoding/binary" "net" "time" @@ -41,21 +40,22 @@ import ( "github.com/google/gopacket/layers" "github.com/mdlayher/packet" "golang.org/x/net/bpf" - "golang.org/x/net/ipv4" "golang.org/x/sys/unix" + "yunion.io/x/log" "yunion.io/x/pkg/errors" ) type rawSocketConn struct { conn *packet.Conn - iface *net.Interface - ip net.IP - dhcpServerPort uint16 + iface *net.Interface + ip net.IP + + serverPort uint16 } -func newRawSocketConn(iface string, filter []bpf.RawInstruction, dhcpServerPort uint16) (conn, error) { +func newRawSocketConn(iface string, filter []bpf.RawInstruction, serverPort uint16) (conn, error) { ifi, err := net.InterfaceByName(iface) if err != nil { return nil, errors.Wrap(err, "interface by name") @@ -63,18 +63,24 @@ func newRawSocketConn(iface string, filter []bpf.RawInstruction, dhcpServerPort ip, err := interfaceToIPv4Addr(ifi) if err != nil { - return nil, err + return nil, errors.Wrap(err, "interfaceToIPv4Addr") } // unix.ETH_P_ALL conn, err := packet.Listen(ifi, packet.Raw, unix.ETH_P_IP, &packet.Config{ - // NoCumulativeStats: true, Filter: filter, }) if err != nil { return nil, errors.Wrap(err, "packet.Listen") } - return &rawSocketConn{conn, ifi, ip, dhcpServerPort}, nil + log.Debugf("newRawSocketConn on %s %s", ifi.Name, ip) + + return &rawSocketConn{ + conn: conn, + iface: ifi, + ip: ip, + serverPort: serverPort, + }, nil } func (s *rawSocketConn) Close() error { @@ -87,6 +93,8 @@ func (s *rawSocketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, if err != nil { return nil, nil, nil, 0, errors.Wrap(err, "Read from errror") } + log.Debugf("rawSocketConn Recv %d bytes", n) + b = b[:n] srcMac, err := net.ParseMAC(addr.String()) @@ -100,12 +108,15 @@ func (s *rawSocketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, } var srcIp net.IP - ipLayer := p.Layer(layers.LayerTypeIPv4) - if ipLayer != nil { - ip4 := ipLayer.(*layers.IPv4) - srcIp = ip4.SrcIP - } else { - return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Fetch ip layer failed") + { + ipLayer := p.Layer(layers.LayerTypeIPv4) + if ipLayer != nil { + // ipv4 + ip4 := ipLayer.(*layers.IPv4) + srcIp = ip4.SrcIP + } else { + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Expect IP packet") + } } var srcPort uint16 @@ -114,11 +125,12 @@ func (s *rawSocketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, udpInfo := udpLayer.(*layers.UDP) srcPort = uint16(udpInfo.SrcPort) } else { - return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Fetch upd layer failed") + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Expect UDP packet") } dhcpLayer := p.Layer(layers.LayerTypeDHCPv4) if dhcpLayer != nil { + // dhcpv4 dhcp4 := dhcpLayer.(*layers.DHCPv4) sbf := gopacket.NewSerializeBuffer() if err := dhcp4.SerializeTo(sbf, gopacket.SerializeOptions{}); err != nil { @@ -126,11 +138,11 @@ func (s *rawSocketConn) Recv(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, } return sbf.Bytes(), &net.UDPAddr{IP: srcIp, Port: int(srcPort)}, srcMac, 0, nil } else { - return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Fetch dhcp layer failed") + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Expect DHCP packet") } } -func (s *rawSocketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr, ifidx int) error { +func (s *rawSocketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error { var dhcp = new(layers.DHCPv4) if err := dhcp.DecodeFromBytes(b, gopacket.NilDecodeFeedback); err != nil { return errors.Wrap(err, "Decode dhcp bytes error") @@ -151,7 +163,7 @@ func (s *rawSocketConn) Send(b []byte, addr *net.UDPAddr, destMac net.HardwareAd } var ( - srcPort = layers.UDPPort(s.dhcpServerPort) + srcPort = layers.UDPPort(s.serverPort) dstPort = layers.UDPPort(addr.Port) ) @@ -183,115 +195,3 @@ func (s *rawSocketConn) SetReadDeadline(t time.Time) error { func (s *rawSocketConn) SetWriteDeadline(t time.Time) error { return s.conn.SetWriteDeadline(t) } - -type linuxConn struct { - port uint16 - conn *ipv4.RawConn -} - -// NewSnooperConn creates a Conn that listens on the given UDP ip:port. -// -// Unlike NewConn, NewSnooperConn does not bind to the ip:port, -// enabling the Conn to coexist with other services on the machine. -func NewSnooperConn(addr string) (*Conn, error) { - return newConn(addr, false, newLinuxConn) -} - -func newLinuxConn(_ net.IP, port int, disableBroadcast bool) (conn, error) { - if port == 0 { - return nil, errors.Error("must specify a listen port") - } - - filter, err := bpf.Assemble([]bpf.Instruction{ - // Load IPv4 packet length - bpf.LoadMemShift{Off: 0}, - // Get UDP dport - bpf.LoadIndirect{Off: 2, Size: 2}, - // Correct dport? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipFalse: 1}, - // Accept - bpf.RetConstant{Val: 1500}, - // Ignore - bpf.RetConstant{Val: 0}, - }) - if err != nil { - return nil, err - } - - c, err := net.ListenPacket("ip4:17", "0.0.0.0") - if err != nil { - return nil, err - } - r, err := ipv4.NewRawConn(c) - if err != nil { - c.Close() - return nil, err - } - if err = r.SetControlMessage(ipv4.FlagInterface, true); err != nil { - c.Close() - return nil, errors.Wrap(err, "setting packet filter") - } - if err = r.SetBPF(filter); err != nil { - c.Close() - return nil, errors.Wrap(err, "setting packet filter") - } - - ret := &linuxConn{ - port: uint16(port), - conn: r, - } - return ret, nil -} - -func (c *linuxConn) Close() error { - return c.conn.Close() -} - -func (c *linuxConn) Recv(b []byte) (rb []byte, addr *net.UDPAddr, mac net.HardwareAddr, ifidx int, err error) { - hdr, p, cm, err := c.conn.ReadFrom(b) - if err != nil { - return nil, nil, nil, 0, err - } - if len(p) < 8 { - return nil, nil, nil, 0, errors.Error("not a UDP packet, too short") - } - sport := int(binary.BigEndian.Uint16(p[:2])) - return p[8:], &net.UDPAddr{IP: hdr.Src, Port: sport}, nil, cm.IfIndex, nil -} - -func (c *linuxConn) Send(b []byte, addr *net.UDPAddr, _ net.HardwareAddr, ifidx int) error { - packet := make([]byte, 8+len(b)) - // src port - binary.BigEndian.PutUint16(packet[:2], c.port) - // dst port - binary.BigEndian.PutUint16(packet[2:4], uint16(addr.Port)) - // length - binary.BigEndian.PutUint16(packet[4:6], uint16(8+len(b))) - copy(packet[8:], b) - - hdr := ipv4.Header{ - Version: 4, - Len: ipv4.HeaderLen, - TOS: 0xc0, // DSCP CS6 (Network Control) - TotalLen: ipv4.HeaderLen + 8 + len(b), - TTL: 64, - Protocol: 17, - Dst: addr.IP, - } - - if ifidx > 0 { - cm := ipv4.ControlMessage{ - IfIndex: ifidx, - } - return c.conn.WriteTo(&hdr, packet, &cm) - } - return c.conn.WriteTo(&hdr, packet, nil) -} - -func (c *linuxConn) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -func (c *linuxConn) SetWriteDeadline(t time.Time) error { - return c.conn.SetWriteDeadline(t) -} diff --git a/pkg/util/dhcp/conn_linux6.go b/pkg/util/dhcp/conn_linux6.go new file mode 100644 index 0000000000..e1666ebd40 --- /dev/null +++ b/pkg/util/dhcp/conn_linux6.go @@ -0,0 +1,190 @@ +// 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. + +// Copyright 2019 Yunion +// Copyright 2016 Google Inc. +// +// 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. + +//go:build linux +// +build linux + +package dhcp + +import ( + "net" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/mdlayher/packet" + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" +) + +func newRawSocketConn6(iface string, filter []bpf.RawInstruction, serverPort uint16) (conn, error) { + ifi, err := net.InterfaceByName(iface) + if err != nil { + return nil, errors.Wrap(err, "interface by name") + } + + ip, err := interfaceToIPv6Addr(ifi) + if err != nil { + return nil, errors.Wrap(err, "interfaceToIPv6Addr") + } + + // unix.ETH_P_ALL + conn, err := packet.Listen(ifi, packet.Raw, unix.ETH_P_IPV6, &packet.Config{ + Filter: filter, + }) + if err != nil { + return nil, errors.Wrap(err, "packet.Listen") + } + log.Debugf("newRawSocketConn6 on %s %s", ifi.Name, ip) + return &rawSocketConn{ + conn: conn, + iface: ifi, + ip: ip, + serverPort: serverPort, + }, nil +} + +func (s *rawSocketConn) Recv6(b []byte) ([]byte, *net.UDPAddr, net.HardwareAddr, int, error) { + // read packet + n, addr, err := s.conn.ReadFrom(b) + if err != nil { + return nil, nil, nil, 0, errors.Wrap(err, "Read from errror") + } + log.Debugf("rawSocketConn Recv6 %d bytes from %s", n, addr.String()) + + b = b[:n] + + srcMac, err := net.ParseMAC(addr.String()) + if err != nil { + return nil, nil, nil, 0, errors.Wrap(err, "Parse mac error") + } + + p := gopacket.NewPacket(b, layers.LayerTypeEthernet, gopacket.Default) + if p.ErrorLayer() != nil { + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Failed to decode packet") + } + + var srcIp net.IP + { + ipLayer := p.Layer(layers.LayerTypeIPv6) + if ipLayer != nil { + // ipv6 + ip6 := ipLayer.(*layers.IPv6) + srcIp = ip6.SrcIP + } else { + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Expect IPv6 packet") + } + } + + icmpLayer := p.Layer(layers.LayerTypeICMPv6) + if icmpLayer != nil { + 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") + } + } + + var srcPort uint16 + udpLayer := p.Layer(layers.LayerTypeUDP) + if udpLayer != nil { + udpInfo := udpLayer.(*layers.UDP) + srcPort = uint16(udpInfo.SrcPort) + } else { + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "expect UDP packet") + } + + dhcpLayer := p.Layer(layers.LayerTypeDHCPv6) + if dhcpLayer != nil { + // dhcpv6 + dhcp6 := dhcpLayer.(*layers.DHCPv6) + sbf := gopacket.NewSerializeBuffer() + if err := dhcp6.SerializeTo(sbf, gopacket.SerializeOptions{}); err != nil { + return nil, nil, nil, 0, errors.Wrap(err, "Serialize dhcp6 packet error") + } + return sbf.Bytes(), &net.UDPAddr{IP: srcIp, Port: int(srcPort)}, srcMac, 0, nil + } else { + return nil, nil, nil, 0, errors.Wrap(p.ErrorLayer().Error(), "Fetch dhcp layer failed") + } +} + +func (s *rawSocketConn) Send6(b []byte, addr *net.UDPAddr, destMac net.HardwareAddr) error { + var dhcp = new(layers.DHCPv4) + if err := dhcp.DecodeFromBytes(b, gopacket.NilDecodeFeedback); err != nil { + return errors.Wrap(err, "Decode dhcp bytes error") + } + + var eth = &layers.Ethernet{ + EthernetType: layers.EthernetTypeIPv4, + SrcMAC: s.iface.HardwareAddr, + DstMAC: destMac, + } + + var ip = &layers.IPv6{ + Version: 6, + HopLimit: 64, + SrcIP: s.ip, + DstIP: addr.IP, + NextHeader: layers.IPProtocolUDP, + } + + var ( + srcPort = layers.UDPPort(s.serverPort) + dstPort = layers.UDPPort(addr.Port) + ) + + var udp = &layers.UDP{ + SrcPort: srcPort, + DstPort: dstPort, + } + udp.SetNetworkLayerForChecksum(ip) + + var ( + buf = gopacket.NewSerializeBuffer() + opts = gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true} + ) + if err := gopacket.SerializeLayers(buf, opts, eth, ip, udp, dhcp); 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 dhcp packet error") + } + return nil +} diff --git a/pkg/util/dhcp/conn_unsupported.go b/pkg/util/dhcp/conn_unsupported.go index 2d85dbbe06..cd3db2260d 100644 --- a/pkg/util/dhcp/conn_unsupported.go +++ b/pkg/util/dhcp/conn_unsupported.go @@ -49,3 +49,7 @@ func NewSnooperConn(addr string) (*Conn, error) { func newRawSocketConn(iface string, filter []bpf.RawInstruction, dhcpServerPort uint16) (conn, error) { return nil, errors.New("raw socket Conns not supported on this OS") } + +func newRawSocketConn6(iface string, filter []bpf.RawInstruction, dhcpServerPort uint16) (conn, error) { + return nil, errors.New("raw IPv6 socket Conns not supported on this OS") +} diff --git a/pkg/util/dhcp/const.go b/pkg/util/dhcp/const.go index fdffb43957..084b8ac9fc 100644 --- a/pkg/util/dhcp/const.go +++ b/pkg/util/dhcp/const.go @@ -44,6 +44,10 @@ const ( CLIENT_ARCH_EFI_ARM64 ) +const ( + icmpRAFakePort = int(-1111) +) + func IsUEFIPxeArch(arch uint16) bool { switch arch { case CLIENT_ARCH_EFI_IA32: diff --git a/pkg/util/dhcp/helpers.go b/pkg/util/dhcp/helpers.go index 0cc38ca0df..ad6726dfd9 100644 --- a/pkg/util/dhcp/helpers.go +++ b/pkg/util/dhcp/helpers.go @@ -49,6 +49,10 @@ type ResponseConfig struct { NTPServers []net.IP // OptNTPServers 42 MTU uint16 // OptMTU 26 + ClientIP6 net.IP + Gateway6 net.IP + PrefixLen6 uint8 + // Relay Info https://datatracker.ietf.org/doc/html/rfc3046 RelayInfo []byte diff --git a/pkg/util/dhcp/interface.go b/pkg/util/dhcp/interface.go new file mode 100644 index 0000000000..8ff9d6c389 --- /dev/null +++ b/pkg/util/dhcp/interface.go @@ -0,0 +1,29 @@ +// 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 dhcp + +import ( + "net" +) + +type DHCPHandler interface { + ServeDHCP(pkt Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (Packet, []string, error) +} + +type DHCP6Handler interface { + DHCPHandler + + ServeRA(pkt Packet, cliMac net.HardwareAddr, addr *net.UDPAddr) (Packet, error) +} diff --git a/pkg/util/dhcp/packet6.go b/pkg/util/dhcp/packet6.go new file mode 100644 index 0000000000..c6f2049649 --- /dev/null +++ b/pkg/util/dhcp/packet6.go @@ -0,0 +1,303 @@ +// 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 dhcp + +import ( + "encoding/binary" + "net" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/netutils" +) + +type icmpV6OptMtu struct { + MTU uint32 +} + +func (opt icmpV6OptMtu) Bytes() []byte { + buf := make([]byte, 6) + binary.BigEndian.PutUint32(buf[2:], opt.MTU) + return buf +} + +func NewIcmpV6OptMtu(mtu uint32) icmpV6OptMtu { + return icmpV6OptMtu{ + MTU: mtu, + } +} + +/* +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 |L|A| Reserved1 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Valid Lifetime | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Preferred Lifetime | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reserved2 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + +| | ++ Prefix + +| | ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type icmpv6OptPrefixInfo struct { + PrefixLen uint8 + Flag uint8 + ValidLifetime uint32 + PreferredLifetime uint32 + Reserved2 uint32 + Prefix [16]byte +} + +func (opt icmpv6OptPrefixInfo) Bytes() []byte { + buf := make([]byte, 30) + buf[0] = opt.PrefixLen + buf[1] = opt.Flag + binary.BigEndian.PutUint32(buf[2:], opt.ValidLifetime) + binary.BigEndian.PutUint32(buf[6:], opt.PreferredLifetime) + binary.BigEndian.PutUint32(buf[10:], opt.Reserved2) + copy(buf[14:], opt.Prefix[:]) + return buf +} + +func NewIcmpv6OptPrefixInfo(gw net.IP, preflen uint8) icmpv6OptPrefixInfo { + return icmpv6OptPrefixInfo{ + PrefixLen: preflen, + Flag: 0x80, + ValidLifetime: 0xffffffff, + PreferredLifetime: 0xffffffff, + Reserved2: 0, + Prefix: [16]byte(gw), + } +} + +type icmpv6OptSourceTargetAddress struct { + Mac netutils.SMacAddr +} + +func (opt icmpv6OptSourceTargetAddress) Bytes() []byte { + return opt.Mac[:] +} + +func NewIcmpv6OptSourceTargetAddress(macAddr string) icmpv6OptSourceTargetAddress { + mac, _ := netutils.ParseMac(macAddr) + return icmpv6OptSourceTargetAddress{ + Mac: mac, + } +} + +func MakeRouterAdverPacket(gwIP net.IP, preflen uint8, mtu uint32) (Packet, error) { + pkt := layers.ICMPv6RouterAdvertisement{} + pkt.Flags = 0x80 // M=1, O=0 stateful DHCPv6 + + pkt.Options = layers.ICMPv6Options{ + layers.ICMPv6Option{ + Type: layers.ICMPv6OptMTU, + Data: NewIcmpV6OptMtu(mtu).Bytes(), + }, + layers.ICMPv6Option{ + Type: layers.ICMPv6OptPrefixInfo, + Data: NewIcmpv6OptPrefixInfo(gwIP, preflen).Bytes(), + }, + } + + sbf := gopacket.NewSerializeBuffer() + err := pkt.SerializeTo(sbf, gopacket.SerializeOptions{}) + if err != nil { + return nil, errors.Wrap(err, "SerializeTo") + } + return sbf.Bytes(), nil +} + +// DHCPv6 https://datatracker.ietf.org/doc/html/rfc8415 +/* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | msg-type | transaction-id | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . options . + . (variable number and length) . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +/* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | msg-type | hop-count | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | link-address | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | peer-address | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + . . + . options (variable number and length) .... . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// DHCPv6 Message Type +const ( + DHCPV6_SOLICIT MessageType = 1 + DHCPV6_ADVERTISE MessageType = 2 + DHCPV6_REQUEST MessageType = 3 + DHCPV6_CONFIRM MessageType = 4 + DHCPV6_RENEW MessageType = 5 + DHCPV6_REBIND MessageType = 6 + DHCPV6_REPLY MessageType = 7 + DHCPV6_RELEASE MessageType = 8 + DHCPV6_DECLINE MessageType = 9 + DHCPV6_RECONFIGURE MessageType = 10 + DHCPV6_INFORMATION_REQUEST MessageType = 11 + DHCPV6_RELAY_FORW MessageType = 12 + DHCPV6_RELAY_REPL MessageType = 13 +) + +type OptionCode6 uint16 + +const ( + DHCPV6_OPTION_CLIENTID OptionCode6 = 1 + DHCPV6_OPTION_SERVERID OptionCode6 = 2 + DHCPV6_OPTION_IA_NA OptionCode6 = 3 + DHCPV6_OPTION_IA_TA OptionCode6 = 4 + DHCPV6_OPTION_IAADDR OptionCode6 = 5 + DHCPV6_OPTION_ORO OptionCode6 = 6 + DHCPV6_OPTION_PREFERENCE OptionCode6 = 7 + DHCPV6_OPTION_ELAPSED_TIME OptionCode6 = 8 + DHCPV6_OPTION_RELAY_MSG OptionCode6 = 9 + DHCPV6_OPTION_AUTH OptionCode6 = 11 + DHCPV6_OPTION_UNICAST OptionCode6 = 12 + DHCPV6_OPTION_STATUS_CODE OptionCode6 = 13 + DHCPV6_OPTION_RAPID_COMMIT OptionCode6 = 14 + DHCPV6_OPTION_USER_CLASS OptionCode6 = 15 + DHCPV6_OPTION_VENDOR_CLASS OptionCode6 = 16 + DHCPV6_OPTION_VENDOR_OPTS OptionCode6 = 17 + DHCPV6_OPTION_INTERFACE_ID OptionCode6 = 18 + DHCPV6_OPTION_RECONF_MSG OptionCode6 = 19 + DHCPV6_OPTION_RECONF_ACCEPT OptionCode6 = 20 + DHCPV6_OPTION_IA_PD OptionCode6 = 25 + DHCPV6_OPTION_IAPREFIX OptionCode6 = 26 + DHCPV6_OPTION_INFORMATION_REFRESH_TIME OptionCode6 = 32 + DHCPV6_OPTION_SOL_MAX_RT OptionCode6 = 82 + DHCPV6_OPTION_INF_MAX_RT OptionCode6 = 83 +) + +// 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]}) +} + +// DHCPv6 hop Count for relay message +func (p Packet) HopCount() byte { + return p[1] +} + +// DHCPv6 link address for relay message +func (p Packet) LinkAddr() net.IP { + return net.IP(p[2:18]) +} + +// DHCPv6 peer address for relay message +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) SetTID(tid uint32) { + tidBytes := make([]byte, 4) + binary.BigEndian.PutUint32(tidBytes, tid) + p[1] = tidBytes[1] + p[2] = tidBytes[2] + p[3] = tidBytes[3] +} + +func (p Packet) SetHopCount(hops byte) { + p[1] = hops +} + +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 NewPacket6(opCode MessageType, tid uint32) Packet { + p := make(Packet, 4) + p.SetType6(opCode) + p.SetTID(tid) + return p +} + +type Option6 struct { + Code OptionCode6 + Value []byte +} + +// Appends a DHCP option to the end of a packet +/* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | option-code | option-len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | option-data | + | (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...) +} + +// 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 +} diff --git a/pkg/util/dhcp/server.go b/pkg/util/dhcp/server.go index 83962b7194..8075885423 100644 --- a/pkg/util/dhcp/server.go +++ b/pkg/util/dhcp/server.go @@ -15,14 +15,13 @@ package dhcp import ( - "context" - "fmt" "net" "runtime/debug" "golang.org/x/net/bpf" "yunion.io/x/log" + "yunion.io/x/pkg/errors" ) type DHCPServer struct { @@ -31,25 +30,15 @@ type DHCPServer struct { conn *Conn } -// net.ListenPacket -func NewDHCPServer(address string, port int) (*DHCPServer, error) { - dhcpAddr := fmt.Sprintf("%s:%d", address, port) - dhcpConn, err := NewConn(dhcpAddr, false) - if err != nil { - return nil, fmt.Errorf("New DHCP connection error: %v", err) - } - return &DHCPServer{ - Address: address, - Port: port, - conn: dhcpConn, - }, nil +type DHCP6Server struct { + DHCPServer } -// udp socket +// udp unicast socket func NewDHCPServer3(address string, port int) (*DHCPServer, error) { dhcpConn, err := NewSocketConn(address, port) if err != nil { - return nil, fmt.Errorf("New DHCP connection error: %v", err) + return nil, errors.Wrap(err, "New DHCP connection") } return &DHCPServer{ Address: address, @@ -58,28 +47,137 @@ func NewDHCPServer3(address string, port int) (*DHCPServer, error) { }, nil } -// raw socket -func NewDHCPServer2(iface string, dhcpServerPort, dhcpRelayPort uint16) (*DHCPServer, *Conn, error) { - // ip and udp and port 67 and port 68 - bpf := []bpf.RawInstruction{ - {Op: 0x28, Jt: 0, Jf: 0, K: 0x0000000c}, - {Op: 0x15, Jt: 0, Jf: 13, K: 0x00000800}, - {Op: 0x30, Jt: 0, Jf: 0, K: 0x00000017}, - {Op: 0x15, Jt: 0, Jf: 11, K: 0x00000011}, - {Op: 0x28, Jt: 0, Jf: 0, K: 0x00000014}, - {Op: 0x45, Jt: 9, Jf: 0, K: 0x00001fff}, - {Op: 0xb1, Jt: 0, Jf: 0, K: 0x0000000e}, - {Op: 0x48, Jt: 0, Jf: 0, K: 0x0000000e}, - {Op: 0x15, Jt: 0, Jf: 2, K: uint32(dhcpServerPort)}, - {Op: 0x48, Jt: 0, Jf: 0, K: 0x00000010}, - {Op: 0x15, Jt: 3, Jf: 4, K: uint32(dhcpRelayPort)}, - {Op: 0x15, Jt: 0, Jf: 3, K: uint32(dhcpRelayPort)}, - {Op: 0x48, Jt: 0, Jf: 0, K: 0x00000010}, - {Op: 0x15, Jt: 0, Jf: 1, K: uint32(dhcpServerPort)}, - {Op: 0x6, Jt: 0, Jf: 0, K: 0x00040000}, - {Op: 0x6, Jt: 0, Jf: 0, K: 0x00000000}, +// udp unicast socket +func NewDHCP6Server3(address string, port int) (*DHCP6Server, error) { + dhcpConn, err := NewSocketConn(address, port) + if err != nil { + return nil, errors.Wrap(err, "New DHCP6 connection") } - conn, err := NewRawSocketConn(iface, bpf, dhcpServerPort) + return &DHCP6Server{ + DHCPServer{ + Address: address, + Port: port, + conn: dhcpConn, + }, + }, nil +} + +const ( + bpfContinue uint8 = 0 + bpfProcDhcpResp uint8 = 11 + + bpfProcDhcpResp6 uint8 = 8 + bpfProcICMPv6 uint8 = 11 + bpfProcReadPkt6 uint8 = 14 + bpfProcEnd6 uint8 = 15 + + bpfProcReadPkt4 uint8 = 14 + bpfProcEnd4 uint8 = 15 + + udpProtocol uint32 = 17 + icmpProtocol uint32 = 58 +) + +var ( + v4bpf = []bpf.Instruction{ + /* 0 load ethernet type, 2bytes */ + &bpf.LoadAbsolute{Off: 12, Size: 2}, + + /* 1 if ether_type != 0x0800(IPv4) then jump to [end] else continue */ + &bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0800, SkipTrue: bpfGoto(2, bpfProcEnd4), SkipFalse: bpfContinue}, + /* 2 load ipv4 protocol, offset 23, 1 byte */ + &bpf.LoadAbsolute{Off: 23, Size: 1}, + /* 3 if ip_proto != UDP then jump to [end] else continue */ + &bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: udpProtocol, SkipTrue: bpfGoto(4, bpfProcEnd4), SkipFalse: bpfContinue}, + /* 4 if load ip flags & fragment_offset */ + &bpf.LoadAbsolute{Off: 20, Size: 2}, + /* 5 if ip_fragment_offset != 0 then jump to end/25(6+19) else continue */ + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: bpfGoto(6, bpfProcEnd4), SkipFalse: bpfContinue}, + /* 6 store ip_header_length*4 in register X */ + bpf.LoadMemShift{Off: 14}, + /* 7 load 14 + ip_header_len + 0 (UDP src port), 2 bytes */ + bpf.LoadIndirect{Off: 14, Size: 2}, + /* 8 if udp_src_port != 67 then jump to dhcp_resp/11(9+2) else continue */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 67, SkipTrue: bpfGoto(9, bpfProcDhcpResp), SkipFalse: bpfContinue}, + /* 9 load 14 + ip_header_len + 2 (udp dst port), 2 bytes */ + bpf.LoadIndirect{Off: 16, Size: 2}, + /* 10 if udp_dst_port == 68 then jump to read/24(11+13) else jump to end/25(11+14) */ + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 68, SkipTrue: bpfGoto(11, bpfProcReadPkt4), SkipFalse: bpfGoto(11, bpfProcEnd4)}, + /* 11 [dhcp_resp] if udp_src_port != 68 then jump to end/25(12+13) else continue, which means a UDP response */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 68, SkipTrue: bpfGoto(12, bpfProcEnd4), SkipFalse: bpfContinue}, + /* 12 load 14 + ip_header_len + 2 (udp dst port), 2 bytes */ + bpf.LoadIndirect{Off: 16, Size: 2}, + /* 13 if udp_dst_port != 67 then jump to end/25(14+11) else continue */ + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 67, SkipTrue: bpfGoto(14, bpfProcEnd4), SkipFalse: bpfContinue}, + + /* 14 [read] return the whole packet */ + bpf.RetConstant{Val: 0x40000}, + /* 15 [end] return none */ + bpf.RetConstant{Val: 0x0}, + } + + v6bpf = []bpf.Instruction{ + /* 0 load ethernet type, 2bytes */ + &bpf.LoadAbsolute{Off: 12, Size: 2}, + + /* 1 [ipv6] if ether_type != 0x86dd(IPv6) then jump to end/25(15 + 10) else continue */ + &bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x86dd, SkipTrue: bpfGoto(2, bpfProcEnd6), SkipFalse: bpfContinue}, + /* 2 load ipv6 next header, offset 20, 1 byte */ + &bpf.LoadAbsolute{Off: 20, Size: 1}, + /* 3 [udp6] if ip_proto != UDP then jump to [icmp6]/24(17 + 7) else continue */ + &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}, + /* 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}, + /* 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)}, + + /* 11 [icmp6] if ip_proto != ICMP then jump to [end]/24(17 + 7) 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)}, + + /* 14 [read] return the whole packet */ + bpf.RetConstant{Val: 0x40000}, + /* 15 [end] return none */ + bpf.RetConstant{Val: 0x0}, + } +) + +func bpfGoto(current uint8, jumpTo uint8) uint8 { + return jumpTo - current +} + +func getRawInstructions(obpf []bpf.Instruction) ([]bpf.RawInstruction, error) { + bpf := make([]bpf.RawInstruction, 0, len(obpf)) + for i := range obpf { + inst, err := obpf[i].Assemble() + if err != nil { + return nil, errors.Wrap(err, "invalid eBPF instruction") + } + bpf = append(bpf, inst) + } + return bpf, nil +} + +// raw socket +func NewDHCPServer2(iface string, port uint16) (*DHCPServer, *Conn, error) { + bpf, err := getRawInstructions(v4bpf) + if err != nil { + return nil, nil, errors.Wrap(err, "getRawInstructions") + } + conn, err := NewRawSocketConn(iface, bpf, port) if err != nil { return nil, nil, err } @@ -88,8 +186,20 @@ func NewDHCPServer2(iface string, dhcpServerPort, dhcpRelayPort uint16) (*DHCPSe }, conn, nil } -type DHCPHandler interface { - ServeDHCP(ctx context.Context, pkt Packet, addr *net.UDPAddr, intf *net.Interface) (Packet, []string, error) +func NewDHCP6Server2(iface string, port uint16) (*DHCP6Server, *Conn, error) { + bpf, err := getRawInstructions(v6bpf) + if err != nil { + return nil, nil, errors.Wrap(err, "getRawInstructions") + } + conn, err := NewRawSocketConn6(iface, bpf, port) + if err != nil { + return nil, nil, err + } + return &DHCP6Server{ + DHCPServer{ + conn: conn, + }, + }, conn, nil } func (s *DHCPServer) ListenAndServe(handler DHCPHandler) error { @@ -97,14 +207,18 @@ func (s *DHCPServer) ListenAndServe(handler DHCPHandler) error { return s.serveDHCP(handler) } +func (s *DHCP6Server) ListenAndServe(handler DHCP6Handler) error { + defer s.conn.Close() + return s.serveDHCP(handler) +} + func (s *DHCPServer) GetConn() *Conn { return s.conn } func (s *DHCPServer) serveDHCP(handler DHCPHandler) error { - ctx := context.WithValue(context.Background(), "dhcp_server", s) for { - pkt, addr, mac, intf, err := s.conn.RecvDHCP() + pkt, addr, mac, err := s.conn.RecvDHCP() if err != nil { log.Errorf("Receiving DHCP packet: %s", err) continue @@ -121,7 +235,7 @@ func (s *DHCPServer) serveDHCP(handler DHCPHandler) error { } }() - resp, targets, err := handler.ServeDHCP(ctx, pkt, addr, intf) + resp, targets, err := handler.ServeDHCP(pkt, mac, addr) if err != nil { log.Warningf("[DHCP] handler serve error: %v", err) return @@ -136,17 +250,84 @@ func (s *DHCPServer) serveDHCP(handler DHCPHandler) error { log.Debugf("[DHCP] Send packet back to %s", target) targetUdpAddr.IP = net.ParseIP(target) resp.SetGIAddr(targetUdpAddr.IP) - if err = s.conn.SendDHCP(resp, &targetUdpAddr, mac, intf); err != nil { + if err = s.conn.SendDHCP(resp, &targetUdpAddr, mac); err != nil { log.Errorf("[DHCP] 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, intf); err != nil { + if err = s.conn.SendDHCP(resp, addr, mac); err != nil { log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err) return } }() } } + +func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error { + for { + pkt, addr, mac, err := s.conn.RecvDHCP6() + if err != nil { + log.Errorf("Receiving DHCP packet: %s", err) + continue + } + + log.Debugf("[DHCP6] received packet %d from %s mac %s", len(pkt), addr, mac) + + go func() { + defer func() { + if r := recover(); r != nil { + log.Errorf("Serve panic error: %v", r) + debug.PrintStack() + } + }() + + if addr.Port == icmpRAFakePort { + // receive a RA solication + resp, err := handler.ServeRA(pkt, mac, addr) + if err != nil { + log.Warningf("[DHCP6] handler ServeRA error: %s", err) + return + } + if resp == nil { + log.Warningf("[DHCP6] hander ServeRA response null packet") + return + } + + //log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf) + err = s.conn.SendDHCP(resp, addr, mac) + if err != nil { + log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err) + } + return + } + resp, targets, err := handler.ServeDHCP(pkt, mac, addr) + if err != nil { + log.Warningf("[DHCP] handler serve error: %s", err) + return + } + if resp == nil { + // log.Warningf("[DHCP] hander response null packet") + return + } + if len(targets) > 0 { + targetUdpAddr := *addr + 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 { + 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 { + 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 e6f25556e6..6c3f69b20d 100644 --- a/pkg/util/netutils2/netutils.go +++ b/pkg/util/netutils2/netutils.go @@ -208,6 +208,11 @@ type SNetInterface struct { Mask net.IPMask mac string + Addr6 string + Mask6 net.IPMask + + Addr6LinkLocal string + Mtu int VlanId int @@ -231,10 +236,10 @@ func NewNetInterface(name string) *SNetInterface { return n } -func NewNetInterfaceWithExpectIp(name string, expectIp string) *SNetInterface { +func NewNetInterfaceWithExpectIp(name string, expectIp string, expectIp6 string) *SNetInterface { n := new(SNetInterface) n.name = name - n.fetchConfig(expectIp) + n.FetchConfig2(expectIp, expectIp6) return n } @@ -257,10 +262,11 @@ func (n *SNetInterface) FetchInter() *net.Interface { } func (n *SNetInterface) FetchConfig() { - n.fetchConfig("") + n.FetchConfig2("", "") } -func (n *SNetInterface) fetchConfig(expectIp string) { +// FetchConfig2 is used to fetch config with expectIp and expectIp6 +func (n *SNetInterface) FetchConfig2(expectIp string, expectIp6 string) { n.Addr = "" n.Mask = nil n.mac = "" @@ -278,12 +284,16 @@ func (n *SNetInterface) fetchConfig(expectIp string) { for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { - n.Addr = ipnet.IP.String() - n.Mask = ipnet.Mask - if len(expectIp) > 0 && n.Addr != expectIp { - continue - } else { - break + if (len(expectIp) > 0 && ipnet.IP.String() == expectIp) || (len(expectIp) == 0 && n.Addr == "") { + n.Addr = ipnet.IP.String() + n.Mask = ipnet.Mask + } + } else if ipnet.IP.To16() != nil { + if ipnet.IP.IsLinkLocalUnicast() { + n.Addr6LinkLocal = ipnet.IP.String() + } else if (len(expectIp6) > 0 && ipnet.IP.String() == expectIp6) || (len(expectIp6) == 0 && n.Addr6 == "") { + n.Addr6 = ipnet.IP.String() + n.Mask6 = ipnet.Mask } } } @@ -349,6 +359,10 @@ func (n *SNetInterface) IsSecretInterface() bool { return n.IsSecretAddress(n.Addr, n.Mask) } +func (n *SNetInterface) IsSecretInterface6() bool { + return n.Addr6LinkLocal != "" && n.Addr6 == "" +} + func (n *SNetInterface) IsSecretAddress(addr string, mask []byte) bool { log.Infof("MASK --- %s", mask) if reflect.DeepEqual(mask, SECRET_MASK) && strings.HasPrefix(addr, SECRET_PREFIX) { @@ -364,11 +378,11 @@ func GetSecretInterfaceAddress() (string, int) { return addr, SECRET_MASK_LEN } -func (n *SNetInterface) GetSlaveAddresses() [][]string { +func (n *SNetInterface) GetSlaveAddresses() []SNicAddress { addrs := n.GetAddresses() - var slaves = make([][]string, 0) + var slaves = make([]SNicAddress, 0) for _, addr := range addrs { - if addr[0] != n.Addr { + if addr.Addr != n.Addr && addr.Addr != n.Addr6 { slaves = append(slaves, addr) } } diff --git a/pkg/util/netutils2/netutils_linux.go b/pkg/util/netutils2/netutils_linux.go index 5126262708..106c261747 100644 --- a/pkg/util/netutils2/netutils_linux.go +++ b/pkg/util/netutils2/netutils_linux.go @@ -28,7 +28,12 @@ import ( "yunion.io/x/onecloud/pkg/util/procutils" ) -func (n *SNetInterface) GetAddresses() [][]string { +type SNicAddress struct { + Addr string + MaskLen int +} + +func (n *SNetInterface) GetAddresses() []SNicAddress { addrList := iproute2.NewAddress(n.name) addrs4 := n.getAddresses(addrList.List4) addrs6 := n.getAddresses(addrList.List6) @@ -38,19 +43,19 @@ func (n *SNetInterface) GetAddresses() [][]string { return addrs4 } -func (n *SNetInterface) getAddresses(listFunc func() ([]net.IPNet, error)) [][]string { +func (n *SNetInterface) getAddresses(listFunc func() ([]net.IPNet, error)) []SNicAddress { ipnets, err := listFunc() if err != nil { log.Errorf("list address %s: %v", n.name, err) return nil } - r := make([][]string, len(ipnets)) + r := make([]SNicAddress, len(ipnets)) for i, ipnet := range ipnets { ip := ipnet.IP masklen, _ := ipnet.Mask.Size() - r[i] = []string{ - ip.String(), - fmt.Sprintf("%d", masklen), + r[i] = SNicAddress{ + Addr: ip.String(), + MaskLen: masklen, } } return r @@ -82,8 +87,24 @@ func (n *SNetInterface) GetRouteSpecs() []iproute2.RouteSpec { return rets } -func (n *SNetInterface) ClearAddrs() error { - cmd := procutils.NewCommand("ip", "addr", "flush", "dev", n.name) +func (n *SNetInterface) Shutdown() error { + return n.setStatus("down") +} + +func (n *SNetInterface) Bringup() error { + return n.setStatus("up") +} + +func (n *SNetInterface) Reset() error { + err := n.Shutdown() + if err != nil { + return errors.Wrap(err, "shutdown") + } + return n.Bringup() +} + +func (n *SNetInterface) setStatus(status string) error { + cmd := procutils.NewCommand("ip", "link", "set", n.name, status) msg, err := cmd.Output() if err != nil { return errors.Wrap(err, strings.TrimSpace(string(msg))) diff --git a/pkg/util/netutils2/netutils_test.go b/pkg/util/netutils2/netutils_test.go index 665b3ebe09..5baf8674c2 100644 --- a/pkg/util/netutils2/netutils_test.go +++ b/pkg/util/netutils2/netutils_test.go @@ -17,6 +17,8 @@ package netutils2 import ( "testing" + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/types" ) @@ -82,17 +84,14 @@ func TestNewNetInterface(t *testing.T) { n := NewNetInterface("eth0") t.Logf("NetInterface: %s %s %s %s", n.name, n.Addr, n.Mask.String(), n.mac) addrs := n.GetAddresses() - t.Logf("addrs: %s", addrs) + t.Logf("addrs: %s", jsonutils.Marshal(addrs).String()) slaves := n.GetSlaveAddresses() - t.Logf("slaves: %s", slaves) + t.Logf("slaves: %s", jsonutils.Marshal(slaves).String()) routes := n.GetRouteSpecs() - t.Logf("routes: %s", routes) + t.Logf("routes: %s", jsonutils.Marshal(routes).String()) for i := range routes { t.Logf("route to %s", routes[i].Dst.String()) } - - m := NewNetInterface("docker0") - m.ClearAddrs() } func TestMyDefault(t *testing.T) {