分类: 未分类

  • CentOS 7 与 Windows 10 双系统安装

    因为某项目的需要,我被要求在一个工作站上安装 CentOS 7 和 Windows 10/11 双系统。考虑到自从研究生毕业先是参加工作后是重回学校读博,很久没有搞过太应用的东西了,而且印象中 Linux 和 Windows 10 系统两者安装起来并不是那么的一帆风顺,因此考虑认真看看具体的步骤。经过一下午的尝试和研究,发现整个安装过程中还是收获蛮大的,遂考虑将这一经历整理一下,供后续自己遇到类似问题能更快结果,当然如果能帮助到其他读者那我更是倍感欣慰。

    XFS 并不支持缩小空间!

    首先,在网上找到一个相关的教程非常容易,尤其是这种 Linux 和 Windows双系统混装的场景更是非常常见,我没花多少功夫便找到一个非常详尽的博客:centos7中安装win10搭建双系统

    由于我在开始着手这一工作时还没有拿到最终需要安装的机器,因此我考虑直接在一个 VMware Workstation 的虚拟机中安装测试。首先是比较顺利的下载CentOS 7 安装 ISO,导入到 VMware Workstation 中安装,所有配置均选择默认选项,磁盘分配了 100 GB 。因为我想完整模拟从一个仅安装了 CentOS 7 系统的机器到双系统的过程,所以我在磁盘配置里选择让 CentOS 安装程序自动为我分区。

    按照博客内容,我应该在第二步的时候将 100 GB 的空间缩小,然后把空余的空间分配到 Windows。然而,我在这一步就首先遇到了问题:CentOS 安装程序默认情况下会采用 LVM 的存储管理方法,一共分配了 3 个逻辑卷(Logical Volume),分别是 swap、root和 home,其中 root 和 home 采用 xfs 文件系统。结果当我用 gparted LiveCD 引导 gparted 图形程序打算缩小 LVM 分区时,发现完全无法缩小。使用命令行 lvreduce --resizefs -L -50G centos 命令手动缩放(https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/logical_volume_manager_administration/lv_reduce#LV_reduce),发现直接报错file system reduce is required and not supported (xfs),即xfs 不能执行 reduce 操作。因为对 xfs 并不是那么了解(经常用 Ubuntu/Debian 这类发行版的更多的和 ext4/3 等文件系统接触更多),我搜索了相关资料(https://unix.stackexchange.com/questions/619099/why-cant-we-reduce-xfs-filesystem),发现结论是

    XFS 并不支持缩小空间!

    之所以没有这样做,是因为基本上没有缩小大型文件系统的需求。存储很便宜,而且在大多数环境中,数据集和容量只会增长。

    大致意思就是说,xfs 的开发人员认为目前而言磁盘空间应该不是太大问题了,所以他们并不是很愿意增加这个功能,虽然按照 StackOverflow 的说法后续的一些 xfsutils 工具提供了体验版的缩小功能,但是我看到这个回复的时间(2021 年)后我决定直接放弃这一想法了,因为我只是做测试环境,环境应当是完全可控的。但是这里就留下来一个未来需要考虑的问题,假如**碰到那种xfs 占据了整个硬盘的系统该如何解决呢?**将所有文件全部备份再安装到一个新系统里固然可行,但是非常繁琐麻烦,如果以后有时间的话可以看看后续的新工具是否有效。

    Resizefs 选项在很多场景下不可用

    我决定绕过 xfs 的这一障碍,因此我开始重新安装了 CentOS 系统,这次我选择先让 CentOS 安装程序默认为我规划一个分区过程,然后我手动删除了 home 逻辑区,用 root 做 Linux 的根目录。安装过程非常顺利,anaconda 提供的安装程序界面也非常简洁美观。安装完毕后我顺利打开了 gparted LiveCD,结果发现 LVM 管理的磁盘分区仍然不支持直接用 gparted 来缩小,所以这次我先运行 lvreduce 命令,结果再一次碰到了问题:

    /usr/libexec/lvresize_fs_helper: execvp failed: No such file or directory
    

    看起来是缺少某个具体做 resize 工作的可执行文件,如果是在联网且系统安装完毕的环境中这个问题应该不大,但是在目前这个 LiveCD 的环境下面对这个问题是有些棘手的。我认为这个问题应当是 lvm 软件包可能缺失了一些非必需文件,讲道理不应当在这种一应俱全的 LiveCD 里出现。直接搜索这一问题有很多解决方案,可见这个问题并不是一个罕见问题,多数需要重新安装包或者做一些 patch 工作等,但是显然这些在 LiveCD 里执行太麻烦了,好在我在某个 maillist 下找到了最快的解决方案(https://bugs-devel.debian.org/cgi-bin/bugreport.cgi?bug=1065606):使用 –fs resize_fsadm 而不是 –resizefs。

    VMware SCSI 控制器大敌

    完成了 CentOS 7 的安装,也为 Windows 系统留足了存储空间,只要完成Windows 安装这一最简单的步骤,是否很快就能开始做引导调整这一核心工作了呢?答案是否定的,因为一直到这里我才了解到一个收获最大的难点。

    按照步骤打开Windows 安装程序,选择刚才分配好的 NTFS 分区安装!界面马上坡上冷水:

    嗯?这什么情况?硬盘没了?我立马重新引导 CentOS,结果一切正常啊,那看来问题应该不在硬盘这里。搜索下 Vmware、Windows、no drive等关键词,一些链接提到 ESXi下安装 Windows 可能出现这一情况,且最终答案是 SCSI 控制器的类型问题(https://answers.microsoft.com/en-us/insider/forum/all/win-10-doesnt-seem-to-install-on-vmware-it-doesnt/d277bcc1-9edf-431d-bb05-dd940ab7dbe3),但是没有找到和 Workstation 有关的。这时候我突然想到,因为我这次使用虚拟机的逻辑和往常有很大不同,一般而言虚拟机在安装后操作系统就已经确定了,一般也没有人会选择在虚拟机里装一个双系统,会不会是因为VMWare 给 CentOS 选择的支持的 SCSI 控制器和 Windows 并不那么无缝兼容(比如需要借助一些驱动程序),因此导致我换成 Windows 安装程序后无法读到原有的磁盘。经过一番查找我终于发现了https://www.nakivo.com/blog/scsi-controller-and-other-vmware-controller-types/这篇帖子,到这里我终于明白了,VMware Workstation 默认给 CentOS 7 准备的 SCSI 控制器是LSI Logic Parallel,但是 Windows 系统在 2008 版本后最佳支持的是LSI Logic SAS,这就是为什么找不到磁盘的原因。由于 ESXI 同样作为 VMware 的虚拟化产品,所以我猜想类似的解决方案应当能直接应用在 Workstation 上。最直接的解决方法当然是用额外的驱动找到原有的 SCSI 控制器(可参考这个帖子https://blog.csdn.net/weixin_42376940/article/details/113322604 ,虽然他是缺少RST 驱动),但是这样做要麻烦很多,由于在测试环境下我采用直接更改 SCSI 控制器类型的方法,由于Workstation 的管理界面并不支持直接指定类型,我直接修改vmx 文件中的scsi0:0.deviceType,根据博客信息:

    • buslogic – BusLogic SCSI
    • lsilogic – LSI Logic SCSI
    • lsisas1068 – LSI Logic SAS
    • pvscsi – VMware Paravirtual SCSI

    所以从 lsilogic 改成 lsisas1068。修改后重新开机,完美!Windows 安装程序已经能够正常运作了,但是这里就会留下另一个隐患:CentOS7 那边是不是就不能正常引导了呢?暂时不考虑这个问题,我们先按部就班把双系统引导工作做了。

    按照双系统安装博客的内容,安装好 Windows 后果然 CentOS 的引导被 Windows 覆盖了,此时系统只能进入 Windows。我重新用 CentOS 7 LiveCD 引导,选择 Troubleshooting — Rescue a CentOS System选项,让 LiveCD 自动找到CentOS 安装位置并挂载到/mnt/sysimage 目录下,然后就是chroot 操作切换到根,执行 grub2-install 和 grub2-mkconfig 操作,将 grub 和 initramdisk 问题解决。操作完成后,此时只看到了 CentOS的两个启动项,Windows 启动项没有出现,这个问题先放一下,我们先进入 CentOS 来看看。

    结果果然没那么简单,点击 CentOS 后系统迟迟没有进入,等待很久后出现了报错:

    dracut-initqueue[1697]: Warning: dracut-initqueue timeout - staring timeout scripts
    dracut-initqueue[1697]: Warning: Could not boot. 
    dracut-initqueue[1697]: Warning: dev/centos/root does not exist 
    dracut-initqueue[1697]: Warning: dev/centos/swap does not exist
    Starting Dracut Emergency Shell...
    

    也就是说,本应存在的两个LVM 分区找不到了。不算太出乎预料,我猜测应当是因为我改了 SCSI 设备的原因,导致 CentOS 已有的驱动并不能识别出来LSI Logic SAS。经过一番搜索,我找到了类似情况https://blog.csdn.net/Micha_Lu/article/details/125063992,但是帖子里对/etc/dracut.conf 文件进行了修改,然而在我的 Dracut 这个紧急情况终端下(我猜测是只有 initramfs ),根本没有/etc/、/boot 这些目录的挂载,甚至也没有大部分终端命令,只能用 journalctl 看看崩溃日志等等。

    因此这里我想到了,如果我进入到刚才用 LiveCD 引导后找到的根目录,chroot 后再执行这些功能应当可行。这样操作后我确实能够直接访问根系统了,但是发现没有/etc/dracut.conf 文件,这个我认为可能是发行版的差异问题。我在https://serverfault.com/questions/296015/change-vmware-scsi-in-redhat 这里终于知道了最终解决方案,帖子里提到 RHEL 7.0(猜想和 CentOS 7 仅仅有商业版和社区版的差别)没法通过 modprobe.conf 来应用驱动,所以应当用dracut -f -v --add-drivers mptsas 命令实现驱动在 initramfs加载,如此操作后问题终于得以解决,重启后 CentOS 7 成功引导。

    最为简单的双系统引导

    结果,我认为可能是最复杂的一个步骤反而没有遇到太大问题。成功进入 CentOS 后确实无论怎么执行 grub-install 和 grub-mkconfig 都没办法自动找到 Windows,因此我根据双系统安装帖子的方法,手动编辑/etc/grub.d/40_custem 文件,根据引导方式(BIOS 还是 UEFI)来决定添加内容。

    UEFI

    menuentry 'Windows 10' { 
      insmod part_gpt 
      insmod ntfs 
      set root='UUID=YOUR_WINDOWS_PARTITION_UUID' 
      chainloader EFI/Microsoft/Boot/bootmgfw.efi
    }
    

    UUID 可以通过 blkid 命令找到指定分区。

    BIOS

    menuentry 'Windows 10' {
      insmod part_msdos
      insmod ntfs
      set root='hd0,msdos3'
      chainloader +1
    }
    

    hd0,msdos3 指/dev/sda3,如果是其他分区则修改数字即可。

    自定义 Windows 位置后执行grub2-mkconfig -o /boot/grub2/grub.cfg 即可。

    结语

    没有想到双系统本身需要做的引导工作并不多,大部分时间竟然花在了测试环境所使用的SCSI 驱动问题上,不过总的来说还是收获不少的😄。

  • Go语言项目的组织方式

    构建方式

    最好保持传统使用make构建,避免新构建工具的构建成本

    Lint

    golangci-lint,并开启所有默认的linter规则

    文档

    doc.go存放该包的一般描述 如果一个文件放不下,那么可以准备一个docs文件夹

    Readme文件

    • 项目目标
    • 快速入门部分:开始处理项目时应该做的事情,任何依赖项,基本示例,描述项目的外部链接

    包组织方式

    最推荐的方法:在不被迫添加新包的前提下,直接将整个代码保存在根目录下,避免循环依赖 创建新包的理由:

    1. 当你有不止一种启动应用程序的方式,例如Web API和CLI共存的时候,创建一个/cmd包并包含cliweb子包,并在运行时指定到文件go run ./cmd/cli
    2. 当你想提取更详细的实现时
    3. 当你开始为密切相关的事物添加公共前缀时
    r := networkReader{}
    //
    r := network.Reader{}
    

    模块化

    两种组织方法

    1. 按种类组织
    .
    |-- handlers
    |   |-- course.go
    |   |-- lecture.go
    |   |-- profile.go
    |   |-- user.go
    |-- main.go
    |-- models
    |   |-- course.go
    |   |-- lecture.go
    |   |-- user.go
    |-- repositories
    |   |-- course.go
    |   |-- lecture.go
    |   |-- user.go
    |-- services
    |   |-- course.go
    |   |-- user.go
    |-- utils
        |-- strings.go
    

    缺点:每个类型、常量或函数都必须是公开的,才能在项目的另一部分访问;在写代码或者debug的时候,经常需要在多个包之间寻找问题。

    1. 按组件组织
    .
    |-- course
    |   |-- httphandler.go
    |   |-- model.go
    |   |-- repository.go
    |   |-- service.go
    |-- main.go
    |-- profile
        |-- httphandler.go
        |-- model.go
        |-- repository.go
        |-- service.go
    

    缺点:容易发生相互依赖的问题 例如,想在用户的个人资料上显示最近的课程,从个人资料的角度上来看,课程是一种外部依赖,解决方法是在profile下创建一个接口。

    type Courses interface {
        MostRecent(ctx context.Context, userID string, max int) ([]course.Model, error)
    }
    

    然后在course包中,公开实现该接口的服务

    type Courses struct {
        // 一些不能导出的变量
    }
    
    func (c Courses) MostRecent(ctx context.Context, userID string, max int) ([]Model, error) {
        // 实现逻辑
    }
    

    最后,在main.go中从course包中创建Courses实例并传递给profile包,便可以在不引发循环依赖的情况下实现该功能。此外,写profile的测试时,可以直接mock创建一个实现,甚至可以在没有course实现包的情况下开发和测试profile的功能。

    简洁架构

    应用程序或模块有4层,分别是Domain、Application、Ports、Adapters。

    • Domain:业务核心,没有外部依赖,不应该知道代码是在哪个上下文执行的
    • Application:应用程序用法的粘合点,所有Domain、Ports、Adapter的逻辑串联位置
    • Ports: 用户输入,获取信息后传入给Application
    • Adapters: 与外部通信,存储和获取数据,对低层细节的抽象。
  • Consul 学习

    consul agent -dev启动一个调试用的Consul agent,该agent运行在Server模式下。使用 <kbd>Ctrl</kbd> + <kbd>C</kbd>

  • 通过WIM的方式控制Windows

    首先需要开启windows中WIM服务,并且授权所有用户都能够进行访问(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system中LocalAccountTokenFilterPolicy设置成1)。

    其次,如果要支持psexec,还需要开启C盘的文件共享

    如果在linux下,使用到的是impacket里的psexec和wimexec工具。(使用时注意windows的防火墙和Windows defender)

    clone下来后在examples里面找到相关工具,不过首先需要先pip install这个包。

    使用方法参考: https://www.freebuf.com/sectool/175208.html https://blog.csdn.net/qq_37077262/article/details/103979530

    此外,可以在windows中直接用wbemtest工具来检查WIM的相关状况。

  • Win10 高版本不支持SNMP的解决方法

    参考:snmp-service-on-windows-10

    Win10 1809版本之后直接去掉了SNMP的支持,改成用自家的CIM模型来配置Windows Remote Management。如果仍然需要SNMP的配置,那么需要使用FoD(features on demand)的方法来安装。

    在管理员权限的powershell里输入

    Add-WindowsCapability -Online -Name "SNMP.Client~~~~0.0.1.0"
    

    或者是DISM的方式

    DISM /online /add-capability /capabilityname:SNMP.Client~~~~0.0.1.0
    

    检查是否安装成功

    Get-WindowsCapability -Online -Name "SNMP*"
    

    卸载方式

    Remove-WindowsCapability -Online -Name "SNMP.Client~~~~0.0.1.0"
    

    为了让SNMP能够访问,还需要进行相关的配置

  • 使用VMware vctl工具创建K8s集群

    vctl是VMware最新开发的一个CLI工具,主要是用来创建满足OCI标准的容器。vctl包含在VMware Workstation Pro 16 和 VMware Fusion 12以上版本中。

    使用vctl

    首先在使用vctl之前,我们首先需要知道,该工具只是一个管理前端,真正的容器操作是由守护进程来实现的。因此在使用该命令之前,我们要首先检查守护进程的状态(默认情况下是关闭的)。vctl的容器运行时包括三个文件:

    • bin/containerd:这个进程就是用来执行容器相关指令的,检查方法是vctl system info,开启方法是vctl system start,结束方法是vctl system stop
    • bin/containerd-shim-crx-v2:看到shim这个词,应该能够从docker联想到是用来作为容器根进程的,工作在CRX VM的容器和containerd进程之间。
    • bin/vctl:这个就是我们上面提到的前端管理工具。

    上面我们提到了CRX VM,这个其实就是VMware专门用来运行容器的轻量级虚拟机,只要有一个容器创建那么就会开启一个CRX VM,同样删掉这个容器这个VM就会停止并删除。容器名和CRX VM的名字是一样的。

    vctl的基本命令和docker完全一致,这也就是说,ps/run/start/stop/rm/rmi/build/create等命令都可以使用相同的方式使用。这里作为示例,给出一个nginx容器的创建过程。

    vctl pull nginx
    vctl run --name=myNginx -t -p 8888:80 -d nginx
    vctl ps -a
    vctl stop nginx
    vctl rm nginx
    vctl rmi nginx
    

    可以从任务管理器上看到相关进程的启动情况。

    进程图

    vctl的详细使用方法可以参考VMware的官方文档

    创建k8s集群

    从上面我们可以看到,vctl提供了类似于docker的容器管理功能,那么现在要创建k8s集群,只需要使用类似kubeadm的工具创建多个容器构成k8s的运行环境即可。这其中,KIND就是一个比较好用的k8s安装工具。从VMware Fusion 12开始,vctl工具提供了KIND的支持。使用方法也非常简单,只需要运行vctl kind即可。这条命令会做四件事情:

    • 在用户目录下创建一个.vctl文件夹

    • 下载kubectlkindcrx.vmdk三个文件,保存在bin目录下

    • 创建一个名为docker的链接文件,指向vctl

    • 创建一个基于vctl的KIND上下文,主要是将~/.vctl/bin文件夹加入到PATH环境变量中

    运行这条命令后,会弹出一个新的终端,这个终端上的kubectl、kind、docker等命令均指向的是刚才安装的KIND上下文环境,而不是原先系统安装的。

    需要注意的是,vctl并不支持kind buildkind export logs命令,且CRX VM的默认配置是2G 2C。

    现在已经有了kind的环境了,剩下的事情就交给kind了。KIND文档

    最简单的命令,kind create cluster

    C:\Program Files (x86)\VMware\VMware Workstation\bin>kind create cluster
    Creating cluster "kind" ...
     • Ensuring node image (kindest/node:v1.18.2) ?  ...
     ✓ Ensuring node image (kindest/node:v1.18.2) ?
     • Preparing nodes ?   ...
     ✓ Preparing nodes ?
     • Writing configuration ?  ...
     ✓ Writing configuration ?
     • Starting control-plane ?️  ...
     ✓ Starting control-plane ?️
     • Installing CNI ?  ...
     ✓ Installing CNI ?
     • Installing StorageClass ?  ...
     ✓ Installing StorageClass ?
    Set kubectl context to "kind-kind"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-kind
    
    Thanks for using kind! ?
    

    使用kubectl cluster --context kind-kind可以看看刚刚创建的k8s集群状态。

    C:\Program Files (x86)\VMware\VMware Workstation\bin>kubectl cluster-info --context kind-kind
    Kubernetes master is running at https://127.0.0.1:64039
    KubeDNS is running at https://127.0.0.1:64039/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    
    

    此时,kubectl get nodeskubectl get pods等命令已经可以正常使用了,我们已经使用vctl创建了一个单节点的k8s环境。

    看看原理

    安装过程到这里就差不多了,更复杂的k8s集群只需要在kind中传入自定义的配置文件即可。如果这个时候我们使用docker ps命令查看真正用来承载k8s集群的容器,就会发现实际上只有一个容器在运行。

    C:\Program Files (x86)\VMware\VMware Workstation\bin>docker ps -a
    ────                 ─────                                                                                  ───────                   ──                ─────            ──────    ─────────────
    NAME                 IMAGE                                                                                  COMMAND                   IP                PORTS            STATUS    CREATION TIME
    ────                 ─────                                                                                  ───────                   ──                ─────            ──────    ─────────────
    kind-control-plane   kindest/node@sha256:7b27a6d0f2517ff88ba444025beae41491b016bc6af573ba467b70c5e8e0d85f   /usr/local/bin/entry...   192.168.100.132   64039:6443/tcp   running   2020-09-17T16:50:22+08:00
    

    是不是感觉很奇怪,因为如果你尝试过其他的安装方法,应该会有印象,那就是k8s的运行组件相当多,控制节点的apiServerschedulercontroller-manager以及计算节点上kubelet这些在其他安装方法中一般都是由独立的容器来运行的,那么在这里为什么会只有一个呢?

    通过查阅KIND文档可以发现,原来KIND使用node-image来运行这些组件,例如kubeadmkubelet等等,而这些运行依赖和node-image本身则是由base-image来生成的。生成过程我们可以不用关心,现在让我们主要来看看node-image这个镜像生成的单个容器是如何运行所有的k8s组件。

    使用docker exec -it kind-control-plane /bin/bash进入到容器中,首先ps看看运行的进程。

    root@kind-control-plane:/kind# ps ajxf
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        0  6178  6178  6178 pts/3     6913 Ss       0   0:00 /bin/bash
     6178  6913  6913  6178 pts/3     6913 R+       0   0:00  \_ ps ajxf
        0     1     1     1 ?           -1 Ss       0   0:00 /sbin/init
        1   104   104   104 ?           -1 S<s      0   0:00 /lib/systemd/systemd-journald
        1   113   113   113 ?           -1 Ssl      0   0:18 /usr/local/bin/containerd
        1   311   311   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 00d79d58327ca059b96cdd9c651d12853a2e2622e65472230414dc15d65821bd -address /run/containerd/con
      311   405   405   405 ?           -1 Ss       0   0:00  \_ /pause
      311   507   507   507 ?           -1 Ssl      0   0:38  \_ kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc
        1   322   322   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 482207771a3b7eba237dc037dba4f5717e54842f7bf38d367f391030a9091f38 -address /run/containerd/con
      322   401   401   401 ?           -1 Ss       0   0:00  \_ /pause
      322   547   547   547 ?           -1 Ssl      0   1:56  \_ kube-apiserver --advertise-address=192.168.100.132 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt
        1   323   323   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 5101a9a24071e5c10d052d01918361dee8a97a2822b4bb82bc772460ce0045a2 -address /run/containerd/con
      323   399   399   399 ?           -1 Ss       0   0:00  \_ /pause
      323   586   586   586 ?           -1 Rsl      0   0:47  \_ etcd --advertise-client-urls=https://192.168.100.132:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/
        1   324   324   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id e68a9f43205edd282548f6db0942c0c0b503a66894b8bf5e3d7d1b7e5be8a71d -address /run/containerd/con
      324   404   404   404 ?           -1 Ss       0   0:00  \_ /pause
      324   500   500   500 ?           -1 Ssl      0   0:10  \_ kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127
        1   632   632   632 pts/0      632 Ssl+     0   0:50 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.
        1   810   810   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 6bb16a0dfa1b006c0c241d54f84c8fc17d719ca791f482ede021e0a9ae118eec -address /run/containerd/con
      810   860   860   860 ?           -1 Ss       0   0:00  \_ /pause
      810   928   928   928 ?           -1 Ssl      0   0:01  \_ /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=kind-control-plane
      928   986   983   928 ?           -1 Z        0   0:01      \_ [iptables-nft-sa] <defunct>
        1   837   837   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id e17005058a2afb322c2c9179d5468b65be0bd8c2a24e3774373988413c93a7ec -address /run/containerd/con
      837   877   877   877 ?           -1 Ss       0   0:00  \_ /pause
      837   935   935   935 ?           -1 Ssl      0   0:00  \_ /bin/kindnetd
      935   994   992   935 ?           -1 Z        0   0:00      \_ [iptables-nft-sa] <defunct>
        1  1108  1108   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 905595d932140fd9406ae9487893d6e6351b15f33ab39528a4da00650df485ec -address /run/containerd/con
     1108  1131  1131  1131 ?           -1 Ss       0   0:00  \_ /pause
     1108  1160  1160  1160 ?           -1 Ssl      0   0:03  \_ local-path-provisioner --debug start --helper-image k8s.gcr.io/debian-base:v2.0.0 --config /etc/config/config.json
        1  1244  1244   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 410e09a86cb001d1b40378e153406d7a1c85c9f04f3e68ded23dc19c1fe50940 -address /run/containerd/con
     1244  1305  1305  1305 ?           -1 Ss       0   0:00  \_ /pause
     1244  1380  1380  1380 ?           -1 Ssl      0   0:08  \_ /coredns -conf /etc/coredns/Corefile
        1  1252  1252   113 ?           -1 Sl       0   0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 0fdc07bfbd62610b783122e3d8dca8f5b1ad2a1aa07f2bb7b1194df280116957 -address /run/containerd/con
     1252  1294  1294  1294 ?           -1 Ss       0   0:00  \_ /pause
     1252  1374  1374  1374 ?           -1 Ssl      0   0:08  \_ /coredns -conf /etc/coredns/Corefile
    

    那么从这里看就可以看出来KIND的构建方法了,在kind-control-plane这个容器中运行由kubelet程序用来支持一个运行节点,控制节点所需要的组件则是由内置的一个containerd来运行的,使用ctr -n k8s.io containers ls命令可以更加直观的看到。

    root@kind-control-plane:/kind# ctr -n k8s.io containers ls
    CONTAINER                                                           IMAGE                                               RUNTIME
    00d79d58327ca059b96cdd9c651d12853a2e2622e65472230414dc15d65821bd    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    07dd95f8b88f11974154232d894e1ac9be76dec8c7e88c74a5d7f611d4bf94a0    k8s.gcr.io/kube-controller-manager:v1.18.2          io.containerd.runc.v2
    0fdc07bfbd62610b783122e3d8dca8f5b1ad2a1aa07f2bb7b1194df280116957    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    410e09a86cb001d1b40378e153406d7a1c85c9f04f3e68ded23dc19c1fe50940    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    470d408aa74f66355ab637295a8e4777ec6775f84aba75a17efa861e5ede6470    k8s.gcr.io/kube-scheduler:v1.18.2                   io.containerd.runc.v2
    482207771a3b7eba237dc037dba4f5717e54842f7bf38d367f391030a9091f38    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    5101a9a24071e5c10d052d01918361dee8a97a2822b4bb82bc772460ce0045a2    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    652f25e9876c8efaae6b21f7289bfd88372ce9ec4ae6593f9e1c81bb3f513ddd    k8s.gcr.io/etcd:3.4.3-0                             io.containerd.runc.v2
    6bb16a0dfa1b006c0c241d54f84c8fc17d719ca791f482ede021e0a9ae118eec    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    7f9f6cd14c83c08f5906a280f7fc5b178e766c0e4bb137a316d35c61f05c5031    k8s.gcr.io/kube-apiserver:v1.18.2                   io.containerd.runc.v2
    905595d932140fd9406ae9487893d6e6351b15f33ab39528a4da00650df485ec    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    c90fa3e64763263042ac6f29657e3a76de67a90308b9c7cf3a5cefbeda6788c8    k8s.gcr.io/kube-proxy:v1.18.2                       io.containerd.runc.v2
    d0c86ec34d96251e5bc4992183b21021c803e033463f581de9adf3bdadd4b4c8    k8s.gcr.io/coredns:1.6.7                            io.containerd.runc.v2
    d43682f949020c15ff5a7997bfbd872fd6959949962e57e9ccfaf4633ae77039    k8s.gcr.io/coredns:1.6.7                            io.containerd.runc.v2
    dbabbb66c17ba1317706b3e13a473b7e52653d6097f1fb4e71ca3aca51c17e37    docker.io/rancher/local-path-provisioner:v0.0.12    io.containerd.runc.v2
    e17005058a2afb322c2c9179d5468b65be0bd8c2a24e3774373988413c93a7ec    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    e68a9f43205edd282548f6db0942c0c0b503a66894b8bf5e3d7d1b7e5be8a71d    k8s.gcr.io/pause:3.2                                io.containerd.runc.v2
    f49eaf48b0b9ac0ef29d5dc6a8c3b8e26c9dd9a681715e8f431ad6ec4d452dbd    docker.io/kindest/kindnetd:0.5.4                    io.containerd.runc.v2
    

    这里还有一个疑问,大部分容器引擎在Nesting运行的时候是需要做一定设置的,然而在containerd这里我没有看到额外的设置。是containerd原生就支持nesting运行还是说这是因为vctl的特殊运行方式(指一个容器一个VM)就不得而知了,未来有空会看看原理。

  • Suricata学习

    Suricata是一款开源免费的网络威胁检测系统,可以在网络中作为IDS(Intrusion Detection System,入侵检测系统)、IPS(Intrusion Prevention System,入侵防御系统)和NSM(Network Security Monitoring,网络安全监控)使用,同样还可以离线分析pcap文件。Suricata使用专门的语言编写的规则来对网络流量进行分析,还可以利用Lua脚本来更加精确地分析,并以类似YAML或JSON的形式输出,可以方便存储在数据库中。目前Suricata项目属于OISF所有,OISF是一个非营利组织。

    Installation

    Suricata和其他的Linux软件类似,也具有两种安装方法,即直接安装发行版和编译安装。

    PPA安装法

    以下操作均在Ubuntu 16.04中,其他发行版可以查阅官方wiki。

    sudo add-apt-repository ppa:oisf/suricata-stable
    sudo apt-get update 
    sudo apt-get install suricata 
    

    编译安装法

    首先需要安装程序依赖库

    sudo apt-get -y install libpcre3 libpcre3-dbg libpcre3-dev \
    build-essential autoconf automake libtool libpcap-dev libnet1-dev \
    libyaml-0-2 libyaml-dev zlib1g zlib1g-dev libcap-ng-dev libcap-ng0 \
    make libmagic-dev libjansson-dev libjansson4 pkg-config
    

    下载源码

    VER=3.1
    wget "http://www.openinfosecfoundation.org/download/suricata-$VER.tar.gz" 
    tar -xvzf "suricata-$VER.tar.gz" 
    cd "suricata-$VER" 
    

    配置安装

    ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
    make
    sudo make install
    sudo ldconfig
    

    Suricata还提供了一些自动安装的脚本

    make install-conf 自动创建和安装配置文件
    make install-rules 自动从Emergeing Threats下载最新的规则集
    make install-full 将上面两者都包括
    

    Setup

    接下来我们需要部署Suricata,保证下面的命令均具备管理员权限。

    mkdir /var/log/suricata # 日志信息
    mkdir /etc/suricata # 配置文件
    cp classification.config /etc/suricata
    cp reference.config /etc/suricata
    cp suricata.yaml /etc/suricata
    

    /etc/suricata/suricata.yaml文件中正确配置好变量。HOME_NET设置为本地网络的IP地址,而EXTERNAL_NET建议的设置值是!$HOME_SET,这样所有不是本地IP的流量均被当作外界地址,当然设置成any也是可以的,只是这样的话会产生一些假的警报。下面的一些服务器均会被默认设置为$HOME_NETAIM_SERVERS设置为any

    Run

    Suricata的运行方式比较简单,只需要选择需要监听的网络接口,使用类似于下面的命令即可。

    sudo suricata -c /etc/suricata/suricata.yaml -i wlan0
    

    运行后产生的日志文件在/var/log/suricata目录下,我们可以使用类似于tail -f http.log stats.log的命令来监视程序的执行结果。

    Rules

    Suricata中最重要的就是关于规则的指定,使用特定的规则集就可以对特定的流量进行分析和处理,如果使用了IPS模式那么还可以直接处理报文内容。一般来说,我们会可以从互联网上下载最新的规则集,一般来自于Emerging Threats(Pro)和Sourcefire的VRT。手动管理的方式比较麻烦,我们可以使用到一款工具Oinkmasteroinkmaster可以自动化下载、管理rules。一般来说,规则由三个部分构成:Action、Header和Rule options。 例如下面这条

    alert tcp $EXTERNAL_NET any -> $HOME_NET 8888 (msg: "meow"; content: "meow"; )
    
    • alert表示动作,表示匹配后将发出警报。
    • tcp表示是TCP报文,还可以是ipudpicmp等,还包括一些常见的应用层协议。
    • $EXTERNAL表示使用前面定义的外部地址,可以使用!1.1.1.1![1.1.1.1, 1.1.1.2][10.0.0.0/24, !10.0.0.5]等形式。
    • any表示端口,有[79,80:82,83]这样的形式。
    • ->表示方向,可以是-><>
    • (msg: "meow"; content: "meow"; )表示规则选项,中间使用分号断开,包括meta-information、headers、payloads和flows等选项。具体内容将在后面说明。

    Meta-settings

    Meta-settings不会影响检测过程,只是用来完成记录等附属功能。

    msg: "some description"; 将显示在日志中
    sid: 123; 每条规则的编号
    rev: 123; 规则的版本号
    gid: 1; 组编号
    classtype: trojan-activity; 规则的分类
    reference: bugtraq, 123; http://www.securityfocus.com/bid; 规则的参考位置
    priority:1; 规则优先级
    metadata: ...;
    target: [src_ip|dest_ip];
    

    Header Keywords

    ttl: 10;
    ipopts: lsrr; IP选项
    sameip; 源IP和目的IP相同
    ip_proto: TCP;
    id: 1;
    geoip: src, RU;
    fragbits:[*+!]<[MDR]>;
    fragoffset:[!|<|>]<number>;
    seq:0;
    ack:1;
    window:[!]<number>;
    itype:min<>max;
    itype:[<|>]<number>;
    icode:min<>max;
    icode:[<|>]<number>;
    icmp_id:<number>;
    icmp_seq:<number>;
    

    Payload Keywords

    content:"a|0D|bc";
    content:"|61 0D 62 63|";
    content:"a|0D|b|63|";
    nocase;
    depth:12;
    offset:3;
    

    Flowbits

    通过在Suricata中保存标志位来判断若干个流量的关联性

    flowbits: set, name                设置name指定的条件
    flowbits: isset, name              检查是否有name指定的条件设置
    flowbits: toggle, name             切换name指定的条件设置情况
    flowbits: unset, name              取消设置name指定的条件
    flowbits: isnotset, name           检查是否没有name指定的条件设置
    flowbits: noalert                  不产生alert
    

    Flow

    匹配流的方向,是否建立连接等

    flow:to_client, established
    flow:to_server, established, only_stream
    flow:to_server, not_established, no_frag
    

    原理

    Suricata有几个关键组件构成:线程、线程模块和队列。Suricata以多线程的方式运行,而线程模块即对应其包获取、解码、检测和输出模块。一个包在Suricata会以类似流水线的方式一级一级地传递给下一个线程模块处理,而在这里的“传送带”就是队列。一个线程可以包含多个线程模块,这就是Runmode。 使用suricata --list-runmodes可以看到Suricata目前可以使用的runmodes。

    ------------------------------------- Runmodes ------------------------------------------
    | RunMode Type      | Custom Mode       | Description 
    |----------------------------------------------------------------------------------------
    | PCAP_DEV          | single            | Single threaded pcap live mode 
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Multi threaded pcap live mode.  Packets from each flow are assigned to a single detect thread, unlike "pcap_live_auto" where packe
    ts from the same flow can be processed by any detect thread 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers pcap live mode, each thread does all tasks from acquisition to logging 
    |----------------------------------------------------------------------------------------
    | PCAP_FILE         | single            | Single threaded pcap file mode 
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Multi threaded pcap file mode.  Packets from each flow are assigned to a single detect thread, unlike "pcap-file-auto" where packe
    ts from the same flow can be processed by any detect thread 
    |----------------------------------------------------------------------------------------
    | PFRING(DISABLED)  | autofp            | Multi threaded pfring mode.  Packets from each flow are assigned to a single detect thread, unlike "pfring_auto" where packets fro
    m the same flow can be processed by any detect thread 
    |                   ---------------------------------------------------------------------
    |                   | single            | Single threaded pfring mode 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers pfring mode, each thread does all tasks from acquisition to logging 
    |----------------------------------------------------------------------------------------
    | NFQ               | autofp            | Multi threaded NFQ IPS mode with respect to flow 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Multi queue NFQ IPS mode with one thread per queue 
    |----------------------------------------------------------------------------------------
    | NFLOG             | autofp            | Multi threaded nflog mode   
    |                   ---------------------------------------------------------------------
    |                   | single            | Single threaded nflog mode  
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers nflog mode          
    |----------------------------------------------------------------------------------------
    | IPFW              | autofp            | Multi threaded IPFW IPS mode with respect to flow 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Multi queue IPFW IPS mode with one thread per queue 
    |----------------------------------------------------------------------------------------
    | ERF_FILE          | single            | Single threaded ERF file mode 
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Multi threaded ERF file mode.  Packets from each flow are assigned to a single detect thread 
    |----------------------------------------------------------------------------------------
    | ERF_DAG           | autofp            | Multi threaded DAG mode.  Packets from each flow are assigned to a single detect thread, unlike "dag_auto" where packets from the 
    same flow can be processed by any detect thread 
    |                   ---------------------------------------------------------------------
    |                   | single            | Singled threaded DAG mode   
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers DAG mode, each thread does all  tasks from acquisition to logging 
    |----------------------------------------------------------------------------------------
    | AF_PACKET_DEV     | single            | Single threaded af-packet mode 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers af-packet mode, each thread does all tasks from acquisition to logging 
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Multi socket AF_PACKET mode.  Packets from each flow are assigned to a single detect thread. 
    |----------------------------------------------------------------------------------------
    | NETMAP(DISABLED)  | single            | Single threaded netmap mode 
    |                   ---------------------------------------------------------------------
    |                   | workers           | Workers netmap mode, each thread does all tasks from acquisition to logging 
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Multi threaded netmap mode.  Packets from each flow are assigned to a single detect thread. 
    |----------------------------------------------------------------------------------------
    | UNIX_SOCKET       | single            | Unix socket mode            
    |                   ---------------------------------------------------------------------
    |                   | autofp            | Unix socket mode            
    |----------------------------------------------------------------------------------------
    

    可以看到,在Suricata中包含三种Custom Mode,single/workers/autofp,根据右边的介绍我们能够知道当前模式的运行特点。在workers模式下,每一个线程上包含一个完整的包处理模块,也就是说将获取到的报文将分发到包处理线程中,而Suricata将会将属于同一个flow的流量放在一个线程中避免出现问题。

    其他支持软件

    Oinkmaster

    oinkmaster.pl -C /etc/oinkmaster.conf -o /etc/suricata/rules -i
    

    Suricata配置文件suricata.yaml中的outputs2 > unified2-alert可以设定在产生alert时dump出可疑数据包的信息,这个格式的好处是:

    • 方便归档管理
    • 生成速度快。

    Barnyard2

    Barnyard2就是个类似Syslog的东西,从Snort/Suricata处取得unified2格式的输入,产生其他格式的输出,比如给Prelude Hybrid IDS system、Syslog、MySQL。

  • OpenFlow协议学习

    OpenFlow 1.0 协议总结

    OpenFlow交换机中包含了1个以上的流表,流表又包含多个流表项。对于接收到的数据包,OpenFlow交换机在流表中选择一个最合适的流表项,并按流表项的内容对数据包进行处理。

    流表中包含的流表项由以下3个基本要素组成:Head Field, Counter, Action。Head Field即匹配规则,Counter即匹配次数,Action即匹配后所需要采取的操作。

    流表项

    Head Field

    • Ingress Port
    • Ethernet source address
    • Ethernet destination address
    • Ethernet type
    • VLAN id
    • VLAN Priority(802.1q PCP)
    • IP source address
    • IP destination address
    • ToS
    • Transport source port/ICMP type
    • Transport destination port/ICMP code

    在1.0版本中,STP(802.1d)数据包不执行流表匹配。完成STP处理后,执行“头字段解析之后”,会在此基础上进行数据包与流表的匹配。当数据包与刘表内设置的多条流表项匹配时,优先级最高的流表项即为匹配结果。OpenFlow中可以存在多个流表,但必须从流表0开始匹配。若编号为0的流表中不存在与数据包相匹配的流表项,而在其他流表中存在的话,则前进到下一流表中执行匹配。 数据包与OpenFlow交换机中任何一个流表项都不匹配的情况称之为Table-miss,这种情况下会利用Packet-In消息将数据包转发至控制器,或者丢弃。具体情况会根据设置决定。

    Counter

    4种计数器 Per Table, Per Port, Per Flow, Per Queue。

    Per Table: Active Entries, Packet Lookups, Packet Matches

    Per Flow: Received Packets/Bytes, Duration(msec/usec)

    Per Queue: Transmit Packets/Bytes, Transmit Overrun Errors

    Per Port: Received Packets/Bytes/Drops/Errors, Transmit Packets/Bytes/Drops/Errors, Receive Frame Alignment Errors, Received Overrun Errors, Received CRC Errors, Collisions

    Action

    Forward, Drop, Enqueue(Optional), Modify-Field(Optional)

    Forward

    名称 说明 虚拟端口
    (port) 转发至指定端口 port
    ALL 除接收端口以外的所有端口 0xfffc
    CONTROLLER 控制器 0xfffd
    LOCAL 发送至本地网络栈 0xfffe
    TABLE 执行流表中行动(仅Packet-Out) 0xfff9
    IN_PORT 从输入端口发出 0xfff8
    NORMAL(optional) 传统L2或L3交换机动作 0xfffa
    FLOOD(optional) 按照STP发送 0xfffb

    Drop

    未在协议中明确说明

    Enqueue

    在等待队列的末尾添加数据包

    Modify-Field

    • 设置VLAN ID
    • 设置VLAN priority
    • 去掉VLAN头
    • 修改源MAC
    • 修改目的MAC
    • 修改源IP
    • 修改目的IP
    • 修改ToS
    • 修改源端口
    • 修改目的端口

    控制器和交换机之间的安全通道

    安全通道通过控制面网络来建立,不受OpenFlow交换机中的流表项的影响。规范中安全通道应该使用TLS实现,但在OpenFlow 1.1开始添加了TCP明文的方式,默认的TCP端口为6653。

    struct openflow_header {
        uint8_t version; // 版本,OpenFlow 1.0-1.3分别对应0x01-0x04
        uint8_t type; // 消息类型,表明OpenFlow消息的类型
        uint16_t length; // Byte数
        uint32_t xid; // 事务ID
    }
    

    安全通道的建立过程

    1. 建立TCP/TLS连接
    2. 确定使用的OpenFlow版本,type字段值为OFPT_HELLO,在version中放入各自支持的最大版本号。
    3. 握手,type字段值为OFPT_FEATURES_REQUEST和OFPT_FEATURES_RESPONSE
    struct features_response {
        uint64_t datapath_id; // datapath id唯一标识OpenFlow交换机
        uint32_t n_buffer; // 缓存数据包的最大个数
        uint8_t n_tables; // 支持的流表个数
        char pad[24]; 
        uint32_t capabilities; // 支持的容量
        uint32_t actions; // 支持的行动
        struct ofp_phy_port ports[??]; // 物理端口信息
    }
    
    struct ofp_phy_port {
        uint16_t port_no; // 物理端口号
        uint8_t hw_addr[OFP_ETH_LENGTH]; // 48位的以太网地址
        uint8_t name[OFP_MAX_PORT_NAME_LEN]; // 128位的端口名称
        uint32_t config; // 端口设置bitmap
        uint32_t state; // 端口状态bitmap
        uint32_t curr; // 当前功能
        uint32_t advertised; // 广播功能
        uint32_t supported;  // 支持功能
        uint32_t peer;  // 连接方广播功能
    }
    
    1. 交换设置(optional)。type为OFPT_SET_CONFIG/OFPT_GET_CONFIG。
    struct set_config_message {
        uint16_t flags; // IP碎片处理方法
        uint16_t miss_send_len; // Table-miss数据包个数
    }
    
    enum set_config_flag {
        OFPC_FRAG_NORMAL = 0, // 依据流表处理
        OFPC_FRAG_DROP, // 丢弃
        OFPC_FRAG_REASM, // 重组
        OFPC_FRAG_MASK // unknown
    }
    
    1. 其他可能交换的内容 STATS,QUEUE_GET_CONFIG,Vendor, …

    Flow-Mod

    对于流表进行修改的消息,可以进行添加、删除和变更设置等操作。 种类包括以下几种

    1. OFPFC_ADD
    2. OFPFC_MODIFY
    3. OFPFC_MODIFY_STRICT
    4. OFPFC_DELETE
    5. OFPFC_DELETE_STRICT 后面加上STRICT表示要完全匹配。如果有错误发生,那么将回复错误信息。如果在流表中存在相同项,那么会将原有计数器清零。
    struct flow_mod_message {
        struct ofp_match match; // 数据包匹配信息
        uint8_t cookie[64];
        uint8_t action[??]; 
        
    }
    
    struct ofp_match {
        uint32_t wildcards; // 无视哪个字段的通配符
        uint16_t in_port; // 输入物理端口
        uint8_t dl_src[OFP_ETH_ALEN]; // 源MAC地址
        uint8_t dl_dst[OFP_ETH_ALEN]; // 目的MAC地址
        uint16_t dl_vlan; // VLAN id
        uint8_t dl_vlan_pcp;  // VLAN 优先级
        uint8_t pad1; 
        uint16_t dl_type; // 以太网帧类型 
        uint8_t nw_tos; // ToS字段
        uint8_t nw_proto; // IP协议号
        uint16_t pad2;
        uint32_t nw_src; // 源IPv4地址
        uint32_t nw_dst; // 目的IPv4地址
        uint16_t tp_src; // 源端口或ICMP类型
        uint16_t tp_dst; // 目的端口或ICMP代码
    }
    
    struct flow_mod_action {
        uint16_t command; // 即上面提到的action种类
        uint16_t idle_timeout; // 如果本条规则在idle_timeout时间内没有应用成功,那么将删除该规则
        uint16_t hard_timeout; // 如果本条规则在hard_timeout时间内还未添加成功,那么将取消添加该规则。
        uint16_t priority; // 规则优先级
        uint32_t buffer_id; // 缓存ID
        uint16_t out_port; // 输出端口号
        uint16_t flags;
        uint8_t actions[??]; // TLV结构,一个头部包含修改种类,后面接上具体的数据
    }
    

    Packet-In

    Packet-In消息用于将到达OpenFlow交换机的数据包发送至OpenFlow交换机。根据交换机是否缓存数据包来设置buffer_id的值:不缓存则设置为-1,将整个数据包发送;缓存则会根据SET_CONFIG消息设置的miss_send_len为最大值的数据包发送,默认值为128。

    struct packet_in_message {
        uint32_t buffer_id; // 缓存ID
        uint16_t total_len; // 帧长度
        uint16_t in_port; // 接受帧端口
        uint8_t reason; // 发送原因(不匹配0,流表指定1)
        uint8_t pad;
        uint8_t data[??]; // 具体数据包
    }
    

    Packet-Out

    Packet-Out即为控制器向交换机发送的消息。

    struct packet_out_message {
        uint32_t buffer_id; // 缓存ID
        uint16_t in_port; // 输入端口,用OFPP_NONE表示未指定,用OFPP_CONTROLLER表示是控制器创建的数据包
        uint16_t actions_len;
        uint8_t actions[actions_len]; // 类似于Flow-Mod时的action
        uint8_t data[??]; // 当buffer_id为-1的时候,即不指定交换器缓存时
    }
    

    Port-Status

    在OpenFlow交换机添加、删除或修改物理端口时,需要发送Port-Status消息来通知OpenFlow控制器。

    struct port_status_message {
        uint8_t reason; // OFPPR_ADD(0)、OFPPR_DELETE(1)、OFPPR_MODIFY(2)
        uint8_t pad[56];
        struct ofp_phy_port; 
    }
    

    Flow-Removed

    当OpenFlow交换机设置的流表项超时时,会向控制器发送Flow-Removed消息。

    struct flow_remove_message {
        struct ofp_match match;
        uint8_t cookie[64];
        uint16_t priority;
        uint8_t reason; // OFPRR_IDLE_TIMEOUT(0)、OFPRR_HARD_TIMEOUT(1)、OFPRR_DELETE(2)
        uint8_t pad;
        uint32_t duration_sec; // 有效时间(秒)
        uint32_t duration_nsec; // 纳秒
        uint16_t idle_timeout;
        uint8_t pad2;
        uint64_t packet_count; // 数据包数
        uint64_t byte_count; // 总字节数
    }
    

    大部分字段可以直接复制于Flow-Mod的信息。

    Error

    当在处理过程中出现错误的时候发送,控制器和交换机均可使用,type为OFPT_ERROR_MSG。

    struct error_message {
        uint16_t type;
        uint16_t code;
        uint8_t data[??];
    }
    
    enum error_type {
        OFPET_HELLO_FAILED,
        OFPET_BAD_REQUEST,
        OFPET_BAD_ACTION,
        OFPET_FLOW_MOD_FAILED,
        OFPET_QUEUE_OP_FAILED
    }
    

    具体code参考OpenFlow说明书。

    Barrier

    用于双方对于事务的完成程度的通信,避免发生有顺序的事务因执行程度未达到而造成错误的情况。例如在发送了三条Flow-Mod信息,xid分别为1、2、3后,可以发送xid为4的Barrier请求,如果可以得到对方xid为4的Barrier响应,则表示前面的消息已经处理完毕。

    Echo

    用于测试两方的连接情况、通信延迟和通信带宽等。

    LLDP

    Link Layer Discovery Protocol

    IEEE 802.1ab

    许多的OpenFlow控制器利用LLDP来检测网络拓扑结构。一般来说,OpenFlow控制器会利用Packet-Out消息向OpenFlow交换机下达发送LLDP帧的命令,接下来利用接收到LLDP帧的OpenFlow交换机向控制器发送的Packet-In消息中得到拓扑消息,构建整个网络拓扑结构。

    LLDP的机制

    • 每隔一段时间(标准中建议是30秒)向L2的组播地址发送LLDP帧,这个特性无视链路状态。
    • 发送源地址为发送设备的MAC地址。
    • 单向发送设备的识别号、端口识别号,这样接收方就可以得知发送设备和端口的信息。

    LLDP使用三种全局性组播群地址

    名称 数值 说明
    Nearest Bridge 01-80-C2-00-00-0E 所有的桥和路由设备不转发
    Nearest non-TPMR Bridge 01-80-C2-00-00-C3 跨越TPMR桥
    Nearest Customer Bridge 01-80-C2-00-00-00 跨越TPMR和S-VLAN桥

    一般来说,仅OpenFlow交换机构成的网络拓扑,会使用Nearest Bridge地址,而如果在中间存在有非OpenFlow的普通L2交换机则使用Nearest non-TPMR Bridge地址,借助WAN远程使用OpenFlow交换机则使用Nearest Customer Bridge地址。

    -----------------------------------------------------
    | 组播地址 | 设备的以太网地址 | 0x88cc(LLDP) | LLDPDU |
    -----------------------------------------------------
    

    OpenFlow 1.1 更新

    头字段变更为匹配字段

    在匹配字段中添加了MPLS标签、MPLS流量类别、元数据

    多级流表

    OpenFlow交换机可以设置多个流表,还可为一个数据包匹配多个流表项,但是在流表内还是只选择一个流表项

    采用流水线处理的方式,新定义了“行动集”的概念,即将所有的行动统一添加到行动集中,但是执行顺序与计入顺序不相同,采用copy TTL inwards->pop->push->copy TTL outwards->decrement TTL->set->qos->group->output的方式,这里面每种类型仅能设置一个。

    如果发生了Table-miss的情况,那么将根据OFPT_TABLE_MOD对流表的设置来决定,具体的方法有发送至控制器、前进到下一流表和丢弃。

  • ONOS官方示例应用解析

    onos-app-calendar

    一个RESTful的web应用,提供添加链路时延、带宽限制的Intent。用到了ConnectivityIntent。完成功能的逻辑

        /**
         * Create an Intent for a bidirectional path with constraints.
         *
         * @param key optional intent key
         * @param src the path source (DPID or hostID)
         * @param dst the path destination (DPID or hostID)
         * @param srcPort the source port (-1 if src/dest is a host)
         * @param dstPort the destination port (-1 if src/dest is a host)
         * @param bandwidth the bandwidth (mbps) requirement for the path
         * @param latency the latency (micro sec) requirement for the path
         * @return the appropriate intent
         */
        private Intent createIntent(Key key,
                                    String src,
                                    String dst,
                                    String srcPort,
                                    String dstPort,
                                    Long bandwidth,
                                    Long latency) {
    
            TrafficSelector selector = buildTrafficSelector();
            TrafficTreatment treatment = builder().build();
    
            final Constraint constraintBandwidth =
                    new BandwidthConstraint(Bandwidth.mbps(bandwidth));
            final Constraint constraintLatency =
                    new LatencyConstraint(Duration.of(latency, ChronoUnit.MICROS));
            final List<Constraint> constraints = new LinkedList<>();
    
            constraints.add(constraintBandwidth);
            constraints.add(constraintLatency);
    
            if (srcPort.equals("-1")) {
                HostId srcPoint = HostId.hostId(src);
                HostId dstPoint = HostId.hostId(dst);
                return HostToHostIntent.builder()
                        .appId(appId())
                        .key(key)
                        .one(srcPoint)
                        .two(dstPoint)
                        .selector(selector)
                        .treatment(treatment)
                        .constraints(constraints)
                        .build();
    
            } else {
                ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort));
                ConnectPoint dstPoint = new ConnectPoint(deviceId(dst), portNumber(dstPort));
                return TwoWayP2PIntent.builder()
                        .appId(appId())
                        .key(key)
                        .one(srcPoint)
                        .two(dstPoint)
                        .selector(selector)
                        .treatment(treatment)
                        .constraints(constraints)
                        .build();
            }
        }
    
    
        /**
         * Synchronously submits an intent to the Intent Service.
         *
         * @param intent intent to submit
         * @return true if operation succeed, false otherwise
         */
        private boolean submitIntent(Intent intent)
                throws InterruptedException {
            IntentService service = get(IntentService.class);
    
            CountDownLatch latch = new CountDownLatch(1);
            InternalIntentListener listener = new InternalIntentListener(intent, service, latch);
            service.addListener(listener);
            service.submit(intent);
            log.info("Submitted Calendar App intent and waiting: {}", intent);
            if (latch.await(TIMEOUT, TimeUnit.SECONDS) &&
                    listener.getState() == INSTALLED) {
                return true;
            }
            return false;
        }
    
    

    onos-app-carrierethernet

    根据pom.xml文件的内容,我们可以知道这个应用是用于运营以太网服务(Carrier Ethernet),具体介绍在Carrier Ethernet,由城域以太网论坛(MEF)建立。CE包含五个模块:保护、QoS、扩展、业务管理、TDM。个人理解是,希望以太网上的流量,能够在SDN环境下加入一定的识别特征,这样才能方便城域网中的网络设备根据这些特征进行服务的定制。这个项目相对来说比较复杂,而且更新也非常的频繁,先挖个坑在这里,以后有时间的话可以认真阅读一下其实现。

    onos-app-database-perf

    一个用来测试ONOS集群存储数据性能的应用,在activate中包含有对于StorageService的使用,包括创建并发式Map的方法,注册序列化器KryoNamespace等,之后创建多个线程测试具体的性能指标。

    onos-app-ecord-co

    一个CORD的实现应用,CORD(Central Office Re-architected as a DataCenter),意为在家庭、公司等网络边界的基础网络设备,实现一个数据中心的服务功能。CORD目前有三种类型:ECORD、RCORD和MCORD,具体的详情可以查看CORD。这个应用给出了一个ECORD的实现方案。这个应用比较全面地展示了ONOS的抽象子系统概念,我们可以从项目的结构看出来。

    CentralOffice.java文件是组件的主文件,主要工作是注册应用,并且创建了一个名为BigSwitchDeviceProvider的对象,那么接下来我们去找找实现。BigSwitchDeviceProvider继承于DeviceProvider。从前面两次的loadConfig操作中我们可以看出来这个应用支持修改config文件,并且提供有RPC和RESTful两种修改模式,然而目前我对于ConfigService还没有太深的了解,因此这里填个坑等到以后再来了解。在activate方法中基本就是向deviceProvider中注册了一个设备,接下来使用LLDP协议发现网络拓扑。为了完成LLDP的工作,程序中使用到了onlab-misc中的ONOSLLDP工具类,方便使用LLDP协议。

        @Activate
        public void activate(ComponentContext context) {
            cfgService.registerProperties(getClass()); // 在ComponentConfigService上进行注册
            loadRpcConfig(context); 
            loadRestConfig(context); 
    
            // setup service to, and register with, providers
            try {
                remoteServiceContext = rpcService.get(URI.create(remoteUri));
            } catch (UnsupportedOperationException e) {
                log.warn("Unsupported URI: {}", remoteUri);
            }
            providerId = new ProviderId(schemeProp, idProp);
            executor = newSingleThreadScheduledExecutor(groupedThreads("onos/bigswitch", "discovery-%d"));
            registerToDeviceProvider();
            prepareProbe();
            registerToLinkServices();
    
            // start listening to config changes
            NetworkConfigListener cfglistener = new InternalConfigListener();
            cfgRegistry.addListener(cfglistener);
            cfgRegistry.registerConfigFactory(xcConfigFactory);
            log.info("Started");
        }
    
        @Deactivate
        public void deactivate() {
            packetService.removeProcessor(packetProcessor);
    
    
            // advertise all Links as vanished
            knownLinks.invalidateAll();
    
            cfgRegistry.unregisterConfigFactory(xcConfigFactory);
            cfgService.unregisterProperties(getClass(), false);
            unregisterFromLinkServices();
            executor.shutdownNow();
            unregisterFromDeviceProvider();
            // Won't hurt but necessary?
            deviceProviderService = null;
            providerId = null;
            log.info("Stopped");
        }
    
        @Modified
        public void modified(ComponentContext context) {
            log.info("Reloading config...");
            // Needs re-registration to DeviceProvider
            if (loadRpcConfig(context)) {
                // unregister from Device and Link Providers with old parameters
                unregisterFromLinkServices();
                unregisterFromDeviceProvider();
                // register to Device and Link Providers with new parameters
                try {
                    remoteServiceContext = rpcService.get(URI.create(remoteUri));
                    providerId = new ProviderId(schemeProp, idProp);
                    registerToDeviceProvider();
                    registerToLinkServices();
                } catch (UnsupportedOperationException e) {
                    log.warn("Unsupported URI: {}", remoteUri);
                }
                log.info("Re-registered with Device and Link Providers");
            }
    
            // Needs to advertise cross-connect links
            if (loadRestConfig(context)) {
                advertiseCrossConnectLinksOnAllPorts();
            }
        }
    
    
  • mininet 学习

    命令语法

    $ 这个符号代表现在处于 Linux 的shell 交互下,需要使用的是 Linux 命令 mininet> 这个符号表示现在处于 Mininet 交互下,需要使用的是 Mininet 的命令 # 这个符号表示的是现在处于 Linux 的 root 权限下。 sudo mn -h 命令用于显示mininet的帮助信息 Mininet使用基于过程虚拟化和网络命名空间的特性来创建虚拟网络,并且创建的网络在当前的Linux内核中是可用的

    sudo mn 启动Mininet

    查看全部节点:nodes

    查看链路信息:net

    输出各节点信息:dump

    sudo mn –test pingpair 直接对主机连通性进行测试

    sudo mn –test iperf启动后直接进行性能测试

    主机端

    sudo mn -x ,通过使用-x参数,Mininet在启动后会在每个节点上自动打开一个XTerm,方便某些情况下对多个节点分别进行操作。

    在进入mn cli 之后,也可以使用xterm node(s1 h2)命令指定启动某些节点上的xterm,如分别启动s1和h2上的xterm

    禁用或启用某条链路,格式为:link node1 node2 up/down

    –switch选项和–controller选项,可以指定采用哪种类型的交换机跟控制器

    –innamespace参数,可以让所有结点拥有各自的名字空间

    启动参数总结 -h, –help 打印帮助信息

    –switch=SWITCH 交换机类型,包括 [kernel user ovsk]

    –host=HOST 模拟主机类型,包括 [process]

    –controller=CONTROLLER 控制器类型,包括 [nox_dump none ref remote nox_pysw]

    –topo=TOPO,arg1,arg2,…argN 指定自带拓扑,包括 [tree reversed single linear minimal]

    -c, –clean清理环境

    –custom=CUSTOM 使用自定义拓扑和节点参数

    –test=TEST 测试命令,包括 [cli build pingall pingpair iperf all iperfudp none]

    -x, –xterms 在每个节点上打开 xterm

    –mac 让MAC 地址跟 DP ID 相同

    –arp 配置所有 ARP 项

    -v VERBOSITY, –verbosity=VERBOSITY [info warning critical error debug output] 输出日志级别

    –ip=IP 远端控制器的IP地址

    –port=PORT 远端控制器监听端口

    –innamespace 在独立的名字空间内

    –listenport=LISTENPORT 被动监听的起始端口

    –nolistenport 不使用被动监听端口

    –pre=PRE 测试前运行的 CLI 脚本

    –post=POST 测试后运行的 CLI 脚本

    常用命令总结 help 默认列出所有命令文档,后面加命令名将介绍该命令用法

    dump 打印节点信息

    gterm 给定节点上开启 gnome-terminal。注:可能导致 Mininet 崩溃

    xterm 给定节点上开启 xterm

    intfs 列出所有的网络接口

    iperf 两个节点之间进行简单的 iperf TCP测试

    iperfudp 两个节点之间用指定带宽 udp 进行测试

    net 显示网络链接情况

    noecho 运行交互式窗口,关闭回应(echoing)

    pingpair 在前两个主机之间互 ping 测试

    source 从外部文件中读入命令

    dpctl 在所有交换机上用 dptcl 执行相关命令,本地为 tcp 127.0.0.1:6634

    link 禁用或启用两个节点之间的链路

    nodes 列出所有的节点信息

    pingall 所有 host 节点之间互 ping

    py 执行 Python 表达式

    sh 运行外部 shell 命令

    quit/exit 退出