整点bug

整点bug 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 typesafe.cn 编辑
编辑

个人动态

整点bug 发布了文章 · 3月13日

Java里的奇技淫巧

Java是一种广泛使用的计算机编程语言、面向对象、泛型编程的特性,广泛应用于企业级Web应用开发和移动应用开发。

1995年3月23日Sun公司发布了Java,至今已有近26年,可以说是一门十分成熟的开发语言了,但在某些不为人知的地方存在着一些意料之外的特性。

Java的保留关键字 goto和const

Java里面没有goto这个功能,但它作为保留字是无法当做变量来使用的,const也是同样。

int goto = 0;
int const = 0;

上面这两行代码的写法存在问题,无法正常编译通过。

Java标签Label

上面说了在Java里面没有goto这个功能,但为了处理多重循环引入了Label,目的是为了在多重循环中方便的使用 break 和coutinue ,但好像在其他地方也可以用。

outerLoop:
while (true) {
    System.out.println("I'm the outer loop");
    int i = 0;
    while (true) {
        System.out.println("I am the inner loop");
        i++;
        if (i >= 3) {
            break outerLoop;
        }
    }
}
System.out.println("Complete the loop");

// 输出
I'm the outer loop
I am the inner loop
I am the inner loop
I am the inner loop
Complete the loop
test:
{
    System.out.println("hello");
    if (true) {
    break test; // works
  }
  System.out.println("world");
}

// 输出
hello
test:
if (true) {
    System.out.println("hello");
    if (true) {
        break test; // works
    }
    System.out.println("world");
}
// 输出
hello
test:
try {
    System.out.println("hello");
    if (true) {
        break test; // works
    }
    System.out.println("world");
} finally {
}
// 输出
hello

Integer的是否相等问题

日常开发使用到Java基本数据类型是不可避免的一件事,但它却包含了一些很容易犯错的点,踩过一些坑的同学可能了解Java基本包装类型的常量池技术,例如Integer就具有数值[-128,127] 的相应类型的缓存数据,但下面定义的4个变量是否相等你是否能说的出来呢?

Integer i1 = 127;
Integer i2 = Integer.valueOf(127);
Integer i3 = Integer.parseInt("127");

Integer i4 = new Integer(127);

System.out.println(i1 == i2);
// true
System.out.println(i1 == i3);
// true
System.out.println(i2 == i3);
// true

