一、安装pox
pox基于python2.7
$ git clone http://github.com/noxrepo/pox
$ cd pox
~/pox$ git checkout dart
二、调用pox
- 如果想快速入门
./pox.py samples.pretty_log Forwarding.l2_learning
- POX本身有几个可选的命令行参数,这些参数可以在命令行开始时使用:
选项 | 含义 |
---|---|
–no-cli | 不要启动交互式shell(从betta开始不再适用 |
–no-openflow | 不要自动开始侦听OpenFlow连接(从dart开始有用,它仅按需加载OpenFlow) |
–verbose | 显示其他信息(对于调试启动问题特别有用) |
l2_learning具有“透明”模式,其中交换机甚至将转发通常丢弃的数据包(例如LLDP消息),并且Web服务器的端口号可以从默认值(8000)更改为任意端口
eg:
./pox.py --no-cli Forwarding.l2_learning --transparent web.webcore --port = 8888
三、POX中的组件
3.1库存成分
-
py
该组件使POX启动一个交互式Python解释器,该解释器可用于调试和交互式实验。在betta版本之前,这是默认行为 -
forwarding.hub
仅在每个交换机上安装通配泛洪规则 -
Forwarding.l2_learning
使OpenFlow交换机充当L2学习交换机的一种。当此组件学习L2地址时,它安装的流在尽可能多的字段上都是完全匹配的。 -
forward.l2_pairs
像l2_learning一样,此组件也使OpenFlow交换机的行为类似于L2学习交换机的类型。但是,与l2_learning不同,l2_pairs仅基于MAC地址安装规则。 -
Forwarding.l3_learning
它最有用的方面是作为使用POX的数据包库检查和构造ARP请求和答复的一个很好的例子。l3_learning并不真正在乎诸如子网之类的传统IP东西,它只是了解IP地址在哪里。 -
Forwarding.l2_multi
L2层地址学习,但该层的学习不是单个交换机的独立学习,而是通过openflow.discovery交换机之间交换拓扑信息,学习整个网络的拓扑结构。只要网络中有一个交换机学习到一个新的Mac地址及其位置,所有的交换机就都能学会。 -
forwarding.l2_nx
Open vSwitch的quick-and-dirty组件,需要使用Openvswitch的Nicira扩展安装。 -
forwarding.topo_proactive
基于重要拓扑的IP地址安装规则。通过DHCP进行地址分配。 -
openflow.spanning_tree
该组件使用discovery组件来创建网络拓扑的视图,构造一棵生成树,然后使不在生成树中的交换机端口的洪泛功能失效,使得网络中不存在洪泛回路。
两个选项:
--no-flood,只要交换机连接上了就使该交换机的所有端口洪泛失效,对于某些端口,稍后将使能。
--hold-down,防止洪泛控制在一个完整的发现回路完成前被改变
因此该组件最安全的的使用方法是
openflow.spanning_tree --no-flood --hold-down . -
openflow.webservice
Openflow的一个简单JSON-RPC-ish web service交互式接口,由of_service信息服务派生而来,依赖于webcore组件。可以使用HTTP POST方式发送JSON进行访问。
目前支持的方法有:
get_flow_stats,获取流表的表项
get_switch_desc,获取指定交换机详细信息
get_switches,获取交换机列表和基本信息
set_table , 设置指定交换机的流表 -
web.webcore
在Pox进程中启动一个web服务,其他组件可以通过它提供静态或动态内容。 -
messenger
该组件通过双向JSON消息为POX在进程间提供了一个交互接口。该组件本质上是API,通过TCP Socket和HTTP进行通信。具体的功能通过Services实现。messenger.log_service允许远程操作log(读log信息, 配置log等)。openflow.of_service 允许一下Openflow的操作(如显示交换机列表,设置流表表项等)。./tools/pox-log.py是一个独立的Python应用,可以通过 TCP同log服务进行交互。 -
openflow.of_01
该组件同openflow 1.0协议版本的交换机 进行通讯,默认启动。 -
openflow.discovery
该组件在交换机之间使用特制的LLDP报文来发现整个网络的拓扑结构。当链路生效或者失效时,该组件都会产生一个事件(Raise Events)。 -
openflow.debug
加载该组件将导致POX创建pcap追踪(进行抓包),包括openflow报文,可导入wireshark进行分析。该工具并不能完全代替wireshark或tcpdump,不过有一个比较好的特性是每一个openflow报文都一个完整的帧中。 -
openflow.keepalive
该组件令POX向已经连接的交换机周期性的发送echo请求。但这会解决两个问题:
第一,有些交换机(包括推荐交换机)会认为空闲连接意味着同控制器连接丢失,将会在一段silence时间后断开
连接。
第二,如果网络与交换机断开,控制器将不会立即获得一个FIN或RST,所以将会很难确定一个交换机失效。通过周期行发送echo请求,并分析交换机的响应,即可解决该问题。 -
proto.pong
该组件是一个简单的检测ICMP echo请求和应答的样例组件 -
proto.arp_responder
该组件为一个ARP应用,可以学习和代理ARP请求,也可以通过查询静态的表项来回复ARP请求。该组件提供了一个控制台交互界面来查询和修改arp表。 -
info.packet_dump
该组件将packet_in信息保存至log中,有点类似于在交换机中运行tcpdump -
proto.dns_spy
检测DNS应答并存储应答结果,其他组件可以通过DNSSpy检测这些信息。 -
proto.dhcp_client
DHCP客户端,在同其他组件进行联合时有用(??) -
proto.dhcpd
简单的DHCP服务器端,服务器本身的默认地址为192.168.0.254,下发的地址域为192.168.0.1~192.168.0.253,同时宣称自身为网关和DNS服务器。 -
misc.of_tutorial
配合openflow tutorial使用的组件,类似于简单的hub,但可以修改成L2 learning的交换机 -
misc.full_payload
默认情况下,当一个数据包在交换机流表中没有命中时,交换机只向控制器发送数据包的前128bytes,使用该组件可以将每一个交换机配置成发送整个数据包 -
misc.mac_blocker
具有Tkinter-based界面,可以阻塞Mac地址 -
misc.nat
实现网络地址转换的组件(木有详细介绍) -
misc.ip_loadbalancer
由carp branch(不理解是啥)启用的TCP负载均衡器 -
misc.gephi_topo
检测拓扑结构,并将其导入到gephi中进行分析
3.2 自定义组件
- ext目录
“ ext”目录是构建您自己的组件的方便位置,因为POX会自动将其添加到Python搜索路径(即在其中查找其他模块),并且将其从POX git存储库中排除。
开始构建自己的POX模块的一种常见方法是简单地将现有模块(例如forwarding/l2_learning.py)复制到ext目录(例如ext/my_component.py)中。
四、POX API
4.1 pox core对象
pox是许多pox api的中心点,它提供的某些功能只是围绕其他功能的便捷包装,而有些则是唯一的。但是,核心对象的另一个主要目的是在组件之间提供一个集合点。
- 注册组件
对于组件来说,将提供api的对象注册在核心对象上会更方便。在OpenFlow实施中可以找到一个示例POS OpenFlow组件默认将OpenFlowNexus的实例住粗为core.openflow,然后其它程序可以从哪里访问许多OpenFlow功能。 - 依赖和事件管理
当pox中的组件依赖于其它组件,通常是因为他们想要监听该组件的事件。(例子看不懂)
4.2 使用地址:pox.lib.addresses
POX中的IPv4,IPv6和以太网地址由pox.lib.addresses的IPAddr,IPAddr6和EthAddr类表示。
from pox.lib.addresses import IPAddr, IPAddr6, EthAddr
ip = IPAddr("192.168.1.1")
print str(ip) # Prints "192.168.1.1"
print ip.toUnsignedN() # Convert to network-order unsigned integer -- 16885952
print ip.raw # Returns a length-four bytes object (a four byte string, more or less)
ip = IPAddr(16885952,networkOrder=True)
print str(ip) # Also prints "192.168.1.1" !
4.3 事件系统:pox.lib.revent
POX中的事件都是revent.Event实例的子类。引发事件的类从revent.EventMixin继承,并声明它在eventMixin.events的类级变量中引发的事件。
4.3.1 处理事件
- 事件处理程序
它是一个函数或方法或可调用的其它东西,只有一个参数 - 聆听事件
chef.addListener(SpamFinished, spam_ready)
or
chef.addListenerByName("SpamFinished", spam_ready)
- 自动设置监听器
使用addListeners()侦听同一源对象的多个事件。
class HungryPerson (object):
""" Models a person that loves to eat spam """
def __init__ (self):
chef.addListeners(self) //查看chef的事件,self名称为方法时,为侦听器
def _handle_SpamStarted (self, event):
print "I can't wait to eat!"
def _handle_SpamFinished (self, event):
print "Spam is ready! Smells delicious!"
要用一个类来监听来自多个事件源的事件,用前缀来区分不同的事件。
class VeryHungryPerson (object):
""" Models a person that is hungry enough to need two chefs """
def __init__ (self):
master_chef.addListeners(self, prefix="master")
backup_chef.addListeners(self, prefix="secondary")
def _handle_master_SpamFinished (self, event):
print "Spam is ready! Smells delicious!"
def _handle_secondary_SpamFinished (self, event):
print "Backup spam is ready. Smells slightly less delicious."
- 创建自己的事件类型
事件是子类revent.event,因此创建事件只需创建子类Event
class SpamStarted (Event):
def __init__ (self, brand = "Hormel"):
Event.__init__(self)
self.brand = brand
@property
def genuine (self):
# If it's not Hormel, it's just canned spiced ham!
return self.brand == "Hormel"
- 引发事件
chef.raiseEvent(SpamStarted("Generic"))
or
chef.raiseEvent(SpamStarted, "Generic")
4.4 使用数据包:pox.lib.packet
POX中的许多应用程序都与数据包进行交互(例如,构造数据包并将其发送到交换机之外,或者会通过ofp_packet_inOpenFlow消息从交换机接收到它们)。为方便起见,POX具有用于解析和构造数据包的库。
pox支持的某些数据包类型包括:
- ethernet
- ARP
- IPv4
- ICMP
- TCP
- UDP
- DHCP
- DNS
- LLDP
- VLAN
引用pox数据包库为:import pox.lib.packet as pkt
包对象的find()方法可用于通过所需的类型名称(例如"icmp")或其类(例如pkt.ICMP)来查找特定的封装包。如果数据包对象未封装所请求类型的数据包,则find()返回无。例如:
def handle_IP_packet (packet):
ip = packet.find('ipv4')
if ip is None:
# This packet isn't IP!
return
print "Source IP:", ip.srcip
4.4.1 以太网(ethernet)
属性:
- dst(EthAddr)
- src(EthAddr)
- type(int)-以太网类型或以太网长度字段。对于带有VLAN标签的帧,该值为0x8100
Effective_ethertype(int)-以太网类型或以太网长度字段。对于带有VLAN标签的帧,这将是VLAN标头中引用的类型。
常数:
IP_TYPE,ARP_TYPE,RARP_TYPE,VLAN_TYPE,LLDP_TYPE,JUMBO_TYPE,QINQ_TYPE-各种以太类型
4.4.2 ipv4
IP版本4(ipv4)
属性:
- srcip(IPAddr)
- dstip(IPAddr)
- tos(int)-8位服务类型/ DSCP + ECN
- id(int)-标识字段
- 标志(int)
- frag(int)-片段偏移
- ttl(int)
- protocol(int)-有效负载的IP协议编号
- csum(int)-校验和
常数:
ICMP_PROTOCOL,TCP_PROTOCOL,UDP_PROTOCOL-各种IP协议编号
DF_FLAG-不分段标志位
MF_FLAG-更多片段标志位
4.4.3 tcp
属性:
- srcport(int)-源TCP端口号
- dstport(int)-目标TCP端口号
- seq(int)-序列号
- ack(int)-ACK号
- off(int)-偏移量
- flags(int)-标记为位字段(更易于使用全大写标记属性)
- csum(int)-校验和
- options(tcp_opt对象的列表)
- win(int)-窗口大小
- urg(int)-紧急指针
- FIN(bool)-设置FIN标志时为真
- SYN(bool)-设置SYN标志时为True
- RST(bool)-设置RST标志时为true
- PSH(bool)-设置PSH标志时为true
- ACK(bool)-设置ACK标志时为真
- URG(bool)-设置URG标志时为真
- ECN(bool)-设置ECN标志时为true
- CWR(bool)-设置CWR标志时为true
常数:
FIN_flag,SYN_flag等 -与标志对应的位
tcp_opt类
属性:
type(int)-TCP选项ID(可能在下面对应于常数)
val(变量)-选项值
常数:
EOL,NOP,MSS,WSOPT,SACKPERM,SACK,TSOPT-选项类型ID
4.4.4 arp messages
def _handle_PacketIn (self, event):
packet = event.parsed
if packet.type == packet.ARP_TYPE:
if packet.payload.opcode == arp.REQUEST:
arp_reply = arp()
arp_reply.hwsrc = <requested mac address>
arp_reply.hwdst = packet.src
arp_reply.opcode = arp.REPLY
arp_reply.protosrc = <IP of requested mac-associated machine>
arp_reply.protodst = packet.payload.protosrc
ether = ethernet()
ether.type = ethernet.ARP_TYPE
ether.dst = packet.src
ether.src = <requested mac address>
ether.payload = arp_reply
#send this packet to the switch
#see section below on this topic
elif packet.payload.opcode == arp.REPLY:
print "It's a reply; do something cool"
else:
print "Some other ARP opcode, probably do something smart here"
五、pox中的OpenFlow
5.1DPID:
OpenFlow中每个数据路径(交换机)具有唯一的数据路径ID或DPID,该ID或DPID是64位值,并且在握手期间通过ofp_switch_features消息从交换机传递到控制器。
5.2 与数据路径通信
- 1)当从控制器到交换机进行通信时,这是由控制器代码执行的,该代码将OpenFlow消息发送到特定的交换机(稍后对此进行详细介绍)。2)当消息来自交换机时,它们作为事件显示在POX中您可以为其编写事件处理程序–通常,有一个事件类型对应于交换机可能发送的每种消息类型。
- 与POX中的数据路径进行通信的方式基本上有两种:通过Connection该特定数据路径的对象或通过管理该数据路径的OpenFlow Nexus。Connection连接到POX的每个数据路径都有一个对象,并且通常有一个OpenFlow Nexus管理所有连接。
- 在正常配置中,只有一个OpenFlow关系,可以作为core.openflow。两者之间有很多重叠Connections和Nexus。
- 侦听所有事件时可以选择侦听nexus,只对一个交换机感兴趣时侦听connection。
5.2.1 connection对象
每次开关连接到POX时,都会有一个关联的Connection对象。
connection对象属性 | 描述 |
---|---|
ofnexus | 对与此连接关联的关系对象的引用 |
dpid | 交换机的数据路径标识符 |
features | 交换机在握手过程中具有交换机发送的回复 |
ports | 交换机上的端口。此属性是对特殊PortCollection对象的引用,有端口号,以太网地址,端口名称 |
sock | 连接到对等方的套接字。 |
send(msg) | 一种用于向交换机发送OpenFlow消息的方法。 |
使用方法:self.connections = set()
5.2.2 Nexus – core.openflow
一个OpenFlow nuxus 本质上是一组OpenFlow的管理器Connections。
属性 | 描述 |
---|---|
miss_send_len | 当数据包与数据路径上的任何表条目都不匹配时,数据路径将在数据包进入消息中将数据包转发到控制器。 |
clear_flows_of_connect | 如果为True(默认值),则POX在连接时将删除交换机第一个表上的所有流。 |
connections | 一个特殊的集合,包含对该关系正在处理的所有连接的引用。 |
getConnection(< dpid>) | 通过其DPID或特定(如果不可用)获取特定数据路径的connection对象。 |
sendToDPID(< dpid>,< msg>) | 将OpenFlow消息发送到特定的数据路径,如果未连接数据路径,则丢弃该消息(并记录警告)。 |
nexus本质上是一个字典,其中的键是DPID,值是Connection对象。
5.3 openflow事件:对交换机的响应
大多数与OpenFlow相关的事件都是直接响应从交换机收到的消息而引发的。
与openflow相关的event具有以下三个属性:
属性 | 类型 | 描述、 |
---|---|---|
connection | connection | 与相关交换机的连接(例如,发送此事件对应的消息的交换机) |
ofp | ofp_header子类 | 导致此事件的OpenFlow消息对象。 |
dpid | long | 相关交换机的数据路径ID |
5.3.1 ConnectionUp
不会响应于从交换机接收到特定的OpenFlow消息而发出-只是响应于通过开关建立新的控制通道而触发。
属性 | 类型 | 描述 |
---|---|---|
ofp | ofp_switch_features | 包含有关交换机的信息,例如受支持的操作类型和端口信息 |
5.3.2 ConnectionDown
此事件不会在响应实际的OpenFlow消息时触发,仅在终止与交换机的连接时触发该事件。
5.3.3 PortStatus
class PortStatus (Event):
def __init__ (self, connection, ofp):
Event.__init__(self)
self.connection = connection
self.dpid = connection.dpid
self.ofp = ofp
self.modified = ofp.reason == of.OFPPR_MODIFY
self.added = ofp.reason == of.OFPPR_ADD
self.deleted = ofp.reason == of.OFPPR_DELETE
self.port = ofp.desc.port_no
5.3.4 FlowRemoved
当控制器ofp_flow_removed从交换机接收到OpenFlow流删除消息()时引发事件
属性 | 类型 | 含义 |
---|---|---|
idleTimeout | bool | 如果由于闲置而删除了条目,则为真 |
hardTimeout | bool | 如果条目由于超时而被删除,则为真 |
timeout | bool | 如果条目由于任何超时而被删除,则为True |
deleted | bool | 如果条目被明确删除则为真 |
5.3.5 Statistics Events
事件 | openflow统计类型 |
---|---|
SwitchDescReceived | ofp_desc_stats |
FlowStatsReceived | ofp_flow_stats |
AggregateFlowStatsReceived | ofp_aggregate_stats_reply |
TableStatsReceived | ofp_table_stats |
PortStatsReceived | ofp_port_stats |
QueueStatsReceived | ofp_queue_stats |
5.3.6 PacketIn
当控制器收到openflow的packet-in消息时触发。该消息表示到达交换机端口的包未能匹配表中的所有条目,或者匹配条目包括的动作–指定发送该包的到控制器。
除了通常的openflow事件属性外:
- port(int)-数据包进入的端口号
- data(bytes)-原始数据包数据
- 解析(包子类)-pox.lib.packet的解析版本
- ofp(ofp_packet_in)-导致此事件的OpenFlow消息
5.3.7 ErrorIn
当从交换机收到一个openflow错误时触发。
除了通常的openflow事件属性外:
属性 | 含义 |
---|---|
should_log | 通常,OpenFlow错误会导致出现日志消息。如果处理ErrorIn事件,则可以将此属性设置为False,以使默认日志消息静音。 |
asString() | 将此错误格式化为字符串。 |
5.3.8 BarrierIn屏障进入
当控制器从交换机接收到OpenFlow屏障回复时触发,表明交换机已在相应的屏障请求之前完成了控制器发送的处理命令.
5.4 openflow消息
OpenFlow消息是OpenFlow交换机与控制器进行通信的方式。
5.4.1 ofp_packet_out-从交换机发送数据包
该消息的主要目的是指示交换机发送数据包(或使其入队)。但是,它也可用作指示交换机丢弃缓冲的数据包的方法。
属性 | 类型 | 默认 | 描述 |
---|---|---|---|
in_port | int | OFPP_NONE | 重新发送数据包时,数据包到达的交换端口。 |
data | 字节/以太网/ ofp_packet_in | ‘’ | 要发送的数据 |
buffer_id | int/none | none | 数据包存储在数据路径中的缓冲区的ID。如果您不是按ID重新发送缓冲区,请使用“无”。 |
actions | ofp_action_XXXX的清单 | [ ] | 如果只有一个项目,则也可以使用初始化程序的命名参数“ action”进行指定。 |
~~
附:
"""
A stupid L3 switch
For each switch:
1) Keep a table that maps IP addresses to MAC addresses and switch ports.
Stock this table using information from ARP and IP packets.
2) When you see an ARP query, try to answer it using information in the table
from step 1. If the info in the table is old, just flood the query.
3) Flood all other ARPs.
4) When you see an IP packet, if you know the destination port (because it's
in the table from step 1), install a flow for it.
"""
from pox.core import core
import pox
log = core.getLogger()
from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST
from pox.lib.packet.ipv4 import ipv4
from pox.lib.packet.arp import arp
from pox.lib.addresses import IPAddr, EthAddr
from pox.lib.util import str_to_bool, dpid_to_str
from pox.lib.recoco import Timer
import pox.openflow.libopenflow_01 as of
from pox.lib.revent import *
import time
# Timeout for flows
FLOW_IDLE_TIMEOUT = 10
# Timeout for ARP entries
ARP_TIMEOUT = 60 * 2
# Maximum number of packet to buffer on a switch for an unknown IP
MAX_BUFFERED_PER_IP = 5
# Maximum time to hang on to a buffer for an unknown IP in seconds
MAX_BUFFER_TIME = 5
class Entry (object):
"""
Not strictly an ARP entry.
We use the port to determine which port to forward traffic out of.
We use the MAC to answer ARP replies.
We use the timeout so that if an entry is older than ARP_TIMEOUT, we
flood the ARP request rather than try to answer it ourselves.
"""
def __init__ (self, port, mac):
self.timeout = time.time() + ARP_TIMEOUT
self.port = port
self.mac = mac
def __eq__ (self, other):
if type(other) == tuple:
return (self.port,self.mac)==other
else:
return (self.port,self.mac)==(other.port,other.mac)
def __ne__ (self, other):
return not self.__eq__(other)
def isExpired (self):
if self.port == of.OFPP_NONE: return False
return time.time() > self.timeout
def dpid_to_mac (dpid):
return EthAddr("%012x" % (dpid & 0xffFFffFFffFF,))
class l3_switch (EventMixin):
def __init__ (self, fakeways = [], arp_for_unknowns = False, wide = False):
# These are "fake gateways" -- we'll answer ARPs for them with MAC
# of the switch they're connected to.
self.fakeways = set(fakeways)
# If True, we create "wide" matches. Otherwise, we create "narrow"
# (exact) matches.
self.wide = wide
# If this is true and we see a packet for an unknown
# host, we'll ARP for it.
self.arp_for_unknowns = arp_for_unknowns
# (dpid,IP) -> expire_time
# We use this to keep from spamming ARPs
self.outstanding_arps = {}
# (dpid,IP) -> [(expire_time,buffer_id,in_port), ...]
# These are buffers we've gotten at this datapath for this IP which
# we can't deliver because we don't know where they go.
self.lost_buffers = {}
# For each switch, we map IP addresses to Entries
self.arpTable = {}
# This timer handles expiring stuff
self._expire_timer = Timer(5, self._handle_expiration, recurring=True)
core.listen_to_dependencies(self)
def _handle_expiration (self):
# Called by a timer so that we can remove old items.
empty = []
for k,v in self.lost_buffers.iteritems(): '''不知道self.lost_buffers的表项是如何'''
dpid,ip = k
for item in list(v): '''v是一个表项,expires_at,buffer_id,in_port'''
expires_at,buffer_id,in_port = item """,expires_at是到期时间,buffer_id为数据包存储再数据路径中的缓冲区id"""
if expires_at < time.time():
# This packet is old. Tell this switch to drop it.
v.remove(item)
po = of.ofp_packet_out(buffer_id = buffer_id, in_port = in_port) '''交换机发出的消息'''
core.openflow.sendToDPID(dpid, po) '''将OpenFlow消息发送到特定的数据路径,如果未连接数据路径,则丢弃该消息'''
if len(v) == 0: empty.append(k) '''条目如果是空的,则将该表项的dpid和ip加入empty'''
# Remove empty buffer bins
for k in empty:
del self.lost_buffers[k] '''删除empty中的表项'''
def _send_lost_buffers (self, dpid, ipaddr, macaddr, port):
"""
We may have "lost" buffers -- packets we got but didn't know
where to send at the time. We may know now. Try and see.
"""
if (dpid,ipaddr) in self.lost_buffers: '''如果有dpid和主机的ip在缓冲区内'''
# Yup!
bucket = self.lost_buffers[(dpid,ipaddr)] '''bucket为一个表项,expires_at, buffer_id, in_port'''
del self.lost_buffers[(dpid,ipaddr)] '''丢掉该表项'''
log.debug("Sending %i buffered packets to %s from %s"
% (len(bucket),ipaddr,dpid_to_str(dpid)))
for _,buffer_id,in_port in bucket: '''对于上述提取的表项发送数据包,设置目的mac和出端口并发送到特定的数据路径,如果不存在,就丢失'''
po = of.ofp_packet_out(buffer_id=buffer_id,in_port=in_port) '''从交换机发出的消息,参数有buffer_id,in_port,actions,date'''
po.actions.append(of.ofp_action_dl_addr.set_dst(macaddr)) '''添加设置目的mac地址的动作'''
po.actions.append(of.ofp_action_output(port = port)) '''设置出端口'''
core.openflow.sendToDPID(dpid, po) '''将OpenFlow消息发送到特定的数据路径,如果未连接数据路径,则丢弃该消息'''
def _handle_openflow_PacketIn (self, event):
dpid = event.connection.dpid '''connection对象有ofnexus,dpid,features,ports,sock,send(msg)对象'''
inport = event.port
packet = event.parsed '''pox.lib.packet的解析版本'''
if not packet.parsed: '''如果不能解析,则丢弃该数据包'''
log.warning("%i %i ignoring unparsed packet", dpid, inport)
return
if dpid not in self.arpTable: '''如果交换机的dpid不在arp表中'''
# New switch -- create an empty table
self.arpTable[dpid] = {} '''创建一个该交换机的arp表项'''
for fake in self.fakeways: '''将该交换机匹配虚拟网关加入arptable中'''
self.arpTable[dpid][IPAddr(fake)] = Entry(of.OFPP_NONE,
dpid_to_mac(dpid))
if packet.type == ethernet.LLDP_TYPE: '''如果包类型是lldp(链路层发现协议)则丢弃'''
# Ignore LLDP packets
return
if isinstance(packet.next, ipv4): '''如果报文是ipv4的话执行,next中包含 包类型,源地址和目的地址 '''
log.debug("%i %i IP %s => %s", dpid,inport,
packet.next.srcip,packet.next.dstip)
# Send any waiting packets...
self._send_lost_buffers(dpid, packet.next.srcip, packet.src, inport) '''尝试能不能从缓冲区发出去(next.srcip就是ipaddr)'''
# Learn or update port/MAC info
if packet.next.srcip in self.arpTable[dpid]: '''如果包源ip在arp表中'''
if self.arpTable[dpid][packet.next.srcip] != (inport, packet.src): '''如果arp表项的入端口和包源地址不匹配'''
log.info("%i %i RE-learned %s", dpid,inport,packet.next.srcip)
if self.wide:
# Make sure we don't have any entries with the old info...
msg = of.ofp_flow_mod(command=of.OFPFC_DELETE) '''删除所有流表项'''
msg.match.nw_dst = packet.next.srcip '''设置流表项目的地址为包的目的地址'''
msg.match.dl_type = ethernet.IP_TYPE
event.connection.send(msg)
else: '''包源ip不在arp表中'''
log.debug("%i %i learned %s", dpid,inport,packet.next.srcip)
self.arpTable[dpid][packet.next.srcip] = Entry(inport, packet.src) '''对应包dpid和包源地址的条例创建为此包的匹配项目'''
# Try to forward
dstaddr = packet.next.dstip '''取包的目的地址'''
if dstaddr in self.arpTable[dpid]: '''如果目的地址在arp表中'''
# We have info about what port to send it out on...
prt = self.arpTable[dpid][dstaddr].port '''取表中目的交换机和目的地址对应的端口'''
mac = self.arpTable[dpid][dstaddr].mac '''取表中目的交换机和目的地址对应的地址'''
if prt == inport: '''如果出端口等于入端口,不发送'''
log.warning("%i %i not sending packet for %s back out of the "
"input port" % (dpid, inport, dstaddr))
else: '''如果出端口不等于入端口,发送'''
log.debug("%i %i installing flow for %s => %s out port %i"
% (dpid, inport, packet.next.srcip, dstaddr, prt))
actions = [] '''建立行为列表'''
actions.append(of.ofp_action_dl_addr.set_dst(mac)) '''添加动作,为设置目的mac地址'''
actions.append(of.ofp_action_output(port = prt)) '''添加动作,为交换机发消息的出端口为arp表中目的交换机的端口'''
if self.wide: '''如果为广泛匹配'''
match = of.ofp_match(dl_type = packet.type, nw_dst = dstaddr)
else:
match = of.ofp_match.from_packet(packet, inport)
msg = of.ofp_flow_mod(command=of.OFPFC_ADD, '''添加流表项'''
idle_timeout=FLOW_IDLE_TIMEOUT, '''在FLOW_IDLE_TIMEOUT时间内,如果没有报文触发,则该规则删除'''
hard_timeout=of.OFP_FLOW_PERMANENT, '''到达OFP_FLOW_PERMANENT时间时,无论如何都删除该规则'''
buffer_id=event.ofp.buffer_id, '''设计数据包存储在数据路径中的缓冲区的ID'''
actions=actions, '''动作为动作列表'''
match=match)
event.connection.send(msg.pack())
elif self.arp_for_unknowns: '''也就是说目的地址不在arp表中'''
# We don't know this destination.
# First, we track this buffer so that we can try to resend it later
# if we learn the destination, second we ARP for the destination,
# which should ultimately result in it responding and us learning
# where it is
# Add to tracked buffers
if (dpid,dstaddr) not in self.lost_buffers: '''如果交换机的dpid和目的地址不在缓冲区内'''
self.lost_buffers[(dpid,dstaddr)] = [] '''创建该交换机的表项'''
bucket = self.lost_buffers[(dpid,dstaddr)] '''取缓冲区的表项'''
entry = (time.time() + MAX_BUFFER_TIME,event.ofp.buffer_id,inport) '''建立条例,当前的时间,条例最大存在时间,数据包存储在数据路径中的缓冲区的ID,入端口'''
bucket.append(entry)
while len(bucket) > MAX_BUFFERED_PER_IP: del bucket[0] '''如果bucket项目的长度大于交换机上用于未知ip的最大数据包数'''
# Expire things from our outstanding ARP list...
self.outstanding_arps = {k:v for k,v in
self.outstanding_arps.iteritems() if v > time.time()}
# Check if we've already ARPed recently
if (dpid,dstaddr) in self.outstanding_arps:
# Oop, we've already done this one recently.
return
# And ARP...
self.outstanding_arps[(dpid,dstaddr)] = time.time() + 4 '''目的地址表项过期时间为4秒,不再接收'''
r = arp() '''构建一个arp消息'''
r.hwtype = r.HW_TYPE_ETHERNET '''arp消息的类型为以太网'''
r.prototype = r.PROTO_TYPE_IP '''报文proto类型为ip'''
r.hwlen = 6
r.protolen = r.protolen '''协议长度'''
r.opcode = r.REQUEST '''广播请求'''
r.hwdst = ETHER_BROADCAST '''广播'''
r.protodst = dstaddr '''协议目的为目标地址'''
r.hwsrc = packet.src '''源地址是请求包的地址'''
r.protosrc = packet.next.srcip '''协议源是请求包源ip'''
e = ethernet(type=ethernet.ARP_TYPE, src=packet.src,
dst=ETHER_BROADCAST)
e.set_payload(r) '''将arp消息封装在链路层协议中'''
log.debug("%i %i ARPing for %s on behalf of %s" % (dpid, inport,
r.protodst, r.protosrc))
msg = of.ofp_packet_out() '''指示交换机发送数据包'''
msg.data = e.pack() '''将e封装在msg的数据域中'''
msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD))
msg.in_port = inport
event.connection.send(msg)
elif isinstance(packet.next, arp):
a = packet.next '''a中有包类型,目的地址,源地址'''
log.debug("%i %i ARP %s %s => %s", dpid, inport,
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), a.protosrc, a.protodst)
if a.prototype == arp.PROTO_TYPE_IP: '''如果a的协议类型为arp协议'''
if a.hwtype == arp.HW_TYPE_ETHERNET: '''如果为链路层协议'''
if a.protosrc != 0: '''并且包的协议源地址不为0.0.0.0'''
# Learn or update port/MAC info
if a.protosrc in self.arpTable[dpid]: '''如果协议源地址在arp表中'''
if self.arpTable[dpid][a.protosrc] != (inport, packet.src): '''并且交换机号和源地址不等于入端口和包源地址的是哦胡'''
log.info("%i %i RE-learned %s", dpid,inport,a.protosrc)
if self.wide:
# Make sure we don't have any entries with the old info...
msg = of.ofp_flow_mod(command=of.OFPFC_DELETE) '''删除流表项'''
msg.match.dl_type = ethernet.IP_TYPE
msg.match.nw_dst = a.protosrc
event.connection.send(msg)
else:
log.debug("%i %i learned %s", dpid,inport,a.protosrc)
self.arpTable[dpid][a.protosrc] = Entry(inport, packet.src) '''不在的话建立表项'''
# Send any waiting packets...
self._send_lost_buffers(dpid, a.protosrc, packet.src, inport) '''尝试把缓冲区发出去'''
if a.opcode == arp.REQUEST: '''如果操作码为请求'''
# Maybe we can answer
if a.protodst in self.arpTable[dpid]:
# We have an answer...
if not self.arpTable[dpid][a.protodst].isExpired():
# .. and it's relatively current, so we'll reply ourselves
r = arp()
r.hwtype = a.hwtype
r.prototype = a.prototype
r.hwlen = a.hwlen
r.protolen = a.protolen
r.opcode = arp.REPLY
r.hwdst = a.hwsrc
r.protodst = a.protosrc
r.protosrc = a.protodst
r.hwsrc = self.arpTable[dpid][a.protodst].mac
e = ethernet(type=packet.type, src=dpid_to_mac(dpid),
dst=a.hwsrc)
e.set_payload(r)
log.debug("%i %i answering ARP for %s" % (dpid, inport,
r.protosrc))
msg = of.ofp_packet_out()
msg.data = e.pack()
msg.actions.append(of.ofp_action_output(port =
of.OFPP_IN_PORT))
msg.in_port = inport
event.connection.send(msg)
return
# Didn't know how to answer or otherwise handle this ARP, so just flood it
log.debug("%i %i flooding ARP %s %s => %s" % (dpid, inport,
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), a.protosrc, a.protodst))
msg = of.ofp_packet_out(in_port = inport, data = event.ofp,
action = of.ofp_action_output(port = of.OFPP_FLOOD))
event.connection.send(msg)
def launch (fakeways="", arp_for_unknowns=None, wide=False):
fakeways = fakeways.replace(","," ").split()
fakeways = [IPAddr(x) for x in fakeways]
if arp_for_unknowns is None:
arp_for_unknowns = len(fakeways) > 0
else:
arp_for_unknowns = str_to_bool(arp_for_unknowns)
core.registerNew(l3_switch, fakeways, arp_for_unknowns, wide)