System.out.println(i4 == i1);
// false
System.out.println(i4 == i2);
// false
System.out.println(i4 == i3);
// false
  1. Integer i1 = 127;Java 在编译的时候会直接将代码优化为 Integer i1=Integer.valueOf(127);,从而使用常量池中的对象。
  2. Integer i2 = Integer.valueOf(127); 会从Integer缓存中获取。

    Integer.valueOf源码如下。

    /**
     * 会从缓存数组中获取范围为[-128, 127]的值,如果没有就创建一个新的对象。
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
  3. Integer.parseInt("127"); 返回值是 int数字,在[-128, 127]范围的会从常量池中获取。
  4. new Integer(127); 创建一个新的对象。

Integer对象的缓存值的大小范围是在[-128 127]区间。这意味着当我们在此数值范围内操作时,“==”比较能正常返回结果。但当数值不在此范围,对象相等的比较是否正常返回结果就很难说了。所以为了安全,在使用对象数值进行比较相等时,请使用“.equals()”,而不是依赖于“==”比较相等,除非你非常肯定这种做法没有错误。

思考一个问题,当变量的数值均为128时会输出什么结果呢?

Double.MIN_VALUE 竟然不是负数?

我们可能是受了太多IntegerLong的影响,理所当然的认为Double.MIN_VALUE应该是一个负数,但实际上Double.MIN_VALUE是非0非负的最小值4.9E-324Float也是同样。如果你想要最小的Double类型的数字,请使用-Double.MAX_VALUE

System.out.println(Double.MAX_VALUE);
// 1.7976931348623157E308
System.out.println(Double.MIN_VALUE);
// 4.9E-324
System.out.println(Float.MAX_VALUE);
// 3.4028235E38
System.out.println(Float.MIN_VALUE);
// 1.4E-45

使用 for 做死循环?

提到死循环大家都会想到while(true),但还有另一种写法 for(;;),在字节码层面他们会被编译为同样的内容,一些上古C/C++程序员写Java的时候还会使用for(;;)做死循环,遇到这样的朋友一定要珍惜哦。

while (true){
  // do something
}
for(;;){
    // do something
}

下划线也能当做变量?

Object _ = new Object();
System.out.println(_.toString());

在Java8中还能使用下划线当做变量,但在Java9之后就标记为不再支持,请珍惜和它的最后这段时光吧。

WTF!!! finally还能返回内容?

public class Finally {

    public int fun() {
        try {
            return 0;
        } finally {
            return 1;
        }
    }

    public static void main(String[] args) {
        Finally aFinally = new Finally();
        System.out.println(aFinally.fun());
          // 1
    }
}

面试的时候finally返回值坑了多少英雄好汉?

一个类可以有几个static代码块?

public class Static {

    static {
        System.out.println("hello");
    }

    static {
        System.out.println("world");
    }

    static {
        System.out.println("java");
    }

    public static void main(String[] args) {

    }
}

// 输出
hello
world
java

Java🐂🍺

final 类型一定要声明的时候进行初始化吗?

关于final类型的变量不可修改是每一个Java开发的常识,声明变量的时候就要对其进行初始化,但真的是这样的吗?

public class Final {
    public static void main(String[] args) {
        final int a;
        if (args != null) {
            a = 0;
        } else {
            a = 1;
        }
        System.out.println(a);
        // 0 
    }
}

是否有点违背认知了?

查看原文

赞 0 收藏 0 评论 0

整点bug 赞了文章 · 1月31日

将footer固定在页面底部的实现方法

方法一:footer高度固定+绝对定位

HTML结构:

<body>
    <header>header</header>
    <main>main content</main>
    <footer>footer</footer>
</body>

CSS设置:

html{height:100%;}
body{min-height:100%;margin:0;padding:0;position:relative;}

header{background-color: #ffe4c4;}
main{padding-bottom:100px;background-color: #bdb76b;}/* main的padding-bottom值要等于或大于footer的height值 */
footer{position:absolute;bottom:0;width:100%;height:100px;background-color: #ffc0cb;}

首先,设置body的高度至少充满整个屏幕,并且body作为footer绝对定位的参考节点;
其次,设置main(footer前一个兄弟元素)的padding-bottom值大于等于footer的height值,以保证main的内容能够全部显示出来而不被footer遮盖;
最后,设置footer绝对定位,并设置height为固定高度值

方法二:footer高度固定+margin负值

HTML结构:

<body>
    <div class="container">
        <header>header</header>
        <main>main content</main>
    </div>
    <footer>footer</footer>
</body>

CSS设置:

html,body{height:100%;margin:0;padding:0;}

.container{min-height:100%;}
header{background-color: #ffe4c4;}
main{padding-bottom:100px;background-color: #bdb76b;}/* main的padding-bottom值要等于或大于footer的height值 */
footer{height:100px;margin-top:-100px;background-color: #ffc0cb;}/* margin-top(负值的)高度等于footer的height值 */

此方法把footer之前的元素放在一个容器里面,形成了container和footer并列的结构:
首先,设置.container的高度至少充满整个屏幕;
其次,设置main(.container最后一个子元素)的padding-bottom值大于等于footer的height值;
最后,设置footer的height值和margin-top负值

这种方法没有使用绝对定位,但html结构的语义化不如方法一中的结构清晰。

也可以设置负值的margin-bottom在.container上面,此时html结构变化如下:

<body>
    <div class="container">
        <header>header</header>
        <main>main content</main>
        <div class="empty"></div>
    </div>
    <footer>footer</footer>
</body>

CSS设置:

html,body{height:100%;margin:0;padding:0;}
.container{min-height:100%;margin-bottom:-100px;}
.empty,footer{height:100px;}

使用一个空的div把footer容器推到页面最底部,同时给container设置一个负值的margin-bottom,这个margin-bottom与footer和.empty的高度相等。

虽然多了一个空的div,语义上也不怎么好,但是相比前面为main元素设置padding-bottom的方法有一个明显的好处:当网页内容不满一屏的时候,如果需要为main元素设置边框、背景色的时候,padding-bottom硬生生地撑出了一片空白,真真是有点丑,但是加个空的div之后,布局方式作用在.empty上,对main的css设置就不影响了,算是一个好处吧!

方法三:footer高度任意+js

HTML结构:

<body>
    <header>header</header>
    <main>main content</main>
    <footer>footer</footer>
</body>

CSS设置:

html,body{margin:0;padding: 0;}
header{background-color: #ffe4c4;}
main{background-color: #bdb76b;}
footer{background-color: #ffc0cb;}

/* 动态为footer添加类fixed-bottom */
.fixed-bottom {position: fixed;bottom: 0;width:100%;}

js代码:

$(function(){
    function footerPosition(){
        $("footer").removeClass("fixed-bottom");
        var contentHeight = document.body.scrollHeight,//网页正文全文高度
            winHeight = window.innerHeight;//可视窗口高度,不包括浏览器顶部工具栏
        if(!(contentHeight > winHeight)){
            //当网页正文高度小于可视窗口高度时,为footer添加类fixed-bottom
            $("footer").addClass("fixed-bottom");
        }
    }
    footerPosition();
    $(window).resize(footerPosition);
});
查看原文

赞 28 收藏 92 评论 4

整点bug 发布了文章 · 1月28日

Linux 环回网络接口

在开发或者调试时,我们经常需要和本地的服务器进行通信,例如启动nginx之后,在浏览器输入lcoalhost或者127.0.0.1就可以访问到本机上面的http服务。

Linux是如何访问本机IP的?

大多数操作系统都在网络层实现了环回能力,通常是使用一个虚拟的环回网络接口来实现。这个虚拟的环回网络接口看着像是一个真实的网卡,实际上是操作系统用软件模拟的,它可以通过TCP/IP与同一台主机上的其他服务进行通信,以127开头的IPv4地址就是为它保留的,主流Linux操作系统为环回网卡分配的地址都是127.0.0.1,主机名是localhost

环回网络接口之所以被称之为环回网络接口,是因为从本机发送到本机任意一个IP的数据报文都会在网络层交给环回网络接口,不再下发到数据链路层进行处理,环回网络接口直接发送回网络层,最终交由应用层软件程序进行处理。这种方式对于性能测试非常有用,因为省去了硬件的开销,可以直接测试协议栈软件所需要的时间。

那环回网络接口是如何判断目的IP是否为本机地址的呢?

答案就是网络层在进行路由转发的时候会先查本地的路由表,发现是本机IP后交给环回网络接口。查看本地路由表的命令如下:

ip route show table local

输出内容如下:

broadcast 10.141.128.0 dev eth0 proto kernel scope link src 10.141.155.131 
local 10.141.155.131 dev eth0 proto kernel scope host src 10.141.155.131 
broadcast 10.141.191.255 dev eth0 proto kernel scope link src 10.141.155.131 
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1

其中local开头的便是本地IP,dev后面是网卡名称。

查完了本地路由表之后会再查主路由表,也就是我们经常操作的路由表。

ip route show table main

输出内容如下

default via 10.141.128.1 dev eth0 proto static metric 100 
10.141.128.0/18 dev eth0 proto kernel scope link src 10.141.155.131 metric 100

环回网络接口

现在我们再来看下环回网络接口

ifconfig lo

输出

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 1554227  bytes 123327716 (117.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1554227  bytes 123327716 (117.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以看到本地环回接口的IPv4地址是127.0.0.1,子网掩码是255.0.0.0,对应A类网络号127,有趣的是当我们访问 127.0.0.1-127.255.255.254之间的任意一个地址都会访问到本机。

IPv6地址::1,前缀是128位,表示只有一个地址。

环回网络接口的当前MTU64KB,不过最高可以设置到2GB,真是恐怖如斯。

下面几条RX,TX开头的分别代表收发到的数据报文个数和大小以及错包、丢包、溢出次数和无效帧。

FAQ

虚拟网卡的IP属于本机IP吗?

属于,因为与宿主机器共用同一个网络协议栈。

宿主机器上创建netns,netns内部的IP属于本机IP吗?

不属于,因为netns拥有独立的网络协议栈,在netns内部也可以看到它本身的环回网络接口。

本文首发我的微信公众号:我在对面的角落
欢迎关注,接收第一时间更新通知。

欢迎关注微信公众号 接收第一时间更新通知

查看原文

赞 0 收藏 0 评论 0

整点bug 赞了回答 · 1月23日

解决es6如何快速的删除数组元素

ES6 findIndexMDN :Array.prototype.findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

arr.splice(arr.findIndex(item => item.id === 8), 1)

推荐文章:【深度长文】JavaScript数组所有API全解密 | louis blog

关注 14 回答 13

整点bug 赞了文章 · 1月9日

js获取剪切板内容,js控制图片粘贴。

在用户执行粘贴操作的时候,js能够获得剪切板的内容,本文讨论一下这个问题。

目前只有Chrome支持获取剪切板中的图片数据。还好需要这个功能的产品目前只支持ChromeSafari,一些Chrome的新特性是可以尽情使用了,还是能够覆盖到大部分用户的。所以本文只讨论Chrome如何使用和如何阻止Safari,原理大概了解了,再研究其他浏览器相关的问题就容易多了。

paste事件

可以用js给页面中的元素绑定paste事件的方法,当用户鼠标在该元素上或者该元素处于focus状态,绑定到paste事件的方法就运行了。

绑定的元素不一定是input,普通的div也是可以绑定的,如果是给document绑定了,就相当于全局了,任何时候的粘贴操作都会触发。

事件对象

获取事件对象

先写一下事件绑定的代码

pasteEle.addEventListener("paste", function (e){
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return;
    }
});

粘贴事件提供了一个clipboardData的属性,如果该属性有items属性,那么就可以查看items中是否有图片类型的数据了。Chrome有该属性,Safari没有。

clipboardData介绍

介绍一下clipboardData对象,它实际上是一个DataTransfer类型的对象,DataTransfer 是拖动产生的一个对象,但实际上粘贴事件也是它。

clipboardData的属性介绍

属性类型说明
dropEffectString默认是 none
effectAllowedString默认是 uninitialized
filesFileList粘贴操作为空List
itemsDataTransferItemList剪切板中的各项数据
typesArray剪切板中的数据类型 该属性在Safari下比较混乱

items介绍

items是一个DataTransferItemList对象,自然里面都是DataTransferItem类型的数据了。

属性

itemsDataTransferItem有两个属性kindtype

属性说明
kind一般为string或者file
type具体的数据类型,例如具体是哪种类型字符串或者哪种类型的文件,即MIME-Type

方法

方法参数说明
getAsFile如果kindfile,可以用该方法获取到文件
getAsString回调函数如果kindstring,可以用该方法获取到字符串,字符串需要用回调函数得到,回调函数的第一个参数就是剪切板中的字符串

在原型上还有一些其他方法,不过在处理剪切板操作的时候一般用不到了。

types介绍

一般types中常见的值有text/plaintext/htmlFiles

说明
text/plain普通字符串
text/html带有样式的html
Files文件(例如剪切板中的数据)

简单demo

pasteEle.addEventListener("paste", function (e){
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return ;
    }

    for (var i = 0, len = e.clipboardData.items.length; i < len; i++) {
        var item = e.clipboardData.items[i];

        if (item.kind === "string") {
            item.getAsString(function (str) {
                // str 是获取到的字符串
            })
        } else if (item.kind === "file") {
            var pasteFile = item.getAsFile();
            // pasteFile就是获取到的文件
        }
    }
});

注意如果是string类型的数据,可能针对具体是text/plaintext/html进行分别的处理。

问题来了

一切看似都很顺利,如果用户粘贴了图片,通过上面的方法我们是可以获取到,可以对图片进行上传等操作了。

首先要说一下js通过剪切板能获取到的图片是怎么来的,它必须是用QQ截图或者系统截图功能截下来的图片,或者是网页上某个图片单击右键复制图片等。

但是如果用户复制MacFinder中的一个图片文件,实际上js是没有办法获取到这个图片的。但是js确实会获得一个图片类型的文件,这个图片实际上图片在电脑中的图标标识,说的比较抽象,直接上图。

图片描述

如果复制的是JPEG图片,粘贴过来的却是Mac上的文件缩略图,后面依次是PNGGIFZIPDMGMac目录的文件缩略图。

很明显,这不是我们期待得到的粘贴的结果,我们期待得到文件,但实际上却得到该文件在操作系统上的缩略图。

不过粘贴事件带来的数据还有一个字符串,就是该文件的名字,所以可以用下面的方法Hack掉。

    var cbd = e.clipboardData;
    if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
            cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files"){
        return;
    }

这么多的判断条件,基本可以确定通过剪切板过来的是粘贴的文件。我刚才测试了WindowsChrome,不会有这个问题,当然也不能通过复制文件的方法得到任何文件。

问题又来了

当我打算写这篇博客的时候,Chrome开发版已经升级到了49,上面的Bug突然消失了,囧。
所以上面的Hack应该加上版本限制了。

var ua = window.navigator.userAgent;
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49

应该在上面的Hack再加上这两个判断,即是Mac下的Chrome49版本以下就要return

探究过程走的一点弯路

由于公司IM系统正在迁移到V2消息系统,而且现有的文件类库没有办法满足业务需求,要自己封装一个文件上传库。

然后副总找到产品经理,说新版怎么不支持Excel的粘贴,临时排期一天修复这个问题,当时是这样解决的,如果items长度是1并且是文件类型(单纯粘贴一个文件),则上传,如果items长度是4且第4个是文件类型(经过测试是Excel的粘贴结果),则上传。

当时担心由于用户各种误操作,粘贴了不该粘贴的东西,文件上传错误,用了这种白名单机制去过滤,但是万一以后有比Excel粘贴得到的数据更其他的类型,就需要单独写代码兼容,所以,现在改成了如果判断是有Bug的情况,直接return,属于黑名单机制,这样以后再发现黑名单的情况,再添加。

可以拿来就用的代码

// demo 程序将粘贴事件绑定到 document 上
document.addEventListener("paste", function (e) {
    var cbd = e.clipboardData;
    var ua = window.navigator.userAgent;

    // 如果是 Safari 直接 return
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return;
    }
    
    // Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
    if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
        cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
        ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49){
        return;
    }

    for(var i = 0; i < cbd.items.length; i++) {
        var item = cbd.items[i];
        if(item.kind == "file"){
            var blob = item.getAsFile();
            if (blob.size === 0) {
                return;
            }
            // blob 就是从剪切板获得的文件 可以进行上传或其他操作
        }
    }
}, false);
查看原文

赞 71 收藏 114 评论 20

整点bug 发布了文章 · 2020-12-31

Open vSwitch 入门实践(5)OVS Flow Table 流表规则

OpenvSwitch flow table 流表

OpenFlow(OF)被认为是第一个软件定义网络(SDN)标准之一。它最初在SDN环境中定义了通信协议,使SDN控制器能够与物理和虚拟的交换机和路由器等网络设备的转发平面直接进行交互,从而更好地适应不断变化的业务需求。

如果把OpenFlow控制器比作“大脑”,OVS流表就像是“大腿”一样接受来自“大脑”的指令,决定要向哪个方向前进。但OVS流表功能更加强大,在没有OpenFlow控制器时,也可以自主工作,它本身也供一些命令让我们可以直接管理流表。

操作命令

查看流表规则

# 查看br-tun上的全部流表规则
ovs-ofctl dump-flows br-tun

添加或修改流表规则

ovs-ofctl add−flow/add−flows/mod−flows “流表匹配条件,actions=[动作1][,动作2…]”

如果你有过编程的经验,流表规则其实就是一个个简单的if语句,伪代码如下。

if (流表匹配条件){
    动作1,
    动作2...
}

if (流表匹配条件){
    动作1,
    动作2...
}

删除流表规则

# 删除br-tun上的全部流表规则
ovs-ofctl del-flows br-tun

# 删除br-tun上匹配xx的全部流表规则
ovs-ofctl del-flows br-tun xx  

流表匹配条件

OVS 流表匹配条件较多,下面我将其分成四部分来说明,分别是:

  • OVS匹配条件
  • OSI模型第二层【数据链路层】
  • OSI模型第三层【网络层】
  • OSI模型第四层【传输层】

OVS匹配条件

in_port=port

流量进入的端口编号或者名称,示例 in_port=br-int

table=number

规则保存的流表编号,范围是 0-254,默认值:0。

OSI模型第二层【数据链路层】

dl 即是 data link 的缩写。

dl_type=ethertype

匹配以太网协议类型以太类型,以10到65535之间的整数(包括0和65535)指定,以十进制或以0x前缀的十六进制数表示,示例如下。

  • dl_type=0x0800 匹配IPv4数据包,等同于dl_type=ip
  • dl_type=0x086dd 匹配IPv6数据包,等同于dl_type=ipv6
  • dl_type=0x0806 匹配ARP数据包,等同于dl_type=arp
  • dl_type=0x8035 匹配RARP数据包,等同于 dl_type=rarp

dl_vlan=vlan

数据包的 VLAN Tag 值,范围是 0-4095,0xffff 代表不包含 VLAN Tag 的数据包

dl_vlan_pcp=priority

VLAN 优先级,取值区间为[0-7]。数字越大,表示优先级越高。

dl_data-original=xx:xx:xx:xx:xx:xx

dl_dst=xx:xx:xx:xx:xx:xx

源或目的的 MAC地址

  • 地址01:00:00:00:00:00/01:00:00:00:00:00 代表广播
  • 地址00:00:00:00:00:00/01:00:00:00:00:00 代表单播
  • 地址fe:ff:ff:ff:ff:ff 匹配除多播位以外的所有位,基本上不会用到。
  • 地址ff:ff:ff:ff:ff:ff 完全匹配(等同于省略子网掩码)。
  • 地址00:00:00:00:00:00 匹配全部位(等同于 dl_dst=*)。

OSI模型第三层【网络层】

nw_data-original=ip[/netmask]

nw_dst=ip[/netmask]

如果dl_type为0x0800(可能是通过简写形式,例如ip或tcp),则匹配IPv4源(或目标)地址ip,可以将其指定为IP地址或主机名(例如192.168.1.1或www.example.com)。可选的网络掩码允许将匹配限制为IPv4地址前缀。网络掩码可以指定为点分四边形(例如192.168.1.0/255.255.255.0)或CIDR块(例如192.168.1.0/24)。 Open vSwitch 1.8和更高版本支持任意点状四元掩码;早期版本仅支持CIDR掩码,即等效于某些CIDR块的虚线四边形。

  • 当指定 dl_type0x0800 或者ip时,匹配源或者目标的 IPv4 地址,可以将其指定为IP地址或主机名,例如192.168.1.1www.typesafe.cn。同时也可以写作192.168.1.0/255.255.255.0或者192.168.1.0/24的形式。
  • 当指定dl_type0x0806arp时,分别与IPv4和Ethernet的ARP数据包中的ar_spaar_tpa字段匹配。
  • 当指定dl_type0x8035rarp时,分别与IPv4和Ethernet的RARP数据包中的ar_spaar_tpa字段匹配。
  • 当指定dl_type0x08000x08060x8035之外的其他值时,将忽略nw_srcnw_dst的值。

nw_proto=proto

ipproto=proto

  • 如果指定ipdl_type=0x0800,则匹配IP协议类型proto,该协议类型被指定为0到255之间的十进制数(包括1和0,用于包含ICMP数据包或6以匹配TCP数据包)。
  • 如果指定了ipv6dl_type=0x86dd,则匹配IPv6标头类型原型,该形式指定为0到255之间的十进制数字(例如,包括58以匹配ICMPv6数据包或6以匹配TCP)。标头类型是设计文档中描述的终端标头。
  • 当指定arpdl_type=0x0806时,与ARP操作码的低8位匹配。大于255的ARP操作码被视为0。
  • 指定rarpdl_type=0x8035时,与ARP操作码的低8位匹配。大于255的ARP操作码被视为0。
  • 当通配符dl_type或将其设置为0x08000x08060x80350x86dd之外的其他值时,将忽略nw_proto的值(请参见上面的流语法)。

nw_tos=tos

匹配IP ToS / DSCP或IPv6流量类别字段tos,该字段tos指定为0到255之间的十进制数字(包括0和255)。请注意,出于匹配目的,将忽略两个较低的保留位。

当通配符dl_type或将其设置为0x08000x86dd之外的其他值时,将忽略nw_tos的值。

ip_dscp=dscp

匹配IP ToS / DSCP或IPv6流量类字段dscp,该字段指定为介于0和63之间(含0和63)的十进制数。

当通配符dl_type或将其设置为0x08000x86dd之外的其他值时,将忽略ip_dscp的值(请参见上面的流语法)。

nw_ecn=ecn

ip_ecn=ecn

匹配IP ToS或IPv6流量类别字段中的ecn位,该ecn位指定为0到3(含0和3)之间的十进制数。当通配符dl_type或将其设置为0x08000x86dd之外的其他值时,将忽略nw_ecn的值(请参见上面的流语法)。

nw_ttl=ttl

匹配IP TTL或IPv6跃点限制值ttl,该值指定为0到255之间的十进制数字(包括0和255)。

当通配符dl_type或将其设置为0x08000x86dd之外的其他值时,将忽略nw_ttl的值(请参见上面的流语法)。

OSI模型第四层【传输层】

tcp_data-original=port

tcp_dst=port

udp_data-original=port

udp_dst=port

sctp_data-original=port

sctp_dst=port

匹配TCP,UDP或SCTP源端口或目标端口,端口号指定为0到65535(含0和65535)之间的十进制数。

当通配符dl_type和nw_proto或将其设置为不表示适当协议的值时,这些设置的值将被忽略(请参见上面的流语法)。

tcp_data-original=port/mask

tcp_dst=port/mask

udp_data-original=port/mask

udp_dst=port/mask

sctp_data-original=port/mask

sctp_dst=port/mask

TCP(或UDP或SCTP)源或目标端口上的按位匹配。端口和掩码是16位数字,以十进制或十六进制写为0x。掩码中的每个1位要求端口中的相应位必须匹配。掩码中的每个0位都会导致忽略相应的位。

传输端口上的按位匹配很少在隔离中有用,但是可以使用一组匹配项来减少在一系列传输端口上进行匹配所需的流数。例如,假设目标是将TCP源端口1000匹配到1999(含)。一种方法是插入1000个流,每个流在单个源端口上匹配。另一种方法是查看1000和1999的二进制表示形式,如下所示:

01111101000
11111001111

然后将其转换为一系列按位匹配,以实现相同的结果:

01111101xxx
0111111xxxx
10xxxxxxxxx
110xxxxxxxx
1110xxxxxxx
11110xxxxxx
1111100xxxx

使用ovs-ofctl所需的语法编写时,这些内容如下:

tcp,tcp_data-original=0x03e8/0xfff8
tcp,tcp_data-original=0x03f0/0xfff0
tcp,tcp_data-original=0x0400/0xfe00
tcp,tcp_data-original=0x0600/0xff00
tcp,tcp_data-original=0x0700/0xff80
tcp,tcp_data-original=0x0780/0xffc0
tcp,tcp_data-original=0x07c0/0xfff0

仅Open vSwitch 1.6和更高版本支持传输端口上的按位匹配。

与上述完全匹配形式一样,按位匹配形式仅在dl_type和nw_proto指定TCP或UDP或SCTP时适用。

tp_data-original=port

tp_dst=port

这些是L4端口匹配项已弃用的通用形式。在新代码中,请使用上述特定于TCP,UDP或SCTP的形式。

tcp_flags=flags/mask

tcp_flags=[+flag...][-flag...]

TCP标志按位匹配。标志和掩码是16位数字,以十进制或以0x为前缀的十六进制表示。掩码中的每个1位要求标志中的相应位必须匹配。掩码中的每个0位都会导致忽略相应的位。
或者,可以通过标志的符号名(在下面列出)来指定标志,每个标志名的前面都带有+,表示必须设置的标志,或者-表示必须取消设置的标志,且标志之间没有其他定界符。未提及的标志是通配符。例如,tcp,tcp_flags = + syn-ack匹配不是ACK的TCP SYN。 TCP协议当前定义9个标志位,并保留另外3个位(必须作为零发送),请参阅RFC 793、3168和3540。这些标志位的编号从最低有效位开始:

  • 0:fin 查找不再有来自发送方的数据。
  • 1:syn 同步同步序列号。
  • 2:rst 重置连接。
  • 3:psh 推送功能。
  • 4:ack 确认字段有效。
  • 5:urg 紧急指针字段有效。
  • 6:ece ECN回显。
  • 7:cer 减少拥塞窗口。
  • 8:ns 现时总和
  • 9-11:保留。
  • 12-15:不处理,必须为零。

icmp_type=type

icmp_code=code

当dl_type和nw_proto指定ICMP或ICMPv6时,type匹配ICMP类型,而代码匹配ICMP代码。每个参数都指定为介于0和255之间(含两端)的十进制数。

当dl_type和nw_proto采用其他值时,这些设置的值将被忽略(请参见上面的流语法)。

metadata=value[/mask]

在元数据字段中完全匹配值或使用可选掩码匹配值。 value和mask是64位整数,默认情况下为十进制(使用0x前缀指定十六进制)。允许使用任意掩码值:掩码中的1位表示值中的对应位必须完全匹配,而该位则使用0位通配符。在Open vSwitch 1.8中添加了对元数据的匹配。

ip

等同于dl_type=0x0800

ipv6

等同于dl_type=0x86dd

icmp

等同于dl_type=0x0800,nw_proto=1

icmp6

等同于dl_type=0x86dd,nw_proto=58

tcp

等同于dl_type=0x0800,nw_proto=6

tcp6

等同于dl_type=0x86dd,nw_proto=6

udp

等同于dl_type=0x0800,nw_proto=17

udp6

等同于dl_type=0x86dd,nw_proto=17

sctp

等同于dl_type=0x0800,nw_proto=132

sctp6

等同于dl_type=0x86dd,nw_proto=132

arp

等同于dl_type=0x0806

rarp

等同于dl_type=0x8035

mpls

等同于dl_type=0x8847

mplsm

等同于dl_type=0x8848

vlan_tci=tci[/mask]

匹配修改后的VLAN TCI tci。如果省略mask,则tci是要匹配的确切VLAN TCI;如果指定了mask,则mask中的1位表示tci中的对应位必须完全匹配,而0位通配符表示该位。 tci和mask均为16位值,默认情况下为十进制。使用0x前缀以十六进制指定它们。

对于没有802.1Q标头的数据包,vlan_tci与之匹配的值为0。否则,它是802.1Q标头中的TCI值,其中CFI位(值为0x1000)被强制为1。

  • vlan_tci=0
    仅匹配没有802.1Q标头的数据包。
  • vlan_tci=0xf123
    匹配VLAN 0x123中标记为优先级7的数据包。
  • vlan_tci=0x1123/0x1fff
    匹配标记有VLAN 0x123(和任何优先级)的数据包。
  • vlan_tci=0x5000/0xf000
    匹配标记为优先级2的数据包(在任何VLAN中)。
  • vlan_tci=0/0xfff
    匹配没有802.1Q标头或带有VLAN 0(和任何优先级)标记的数据包。
  • vlan_tci=0x5000/0xe000
    匹配没有802.1Q标头或带有优先级2标记的数据包(在任何VLAN中)。
  • vlan_tci=0/0xefff
    匹配没有802.1Q标头或带有VLAN 0和优先级0标记的数据包。

使用dl_vlan和dl_vlan_pcp也可以实现某些匹配可能性。

ip_frag=frag_type

当dl_type指定IP或IPv6时,frag_type指定要匹配的IP片段或非片段类型。支持以下frag_type值:

  • no

    仅匹配非分段数据包。

  • yes

    匹配所有片段。

  • first

    仅匹配偏移量为0的片段。

  • later

    仅匹配非零偏移量的片段。

  • not_later
    匹配零碎的非碎片数据包和碎片。

arp_spa=ip[/netmask]

arp_tpa=ip[/netmask]

当dl_type指定ARP或RARP时,arp_spa和arp_tpa分别与源和目标IPv4地址匹配。可以将地址指定为IP地址或主机名(例如192.168.1.1或www.example.com)。可选的网络掩码允许将匹配限制为IPv4地址前缀。网络掩码可以指定为点分四边形(例如192.168.1.0/255.255.255.0)或CIDR块(例如192.168.1.0/24)。

arp_sha=xx:xx:xx:xx:xx:xx

arp_tha=xx:xx:xx:xx:xx:xx

当dl_type指定ARP或RARP时,arp_sha和arp_tha分别匹配源和目标硬件地址。地址指定为以冒号分隔的6对十六进制数字(例如00:0A:E4:25:6B:B0)。

arp_sha=xx:xx:xx:xx:xx:xx/xx:xx:xx:xx:xx:xx

arp_tha=xx:xx:xx:xx:xx:xx/xx:xx:xx:xx:xx:xx

当dl_type指定ARP或RARP时,arp_sha和arp_tha分别匹配源和目标硬件地址。地址指定为以冒号分隔的6对十六进制数字(例如00:0A:E4:25:6B:B0),并在斜杠后加上通配符掩码。

arp_op=opcode

当dl_type指定ARP或RARP时,arp_op与ARP操作码匹配。只能指定1到255之间的ARP操作码进行匹配。

ipv6_data-original=ipv6[/netmask]

ipv6_dst=ipv6[/netmask]

当dl_type为0x86dd时(可能通过简写形式,例如ipv6或tcp6),匹配IPv6源(或目标)地址ipv6,该地址可以按RFC 2373中的规定指定。首选格式为x:x:x:x:x:x:x:x,其中x是地址的八个16位块的十六进制值。 ::的单个实例可用于指示16位零的多个组。可选的网络掩码允许将匹配限制为IPv6地址前缀。网络掩码被指定为IPv6地址(例如2001:db8:3c4d:1::/ffff:ffff:ffff:ffff::)或CIDR块(例如2001:db8:3c4d:1::/64)。打开vSwitch 1.8及更高版本,支持仲裁掩码;早期版本仅支持CIDR掩码,即CIDR块和等同于CIDR块的IPv6地址。

ipv6_label=label

当dl_type为0x86dd时(可能通过简写形式,例如ipv6或tcp6),匹配IPv6流标签label。

tun_id=tunnel-id[/mask]

tunnel_id=tunnel-id[/mask]

匹配隧道标识符tunnel-id。只有通过带有密钥的隧道到达的数据包(例如具有RFC 2890密钥扩展名和非零密钥值的GRE)才会具有非零的隧道ID。如果省略mask,则tunnel-id是要匹配的确切隧道ID;如果指定了mask,则mask中的1位表示tunnel-id中的相应位必须完全匹配,而0位通配符则将该位匹配。

tun_flags=flags

匹配标志,指示隧道封装的各个方面。当前,仅定义一个标志:

  • oma

    隧道协议指示这是一个OAM控制数据包。

可以在标志前面加上+或-来分别指示该标志应匹配为存在或不存在。另外,可以指定没有前缀的标志,并用|分隔。表示完全匹配。

请注意,较新版本的Open vSwitch可能会引入具有不同含义的其他标志。因此,不建议在此字段上使用完全匹配,因为这些新标志的行为是未知的,应忽略。

对于非隧道数据包,该值为0。

此字段是在Open vSwitch 2.5中引入的。

tun_data-original=ip[/netmask]

tun_dst=ip[/netmask]

匹配隧道IPv4源(或目标)地址ip。仅通过隧道到达的数据包将具有非零的隧道地址。该地址可以指定为IP地址或主机名(例如192.168.1.1或www.example.com)。可选的网络掩码允许将匹配限制为被掩码的IPv4地址。子网掩码可以指定为点分四边形(例如192.168.1.0/255.255.255.0)或CIDR块(例如192.168.1.0/24)。

ipv6

等同于dl_type=0x86dd

tcp6

等同于dl_type=0x86dd,nw_proto=6

udp6

等同于dl_type=0x86dd,nw_proto=17

sctp6

等同于dl_type=0x86dd,nw_proto=132

icmp6

等同于dl_type=0x86dd,nw_proto=58

流表动作

满足匹配条件之后将会执行的动作。

output:port

将数据包输出到OpenFlow端口号port。如果port是数据包的输入端口,则不输出数据包。

group:group_id

将数据包输出到OpenFlow组group_id。仅OpenFlow 1.1+支持组表。有关更多详细信息,请参见组语法。

normal

使数据包经过设备的常规L2 / L3处理。 (并非所有OpenFlow交换机都执行此操作。)

flood

在所有交换机物理端口上输出数据包,而不是在接收数据包的端口以及任何禁用洪泛的端口上进行输出(通常,这些端口是IEEE 802.1D生成树协议禁用的端口)。

all

在所有交换机物理端口上输出数据包,而不是在接收数据包的端口上。

local

在与本地网桥名称相同的网络设备对应的``本地端口''上输出数据包。

in_port

在接收数据包的端口上输出数据包。

enqueue(port,queue)

将数据包放入端口port中的指定队列中,该队列必须是OpenFlow端口号或关键字(例如LOCAL)。支持的队列数取决于交换机;具体取决于交换机。一些OpenFlow实现根本不支持排队。

drop

丢弃数据包,因此不会进行进一步的处理或转发。如果使用丢弃动作,则不能指定其他动作。

mod_vlan_vid:vlan_vid

修改报文的VLAN ID。根据需要添加或修改VLAN标记以匹配指定的值。如果添加了VLAN标记,则使用零优先级(请参阅mod_vlan_pcp操作来设置此优先级)。

mod_vlan_pcp:vlan_pcp

修改报文的VLAN优先级。根据需要添加或修改VLAN标记以匹配指定的值。有效值介于0(最低)和7(最高)之间。如果添加了VLAN标记,则使用的vid为零(请参阅mod_vlan_vid操作进行设置)。

strip_vlan

从数据包中剥离VLAN标记(如果存在)。

push_vlan:ethertype

将新的VLAN标签推入数据包。以太网类型用作标签的以太网类型。仅应使用ethertype 0x8100。 (目前尚不支持规范允许的0x88a8。)新标签使用优先级为零且标签为零。

mod_dl_src:mac

将源以太网地址设置为mac。

mod_dl_dst:mac

将目标以太网地址设置为mac。

mod_nw_src:ip

将IPv4源地址设置为ip。

mod_nw_dst:ip

将IPv4目标地址设置为ip。

mod_tp_src:port

将TCP或UDP或SCTP源端口设置为port。

mod_tp_dst:port

将TCP或UDP或SCTP目标端口设置为port。

mod_nw_tos:tos

将IPv4 ToS / DSCP或IPv6流量类字段中的DSCP位设置为tos,该值必须为0到255之间的4的倍数。此操作不会修改ToS字段的两个最低有效位(ECN位)。

mod_nw_ecn:ecn

将IPv4 ToS或IPv6流量类别字段中的ECN比特设置为ecn,该值必须介于0和3之间(包括0和3)。此操作不会修改该字段的六个最高有效位(DSCP位)。

需要OpenFlow 1.1或更高版本。

mod_nw_ttl:ttl

将IPv4 TTL或IPv6跳数限制字段设置为ttl,指定为0到255之间的十进制数(包括0和255)。但是,没有很好地指定将ttl设置为零时的开关行为。

需要OpenFlow 1.1或更高版本。

resubmit:port

resubmit([port],[table])

重新搜索此OpenFlow流表(或由表指定其编号的表),用in_port字段替换为端口(如果指定了port),并执行找到的操作(如果有),以及此流条目中的任何其他操作。

set_tunnel:id

set_tunnel64:id

如果输出到将数据包封装在隧道中并支持标识符(例如GRE)的端口,则将标识符设置为id。如果使用set_tunnel形式,并且id可以容纳32位,则此操作将使用Open vSwitch 1.0和更高版本支持的操作扩展。否则,如果id是64位值,则需要Open vSwitch 1.1或更高版本。

set_queue:queue

设置输出数据包时应用于排队的队列。支持的队列数取决于交换机;具体取决于交换机。一些OpenFlow实现根本不支持排队。

pop_queue

将队列恢复为应用任何set_queue操作之前的值。

move:src[start..end]->dst[start..end]

将已命名的位从字段src复制到字段dst。 src和dst必须是nicira-ext.h中定义的NXM字段名称,例如NXM_OF_UDP_SRC或NXM_NX_REG0。每个开始和结束对(包括首尾对)必须指定相同的位数,并且必须适合其各自的字段。存在[start..end]的简写形式:使用[bit]指定单个位,或使用[]指定整个字段。

set_field:value[/mask]->dst

load:value−>dst[start..end]

将文字值加载到字段或字段的一部分中。对于set_field,在字段dst的惯用语法中给出了值和可选掩码,表示为字段名。例如,set_field:00:11:22:33:44:55-> eth_src将以太网源地址设置为00:11:22:33:44:55。加载时,值必须是整数值(十进制或以0x开头的十六进制前缀),而dst是该字段的NXM或OXM名称。例如,load:0x001122334455-> OXM_OF_ETH_DST []与前面的set_field示例具有相同的效果。

出于历史原因,存在这两种形式。 Open vSwitch 1.1引入了NXAST_REG_LOAD作为OpenFlow 1.0的Nicira扩展,并使用load来表达它。后来,Open-Flow 1.2引入了一个标准的OFPAT_SET_FIELD操作,该操作仅限于加载整个字段,因此Open vSwitch添加了具有此限制的表单set_field。 OpenFlow 1.5将OFPAT_SET_FIELD扩展到了它成为NXAST_REG_LOAD的超集的地步。 Open vSwitch会根据所使用的OpenFlow版本转换两种语法:OpenFlow 1.0和1.1中的NXAST_REG_LOAD;在OpenFlow 1.2、1.3和1.4中,NXAST_REG_LOAD用于加载或加载子字段,否则为OFPAT_SET_FIELD; OpenFlow 1.5及更高版本,OFPAT_SET_FIELD。

push:src[start..end]

在堆栈顶部的字段中,压入开始(包括结束)位。

示例:push:NXM_NX_REG2 [0..5]将存储在寄存器2位0到5(含0和5)中的值压入内部堆栈。

pop:dst[start..end]

从堆栈的顶部弹出,从弹出的值中检索包含开始到结束的位,并将它们存储在dst中的相应位中。

示例:pop:NXM_NX_REG2 [0..5]从堆栈顶部弹出该值。根据刚刚弹出的值的0至5位,将寄存器2的0至5位(包括0和5)设置为1。

multipath(fields,basis,algorithm,n_links,arg,dst[start..end])

使用base作为通用哈希参数对字段进行哈希处理,然后应用多路径链接选择算法(带有参数arg)从0到n_links减去1来选择n_links输出链接之一,并将链接存储到dst [start..end]中,它必须是如上所述的NXM字段。

其他的请参考官方操作手册

查看原文

赞 0 收藏 0 评论 0

整点bug 发布了文章 · 2020-12-16

Open vSwitch 入门实践(4)使用OVS配置端口镜像

前言

当我们想要在不影响虚拟网络设备数据报文收发的情况下获取对应虚拟网络设备的流量时,端口镜像是一个很好的选择。端口镜像是指将经过指定端口(镜像端口)的报文复制一份到另一个指定端口(观察端口),通过观察端口接收到的数据报文,就可以有效识别虚拟网络的运行情况。

OVS提供了相关命令来配置或删除端口镜像,下面我们来实验一下。

如何使用

端口镜像类型

端口镜像分为镜像源和镜像目的两部分。

镜像源

  • select_all:布尔类型(true,false)。设置为 true 时,表示此网桥上的所有流量。
  • select_dst_port:字符串(端口名称)。表示此端口接收的所有流量。
  • select_src_port:字符串(端口名称)。表示此端口发送的所有流量。
  • select_vlan:整形(1-4096)。表示携带此VLAN标签的流量。

镜像目的

  • output_port:字符串(端口名称)。接收流量报文的观察端口。
  • output_vlan:整形(1-4096)。表示只修改VLAN标签,原VLAN标签会被剥离。

基础操作命令

新增端口镜像

ovs-vsctl -- set Bridge <bridge_name> mirrors=@m \
 -- --id=@<port0> get Port <port0> \
 -- --id=@<port1> get Port <port1> \
 -- --id=@m create Mirror name=<mirror_name> select-dst-port=@<port0> select-src-port=@<port0> output-port=@<port1>
这行命令会输出一个镜像ID

删除端口镜像

ovs-vsctl remove Bridge <bridge-name> mirrors <mirror-id>

在原端口镜像的基础上增加一个镜像源

# 获取端口的ID
ovs-vsctl get port <port_name> _uuid

# 在原端口镜像的基础上增加镜像源
ovs-vsctl add Mirror <mirror-name> select_src_port <port-id>
ovs-vsctl add Mirror <mirror-name> select_dst_port <port-id>

在原端口镜像的基础上删除一个镜像源

# 获取端口的ID
ovs-vsctl get port <port_name> _uuid

ovs-vsctl remove Mirror <mirror-name> select_src_port <port-id>
ovs-vsctl remove Mirror <mirror-name> select_dst_port <port-id>

清空端口镜像

ovs-vsctl clear Mirror 

查看端口镜像

ovs-vsctl list Mirror 

关闭端口的MAC地址学习

ovs-ofctl mod-port <bridge-name> <port-name> NO-FLOOD

实验

实验拓扑

实验拓扑分为一个网桥,三个虚拟网络设备,

# 添加网桥
ovs-vsctl add-br br-int
# 添加三个内部端口
ovs-vsctl add-port br-int vnet0 -- set Interface vnet0 type=internal
ovs-vsctl add-port br-int vnet1 -- set Interface vnet1 type=internal
ovs-vsctl add-port br-int vnet2 -- set Interface vnet2 type=internal
# 添加三个netns
ip netns add ns0
ip netns add ns1
ip netns add ns2
# 将内部端口分别移动到netns中
ip link set vnet0 netns ns0
ip link set vnet1 netns ns1
ip link set vnet2 netns ns2

# 启动端口并配置IP
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set vnet0 up
ip netns exec ns0 ip addr add 10.0.0.1/24 dev vnet0

ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip link set vnet1 up
ip netns exec ns1 ip addr add 10.0.0.2/24 dev vnet1
# 注意这里只启动了网卡,但没有配置IP
ip netns exec ns2 ip link set lo up
ip netns exec ns2 ip link set vnet2 up

ovs-vsctl -- set Bridge br-int mirrors=@m \
 -- --id=@vnet1 get Port vnet1 \
 -- --id=@vnet2 get Port vnet2 \
 -- --id=@m create Mirror name=mirror_test select-dst-port=@vnet1 select-src-port=@vnet1 output-port=@vnet2

测试

执行以下命令产生流量

ip netns exec ns0 ping 10.0.0.2

重新打开一个终端执行以下命令抓包

ip netns exec ns2 tcpdump -i vnet2
需要安装tcpdump才能使用

输出

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vnet2, link-type EN10MB (Ethernet), capture size 262144 bytes
22:26:31.140974 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 4599, seq 23, length 64
22:26:31.140996 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 4599, seq 23, length 64
22:26:32.141066 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 4599, seq 24, length 64
22:26:32.141085 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 4599, seq 24, length 64
22:26:33.141066 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 4599, seq 25, length 64
22:26:33.141108 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 4599, seq 25, length 64
22:26:34.141044 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 4599, seq 26, length 64
22:26:34.141062 IP 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 4599, seq 26, length 64
^C
8 packets captured
8 packets received by filter
0 packets dropped by kernel

清理实验环境

ip netns del ns0
ip netns del ns1
ip netns del ns2

ovs-vsctl del-br br-int
本文首发我的微信公众号:我在对面的角落
欢迎关注,接收第一时间更新通知。

欢迎关注微信公众号 接收第一时间更新通知

查看原文

赞 0 收藏 0 评论 0

整点bug 发布了文章 · 2020-12-15

Open vSwitch 入门实践(3)使用OVS构建分布式隔离网络

使用OVS构建分布式隔离网络

前言

上一节我们使用OVS构建了单机隔离网络,但是随着网络规模的扩张,单节点已经不再能满足业务的需要,分布式网络成了必不可少的环节。分布式网络与单节点网络在细节实现上基本一致,只有物理环境网络连线上的一点区别。

实验1:分布式无隔离网络

网络拓扑如下图所示,我们每一台节点都有两张网卡,一张用于管理,一张用于业务。之所以使用两张网卡有两个原因:

  1. 管理网卡用于日常的维护登录,业务网卡用于传输虚拟节点的数据报文,避免相互之间影响。
  2. 我们要将业务网卡绑定到OVS网桥上,也就是Normal类型的Port。这种方式添加的Port不支持分配IP地址,如果之前网卡上配置的有IP,挂载到OVS上面之后将不可访问。
需要注意的是,如果是使用物理环境搭建网络拓扑,需要把业务网卡对应的交换机端口配置为trunk模式。如果是使用VmWare搭建网络拓扑,业务网卡需要配置网络类型为仅主机模式

分布式无隔离网络

配置

  • 配置环境 主机A
ovs-vsctl add-br br-int
# 请修改eth1为当前实验环境的业务网卡名称
ovs-vsctl add-port br-int eth1

# 添加两个内部端口
ovs-vsctl add-port br-int vnet0 -- set Interface vnet0 type=internal
ovs-vsctl add-port br-int vnet1 -- set Interface vnet1 type=internal
# 添加两个netns
ip netns add ns0
ip netns add ns1
# 将内部端口分别移动到netns中
ip link set vnet0 netns ns0
ip link set vnet1 netns ns1

# 启动端口并配置IP
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set vnet0 up
ip netns exec ns0 ip addr add 10.0.0.1/24 dev vnet0

ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip link set vnet1 up
ip netns exec ns1 ip addr add 10.0.0.2/24 dev vnet1
  • 配置环境 主机B
ovs-vsctl add-br br-int
# 请修改eth1为当前实验环境的业务网卡名称
ovs-vsctl add-port br-int eth1

# 添加两个内部端口
ovs-vsctl add-port br-int vnet0 -- set Interface vnet0 type=internal
ovs-vsctl add-port br-int vnet1 -- set Interface vnet1 type=internal
# 添加两个netns
ip netns add ns0
ip netns add ns1
# 将内部端口分别移动到netns中
ip link set vnet0 netns ns0
ip link set vnet1 netns ns1

# 启动端口并配置IP
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set vnet0 up
ip netns exec ns0 ip addr add 10.0.0.3/24 dev vnet0

ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip link set vnet1 up
ip netns exec ns1 ip addr add 10.0.0.4/24 dev vnet1

测试

  • 测试 主机A
ip netns exec ns0 ping 10.0.0.3
ip netns exec ns0 ping 10.0.0.4
ip netns exec ns1 ping 10.0.0.3
ip netns exec ns1 ping 10.0.0.4
  • 测试 主机B
ip netns exec ns0 ping 10.0.0.1
ip netns exec ns0 ping 10.0.0.2
ip netns exec ns1 ping 10.0.0.1
ip netns exec ns1 ping 10.0.0.2
  • 测试结果
主机A主机Bping 结果
ns0ns0可通信 ✅
ns0ns1可通信 ✅
ns1ns0可通信 ✅
ns1ns1可通信 ✅

根据测试结果可以看到我们使用OVS成功的联通了分布在不同主机上的虚拟网络设备。

实验2:分布式隔离网络

构建分布式隔离网络和单节点的操作方法一致,即给对应的端口配置VLAN tag。如下图所示,我们分别给主机A、B上的端口配置VLAN tag为100和200。

分布式无隔离网络

配置

  • 配置环境 主机A
ovs-vsctl set Port vnet0 tag=100
ovs-vsctl set Port vnet1 tag=200
  • 配置环境 主机B
ovs-vsctl set Port vnet0 tag=100
ovs-vsctl set Port vnet1 tag=200

测试

  • 测试 主机A
ip netns exec ns0 ping 10.0.0.3
ip netns exec ns0 ping 10.0.0.4
ip netns exec ns1 ping 10.0.0.3
ip netns exec ns1 ping 10.0.0.4
  • 测试 主机B
ip netns exec ns0 ping 10.0.0.1
ip netns exec ns0 ping 10.0.0.2
ip netns exec ns1 ping 10.0.0.1
ip netns exec ns1 ping 10.0.0.2
  • 测试结果
主机A主机Bping 结果
ns0ns0可通信 ✅
ns0ns1不通信 ❌
ns1ns0不通信 ❌
ns1ns1可通信 ✅

根据测试结果可以看到我们使用OVS成功的隔离了分布在不同主机上的虚拟网络设备。

本文首发我的微信公众号:我在对面的角落
欢迎关注,接收第一时间更新通知。

欢迎关注微信公众号 接收第一时间更新通知

查看原文

赞 0 收藏 0 评论 0

整点bug 发布了文章 · 2020-12-14

Open vSwitch 入门实践(2)使用OVS构建隔离网络

前言

在前面我们已经使用Linux Bridge完成了多台网络设备的通信,但是它对于网络隔离的支持不是很好,长期以来,在Linux平台上缺少一个功能完备的虚拟交换机,直到OVS的出现。

实验

接下来我们来尝试完成两个实验,单机无隔离网络、单机隔离网络。

实验一:单机无隔离网络

使用ovs构建无隔离网络非常简单,只需要添加一个网桥,然后在这个网桥上再增加几个内部端口,最后把端口移动到netns中即可。

# 添加网桥
ovs-vsctl add-br br-int
# 添加三个内部端口
ovs-vsctl add-port br-int vnet0 -- set Interface vnet0 type=internal
ovs-vsctl add-port br-int vnet1 -- set Interface vnet1 type=internal
ovs-vsctl add-port br-int vnet2 -- set Interface vnet2 type=internal
# 添加三个netns
ip netns add ns0
ip netns add ns1
ip netns add ns2
# 将内部端口分别移动到netns中
ip link set vnet0 netns ns0
ip link set vnet1 netns ns1
ip link set vnet2 netns ns2

# 启动端口并配置IP
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set vnet0 up
ip netns exec ns0 ip addr add 10.0.0.1/24 dev vnet0

ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip link set vnet1 up
ip netns exec ns1 ip addr add 10.0.0.2/24 dev vnet1

ip netns exec ns2 ip link set lo up
ip netns exec ns2 ip link set vnet2 up
ip netns exec ns2 ip addr add 10.0.0.3/24 dev vnet2

测试

测试ns0ns1能否通信ip netns exec ns0 ping 10.0.0.2

PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.05 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.059 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.056 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.053 ms
^C
--- 10.0.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.053/0.304/1.051/0.431 ms

测试ns0和ns2能否通信ip netns exec ns0 ping 10.0.0.3

PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data.
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=1.17 ms
64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.067 ms
64 bytes from 10.0.0.3: icmp_seq=3 ttl=64 time=0.058 ms
64 bytes from 10.0.0.3: icmp_seq=4 ttl=64 time=0.064 ms
^C
--- 10.0.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 0.058/0.341/1.177/0.482 ms

根据测试结果可以看到,三台设备都是可以互相访问的,这样我们就成功搭建了一个无隔离的二层互通网络。

实验二: 单机隔离网络

使用ovs构建隔离网络也很简单,只需要给相应的端口设置上VLAN标签,就能实现网络的隔离。

# 设置vnet0的VLAN tag为100
ovs-vsctl set Port vnet0 tag=100
# 设置vnet1和vnet2的VLAN tag为200
ovs-vsctl set Port vnet1 tag=200
ovs-vsctl set Port vnet2 tag=200

使用ovs-vsctl show命令查看VLAN tag是否配置成功

90139c71-8d11-49b2-b44c-f34174259dc8
    Bridge br-int
        Port "vnet0"
            tag: 100
            Interface "vnet0"
                type: internal
        Port br-int
            Interface br-int
                type: internal
        Port "vnet2"
            tag: 200
            Interface "vnet2"
                type: internal
        Port "vnet1"
            tag: 200
            Interface "vnet1"
                type: internal
    ovs_version: "2.9.0"

测试

测试ns0ns1的能否通信 ip netns exec ns0 ping 10.0.0.2

PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
^C
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1000ms

测试ns0ns2的能否通信 ip netns exec ns0 ping 10.0.0.3

PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data.
^C
--- 10.0.0.3 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 999ms

测试ns1ns2的能否通信 ip netns exec ns1 ping 10.0.0.3

PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data.
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.930 ms
64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.057 ms
64 bytes from 10.0.0.3: icmp_seq=3 ttl=64 time=0.056 ms
64 bytes from 10.0.0.3: icmp_seq=4 ttl=64 time=0.057 ms
^C
--- 10.0.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.056/0.275/0.930/0.378 ms

测试ns2ns1的能否通信 ip netns exec ns2 ping 10.0.0.2

PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.088 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.057 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.050 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.060 ms
^C
--- 10.0.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 2999ms
rtt min/avg/max/mdev = 0.050/0.063/0.088/0.017 ms

根据测试结果可以看出,ns0是无法访问到ns1ns2的,ns1ns2可以互相访问。这是因为端口vnet0的数据报文发出后被OVS修改了包头,增加了VLAN 100标签,与vnet1vnet2的VLAN 200标签不匹配,OVS交换机便不再将vnet0的数据报文发送给其他两个端口,由此便实现了网络隔离。

清理实验环境

ovs-vsctl del-br br-int
ip netns del ns0
ip netns del ns1
ip netns del ns2
本文首发我的微信公众号:我在对面的角落
欢迎关注,接收第一时间更新通知。

image

查看原文

赞 0 收藏 0 评论 0

整点bug 发布了文章 · 2020-12-12

Open vSwitch 入门实践(1)简介

OVS简介

Open vSwitch 是什么?

Open vSwitch(以下简称OVS)是一个用C语言开发的多层虚拟交换机,使用Apcahe 2开源许可证,现如今基本上已经成为了开源SDN(软件定义网络)基础设施层的事实标准。

OVS支持哪些功能

  • 支持NetFlow、sFlow(R)、IPFIX、SPAN、RSPAN和GRE隧道镜像等多种流量监控协议
  • 支持LACP (IEEE 802.1AX-2008)
  • 支持标准802.1Q VLAN协议,允许端口配置trunk模式
  • 支持组播
  • 支持BFD和802.1ag链路监控
  • 支持STP(IEEE 802.1D-1998)和RSTP(IEEE 802.1D-2004)
  • 支持细粒度的QoS(服务质量)配置
  • 支持HFSC qdisc
  • 支持接管每一个虚拟机的流量
  • 支持基于源MAC的负载均衡、主备模式和L4哈希的端口绑带
  • 支持OpenFlow协议(包含了很多对虚拟化的扩展)
  • 支持IPv6
  • 支持多种隧道协议(GRE、VXLAN、STT、Geneve和IPsec)
  • 支持C和Python的远程配置协议
  • 支持内核和用户空间的转发引擎选项
  • 具有流缓存引擎的多表转发管道
  • 转发层抽象以简化向新软件和硬件平台的移植

OVS的术语解释

Bridge

中文名称网桥,一个Bridge代表一个以太网交换机(Switch),一台主机中可以创建一个或多个Bridge,Bridge可以根据一定的规则,把某一个端口接收到的数据报文转发到另一个或多个端口上,也可以修改或者丢弃数据报文。

Port

中文名称端口,需要注意的是它和TCP里面的端口不是同样的概念,它更像是物理交换机上面的插口,可以接水晶头的那种。Port隶属于Bridge,必须先添加了Bridge才能在Bridge上添加Port。Port有以下几种类型:

  • Normal

    用户可以把操作系统中已有的网卡添加到Open vSwicth上,Open vSwitct会自动生成一个同名的Port开处理这张网卡进和出的数据报文。

    不过需要注意的是这种方式添加的Port不支持分配IP地址,如果之前网卡上配置的有IP,挂载到OVS上面之后将不可访问。此类型的Port常用于VLAN模式的多台物理主机相连的那个口,交换机一端属于Trunk模式。
  • Internal

    当Port的类型是Internal时,OVS会自动创建一个虚拟网卡(Interface),此端口收到的数据报文都会转发给这块网卡,从这块网卡发出的数据报文也会通过Port交给OVS处理。当OVS创建一个新的网桥时,会自动创建一个与网桥同名的Internal Port,同时也会创建一个与网桥同名的Interface,因此可以通过ip命令在操作系统中查看到这张虚拟网卡,但是状态是down的。

  • Patch

    Patch Port和veth pair功能相同,总是成双成对的出现,在其中一端收到的数据报文会被转发到另一个Patch Port上,就像是一根网线一样。Patch Port常用于连接两个Bridge,这样两个网桥就和一个网桥一样了。

  • Tunnel

    OVS 支持 GRE、VXLAN、STT、Geneve和IPsec隧道协议,这些隧道协议就是overlay网络的基础协议,通过对物理网络做的一层封装和扩展,解决了二层网络数量不足的问题,最大限度的减少对底层物理网络拓扑的依赖性,同时也最大限度的增加了对网络的控制。

Interface

(iface/接口)接口是OVS与操作系统交换数据报文的组件,一个接口即是操作系统上的一块网卡,这个网卡可能是OVS生成的虚拟网卡,也有可能是挂载在OVS上的物理网卡,操作系统上的虚拟网卡(TUN/TAP)也可以被挂载在OVS上。

Controller

OpenFlow控制器,OVS可以接收一个或者多个OpenFlow控制器的管理,功能主要是下发流表,控制转发规则。

Flow

流表是OVS进行数据转发的核心功能,定义了端口之间转发数据报文的规则,一条流表规则主要分为匹配和动作两部分,匹配部分决定哪些数据报文需要被处理,动作决定了匹配到的数据报文该如何处理。

OVS常用操作

安装

yum install openvswitch
systemctl enable openvswitch
systemctl start openvswitch
如果当前软件源中没有openvswitch,可以通过阿里云官方镜像站下载和操作系统版本对应的rpm包到本地再安装。 示例命令: yum localinstall openvswitch-2.9.0-3.el7.x86_64.rpm

Bridge 操作

添加网桥

ovs-vsctl add-br br-int

查询网桥列表

ovs-vsctl list-br

删除网桥

ovs-vsctl del-br br-int

Port 操作

  • Normal Port
# 将物理网卡eth0添加到网桥br-int上
ovs-vsctl add-port br-int eth0
# 移除网桥br-int上的Port
ovs-vsctl del-port br-int eth0
  • Internal Port
# 添加Internal Port 
ovs-vsctl add-port br-int vnet0 -- set Interface vnet0 type=internal
# 把网卡vnet0启动并配置IP
ip link set vnet0 up
ip addr add 192.168.0.1/24 dev vnet0
# 设置VLAN tag
ovs-vsctl set Port vnet0 tag=100
# 移除vnet0上面的VLAN tag配置
ovs-vsctl remove Port vnet0 tag 100
# 设置vnet0允许通过的VLAN tag
ovs-vsctl set Port vnet0 trunks=100,200
# 移除vnet0允许通过的的VLAN tag配置
ovs-vsctl remove Port vnet0 trunks 100,200
  • Patch Port
ovs-vsctl add-br br0
ovs-vsctl add-br br1
ovs-vsctl \
-- add-port br0 patch0 -- set interface patch0 type=patch options:peer=patch1 \
-- add-port br1 patch1 -- set interface patch1 type=patch options:peer=patch0
  • Tunnel Port
#主机10.1.7.21上
ovs-vsctl add-br br-tun
ovs-vsctl add-port br-tun vxlan-vx01 -- set Interface vxlan-vx01 type=vxlan options:remote_ip=10.1.7.22 options:key=flow
ovs-vsctl add-port br-tun vxlan-vx02 -- set Interface vxlan-vx02 type=vxlan options:remote_ip=10.1.7.23 options:key=flow

#主机10.1.7.22上
ovs-vsctl add-br br-tun
ovs-vsctl add-port br-tun vxlan-vx01 -- set Interface vxlan-vx01 type=vxlan options:remote_ip=10.1.7.21 options:key=flow
ovs-vsctl add-port br-tun vxlan-vx02 -- set Interface vxlan-vx02 type=vxlan options:remote_ip=10.1.7.23 options:key=flow

#主机10.1.7.23上
ovs-vsctl add-br br-tun
ovs-vsctl add-port br-tun vxlan-vx01 -- set Interface vxlan-vx01 type=vxlan options:remote_ip=10.1.7.21 options:key=flow
ovs-vsctl add-port br-tun vxlan-vx02 -- set Interface vxlan-vx02 type=vxlan options:remote_ip=10.1.7.22 options:key=flow
  • 其他基本操作
# 设置VLAN mode
ovs-vsctl set port <port name> VLAN_mode=trunk|access|native-tagged|native-untagged
# 设置VLAN tag
ovs-vsctl set port <port name> tag=<1-4095>
# 设置VLAN trunk
ovs-vsctl set port <port name> trunk=100,200
# 移除Port的属性
ovs-vsctl remove port <port name> <property name> <property value>
# 查看Port的属性
ovs-vsctl list interface <port name>

接下来我们将使用OVS来实现单机和多台物理服务器下的虚拟VLAN网络。

本文首发我的微信公众号:我在对面的角落
欢迎关注,接收第一时间更新通知。

image

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 50 次点赞
  • 获得 9 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 8 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • tcpwall

    tcpkill 在go语言下的实现和增强

  • 4dnat

    200行go代码实现TCP报文转发,可用于内网穿透和端口转发,工作在第四层。

注册于 2015-11-30
个人主页被 2.9k 人浏览