Merge branch 'release/0.1.1'

This commit is contained in:
Eishun Kondoh 2019-06-15 11:01:55 +09:00
commit c8e1fbbbc5
228 changed files with 10073 additions and 3841 deletions

View file

@ -1,5 +1,5 @@
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"],
inputs: ["mix.exs", "{config,lib,test,priv}/**/*.{ex,exs}"],
locals_without_parens: [
# Formatter tests

2
.gitignore vendored
View file

@ -21,3 +21,5 @@ erl_crash.dump
# Examples
*/polaris/*
/bin/enum_gen

34
.travis.yml Normal file
View file

@ -0,0 +1,34 @@
sudo: true
dist: xenial
language: elixir
elixir:
- "1.8.1"
otp_release:
- "21.3.6"
before_install:
- sudo apt-get install -y wget curl gdebi debhelper unbound
- sudo apt-get install build-essential fakeroot autoconf automake libssl-dev bzip2 openssl graphviz python-all procps python-qt4 python-zopeinterface python-twisted-conch libtool dh-autoreconf libunbound-dev
- wget https://github.com/openvswitch/ovs/archive/v2.11.0.tar.gz
- tar xvzf v2.11.0.tar.gz
- cd ovs-2.11.0/
- sudo DEB_BUILD_OPTIONS='parallel=8 nocheck' fakeroot debian/rules binary
- sudo dpkg -i ../python-openvswitch_2.11.0-1_all.deb
- sudo dpkg -i ../libopenvswitch_2.11.0-1_amd64.deb
- sudo dpkg -i ../libopenvswitch-dev_2.11.0-1_amd64.deb
- sudo dpkg -i ../openvswitch-common_2.11.0-1_amd64.deb
- sudo dpkg -i ../openvswitch-switch_2.11.0-1_amd64.deb
- sudo ovs-vsctl add-br br0
- sudo ovs-vsctl set bridge br0 datapath_type=netdev
- sudo ovs-vsctl set bridge br0 protocols=OpenFlow13
- sudo ovs-vsctl set bridge br0 other-config:datapath-id=0000000000000001
- sudo ovs-vsctl set-controller br0 tcp:127.0.0.1:6653
- sudo ovs-vsctl set-manager ptcp:6640
- sudo ovs-vsctl show
- cd -
script:
- MIX_ENV=test mix do compile, coveralls.json
after_success:
- bash <(curl -s https://codecov.io/bash)

10
CHANGELOG.md Normal file
View file

@ -0,0 +1,10 @@
# Changelog
## develop (unreleased)
### New Features
* tres/secure_channel: Add a new function for blocking send
### Bugs Fixed
* tres/secure_channel: Fix to interpolate terms when logging
* tres/message_handler: Fix to use `DynamicSupervisor` instead of `:simple_one_for_one`

19
LICENSE
View file

@ -1,13 +1,12 @@
Copyright [2018] [Eishun Kondoh]
# "THE SUSHI-WARE LICENSE"
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<dreamdiagnosis@gmail.com> wrote this file.
http://www.apache.org/licenses/LICENSE-2.0
As long as you retain this notice you can do whatever you want
with this stuff. If we meet some day, and you think this stuff
is worth it, you can buy me a **sushi 🍣** in return.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
(This license is based on ["THE BEER-WARE LICENSE" (Revision 42)].
Thanks a lot, Poul-Henning Kamp ;)
["THE BEER-WARE LICENSE" (Revision 42)]: https://people.freebsd.org/~phk/

View file

@ -1,5 +1,11 @@
# Tres - an Elixir OpenFlow development platform
[![Build Status](https://img.shields.io/travis/shun159/tres.svg?style=flat-square)](https://travis-ci.org/shun159/tres)
[![codecov](https://img.shields.io/codecov/c/github/shun159/tres/develop.svg?style=flat-square)](https://codecov.io/gh/shun159/tres)
[![Supported OTP version](https://img.shields.io/badge/erlang-22.x-blue.svg?style=flat-square)](http://erlang.org/)
[![LICENSE](https://img.shields.io/badge/license-SUSHI--WARE%F0%9F%8D%A3-blue.svg?style=flat-square)](https://github.com/MakeNowJust/sushi-ware)
[![hex version](https://img.shields.io/badge/hex-0.1.0-yellow.svg?style=flat-square)](https://hex.pm/packages/tres/0.1.0)
## Overview
Tres is a framework and set of helper libraries to develop OpenFlow controllers in Elixir.
@ -11,7 +17,7 @@ Tres is a framework and set of helper libraries to develop OpenFlow controllers
```elixir
def deps do
[
{:tres, github: "shun159/tres", branch: "develop"}
{:tres, "~> 0.1.0"}
]
end
```
@ -25,7 +31,7 @@ config :tres,
```
To use `Tres.Controller` with your code, set the handler callback_module to your callback module.
This module must implement the `Module.start_link/2` that returns `on_start`.
This module must implement the `Module.start_link/1` that returns `on_start`.
Set the callback_args to the terms you want pass to the `start_link/2` callback function.
@ -35,7 +41,7 @@ defmodule Sample do
use GenServer
use Tres.Controller
def start_link(datapath_id, _start_args) do
def start_link([datapath_id, _start_args]) do
GenServer.start_link(__MODULE__, [datapath_id])
end
@ -70,9 +76,11 @@ $ iex -S mix
- learning-switch: Simple Layer2 switch
- leader-example: Simple election based multiple controller using Ulf Wiger's Locks Leader
- patch\_panel: inteligent patch\_panel example
- simple\_router: An OpenFlow controller that emulates layer 3 switch (router).
- nx\_learning\_switch: Example for very simple-minded L2 switch using NXAST_LEARN action.
License
-------
Tres is released under the Apache license Version 2.0:
Tres is released under the __SUSHI-WARE LICENSE__.
* https://www.apache.org/licenses/LICENSE-2.0
__私に寿司をおごってください__

Binary file not shown.

2
codecov.yml Normal file
View file

@ -0,0 +1,2 @@
codecov:
token: db65a412-7a83-4c38-ad5b-9c68677c6680

View file

@ -11,7 +11,7 @@ config :tres,
callback_args: []
config :logger,
level: :info,
format: "$date $time [$level] $metadata$message\n",
metadata: [:application],
level: :debug,
format: "$date $time [$level] $message\n",
metadata: [],
handle_otp_reports: true

11
coveralls.json Normal file
View file

@ -0,0 +1,11 @@
{
"skip_files": [
"lib/openflow/enums.ex"
],
"coverage_options": {
"output_dir": "cover/",
"template_path": "lib/templates/html/htmlcov/",
"minimum_coverage": 0
}
}

View file

@ -63,14 +63,18 @@ defmodule LearningSwitch.Ofctl do
end
defp init_flow_tables(datapath_id) do
:ok = onf_bundle_open(datapath_id, bundle_id: 1, flags: [:atomic, :ordered])
for flow_options <- [
add_default_broadcast_flow_entry(),
add_default_flooding_flow_entry(),
add_multicast_mac_drop_flow_entry(),
add_ipv6_multicast_mac_drop_flow_entry(),
add_default_forwarding_flow_entry()] do
send_flow_mod_add(datapath_id, flow_options)
send_flow_mod_add(datapath_id, Keyword.merge(flow_options, [bundle_id: 1, bundle_flags: [:atomic, :ordered]]))
end
:ok = onf_bundle_commit(datapath_id, bundle_id: 1, flags: [:atomic, :ordered])
end
defp add_forwarding_flow_and_packet_out(packet_in, state) do
@ -81,12 +85,8 @@ defmodule LearningSwitch.Ofctl do
packet_out(packet_in, port_no || :flood)
end
defp packet_out(%PacketIn{datapath_id: datapath_id, data: data}, port_no) do
send_packet_out(
datapath_id,
data: data,
actions: [Output.new(port_no)]
)
defp packet_out(%PacketIn{} = pin, port_no) do
send_packet_out(packet_in: pin, actions: [Output.new(port_no)])
end
defp add_forwarding_flow_entry(_packet_in, nil), do: :noop

View file

@ -1,7 +1,7 @@
%{
"binpp": {:git, "https://github.com/jtendo/binpp.git", "64bd68d215d1a6cd35871e7c134d7fe2e46214ea", [branch: "master"]},
"eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]},
"jsone": {:git, "https://github.com/sile/jsone.git", "eecc9666c7165e1870b78a7a762549ae8d1c391b", [tag: "1.2.1"]},
"ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [], [], "hexpm"},
"jsone": {:git, "https://github.com/sile/jsone.git", "b23d312a5ed051ea7ad0989a9f2cb1a9c3f9a502", [tag: "1.4.6"]},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]},
}

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
examples/nx_learning_switch/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
nx_learning_switch-*.tar

View file

@ -0,0 +1,3 @@
# NxLearningSwitch
Example for a very simple-minded MAC learning switch

View file

@ -0,0 +1,17 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
config :tres,
protocol: :tcp,
port: 6653,
max_connections: 10,
num_acceptors: 10,
callback_module: NxLearningSwitch,
callback_args: []
config :logger,
level: :debug,
format: "$date $time [$level] $metadata$message\n",
metadata: [:application],
handle_otp_reports: true

View file

@ -0,0 +1,93 @@
defmodule NxLearningSwitch do
@moduledoc """
Emulates Layer2 switch
"""
use GenServer
use Tres.Controller
import Logger
# API functions
def start_link(datapath_id, _args) do
GenServer.start_link(__MODULE__, [datapath_id])
end
# GenServer callback functions
@impl GenServer
def init([{datapath_id, _aux_id}]) do
:ok = info("Connected: datapath_id: #{datapath_id}")
{:ok, datapath_id, {:continue, :init}}
end
@impl GenServer
def handle_continue(:init, datapath_id) do
:ok = l2_learning_flow(datapath_id)
:ok = l2_flooding_flows(datapath_id)
{:noreply, datapath_id}
end
@impl GenServer
def handle_call(_request, _from, state) do
{:reply, :ok, state}
end
@impl GenServer
def handle_cast(_request, state) do
{:noreply, state}
end
@impl GenServer
def handle_info({:switch_disconnected, reason}, datapath_id) do
:ok = warn("Disconnected: datapath_id: #{datapath_id} by #{inspect(reason)}")
{:stop, :normal, datapath_id}
end
@impl GenServer
def handle_info(_info, state) do
{:noreply, state}
end
## private functions
defp l2_learning_flow(datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: 0,
instructions: ApplyActions.new([
NxLearn.new(
table_id: 1,
priority: 2,
hard_timeout: 10,
flow_specs: [
NxFlowSpecMatch.new(
src: :nx_eth_src,
dst: :nx_eth_dst
),
NxFlowSpecMatch.new(
src: :nx_vlan_tci,
dst: :nx_vlan_tci,
offset: 0,
n_bits: 12
),
NxFlowSpecOutput.new(
src: :nx_in_port
)
]
),
NxResubmitTable.new(1)
])
)
end
defp l2_flooding_flows(datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: 1,
priority: 0,
instructions: ApplyActions.new(Output.new(:flood))
)
end
end

View file

@ -0,0 +1,27 @@
defmodule NxLearningSwitch.MixProject do
use Mix.Project
def project do
[
app: :nx_learning_switch,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:tres, path: "../../../tres"}
]
end
end

View file

@ -0,0 +1,3 @@
%{
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
}

View file

@ -0,0 +1,8 @@
defmodule NxLearningSwitchTest do
use ExUnit.Case
doctest NxLearningSwitch
test "greets the world" do
assert NxLearningSwitch.hello() == :world
end
end

View file

@ -0,0 +1 @@
ExUnit.start()

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
examples/simple_router/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
simple_router-*.tar

View file

@ -0,0 +1,47 @@
# SimpleRouter
#### An OpenFlow controller that emulates layer 3 switch (router).
## Configuration
```elixir
config :simple_router,
interfaces: %{
"veth1" => %{mac_address: "02:00:00:01:00:01", ip_address: "192.168.1.1/24"},
"veth3" => %{mac_address: "02:00:00:01:00:02", ip_address: "192.168.2.1/24"},
},
routes: %{
"0.0.0.0/0" => "192.168.1.2"
}
```
## Flow Tables
```
> $ sudo ovs-ofctl dump-flows br0 -OOpenFlow13 --color=auto
cookie=0x0, duration=10.217s, table=0, n_packets=3, n_bytes=126, priority=1,arp actions=goto_table:1
cookie=0x0, duration=10.217s, table=0, n_packets=19, n_bytes=1862, priority=1,ip actions=goto_table:2
cookie=0x0, duration=10.208s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth1,arp_tpa=192.168.1.1,arp_op=1 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]),push:NXM_OF_ARP_TPA[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],pop:NXM_OF_ARP_TPA[],pop:NXM_NX_ARP_THA[],pop:NXM_OF_ARP_SPA[],set_field:2->arp_op,set_field:02:00:00:01:00:01->arp_sha,set_field:02:00:00:01:00:01->eth_src,IN_PORT
cookie=0x0, duration=10.207s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth1,arp_tpa=192.168.1.1,arp_op=2 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[])
cookie=0x0, duration=10.206s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth3,arp_tpa=192.168.2.1,arp_op=1 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]),push:NXM_OF_ARP_TPA[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],pop:NXM_OF_ARP_TPA[],pop:NXM_NX_ARP_THA[],pop:NXM_OF_ARP_SPA[],set_field:2->arp_op,set_field:02:00:00:01:00:02->arp_sha,set_field:02:00:00:01:00:02->eth_src,IN_PORT
cookie=0x0, duration=10.206s, table=1, n_packets=0, n_bytes=0, priority=0,arp,in_port=veth3,arp_tpa=192.168.2.1,arp_op=2 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[])
cookie=0x0, duration=10.205s, table=2, n_packets=0, n_bytes=0, priority=124,ip,nw_dst=192.168.1.0/24 actions=move:NXM_OF_IP_DST[]->NXM_NX_REG0[],goto_table:3
cookie=0x0, duration=10.205s, table=2, n_packets=9, n_bytes=882, priority=124,ip,nw_dst=192.168.2.0/24 actions=move:NXM_OF_IP_DST[]->NXM_NX_REG0[],goto_table:3
cookie=0x0, duration=10.205s, table=2, n_packets=10, n_bytes=980, priority=0,ip actions=set_field:0xc0a80102->reg0,goto_table:3
cookie=0x0, duration=10.212s, table=3, n_packets=10, n_bytes=980, priority=24,reg0=0xc0a80100/0xffffff00 actions=set_field:0x1->reg1,resubmit(,4),resubmit(,5)
cookie=0x0, duration=10.212s, table=3, n_packets=9, n_bytes=882, priority=24,reg0=0xc0a80200/0xffffff00 actions=set_field:0x2->reg1,resubmit(,4),resubmit(,5)
cookie=0x0, duration=9.891s, table=4, n_packets=9, n_bytes=882, idle_timeout=300, priority=1,reg0=0xc0a80202 actions=load:0x9a9114dd25d8->NXM_OF_ETH_DST[]
cookie=0x0, duration=9.855s, table=4, n_packets=9, n_bytes=882, idle_timeout=300, priority=1,reg0=0xc0a80102 actions=load:0x7a69d574651c->NXM_OF_ETH_DST[]
cookie=0x0, duration=10.207s, table=4, n_packets=1, n_bytes=98, priority=0,reg1=0x1 actions=controller(userdata=41.52.50.5f.6d.69.73.73.69.6e.67.ff.ff.ff.ff.ff.ff.02.00.00.01.00.01.08.06.00.01.08.00.06.04.00.01.02.00.00.01.00.01.c0.a8.01.01.00.00.00.00.00.00,pause)
cookie=0x0, duration=10.206s, table=4, n_packets=0, n_bytes=0, priority=0,reg1=0x2 actions=controller(userdata=41.52.50.5f.6d.69.73.73.69.6e.67.ff.ff.ff.ff.ff.ff.02.00.00.01.00.02.08.06.00.01.08.00.06.04.00.01.02.00.00.01.00.02.c0.a8.02.01.00.00.00.00.00.00,pause)
cookie=0x0, duration=10.206s, table=5, n_packets=9, n_bytes=882, priority=1,reg1=0x1 actions=group:1
cookie=0x0, duration=10.205s, table=5, n_packets=9, n_bytes=882, priority=1,reg1=0x2 actions=group:2
```
## Group Tables
```
> $ sudo ovs-ofctl dump-groups br0 -OOpenFlow13 --color=auto
OFPST_GROUP_DESC reply (OF1.3) (xid=0x2):
group_id=1,type=indirect,bucket=actions=set_field:02:00:00:01:00:01->eth_src,output:veth1
group_id=2,type=indirect,bucket=actions=set_field:02:00:00:01:00:02->eth_src,output:veth3
```

View file

@ -0,0 +1,20 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
config :tres,
protocol: :tcp,
port: 6653,
max_connections: 10,
num_acceptors: 10,
callback_module: SimpleRouter.Openflow.Controller,
callback_args: []
config :simple_router,
interfaces: %{
"veth1" => %{mac_address: "02:00:00:01:00:01", ip_address: "192.168.1.1/24"},
"veth3" => %{mac_address: "02:00:00:01:00:02", ip_address: "192.168.2.1/24"},
},
routes: %{
"0.0.0.0/0" => "192.168.1.2"
}

View file

@ -0,0 +1,18 @@
defmodule SimpleRouter do
@moduledoc """
Documentation for SimpleRouter.
"""
@doc """
Hello world.
## Examples
iex> SimpleRouter.hello()
:world
"""
def hello do
:world
end
end

View file

@ -0,0 +1,9 @@
defmodule SimpleRouter.Application do
@moduledoc false
use Application
def start(_type, _args) do
SimpleRouter.Supervisor.start_link()
end
end

View file

@ -0,0 +1,55 @@
defmodule SimpleRouter.Config do
@moduledoc false
alias Tres.IPv4Address
@spec interfaces() :: %{String.t() => map()}
def interfaces do
:interfaces
|> get_env(%{})
|> Enum.reduce(%{}, &interfaces_to_erl/2)
end
@spec routes() :: [tuple()]
def routes do
:routes
|> get_env(%{})
|> Enum.reduce([], &routes_to_erl/2)
end
# private functions
defp interfaces_to_erl({ifname, %{mac_address: mac, ip_address: ip}}, acc) do
{ipaddr, mask} = IPv4Address.parse(ip)
entry =
%{
mac_address: String.replace(mac, ~r/:/, ""),
ip_address: ipaddr,
subnet_mask: mask,
network_address: IPv4Address.to_network({ipaddr, mask}),
prefix_length: get_cidr(ip)
}
Map.merge(acc, %{ifname => entry})
end
defp routes_to_erl({dst, nexthop}, acc) do
{dstaddr, mask} = IPv4Address.parse(dst)
{nexthop, _netmask} = IPv4Address.parse(nexthop)
entry = %{dst: dstaddr, mask: mask, prefix_length: get_cidr(dst), nexthop: nexthop}
[entry | acc]
end
defp get_cidr(ip) do
case String.split(ip, ~r/\//) do
[^ip] ->
32
[_addr, cidr] ->
String.to_integer(cidr)
end
end
defp get_env(key, default),
do: Application.get_env(:simple_router, key, default)
end

View file

@ -0,0 +1,139 @@
defmodule SimpleRouter.Openflow.Controller do
@moduledoc """
Emulates Router
"""
use GenServer
use Tres.Controller
import Logger
alias SimpleRouter.Config
alias SimpleRouter.Openflow.FlowTables
alias SimpleRouter.Openflow.GroupTables
defmodule State do
@moduledoc false
defstruct [
datapath_id: nil,
ports: %{}
]
end
def start_link({datapath_id, _aux_id}, _start_args) do
GenServer.start_link(__MODULE__, [datapath_id])
end
def init([datapath_id]) do
:ok = debug("Switch Connected: datapath_id = #{datapath_id}")
{:ok, %State{datapath_id: datapath_id}, {:continue, :init_datapath}}
end
def handle_continue(:init_datapath, state) do
:ok = init_datapath(state.datapath_id)
{:noreply, state, {:continue, :init_interface}}
end
def handle_continue(:init_interface, state) do
port_desc = PortDesc.Request.new()
:ok = send_message(port_desc, state.datapath_id)
{:noreply, state}
end
def handle_info(%PortDesc.Reply{datapath_id: dpid, ports: ports}, state) do
:ok = debug("port_reply from dpid: #{dpid}")
ports = handle_ports(ports)
routes = Config.routes()
:ok = onf_bundle_open(dpid, bundle_id: 1, flags: [:atomic, :ordered])
:ok = FlowTables.classifier(dpid)
:ok = init_egress_groups(ports, dpid)
:ok = init_interface_lookup_flows(ports, dpid)
:ok = init_arp_handler_flows(ports, dpid)
:ok = init_egress_flows(ports, dpid)
:ok = init_connected_routes(ports, dpid)
:ok = init_static_routes(routes, dpid)
:ok = onf_bundle_commit(dpid, bundle_id: 1, flags: [:atomic, :ordered])
{:noreply, %{state | ports: ports}}
end
def handle_info(%NxPacketIn2{userdata: <<"ARP_missing", arp_req::bytes>>} = pin, state) do
:ok = debug("ARP Entry missing: #{inspect(pin)}")
nexthop_address = pin.metadata[:reg0]
send_packet_out(
packet_in: pin,
metadata: Keyword.replace!(pin.metadata, :in_port, :controller),
data: <<arp_req::bytes, nexthop_address::32>>
)
{:noreply, state}
end
def handle_info(info, state) do
:ok = warn("Unhandled info received: #{inspect(info)}")
{:noreply, state}
end
# private functions
@spec handle_ports([%Openflow.Port{}]) :: [map()]
defp handle_ports(ports) do
ifaces = Config.interfaces()
handle_ports([], ports, ifaces)
end
defp handle_ports(acc, [], _ifaces), do: acc
defp handle_ports(acc0, [port|rest], ifaces) do
case Map.get(ifaces, port.name) do
iface0 when is_map(iface0) ->
iface = Map.merge(iface0, %{number: port.number})
handle_ports([iface|acc0], rest, ifaces)
_ ->
handle_ports(acc0, rest, ifaces)
end
end
@spec init_egress_groups([map()], String.t()) :: :ok
defp init_egress_groups(ifaces, datapath_id),
do: Enum.each(ifaces, &GroupTables.egress(&1, datapath_id))
@spec init_interface_lookup_flows([map()], String.t()) :: :ok
defp init_interface_lookup_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.lookup_iface(&1, datapath_id))
@spec init_arp_handler_flows([map()], String.t()) :: :ok
defp init_arp_handler_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.arp_handlers(&1, datapath_id))
@spec init_egress_flows([map()], String.t()) :: :ok
defp init_egress_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.egress(&1, datapath_id))
@spec init_connected_routes([map()], String.t()) :: :ok
defp init_connected_routes(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.connected_route(&1, datapath_id))
@spec init_static_routes([map()], String.t()) :: :ok
defp init_static_routes(routes, datapath_id),
do: Enum.each(routes, &FlowTables.static_route(&1, datapath_id))
@spec init_datapath(String.t()) :: :ok
defp init_datapath(datapath_id) do
:ok = set_config(datapath_id)
:ok = set_packet_in_format(datapath_id)
end
@spec set_config(String.t()) :: :ok
defp set_config(datapath_id) do
set_config = SetConfig.new(miss_send_len: :no_buffer)
:ok = send_message(set_config, datapath_id)
end
@spec set_packet_in_format(String.t()) :: :ok
defp set_packet_in_format(datapath_id) do
pin_format = NxSetPacketInFormat.new(:nxt_packet_in2)
:ok = send_message(pin_format, datapath_id)
end
end

View file

@ -0,0 +1,240 @@
defmodule SimpleRouter.Openflow.FlowTables do
@moduledoc """
Flow Table Definitions
"""
use Tres.Controller
alias Tres.IPv4Address
alias Tres.MacAddress
@classifier_table_id 0
@arp_handler_table_id 1
@routing_table_id 2
@interface_lookup_table_id 3
@arp_lookup_table_id 4
@egress_table_id 5
def classifier(datapath_id) do
# ARP
send_flow_mod_add(
datapath_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
table_id: @classifier_table_id,
priority: 1,
match: Match.new(eth_type: 0x0806),
instructions: [GotoTable.new(@arp_handler_table_id)]
)
# IPv4
send_flow_mod_add(
datapath_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
table_id: @classifier_table_id,
priority: 1,
match: Match.new(eth_type: 0x0800),
instructions: [GotoTable.new(@routing_table_id)]
)
end
def connected_route(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @routing_table_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
priority: 100 + iface.prefix_length,
match: Match.new(
eth_type: 0x0800,
ipv4_dst: {iface.network_address, iface.subnet_mask}
),
instructions: [
ApplyActions.new(NxRegMove.new(src_field: :ipv4_dst, dst_field: :reg0)),
GotoTable.new(@interface_lookup_table_id)
]
)
end
def static_route(route, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @routing_table_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
priority: route.prefix_length,
match: Match.new(
eth_type: 0x0800,
ipv4_dst: {route.dst, route.mask}
),
instructions: [
ApplyActions.new(SetField.new(reg0: IPv4Address.to_int(route.nexthop))),
GotoTable.new(@interface_lookup_table_id)
]
)
end
def arp_handlers(iface, datapath_id) do
:ok = arp_request_handler(iface, datapath_id)
:ok = arp_reply_handler(iface, datapath_id)
:ok = arp_entry_is_missing(iface, datapath_id)
end
def arp_entry_is_missing(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
priority: 0,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
table_id: @arp_lookup_table_id,
match: Match.new(reg1: iface.number),
instructions: [
ApplyActions.new(
NxController2.new(userdata: arp_packet(iface), pause: true)
)
]
)
end
def lookup_iface(iface, datapath_id) do
match =
Match.new(
reg0: {
IPv4Address.to_int(iface.network_address),
IPv4Address.to_int(iface.subnet_mask)
}
)
send_flow_mod_add(
datapath_id,
table_id: @interface_lookup_table_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
priority: iface.prefix_length,
match: match,
instructions: [
ApplyActions.new([
SetField.new(reg1: iface.number),
NxResubmitTable.new(@arp_lookup_table_id),
NxResubmitTable.new(@egress_table_id)
])
]
)
end
def egress(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @egress_table_id,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
priority: 1,
match: Match.new(reg1: iface.number),
instructions: ApplyActions.new(Group.new(iface.number))
)
end
# private functions
defp arp_request_handler(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @arp_handler_table_id,
priority: 0,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
match: Match.new(
in_port: iface.number,
eth_type: 0x0806,
# Request
arp_op: 0x1,
arp_tpa: iface.ip_address
),
instructions: ApplyActions.new(arp_respond_and_learn_actions(iface.mac_address))
)
end
defp arp_reply_handler(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @arp_handler_table_id,
priority: 0,
bundle_id: 1,
bundle_flags: [:atomic, :ordered],
match: Match.new(
in_port: iface.number,
eth_type: 0x0806,
# Reply
arp_op: 0x2,
arp_tpa: iface.ip_address
),
instructions: ApplyActions.new(learn_arp_reply_actions())
)
end
defp arp_respond_and_learn_actions(mac) do
[
NxLearn.new(
priority: 1,
table_id: @arp_lookup_table_id,
idle_timeout: 300,
flow_specs: [
NxFlowSpecMatch.new(src: :arp_spa, dst: :reg0),
NxFlowSpecLoad.new(src: :eth_src, dst: :eth_dst)
]
),
NxStackPush.new(field: :arp_tpa),
NxStackPush.new(field: :arp_sha),
NxStackPush.new(field: :arp_spa),
NxStackPush.new(field: :eth_src),
NxStackPop.new(field: :eth_dst),
NxStackPop.new(field: :arp_tpa),
NxStackPop.new(field: :arp_tha),
NxStackPop.new(field: :arp_spa),
SetField.new(arp_op: 0x2),
SetField.new(arp_sha: mac),
SetField.new(eth_src: mac),
Output.new(:in_port)
]
end
defp learn_arp_reply_actions do
[
NxLearn.new(
priority: 1,
table_id: @arp_lookup_table_id,
idle_timeout: 300,
flow_specs: [
NxFlowSpecMatch.new(src: :arp_spa, dst: :reg0),
NxFlowSpecLoad.new(src: :eth_src, dst: :eth_dst)
]
)
]
end
@arphrd_ether 1
@eth_p_ip 0x0800
defp arp_packet(iface) do
ether_header = <<
0xffffffffffff::48, # destination ethernet address
(MacAddress.to_i(iface.mac_address))::48, # source ethernet address
0x0806::16 # ethernet type
>>
# Target Protocol Address will append in NX_PACKET_IN2
arp_header = <<
@arphrd_ether::16, # hardware type
@eth_p_ip::16, # protocol type
6::8, # hardware address length
4::8, # protocol address length
1::16, # ARPOP_REQUEST
(MacAddress.to_i(iface.mac_address))::48, # Source Hardware Address
(IPv4Address.to_int(iface.ip_address))::32, # Source Protocol Address
0x000000000000::48 # Target Hardware Address
>>
<<"ARP_missing", ether_header::bytes, arp_header::bytes>>
end
end

View file

@ -0,0 +1,24 @@
defmodule SimpleRouter.Openflow.GroupTables do
@moduledoc """
Group Table Definitions
"""
use Tres.Controller
def egress(iface, datapath_id) do
bucket =
Openflow.Bucket.new(
actions: [
SetField.new(eth_src: iface.mac_address),
Output.new(iface.number)
]
)
send_group_mod_add(
datapath_id,
type: :indirect,
group_id: iface.number,
buckets: [bucket]
)
end
end

View file

@ -0,0 +1,17 @@
defmodule SimpleRouter.Supervisor do
@moduledoc """
Top Level supervisor
"""
use Supervisor
@children []
def start_link do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init(_args) do
Supervisor.init(@children, strategy: :one_for_one)
end
end

View file

@ -0,0 +1,27 @@
defmodule SimpleRouter.MixProject do
use Mix.Project
def project do
[
app: :simple_router,
version: "0.1.0",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger, :tres, :pkt],
mod: {SimpleRouter.Application, []}
]
end
defp deps do
[
{:tres, path: "../../../tres"},
{:pkt, github: "msantos/pkt"}
]
end
end

View file

@ -0,0 +1,7 @@
%{
"eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]},
"jsone": {:git, "https://github.com/sile/jsone.git", "b23d312a5ed051ea7ad0989a9f2cb1a9c3f9a502", [tag: "1.4.6"]},
"pkt": {:git, "https://github.com/msantos/pkt.git", "ff0e9a7d28cdae941bce935602cd252cad1ea296", []},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]},
}

View file

@ -0,0 +1,8 @@
defmodule SimpleRouterTest do
use ExUnit.Case
doctest SimpleRouter
test "greets the world" do
assert SimpleRouter.hello() == :world
end
end

View file

@ -0,0 +1 @@
ExUnit.start()

View file

@ -15,24 +15,27 @@ defmodule Openflow do
end
def read(<<ver::8, type::8, len::16, xid::32, binary2::bytes>>) do
body_len = len - @ofp_header_size
<<body_bin::bytes-size(body_len), rest::bytes>> = binary2
try do
body_len = len - @ofp_header_size
<<body_bin::bytes-size(body_len), rest::bytes>> = binary2
result =
type
|> Openflow.Enums.to_atom(:openflow_codec)
|> do_read(body_bin)
result =
type
|> Openflow.Enums.to_atom(:openflow_codec)
|> do_read(body_bin)
case result do
{:ok, struct} -> {:ok, %{struct | version: ver, xid: xid}, rest}
{:error, reason} -> {:error, reason}
case result do
{:ok, struct} -> {:ok, %{struct | version: ver, xid: xid}, rest}
{:error, reason} -> {:error, reason}
end
catch
_c, reason ->
{:error, {:malformed_packet, {reason, __STACKTRACE__}}}
end
end
def to_binary(messages) when is_list(messages) do
binaries = for message <- messages, do: to_binary(message)
Enum.join(binaries, "")
end
def to_binary(messages) when is_list(messages),
do: Enum.reduce(messages, "", &(&2 <> to_binary(&1)))
def to_binary(%{__struct__: encoder, version: version, xid: xid} = msg) do
case encoder.to_binary(msg) do

View file

@ -1,4 +1,63 @@
defmodule Openflow.Action do
@moduledoc """
Openflow parser handler
"""
@type type ::
Openflow.Action.Output.t()
| Openflow.Action.CopyTtlOut.t()
| Openflow.Action.CopyTtlIn.t()
| Openflow.Action.SetMplsTtl.t()
| Openflow.Action.DecMplsTtl.t()
| Openflow.Action.PushVlan.t()
| Openflow.Action.PopVlan.t()
| Openflow.Action.PushMpls.t()
| Openflow.Action.PopMpls.t()
| Openflow.Action.SetQueue.t()
| Openflow.Action.Group.t()
| Openflow.Action.SetNwTtl.t()
| Openflow.Action.DecNwTtl.t()
| Openflow.Action.SetField.t()
| Openflow.Action.PushPbb.t()
| Openflow.Action.PopPbb.t()
| Openflow.Action.NxResubmit.t()
| Openflow.Action.NxSetTunnel.t()
| Openflow.Action.NxRegMove.t()
| Openflow.Action.NxRegLoad.t()
| Openflow.Action.NxNote.t()
| Openflow.Action.NxSetTunnel64.t()
| Openflow.Action.NxMultipath.t()
| Openflow.Action.NxBundle.t()
| Openflow.Action.NxBundleLoad.t()
| Openflow.Action.NxResubmitTable.t()
| Openflow.Action.NxOutputReg.t()
| Openflow.Action.NxLearn.t()
| Openflow.Action.NxExit.t()
| Openflow.Action.NxDecTtl.t()
| Openflow.Action.NxFinTimeout.t()
| Openflow.Action.NxController.t()
| Openflow.Action.NxDecTtlCntIds.t()
| Openflow.Action.NxWriteMetadata.t()
| Openflow.Action.NxPushMpls.t()
| Openflow.Action.NxPopMpls.t()
| Openflow.Action.NxStackPush.t()
| Openflow.Action.NxStackPop.t()
| Openflow.Action.NxSample.t()
| Openflow.Action.NxOutputReg2.t()
| Openflow.Action.NxRegLoad2.t()
| Openflow.Action.NxConjunction.t()
| Openflow.Action.NxConntrack.t()
| Openflow.Action.NxNat.t()
| Openflow.Action.NxController2.t()
| Openflow.Action.NxSample2.t()
| Openflow.Action.NxOutputTrunc.t()
| Openflow.Action.NxGroup.t()
| Openflow.Action.NxSample3.t()
| Openflow.Action.NxClone.t()
| Openflow.Action.NxCtClear.t()
| Openflow.Action.NxResubmitTableCt.t()
| Openflow.Action.NxLearn2.t()
def read(action_bin) do
do_read([], action_bin)
end

View file

@ -1,18 +1,37 @@
defmodule Openflow.Action.CopyTtlIn do
@moduledoc """
Copy the TTL from next-to-outermost to outermost header with TTL.\\
Copy can be IP-to-IP, MPLS-to-MPLS, or IP-to-MPLS.
"""
defstruct([])
alias __MODULE__
@type t :: %CopyTtlIn{}
@spec ofpat() :: 12
def ofpat, do: 12
@doc """
Create a copy_ttl_in action struct
## Example:
```elixir
iex> %CopyTtlIn{} = Openflow.Action.CopyTtlIn.new()
```
"""
@spec new() :: CopyTtlIn.t()
def new do
%CopyTtlIn{}
end
@spec to_binary(CopyTtlIn.t()) :: <<_::16, _::_*8>>
def to_binary(%CopyTtlIn{}) do
<<12::16, 8::16, 0::size(4)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: CopyTtlIn.t()
def read(<<12::16, 8::16, _::size(4)-unit(8)>>) do
%CopyTtlIn{}
end

View file

@ -1,18 +1,38 @@
defmodule Openflow.Action.CopyTtlOut do
@moduledoc """
Copy the TTL from outermost to next-to-outermost header with TTL.\\
Copy can be IP-to-IP, MPLS-to-MPLS, or MPLS-to-IP.
"""
defstruct([])
alias __MODULE__
@type t :: %CopyTtlOut{}
@spec ofpat() :: 11
def ofpat, do: 11
@doc """
Create a copy_ttl_out action structure
## Example
```elixir
iex> %CopyTtlOut{} = Openflow.Action.CopyTtlOut.new()
```
"""
@spec new() :: CopyTtlOut.t()
def new do
%CopyTtlOut{}
end
@spec to_binary(CopyTtlIn.t()) :: <<_::16, _::_*8>>
def to_binary(%CopyTtlOut{}) do
<<11::16, 8::16, 0::size(4)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: CopyTtlOut.t()
def read(<<11::16, 8::16, _::size(4)-unit(8)>>) do
%CopyTtlOut{}
end

View file

@ -1,18 +1,39 @@
defmodule Openflow.Action.DecMplsTtl do
@moduledoc """
Decrement MPLS TTL action
"""
defstruct([])
alias __MODULE__
@type t :: %DecMplsTtl{}
@spec ofpat() :: 16
def ofpat, do: 16
@doc """
Create a new dec_mpls_ttl action struct.
Note: Need to be specified the one of `ETH_P_MPLS_*` on `eth_type` match field when using this action.
## Example:
````elixir
iex> %DecMplsTtl{} = DecMplsTtl.new()
````
"""
@spec new() :: t()
def new do
%DecMplsTtl{}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%DecMplsTtl{}) do
<<16::16, 8::16, 0::size(4)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<16::16, 8::16, _::size(4)-unit(8)>>) do
%DecMplsTtl{}
end

View file

@ -1,18 +1,35 @@
defmodule Openflow.Action.DecNwTtl do
@moduledoc """
Decrement IP TTL
"""
defstruct([])
alias __MODULE__
@type t :: %DecNwTtl{}
@spec ofpat() :: 24
def ofpat, do: 24
@doc """
Create a new dec_nw_ttl action struct
```elixir
iex> %DecNwTtl{} = DecNwTtl.new()
```
"""
@spec new() :: t()
def new do
%DecNwTtl{}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%DecNwTtl{}) do
<<24::16, 8::16, 0::size(4)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<24::16, 8::16, _::size(4)-unit(8)>>) do
%DecNwTtl{}
end

View file

@ -3,6 +3,8 @@ defmodule Openflow.Action.Experimenter do
alias __MODULE__
@type t :: %Experimenter{exp_id: 0..0xFFFFFFFF, data: binary()}
@experimter_size 8
def ofpat, do: 0xFFFF
@ -16,6 +18,23 @@ defmodule Openflow.Action.Experimenter do
<<0xFFFF::16, length::16, exp_id::32, data::bytes>>
end
@spec pack_exp_header(binary()) :: binary()
def pack_exp_header(exp_body) do
pad_length =
exp_body
|> Kernel.byte_size()
|> Kernel.+(4)
|> Openflow.Utils.padding(8)
length =
exp_body
|> byte_size()
|> Kernel.+(4)
|> Kernel.+(pad_length)
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(pad_length)-unit(8)>>
end
def read(<<0xFFFF::16, _length::16, exp_id::32, exp_type::16, data::bytes>>) do
case Openflow.Utils.get_enum(exp_id, :action_vendor) do
vendor_id when is_integer(vendor_id) ->

View file

@ -1,18 +1,38 @@
defmodule Openflow.Action.Group do
@moduledoc """
Apply Group
"""
defstruct(id: 0)
alias __MODULE__
@type t :: %Group{id: 0..0xFFFFFFFF}
@spec ofpat() :: 22
def ofpat, do: 22
@doc """
Create a new group action struct
## Options
- id: Group identifier
```elixir
iex> %Group{id: 10} = Group.new(10)
```
"""
@spec new(0..0xFFFFFFFF) :: t()
def new(id) do
%Group{id: id}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%Group{id: id}) do
<<22::16, 8::16, id::32>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<22::16, 8::16, id::32>>) do
%Group{id: id}
end

View file

@ -12,8 +12,35 @@ defmodule Openflow.Action.NxBundle do
@nxast 12
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
@type algorithm :: :active_backup | :highest_random_weight
@type hash_field ::
:eth_src
| :symmetric_l4
| :symmetric_l3l4
| :symmetric_l3l4_udp
| :nw_src
| :nw_dst
@type t :: %NxBundle{
algorithm: algorithm(),
hash_field: hash_field(),
basis: non_neg_integer(),
slave_type: :nx_in_port,
n_slaves: non_neg_integer(),
slaves: [pos_integer()]
}
@spec new(
algorithm: algorithm(),
hash_field: hash_field(),
basis: non_neg_integer(),
slave_type: :nx_in_port,
n_slaves: non_neg_integer(),
slaves: [pos_integer()]
) :: t()
def new(options \\ []) do
slaves = options[:slaves] || []
%NxBundle{
@ -25,28 +52,25 @@ defmodule Openflow.Action.NxBundle do
}
end
def to_binary(%NxBundle{
algorithm: alg,
hash_field: hash_field,
basis: basis,
slave_type: slave_type,
n_slaves: n_slaves,
slaves: slaves
}) do
hash_field_int = Openflow.Enums.to_int(hash_field, :nx_hash_fields)
alg_int = Openflow.Enums.to_int(alg, :nx_bd_algorithm)
slave_type_bin = Openflow.Match.codec_header(slave_type)
slaves_bin = codec_slaves(slaves)
def to_binary(%NxBundle{} = bundle) do
bundle_hash_field_int = Openflow.Enums.to_int(bundle.hash_field, :nx_hash_fields)
bundle_alg_int = Openflow.Enums.to_int(bundle.algorithm, :nx_bd_algorithm)
bundle_slave_type_bin = Openflow.Match.codec_header(bundle.slave_type)
bundle_slaves_bin = codec_slaves(bundle.slaves)
body =
<<alg_int::16, hash_field_int::16, basis::16, slave_type_bin::4-bytes, n_slaves::16, 0::16,
0::size(4)-unit(8), 0::32, slaves_bin::bytes>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
bundle_alg_int::16,
bundle_hash_field_int::16,
bundle.basis::16,
bundle_slave_type_bin::4-bytes,
bundle.n_slaves::16,
0::size(2)-unit(8),
0::size(4)-unit(8),
0::size(4)-unit(8),
bundle_slaves_bin::bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -17,9 +17,42 @@ defmodule Openflow.Action.NxBundleLoad do
@nxast 13
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
dst_field = options[:dst_field]
@type algorithm :: :active_backup | :highest_random_weight
@type hash_field ::
:eth_src
| :symmetric_l4
| :symmetric_l3l4
| :symmetric_l3l4_udp
| :nw_src
| :nw_dst
@type t :: %NxBundleLoad{
algorithm: algorithm(),
hash_field: hash_field(),
basis: non_neg_integer(),
slave_type: :nx_in_port,
n_slaves: non_neg_integer(),
slaves: [pos_integer()],
offset: non_neg_integer(),
n_bits: pos_integer(),
dst_field: atom()
}
@spec new(
algorithm: algorithm(),
hash_field: hash_field(),
basis: non_neg_integer(),
slave_type: :nx_in_port,
n_slaves: non_neg_integer(),
slaves: [pos_integer()],
offset: non_neg_integer(),
n_bits: pos_integer(),
dst_field: atom()
) :: t()
def new(options \\ []) do
dst_field = options[:dst_field] || raise "dst_field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(dst_field)
slaves = options[:slaves] || []
@ -31,37 +64,31 @@ defmodule Openflow.Action.NxBundleLoad do
slaves: slaves,
offset: options[:offset] || 0,
n_bits: options[:n_bits] || default_n_bits,
dst_field: options[:dst_field]
dst_field: dst_field
}
end
def to_binary(%NxBundleLoad{
algorithm: alg,
hash_field: hash_field,
basis: basis,
slave_type: slave_type,
n_slaves: n_slaves,
slaves: slaves,
offset: ofs,
n_bits: n_bits,
dst_field: dst_field
}) do
hash_field_int = Openflow.Enums.to_int(hash_field, :nx_hash_fields)
alg_int = Openflow.Enums.to_int(alg, :nx_bd_algorithm)
slave_type_bin = Openflow.Match.codec_header(slave_type)
slaves_bin = codec_slaves(slaves)
ofs_nbits = ofs <<< 6 ||| n_bits - 1
dst_field_bin = Openflow.Match.codec_header(dst_field)
def to_binary(%NxBundleLoad{} = bundle_load) do
hash_field_int = Openflow.Enums.to_int(bundle_load.hash_field, :nx_hash_fields)
alg_int = Openflow.Enums.to_int(bundle_load.algorithm, :nx_bd_algorithm)
slave_type_bin = Openflow.Match.codec_header(bundle_load.slave_type)
slaves_bin = codec_slaves(bundle_load.slaves)
ofs_nbits = bundle_load.offset <<< 6 ||| bundle_load.n_bits - 1
dst_field_bin = Openflow.Match.codec_header(bundle_load.dst_field)
body =
<<alg_int::16, hash_field_int::16, basis::16, slave_type_bin::4-bytes, n_slaves::16,
ofs_nbits::16, dst_field_bin::4-bytes, 0::32, slaves_bin::bytes>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
alg_int::16,
hash_field_int::16,
bundle_load.basis::16,
slave_type_bin::4-bytes,
bundle_load.n_slaves::16,
ofs_nbits::16,
dst_field_bin::4-bytes,
0::32,
slaves_bin::bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -5,6 +5,7 @@ defmodule Openflow.Action.NxClone do
@nxast 42
alias __MODULE__
alias Openflow.Action.Experimenter
def new(actions \\ []) do
%NxClone{actions: actions}
@ -12,11 +13,10 @@ defmodule Openflow.Action.NxClone do
def to_binary(%NxClone{actions: actions}) do
actions_bin = Openflow.Action.to_binary(actions)
exp_body = <<@experimenter::32, @nxast::16, 0::size(6)-unit(8), actions_bin::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(
<<@experimenter::32, @nxast::16, 0::size(6)-unit(8), actions_bin::bytes>>
)
end
def read(<<@experimenter::32, @nxast::16, 0::size(6)-unit(8), actions_bin::bytes>>) do

View file

@ -9,18 +9,32 @@ defmodule Openflow.Action.NxConjunction do
@nxast 34
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(Keyword.t()) :: %NxConjunction{}
def new(options) do
clause = (options[:clause] || 0) + 1
n_clauses = options[:n_clauses] || raise "n_clauses must be specified"
n_clauses >= 2 || raise "n_clauses must be greater than 1"
(1 <= clause and clause <= n_clauses and 2 <= n_clauses and n_clauses <= 64) ||
raise "conjunction(id, k/n) must satisfy 1 <= k <= n and 2 <= n <= 64"
%NxConjunction{
clause: options[:clause] || 0,
n_clauses: options[:n_clauses] || 0,
clause: clause,
n_clauses: n_clauses,
id: options[:id] || 0
}
end
def to_binary(%NxConjunction{clause: clause, n_clauses: n_clauses, id: id}) do
exp_body = <<@experimenter::32, @nxast::16, clause::8, n_clauses::8, id::32>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
clause::8,
n_clauses::8,
id::32
>>)
end
def read(<<@experimenter::32, @nxast::16, clause::8, n_clauses::8, id::32>>) do

View file

@ -16,6 +16,7 @@ defmodule Openflow.Action.NxConntrack do
@nxast 35
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options \\ []) do
%NxConntrack{
@ -30,36 +31,21 @@ defmodule Openflow.Action.NxConntrack do
}
end
def to_binary(%NxConntrack{
flags: flags,
zone_src: zone_src,
zone_offset: zone_ofs,
zone_n_bits: zone_n_bits,
zone_imm: zone_imm,
recirc_table: recirc_table,
alg: alg,
exec: exec
}) do
flags_int = Openflow.Enums.flags_to_int(flags, :nx_conntrack_flags)
def to_binary(%NxConntrack{} = ct) do
flags_int = Openflow.Enums.flags_to_int(ct.flags, :nx_conntrack_flags)
ct_context_bin = ct_context_binary(ct)
exec_bin = Openflow.Action.to_binary(ct.exec)
{src_bin, ofs_nbits} =
if not is_nil(zone_src) do
zone_src_bin = Openflow.Match.codec_header(zone_src)
{zone_src_bin, zone_ofs <<< 6 ||| zone_n_bits - 1}
else
{<<0::32>>, zone_imm}
end
exec_bin = Openflow.Action.to_binary(exec)
exp_body =
<<@experimenter::32, @nxast::16, flags_int::16, src_bin::bytes, ofs_nbits::16,
recirc_table::8, 0::size(3)-unit(8), alg::16, exec_bin::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
flags_int::16,
ct_context_bin::bytes,
ct.recirc_table::8,
0::size(3)-unit(8),
ct.alg::16,
exec_bin::bytes
>>)
end
def read(
@ -87,4 +73,15 @@ defmodule Openflow.Action.NxConntrack do
%{ct | zone_src: zone_src, zone_offset: ofs, zone_n_bits: n_bits + 1}
end
end
# private functions
defp ct_context_binary(%NxConntrack{zone_src: nil} = ct),
do: <<0::32, ct.zone_imm::16>>
defp ct_context_binary(%NxConntrack{} = ct) do
zone_src_bin = Openflow.Match.codec_header(ct.zone_src)
ofs_nbits = ct.zone_offset <<< 6 ||| ct.zone_n_bits - 1
<<zone_src_bin::bytes, ofs_nbits::16>>
end
end

View file

@ -9,8 +9,23 @@ defmodule Openflow.Action.NxController do
@nxast 20
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
@type max_len :: :max | :no_buffer | non_neg_integer()
@type packet_in_reason ::
:no_match | :action | :invalid_ttl | :action_set | :group | :packet_out
@type t :: %NxController{
max_len: max_len(),
id: non_neg_integer(),
reason: packet_in_reason()
}
@spec new(
max_len: max_len(),
id: non_neg_integer(),
reason: packet_in_reason()
) :: t()
def new(options \\ []) do
%NxController{
max_len: options[:max_len] || :no_buffer,
id: options[:id] || 0,
@ -18,14 +33,18 @@ defmodule Openflow.Action.NxController do
}
end
def to_binary(%NxController{max_len: max_len, id: controller_id, reason: reason}) do
max_len_int = Openflow.Utils.get_enum(max_len, :controller_max_len)
reason_int = Openflow.Enums.to_int(reason, :packet_in_reason)
def to_binary(%NxController{} = controller) do
max_len_int = Openflow.Utils.get_enum(controller.max_len, :controller_max_len)
reason_int = Openflow.Enums.to_int(controller.reason, :packet_in_reason)
exp_body =
<<@experimenter::32, @nxast::16, max_len_int::16, controller_id::16, reason_int::8, 0::8>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
max_len_int::16,
controller.id::16,
reason_int::8,
0::8
>>)
end
def read(

View file

@ -4,7 +4,8 @@ defmodule Openflow.Action.NxController2 do
id: 0,
reason: :action,
userdata: "",
pause: false
pause: false,
meter_id: 0
)
@experimenter 0x00002320
@ -17,28 +18,57 @@ defmodule Openflow.Action.NxController2 do
@prop_reason 2
@prop_userdata 3
@prop_pause 4
@prop_meter_id 5
@nx_ctlr_no_meter 0
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
@type max_len :: pos_integer() | :max | :no_buffer
@type packet_in_reason ::
:no_match | :action | :invalid_ttl | :action_set | :group | :packet_out
@type meter_id :: pos_integer() | :max | :slowpath | :controller | :all
@type t :: %NxController2{
max_len: max_len(),
id: non_neg_integer(),
reason: packet_in_reason(),
userdata: binary(),
pause: boolean(),
meter_id: meter_id()
}
@spec new(
max_len: max_len(),
id: non_neg_integer(),
reason: packet_in_reason(),
userdata: binary(),
pause: boolean(),
meter_id: meter_id()
) :: t()
def new(options \\ [])
def new(options) when is_list(options) do
%NxController2{
max_len: options[:max_len] || :no_buffer,
id: options[:id] || 0,
reason: options[:reason] || :action,
userdata: options[:userdata],
pause: options[:pause] || false
pause: options[:pause] || false,
meter_id: options[:meter_id] || @nx_ctlr_no_meter
}
end
def to_binary(%NxController2{} = ctl) do
ext_header = <<@experimenter::32, @nxast::16, 0::size(6)-unit(8)>>
prop_keys = get_prop_key(ctl)
props_bin = encode_prop("", prop_keys, ctl)
exp_body = <<ext_header::bytes, props_bin::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes>>
props_bin = encode_props(ctl)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::size(6)-unit(8),
props_bin::bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, 0::size(6)-unit(8), body::bytes>>) do
@ -48,93 +78,144 @@ defmodule Openflow.Action.NxController2 do
# private functions
defp get_prop_key(ctl) do
defp encode_props(ctl) do
ctl
|> Map.from_struct()
|> Enum.map(fn {k, v} -> if(not is_nil(v), do: k, else: nil) end)
|> Enum.filter(fn v -> not is_nil(v) end)
|> Enum.reduce(<<>>, &encode_prop/2)
end
defp encode_prop(acc, [], _ctl), do: acc
defp encode_prop({:max_len, value}, acc)
when value != :no_buffer or value < 0xFFFF do
pad_length = 2
prop_length = @prop_header_size + 2 + pad_length
max_len_int = Openflow.Utils.get_enum(value, :controller_max_len)
defp encode_prop(acc, [prop | rest], ctl) do
value = Map.get(ctl, prop)
<<
acc::bytes,
@prop_max_len::16,
prop_length::16,
max_len_int::16,
0::size(pad_length)-unit(8)
>>
end
prop_bin =
cond do
prop == :max_len and (value != :no_buffer or value < 0xFFFF) ->
padding_length = 2
prop_length = @prop_header_size + 2 + padding_length
max_len_int = Openflow.Utils.get_enum(value, :controller_max_len)
<<@prop_max_len::16, prop_length::16, max_len_int::16, 0::size(padding_length)-unit(8)>>
defp encode_prop({:id, value}, acc) do
pad_length = 2
prop_length = @prop_header_size + 2 + pad_length
prop == :id ->
padding_length = 2
prop_length = @prop_header_size + 2 + padding_length
<<@prop_ctl_id::16, prop_length::16, value::16, 0::size(padding_length)-unit(8)>>
<<
acc::bytes,
@prop_ctl_id::16,
prop_length::16,
value::16,
0::size(pad_length)-unit(8)
>>
end
prop == :reason and value != :action ->
padding_length = 3
prop_length = @prop_header_size + 1 + padding_length
reason_int = Openflow.Utils.get_enum(value, :packet_in_reason)
<<@prop_reason::16, prop_length::16, reason_int::8, 0::size(padding_length)-unit(8)>>
defp encode_prop({:reason, reason}, acc)
when reason != :action do
padding_length = 3
prop_length = @prop_header_size + 1 + padding_length
reason_int = Openflow.Utils.get_enum(reason, :packet_in_reason)
prop == :userdata and byte_size(value) > 0 ->
prop_length = @prop_header_size + byte_size(value)
padding_length = Openflow.Utils.padding(prop_length, 8)
<<@prop_userdata::16, prop_length::16, value::bytes, 0::size(padding_length)-unit(8)>>
<<
acc::bytes,
@prop_reason::16,
prop_length::16,
reason_int::8,
0::size(padding_length)-unit(8)
>>
end
prop == :pause and value == true ->
padding_length = 4
prop_length = @prop_header_size + padding_length
<<@prop_pause::16, prop_length::16, 0::size(padding_length)-unit(8)>>
defp encode_prop({:userdata, value}, acc)
when byte_size(value) > 0 do
prop_length = @prop_header_size + byte_size(value)
padding_length = Openflow.Utils.padding(prop_length, 8)
true ->
""
end
<<
acc::bytes,
@prop_userdata::16,
prop_length::16,
value::bytes,
0::size(padding_length)-unit(8)
>>
end
encode_prop(<<acc::bytes, prop_bin::bytes>>, rest, ctl)
defp encode_prop({:pause, true}, acc) do
padding_length = 4
prop_length = @prop_header_size + padding_length
<<
acc::bytes,
@prop_pause::16,
prop_length::16,
0::size(padding_length)-unit(8)
>>
end
defp encode_prop({:meter_id, value}, acc)
when value != @nx_ctlr_no_meter do
prop_length = @prop_header_size + 4
<<
acc::bytes,
@prop_meter_id::16,
prop_length::16,
value::32
>>
end
defp encode_prop(_, acc) do
acc
end
defp decode_prop(ctl, ""), do: ctl
defp decode_prop(ctl, <<prop_type_int::16, _::bytes>> = bin) do
prop_type = Openflow.Enums.to_atom(prop_type_int, :nx_action_controller2_prop_type)
defp decode_prop(
ctl,
<<
@prop_max_len::16,
_prop_length::16,
max_len_int::16,
_::size(2)-unit(8),
rest::bytes
>>
) do
max_len = Openflow.Utils.get_enum(max_len_int, :controller_max_len)
decode_prop(%{ctl | max_len: max_len}, rest)
end
case prop_type do
:max_len ->
<<@prop_max_len::16, _prop_length::16, max_len_int::16, _::size(2)-unit(8), rest::bytes>> =
bin
defp decode_prop(
ctl,
<<@prop_reason::16, _prop_length::16, reason_int::8, _::size(3)-unit(8), rest::bytes>>
) do
reason = Openflow.Utils.get_enum(reason_int, :packet_in_reason)
decode_prop(%{ctl | reason: reason}, rest)
end
max_len = Openflow.Utils.get_enum(max_len_int, :controller_max_len)
decode_prop(struct(ctl, %{max_len: max_len}), rest)
defp decode_prop(
ctl,
<<@prop_ctl_id::16, _prop_length::16, controller_id::16, _::size(2)-unit(8),
rest::bytes>>
) do
decode_prop(%{ctl | id: controller_id}, rest)
end
:controller_id ->
<<@prop_ctl_id::16, _prop_length::16, controller_id::16, _::size(2)-unit(8), rest::bytes>> =
bin
defp decode_prop(
ctl,
<<@prop_userdata::16, prop_length::16, remains::bytes>>
) do
userdata_len = prop_length - 4
padding_length = Openflow.Utils.padding(prop_length, 8)
<<userdata::size(userdata_len)-bytes, _::size(padding_length)-unit(8), rest::bytes>> = remains
decode_prop(%{ctl | userdata: userdata}, rest)
end
decode_prop(struct(ctl, %{controller_id: controller_id}), rest)
defp decode_prop(ctl, <<@prop_pause::16, _::16, 0::size(4)-unit(8), rest::bytes>>) do
decode_prop(%{ctl | pause: true}, rest)
end
:reason ->
<<@prop_reason::16, _prop_length::16, reason_int::8, _::size(3)-unit(8), rest::bytes>> =
bin
reason = Openflow.Utils.get_enum(reason_int, :packet_in_reason)
decode_prop(struct(ctl, %{reason: reason}), rest)
:userdata ->
<<@prop_userdata::16, prop_length::16, remains::bytes>> = bin
userdata_len = prop_length - 4
padding_length = Openflow.Utils.padding(prop_length, 8)
<<userdata::size(userdata_len)-bytes, _::size(padding_length)-unit(8), rest::bytes>> =
remains
decode_prop(struct(ctl, %{userdata: userdata}), rest)
:pause ->
<<@prop_pause::16, _::16, 0::size(4)-unit(8), rest::bytes>> = bin
decode_prop(struct(ctl, %{pause: true}), rest)
end
defp decode_prop(ctl, <<@prop_meter_id::16, _::16, meter_id::32, rest::bytes>>) do
decode_prop(%{ctl | meter_id: meter_id}, rest)
end
end

View file

@ -5,14 +5,19 @@ defmodule Openflow.Action.NxCtClear do
@nxast 43
alias __MODULE__
alias Openflow.Action.Experimenter
def new do
%NxCtClear{}
end
def to_binary(%NxCtClear{}) do
exp_body = <<@experimenter::32, @nxast::16, 0::16, 0::size(4)-unit(8)>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::16,
0::size(4)-unit(8)
>>)
end
def read(<<@experimenter::32, @nxast::16, _::16, _::size(4)-unit(8)>>) do

View file

@ -1,21 +0,0 @@
defmodule Openflow.Action.NxDecMplsTtl do
defstruct([])
@experimenter 0x00002320
@nxast 26
alias __MODULE__
def new do
%NxDecMplsTtl{}
end
def to_binary(%NxDecMplsTtl{}) do
exp_body = <<@experimenter::32, @nxast::16, 0::size(6)-unit(8)>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
end
def read(<<@experimenter::32, @nxast::16, _::size(6)-unit(8)>>) do
%NxDecMplsTtl{}
end
end

View file

@ -5,14 +5,18 @@ defmodule Openflow.Action.NxDecTtl do
@nxast 18
alias __MODULE__
alias Openflow.Action.Experimenter
def new do
%NxDecTtl{}
end
def to_binary(%NxDecTtl{}) do
exp_body = <<@experimenter::32, @nxast::16, 0::size(6)-unit(8)>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::size(6)-unit(8)
>>)
end
def read(<<@experimenter::32, @nxast::16, _::16, _::size(4)-unit(8)>>) do

View file

@ -5,24 +5,26 @@ defmodule Openflow.Action.NxDecTtlCntIds do
@nxast 21
alias __MODULE__
alias Openflow.Action.Experimenter
@type t :: %NxDecTtlCntIds{ids: [non_neg_integer()]}
@spec new(ids :: [non_neg_integer()]) :: t()
def new(ids) do
%NxDecTtlCntIds{ids: ids}
end
def to_binary(%NxDecTtlCntIds{ids: ids}) do
n_controllers = length(ids)
ids_bin = Enum.join(for(id <- ids, do: <<id::16>>), "")
padding = Openflow.Utils.padding(n_controllers, 8)
ids_bin = Enum.reduce(ids, <<>>, fn id, acc -> <<acc::bytes, id::16>> end)
exp_body =
<<@experimenter::32, @nxast::16, n_controllers::16, 0::size(4)-unit(8), ids_bin::bytes,
0::size(padding)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
n_controllers::16,
0::size(4)-unit(8),
ids_bin::bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, n_controllers::16, body::bitstring>>) do

View file

@ -5,17 +5,17 @@ defmodule Openflow.Action.NxExit do
@nxast 17
alias __MODULE__
alias Openflow.Action.Experimenter
def new do
%NxExit{}
end
def to_binary(%NxExit{}) do
exp_body = <<@experimenter::32, @nxast::16, 0::48>>
<<0xFFFF::16, 16::16, exp_body::bytes>>
Experimenter.pack_exp_header(<<@experimenter::32, @nxast::16, 0::48>>)
end
def read(<<@experimenter::32, @nxast::16, 0::48>>) do
def read(<<@experimenter::32, @nxast::16, 0::48, _::bytes>>) do
%NxExit{}
end
end

View file

@ -8,7 +8,9 @@ defmodule Openflow.Action.NxFinTimeout do
@nxast 19
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(Keyword.t()) :: %NxFinTimeout{}
def new(options) do
%NxFinTimeout{
idle_timeout: options[:idle_timeout] || 0,
@ -16,9 +18,13 @@ defmodule Openflow.Action.NxFinTimeout do
}
end
def to_binary(%NxFinTimeout{idle_timeout: fin_idle, hard_timeout: fin_hard}) do
exp_body = <<@experimenter::32, @nxast::16, fin_idle::16, fin_hard::16>>
<<0xFFFF::16, 16::16, exp_body::bytes, 0::size(2)-unit(8)>>
def to_binary(%NxFinTimeout{} = fin_timeout) do
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
fin_timeout.idle_timeout::16,
fin_timeout.hard_timeout::16
>>)
end
def read(<<@experimenter::32, @nxast::16, fin_idle::16, fin_hard::16, _::size(2)-unit(8)>>) do

View file

@ -13,12 +13,28 @@ defmodule Openflow.Action.NxFlowSpecLoad do
alias __MODULE__
@type t :: %NxFlowSpecLoad{
src: atom(),
dst: atom(),
n_bits: non_neg_integer(),
src_offset: non_neg_integer(),
dst_offset: non_neg_integer()
}
@spec new(
src: atom(),
dst: atom(),
n_bits: non_neg_integer(),
src_offset: non_neg_integer(),
dst_offset: non_neg_integer()
) :: t()
def new(options) do
dst = options[:dst]
dst = options[:dst] || raise(":dst must be specified")
src = options[:src] || raise(":src must be specified")
n_bits = options[:n_bits] || Openflow.Match.Field.n_bits_of(dst)
%NxFlowSpecLoad{
src: options[:src],
src: src,
dst: dst,
n_bits: n_bits,
src_offset: options[:src_offset] || 0,

View file

@ -13,12 +13,28 @@ defmodule Openflow.Action.NxFlowSpecMatch do
alias __MODULE__
def new(options) do
dst = options[:dst]
@type t :: %NxFlowSpecMatch{
src: atom(),
dst: atom(),
n_bits: non_neg_integer(),
src_offset: non_neg_integer(),
dst_offset: non_neg_integer()
}
@spec new(
src: atom(),
dst: atom(),
n_bits: non_neg_integer(),
src_offset: non_neg_integer(),
dst_offset: non_neg_integer()
) :: t()
def new(options \\ []) do
dst = options[:dst] || raise ":dst must be specified"
src = options[:src] || raise ":src must be specified"
n_bits = options[:n_bits] || Openflow.Match.Field.n_bits_of(dst)
%NxFlowSpecMatch{
src: options[:src],
src: src,
dst: dst,
n_bits: n_bits,
src_offset: options[:src_offset] || 0,
@ -26,6 +42,7 @@ defmodule Openflow.Action.NxFlowSpecMatch do
}
end
@spec to_binary(t()) :: binary()
def to_binary(%NxFlowSpecMatch{} = fsm) do
%NxFlowSpecMatch{
dst: dst_field,
@ -48,6 +65,7 @@ defmodule Openflow.Action.NxFlowSpecMatch do
end
end
@spec read(binary()) :: {t(), binary()}
def read(
<<_::2, @learn_src_field::1, @learn_dst::2, n_bits::11, src_bin::4-bytes, src_ofs::16,
dst_bin::4-bytes, dst_ofs::16, rest::bitstring>>

View file

@ -10,23 +10,42 @@ defmodule Openflow.Action.NxFlowSpecOutput do
alias __MODULE__
def new(options) do
src = options[:src]
@type t :: %NxFlowSpecOutput{
n_bits: non_neg_integer(),
src: atom(),
src_offset: non_neg_integer()
}
@spec new(src: atom(), n_bits: non_neg_integer(), src_offset: non_neg_integer()) :: t()
def new(options \\ []) do
src = options[:src] || raise ":src must be specified"
n_bits = options[:n_bits] || Openflow.Match.Field.n_bits_of(src)
%NxFlowSpecOutput{n_bits: n_bits, src: src, src_offset: options[:src_offset] || 0}
end
def to_binary(%NxFlowSpecOutput{n_bits: n_bits, src: src, src_offset: src_ofs}) do
src_bin = Openflow.Match.codec_header(src)
<<0::2, @learn_src_field::1, @learn_dst::2, n_bits::11, src_bin::4-bytes, src_ofs::16>>
@spec to_binary(t()) :: binary()
def to_binary(%NxFlowSpecOutput{} = flow_spec) do
<<
0::2,
@learn_src_field::1,
@learn_dst::2,
flow_spec.n_bits::11,
Openflow.Match.codec_header(flow_spec.src)::4-bytes,
flow_spec.src_offset::16
>>
end
@spec read(binary()) :: {t(), binary()}
def read(
<<0::2, @learn_src_field::1, @learn_dst::2, n_bits::11, src_bin::4-bytes, src_ofs::16,
rest::bitstring>>
) do
src = Openflow.Match.codec_header(src_bin)
flow_spec = %NxFlowSpecOutput{n_bits: n_bits, src: src, src_offset: src_ofs}
flow_spec = %NxFlowSpecOutput{
n_bits: n_bits,
src: Openflow.Match.codec_header(src_bin),
src_offset: src_ofs
}
{flow_spec, rest}
end
end

View file

@ -15,6 +15,7 @@ defmodule Openflow.Action.NxLearn do
@nxast 16
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
%NxLearn{
@ -30,28 +31,24 @@ defmodule Openflow.Action.NxLearn do
}
end
def to_binary(%NxLearn{
idle_timeout: idle,
hard_timeout: hard,
priority: prio,
cookie: cookie,
flags: flags,
table_id: table_id,
fin_idle_timeout: fin_idle,
fin_hard_timeout: fin_hard,
flow_specs: flow_specs
}) do
flags_int = Openflow.Enums.flags_to_int(flags, :nx_learn_flag)
flow_specs_bin = Openflow.Action.NxFlowSpec.to_binary(flow_specs)
def to_binary(%NxLearn{} = learn) do
learn_flags_int = Openflow.Enums.flags_to_int(learn.flags, :nx_learn_flag)
flow_specs_bin = Openflow.Action.NxFlowSpec.to_binary(learn.flow_specs)
exp_body =
<<@experimenter::32, @nxast::16, idle::16, hard::16, prio::16, cookie::64, flags_int::16,
table_id::8, 0::size(1)-unit(8), fin_idle::16, fin_hard::16, flow_specs_bin::bitstring>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
learn.idle_timeout::16,
learn.hard_timeout::16,
learn.priority::16,
learn.cookie::64,
learn_flags_int::16,
learn.table_id::8,
0::8,
learn.fin_idle_timeout::16,
learn.fin_hard_timeout::16,
flow_specs_bin::bitstring
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bitstring>>) do

View file

@ -18,8 +18,9 @@ defmodule Openflow.Action.NxLearn2 do
@nxast 45
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
def new(options \\ []) do
%NxLearn2{
idle_timeout: options[:idle_timeout] || 0,
hard_timeout: options[:hard_timeout] || 0,
@ -36,40 +37,33 @@ defmodule Openflow.Action.NxLearn2 do
}
end
def to_binary(%NxLearn2{
idle_timeout: idle,
hard_timeout: hard,
priority: prio,
cookie: cookie,
flags: flags,
table_id: table_id,
fin_idle_timeout: fin_idle,
fin_hard_timeout: fin_hard,
limit: limit,
result_dst_offset: result_dst_ofs,
result_dst: result_dst,
flow_specs: flow_specs
}) do
flags_int = Openflow.Enums.flags_to_int(flags, :nx_learn_flag)
def to_binary(%NxLearn2{} = learn) do
learn_flags_int = Openflow.Enums.flags_to_int(learn.flags, :nx_learn_flag)
learn_flow_specs_bin = Openflow.Action.NxFlowSpec.to_binary(learn.flow_specs)
result_dst_bin =
if :write_result in flags do
Openflow.Match.codec_header(result_dst)
else
""
end
learn_result_dst_bin =
if :write_result in learn.flags,
do: Openflow.Match.codec_header(learn.result_dst),
else: <<>>
flow_specs_bin = Openflow.Action.NxFlowSpec.to_binary(flow_specs)
exp_body =
<<@experimenter::32, @nxast::16, idle::16, hard::16, prio::16, cookie::64, flags_int::16,
table_id::8, 0::size(1)-unit(8), fin_idle::16, fin_hard::16, limit::32,
result_dst_ofs::16, 0::size(2)-unit(8), result_dst_bin::bytes, flow_specs_bin::bitstring>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
learn.idle_timeout::16,
learn.hard_timeout::16,
learn.priority::16,
learn.cookie::64,
learn_flags_int::16,
learn.table_id::8,
0::size(1)-unit(8),
learn.fin_idle_timeout::16,
learn.fin_hard_timeout::16,
learn.limit::32,
learn.result_dst_offset::16,
0::size(2)-unit(8),
learn_result_dst_bin::bytes,
learn_flow_specs_bin::bitstring
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -16,54 +16,44 @@ defmodule Openflow.Action.NxMultipath do
@nxast 10
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(Keyword.t()) :: %NxMultipath{}
def new(options) do
hash_field = Keyword.get(options, :hash_field, :eth_src)
basis = Keyword.get(options, :basis, 0)
alg = Keyword.get(options, :algorithm, :modulo_n)
max_link = Keyword.get(options, :max_link, 0)
arg = Keyword.get(options, :argument, 0)
dst_field = Keyword.get(options, :dst_field)
dst_field = options[:dst_field] || raise "dst_field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(dst_field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
%NxMultipath{
hash_field: hash_field,
basis: basis,
algorithm: alg,
max_link: max_link,
offset: ofs,
n_bits: n_bits,
argument: arg,
hash_field: options[:hash_field] || :eth_src,
basis: options[:basis] || 0,
algorithm: options[:algorithm] || :modulo_n,
max_link: options[:max_link] || 0,
offset: options[:offset] || 0,
n_bits: options[:n_bits] || default_n_bits,
argument: options[:argument] || 0,
dst_field: dst_field
}
end
def to_binary(%NxMultipath{
hash_field: hash_field,
basis: basis,
algorithm: alg,
max_link: max_link,
argument: arg,
offset: ofs,
n_bits: n_bits,
dst_field: dst_field
}) do
hash_field_int = Openflow.Enums.to_int(hash_field, :nx_hash_fields)
alg_int = Openflow.Enums.to_int(alg, :nx_mp_algorithm)
dst_field_bin = Openflow.Match.codec_header(dst_field)
ofs_nbits = ofs <<< 6 ||| n_bits - 1
def to_binary(%NxMultipath{} = multipath) do
hash_field_int = Openflow.Enums.to_int(multipath.hash_field, :nx_hash_fields)
alg_int = Openflow.Enums.to_int(multipath.algorithm, :nx_mp_algorithm)
dst_field_bin = Openflow.Match.codec_header(multipath.dst_field)
ofs_nbits = multipath.offset <<< 6 ||| multipath.n_bits - 1
body =
<<hash_field_int::16, basis::16, 0::size(2)-unit(8), alg_int::16, max_link::16, arg::32,
0::size(2)-unit(8), ofs_nbits::16, dst_field_bin::4-bytes>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
hash_field_int::16,
multipath.basis::16,
0::size(2)-unit(8),
alg_int::16,
multipath.max_link::16,
multipath.argument::32,
0::size(2)-unit(8),
ofs_nbits::16,
dst_field_bin::4-bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -13,49 +13,62 @@ defmodule Openflow.Action.NxNat do
@nxast 36
alias __MODULE__
alias Openflow.Action.Experimenter
@type in_addr :: :inet.ip4_address()
@type in6_addr :: :inet.ip6_address()
@type port_number :: :inet.port_number()
@type flag :: :src | :dst | :persistent | :protocol_hash | :protocol_random
@type nat_range :: :ipv4_min | :ipv4_max | :ipv6_min | :ipv6_max | :proto_min | :proto_max
@type t :: %NxNat{
flags: [flag()],
ipv4_min: in_addr(),
ipv4_max: in_addr(),
ipv6_min: in6_addr(),
ipv6_max: in6_addr(),
proto_min: port_number(),
proto_max: port_number()
}
@spec new(
flags: [flag()],
ipv4_min: in_addr(),
ipv4_max: in_addr(),
ipv6_min: in6_addr(),
ipv6_max: in6_addr(),
proto_min: port_number(),
proto_max: port_number()
) :: t()
def new(options \\ []) do
flags = Keyword.get(options, :flags, [])
ipv4_min = Keyword.get(options, :ipv4_min)
ipv4_max = Keyword.get(options, :ipv4_max)
ipv6_min = Keyword.get(options, :ipv6_min)
ipv6_max = Keyword.get(options, :ipv6_max)
proto_min = Keyword.get(options, :proto_min)
proto_max = Keyword.get(options, :proto_max)
%NxNat{
flags: flags,
ipv4_min: ipv4_min,
ipv4_max: ipv4_max,
ipv6_min: ipv6_min,
ipv6_max: ipv6_max,
proto_min: proto_min,
proto_max: proto_max
flags: options[:flags] || [],
ipv4_min: options[:ipv4_min],
ipv4_max: options[:ipv4_max],
ipv6_min: options[:ipv6_min],
ipv6_max: options[:ipv6_max],
proto_min: options[:proto_min],
proto_max: options[:proto_max]
}
end
@spec to_binary(t()) :: binary()
def to_binary(%NxNat{flags: flags} = nat) do
flags_int = Openflow.Enums.flags_to_int(flags, :nx_nat_flags)
range_flags =
nat
|> get_ranges
|> Openflow.Enums.flags_to_int(:nx_nat_range)
|> Openflow.Enums.int_to_flags(:nx_nat_range)
range_flags = get_range_flags(nat)
ranges_bin = encode_ranges("", range_flags, nat)
range_flags_int = Openflow.Enums.flags_to_int(range_flags, :nx_nat_range)
exp_body =
<<@experimenter::32, @nxast::16, 0::size(2)-unit(8), flags_int::16, range_flags_int::16,
ranges_bin::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::size(2)-unit(8),
flags_int::16,
range_flags_int::16,
ranges_bin::bytes
>>)
end
@spec read(binary()) :: t()
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do
<<0::size(2)-unit(8), flags_int::16, range_flags_int::16, ranges_bin::bytes>> = body
flags = Openflow.Enums.int_to_flags(flags_int, :nx_nat_flags)
@ -65,81 +78,95 @@ defmodule Openflow.Action.NxNat do
# private functions
defp get_ranges(nat) do
nat
|> Map.from_struct()
|> Map.delete(:flags)
|> Enum.map(fn {k, v} -> if(not is_nil(v), do: k, else: nil) end)
|> Enum.filter(fn v -> not is_nil(v) end)
@range_keys [:ipv4_min, :ipv4_max, :ipv6_min, :ipv6_max, :proto_min, :proto_max]
@spec get_range_flags(t()) :: [nat_range()]
defp get_range_flags(nat) do
@range_keys
|> Enum.reduce([], fn
key, acc when key in @range_keys ->
if not is_nil(Map.get(nat, key)), do: [key | acc], else: acc
end)
|> Enum.reverse()
end
@spec encode_ranges(binary(), [nat_range()], t()) :: binary()
defp encode_ranges(acc, [], _nat), do: acc
defp encode_ranges(acc, [range | rest], nat) do
cond do
range == :ipv4_min or range == :ipv4_max ->
case Map.get(nat, range) do
{a1, a2, a3, a4} ->
encode_ranges(<<acc::bytes, a1, a2, a3, a4>>, rest, nat)
"" ->
encode_ranges(<<acc::bytes>>, rest, nat)
end
range == :ipv6_min or range == :ipv6_max ->
case Map.get(nat, range) do
{a1, a2, a3, a4, a5, a6, a7, a8} ->
encode_ranges(
<<acc::bytes, a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16>>,
rest,
nat
)
"" ->
encode_ranges(<<acc::bytes>>, rest, nat)
end
range == :proto_min or range == :proto_max ->
case Map.get(nat, range) do
proto when is_integer(proto) and proto in 1..0xFFFF ->
encode_ranges(<<acc::bytes, proto::16>>, rest, nat)
_ ->
encode_ranges(<<acc::bytes>>, rest, nat)
end
end
defp encode_ranges(acc, [:ipv4_min | rest], %NxNat{ipv4_min: {a1, a2, a3, a4}} = nat) do
encode_ranges(<<acc::bytes, a1, a2, a3, a4>>, rest, nat)
end
defp encode_ranges(acc, [:ipv4_max | rest], %NxNat{ipv4_max: {a1, a2, a3, a4}} = nat) do
encode_ranges(<<acc::bytes, a1, a2, a3, a4>>, rest, nat)
end
defp encode_ranges(
acc,
[:ipv6_min | rest],
%NxNat{ipv6_min: {a1, a2, a3, a4, a5, a6, a7, a8}} = nat
) do
encode_ranges(
<<acc::bytes, a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16>>,
rest,
nat
)
end
defp encode_ranges(
acc,
[:ipv6_max | rest],
%NxNat{ipv6_max: {a1, a2, a3, a4, a5, a6, a7, a8}} = nat
) do
encode_ranges(
<<acc::bytes, a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16>>,
rest,
nat
)
end
defp encode_ranges(acc, [:proto_min | rest], %NxNat{proto_min: proto} = nat)
when is_integer(proto) do
encode_ranges(<<acc::bytes, proto::16>>, rest, nat)
end
defp encode_ranges(acc, [:proto_max | rest], %NxNat{proto_max: proto} = nat)
when is_integer(proto) do
encode_ranges(<<acc::bytes, proto::16>>, rest, nat)
end
@spec decode_ranges(t(), [nat_range()], binary()) :: t()
defp decode_ranges(nat, [], _), do: nat
defp decode_ranges(nat, [range | ranges], bin) do
cond do
range == :ipv4_min or range == :ipv4_max ->
case bin do
<<a1, a2, a3, a4, rest::bytes>> ->
decode_ranges(struct(nat, %{range => {a1, a2, a3, a4}}), ranges, rest)
defp decode_ranges(acc, [:ipv4_min | rest], <<a1, a2, a3, a4, binary::bytes>>) do
decode_ranges(%{acc | ipv4_min: {a1, a2, a3, a4}}, rest, binary)
end
rest ->
decode_ranges(struct(nat, %{range => ""}), ranges, rest)
end
defp decode_ranges(acc, [:ipv4_max | rest], <<a1, a2, a3, a4, binary::bytes>>) do
decode_ranges(%{acc | ipv4_max: {a1, a2, a3, a4}}, rest, binary)
end
range == :ipv6_min or range == :ipv6_max ->
case bin do
<<a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16, rest::bytes>> ->
decode_ranges(struct(nat, %{range => {a1, a2, a3, a4, a5, a6, a7, a8}}), ranges, rest)
defp decode_ranges(
acc,
[:ipv6_min | rest],
<<a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16, binary::bytes>>
) do
decode_ranges(%{acc | ipv6_min: {a1, a2, a3, a4, a5, a6, a7, a8}}, rest, binary)
end
rest ->
decode_ranges(struct(nat, %{range => ""}), ranges, rest)
end
defp decode_ranges(
acc,
[:ipv6_max | rest],
<<a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16, binary::bytes>>
) do
decode_ranges(%{acc | ipv6_max: {a1, a2, a3, a4, a5, a6, a7, a8}}, rest, binary)
end
range == :proto_min or range == :proto_max ->
case bin do
<<proto::16, rest::bytes>> when proto in 1..0xFFFF ->
decode_ranges(struct(nat, %{range => proto}), ranges, rest)
defp decode_ranges(acc, [:proto_min | rest], <<proto::16, binary::bytes>>) do
decode_ranges(%{acc | proto_min: proto}, rest, binary)
end
rest ->
decode_ranges(struct(nat, %{range => ""}), ranges, rest)
end
end
defp decode_ranges(acc, [:proto_max | rest], <<proto::16, binary::bytes>>) do
decode_ranges(%{acc | proto_max: proto}, rest, binary)
end
end

View file

@ -5,18 +5,14 @@ defmodule Openflow.Action.NxNote do
@nxast 8
alias __MODULE__
alias Openflow.Action.Experimenter
def new(note) do
%NxNote{note: note}
end
def to_binary(%NxNote{note: note}) do
padding = Openflow.Utils.padding(byte_size(note) + 2, 8)
exp_body = <<@experimenter::32, @nxast::16, note::bytes, 0::size(padding)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<@experimenter::32, @nxast::16, note::bytes>>)
end
def read(<<@experimenter::32, @nxast::16, note_bin::bytes>>) do

View file

@ -12,26 +12,47 @@ defmodule Openflow.Action.NxOutputReg do
@nxast 15
alias __MODULE__
alias Openflow.Action.Experimenter
@type max_len :: :no_buffer | :max | non_neg_integer()
@type t :: %NxOutputReg{
n_bits: pos_integer(),
offset: non_neg_integer(),
src_field: atom(),
max_len: max_len()
}
@spec new(
n_bits: pos_integer(),
offset: non_neg_integer(),
src_field: atom(),
max_len: max_len()
) :: t()
def new(options) do
src_field = Keyword.get(options, :src_field)
src_field = options[:src_field] || raise "src_field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(src_field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
max_len = Keyword.get(options, :max_len, :no_buffer)
%NxOutputReg{n_bits: n_bits, offset: ofs, src_field: src_field, max_len: max_len}
%NxOutputReg{
n_bits: options[:n_bits] || default_n_bits,
offset: options[:offset] || 0,
src_field: src_field,
max_len: options[:max_len] || :no_buffer
}
end
def to_binary(%NxOutputReg{n_bits: n_bits, offset: ofs, src_field: src_field, max_len: max_len}) do
src_field_bin = Openflow.Match.codec_header(src_field)
ofs_nbits = ofs <<< 6 ||| n_bits - 1
max_len = Openflow.Utils.get_enum(max_len, :controller_max_len)
body = <<ofs_nbits::16, src_field_bin::4-bytes, max_len::16, 0::size(6)-unit(8)>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxOutputReg{} = output_reg) do
src_field_bin = Openflow.Match.codec_header(output_reg.src_field)
ofs_nbits = output_reg.offset <<< 6 ||| output_reg.n_bits - 1
max_len = Openflow.Utils.get_enum(output_reg.max_len, :controller_max_len)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
ofs_nbits::16,
src_field_bin::4-bytes,
max_len::16,
0::size(6)-unit(8)
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -12,27 +12,52 @@ defmodule Openflow.Action.NxOutputReg2 do
@nxast 32
alias __MODULE__
alias Openflow.Action.Experimenter
@type max_len :: :no_buffer | :max | non_neg_integer()
@type t :: %NxOutputReg2{
n_bits: pos_integer(),
offset: non_neg_integer(),
src_field: atom(),
max_len: max_len()
}
@spec new(
n_bits: pos_integer(),
offset: non_neg_integer(),
src_field: atom(),
max_len: max_len()
) :: t()
def new(options) do
src_field = Keyword.get(options, :src_field)
src_field = options[:src_field] || raise "src_field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(src_field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
max_len = Keyword.get(options, :max_len, :no_buffer)
%NxOutputReg2{n_bits: n_bits, offset: ofs, src_field: src_field, max_len: max_len}
%NxOutputReg2{
n_bits: options[:n_bits] || default_n_bits,
offset: options[:offset] || 0,
src_field: src_field,
max_len: options[:max_len] || :no_buffer
}
end
def to_binary(%NxOutputReg2{n_bits: n_bits, offset: ofs, src_field: src_field, max_len: max_len}) do
src_field_bin = Openflow.Match.codec_header(src_field)
ofs_nbits = ofs <<< 6 ||| n_bits - 1
max_len = Openflow.Utils.get_enum(max_len, :controller_max_len)
padding = Openflow.Utils.padding(byte_size(src_field_bin), 10)
body = <<ofs_nbits::16, max_len::16, src_field_bin::bytes, 0::size(padding)-unit(8)>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxOutputReg2{} = output_reg) do
src_field_bin = Openflow.Match.codec_header(output_reg.src_field)
ofs_nbits = output_reg.offset <<< 6 ||| output_reg.n_bits - 1
max_len = Openflow.Utils.get_enum(output_reg.max_len, :controller_max_len)
padding =
src_field_bin
|> byte_size()
|> Openflow.Utils.padding(10)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
ofs_nbits::16,
max_len::16,
src_field_bin::bytes,
0::size(padding)-unit(8)
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -8,20 +8,28 @@ defmodule Openflow.Action.NxOutputTrunc do
@nxast 39
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(Keyword.t()) :: %NxOutputTrunc{}
def new(options) do
port_no = Keyword.get(options, :port_number)
max_len = Keyword.get(options, :max_len)
%NxOutputTrunc{port_number: port_no, max_len: max_len}
port_no = options[:port_no] || raise "port_no must be specified"
max_len = options[:max_len] || raise "max_len must be specified"
%NxOutputTrunc{
port_number: port_no,
max_len: max_len
}
end
def to_binary(%NxOutputTrunc{port_number: port_no, max_len: max_len}) do
port_no_int = Openflow.Utils.get_enum(port_no, :openflow10_port_no)
exp_body = <<@experimenter::32, @nxast::16, port_no_int::16, max_len::32>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
port_no_int::16,
max_len::32
>>)
end
def read(<<@experimenter::32, @nxast::16, port_no_int::16, max_len::32>>) do

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxPopMpls do
defstruct(ethertype: 0x8847)
@experimenter 0x00002320
@nxast 24
alias __MODULE__
def new(ethertype \\ 0x8847) do
%NxPopMpls{ethertype: ethertype}
end
def to_binary(%NxPopMpls{ethertype: ethertype}) do
exp_body = <<@experimenter::32, @nxast::16, ethertype::16, 0::size(4)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, ethertype::16, _::size(4)-unit(8)>>) do
%NxPopMpls{ethertype: ethertype}
end
end

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxPopQueue do
defstruct([])
@experimenter 0x00002320
@nxast 5
alias __MODULE__
def new do
%NxPopQueue{}
end
def to_binary(%NxPopQueue{}) do
exp_body = <<@experimenter::32, @nxast::16, 0::48>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, 0::size(6)-unit(8)>>) do
%NxPopQueue{}
end
end

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxPushMpls do
defstruct(ethertype: 0x8847)
@experimenter 0x00002320
@nxast 23
alias __MODULE__
def new(ethertype \\ 0x8847) do
%NxPushMpls{ethertype: ethertype}
end
def to_binary(%NxPushMpls{ethertype: ethertype}) do
exp_body = <<@experimenter::32, @nxast::16, ethertype::16, 0::size(4)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, ethertype::16, _::size(4)-unit(8)>>) do
%NxPushMpls{ethertype: ethertype}
end
end

View file

@ -1,4 +1,32 @@
defmodule Openflow.Action.NxRegLoad do
@moduledoc """
Copies value[0:n_bits] to dst[ofs:ofs+n_bits], where a[b:c] denotes the bits
within 'a' numbered 'b' through 'c' (not including bit 'c'). Bit numbering
starts at 0 for the least-significant bit, 1 for the next most significant
bit, and so on.
'dst' is an nxm_header with nxm_hasmask=0. See the documentation for
NXAST_REG_MOVE, above, for the permitted fields and for the side effects of
loading them.
The 'ofs' and 'n_bits' fields are combined into a single 'ofs_nbits' field
to avoid enlarging the structure by another 8 bytes. To allow 'n_bits' to
take a value between 1 and 64 (inclusive) while taking up only 6 bits, it is
also stored as one less than its true value:
```
15 6 5 0
+------------------------------+------------------+
| ofs | n_bits - 1 |
+------------------------------+------------------+
```
The switch will reject actions for which ofs+n_bits is greater than the
width of 'dst', or in which any bits in 'value' with value 2n_bits or
greater are set to 1, with error type OFPET_BAD_ACTION, code
OFPBAC_BAD_ARGUMENT.
"""
import Bitwise
defstruct(
@ -12,28 +40,37 @@ defmodule Openflow.Action.NxRegLoad do
@nxast 7
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
dst_field = Keyword.get(options, :dst_field)
def new(options \\ []) do
dst_field = options[:dst_field] || raise "dst_field must be specified"
value = options[:value] || raise "value must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(dst_field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
value = Keyword.get(options, :value)
%NxRegLoad{n_bits: n_bits, offset: ofs, dst_field: dst_field, value: value}
%NxRegLoad{
n_bits: options[:n_bits] || default_n_bits,
offset: options[:offset] || 0,
dst_field: dst_field,
value: value
}
end
def to_binary(%NxRegLoad{n_bits: n_bits, offset: ofs, dst_field: dst_field, value: value}) do
dst_field_bin = Openflow.Match.codec_header(dst_field)
value_bin0 = Openflow.Match.Field.codec(value, dst_field)
tmp_value = :binary.decode_unsigned(value_bin0, :big)
value_bin = <<tmp_value::64>>
ofs_nbits = ofs <<< 6 ||| n_bits - 1
body = <<ofs_nbits::16, dst_field_bin::4-bytes, value_bin::bytes>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxRegLoad{} = load) do
ofs_nbits = load.offset <<< 6 ||| load.n_bits - 1
dst_field_bin = Openflow.Match.codec_header(load.dst_field)
value_int =
load.value
|> Openflow.Match.Field.codec(load.dst_field)
|> :binary.decode_unsigned(:big)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
ofs_nbits::16,
dst_field_bin::4-bytes,
value_int::size(8)-unit(8)
>>)
end
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do

View file

@ -1,44 +1,38 @@
defmodule Openflow.Action.NxRegLoad2 do
defstruct(
dst_field: nil,
value: nil
)
defstruct(field: nil)
@experimenter 0x00002320
@nxast 33
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
dst_field = Keyword.get(options, :dst_field)
value = Keyword.get(options, :value)
%NxRegLoad2{dst_field: dst_field, value: value}
def new([{_field, _value}] = field) do
%NxRegLoad2{field: field}
end
def to_binary(%NxRegLoad2{dst_field: dst_field, value: value}) do
def to_binary(%NxRegLoad2{field: field}) do
match_bin =
[{dst_field, value}]
field
|> Openflow.Match.new()
|> Openflow.Match.to_binary()
<<1::16, _length::16, padded_field::bytes>> = match_bin
patial_len = 4 + 4 + 2 + 6 + byte_size(padded_field)
padding = Openflow.Utils.padding(patial_len, 8)
<<1::16, length::16, padded_field::bytes>> = match_bin
field_len = length - 4
<<field_bin::size(field_len)-bytes, _::bytes>> = padded_field
exp_body =
<<@experimenter::32, @nxast::16, 0::48, padded_field::bytes, 0::size(padding)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
field_bin::bytes
>>)
end
def read(<<@experimenter::32, @nxast::16, _::48, match_field_bin::bytes>>) do
def read(<<@experimenter::32, @nxast::16, match_field_bin::bytes>>) do
<<_class::16, _field::7, _hm::1, flen::8, _rest::bytes>> = match_field_bin
match_len = 4 + 4 + flen
match_bin = <<1::16, match_len::16, match_field_bin::bytes, 0::size(4)-unit(8)>>
{[{dst_field, value} | _], _rest} = Openflow.Match.read(match_bin)
%NxRegLoad2{dst_field: dst_field, value: value}
match_bin = <<1::16, match_len::16, match_field_bin::bytes>>
{[field | _], _rest} = Openflow.Match.read(match_bin)
%NxRegLoad2{field: [field]}
end
end

View file

@ -1,4 +1,94 @@
defmodule Openflow.Action.NxRegMove do
@moduledoc """
Copies src[src_ofs:src_ofs+n_bits] to dst[dst_ofs:dst_ofs+n_bits], where
a[b:c] denotes the bits within 'a' numbered 'b' through 'c' (not including
bit 'c'). Bit numbering starts at 0 for the least-significant bit, 1 for
the next most significant bit, and so on.
The following nxm_header values are potentially acceptable as 'src':
- `:in_port`
- `:eth_dst`
- `:eth_src`
- `:eth_type`
- `:vlan_tci`
- `:ip_tos`
- `:ip_proto`
- `:ip_src`
- `:ip_dst`
- `:tcp_src`
- `:tcp_dst`
- `:udp_src`
- `:udp_dst`
- `:icmp_type`
- `:icmp_code`
- `:arp_op`
- `:arp_spa`
- `:arp_tpa`
- `:tun_id`
- `:arp_sha`
- `:arp_tha`
- `:icmpv6_type`
- `:icmpv6_code`
- `:nd_sll`
- `:nd_tll`
- `:reg(idx)` for idx in the switch's accepted range.
- `:pkt_mark`
- `:tun_ipv4_src`
- `:tun_ipv4_dst`
The following nxm_header values are potentially acceptable as 'dst':
- `:eth_dst`
- `:eth_src`
- `:ip_tos`
- `:ip_src`
- `:ip_dst`
- `:tcp_src`
- `:tcp_dst`
- `:udp_src`
- `:udp_dst`
- `:icmp_type`
- `:icmp_code`
- `:icmpv6_type`
- `:icmpv6_code`
- `:arp_sha`
- `:arp_tha`
- `:arp_op`
- `:arp_spa`
- `:arp_tpa`
Modifying any of the above fields changes the corresponding packet header.
- `:in_port`
- `:reg(idx)` for idx in the switch's accepted range.
- `:pkt_mark`
- `:vlan_tci`. Modifying this field's value has side effects on the
packet's 802.1Q header. Setting a value with CFI=0 removes the 802.1Q
header (if any), ignoring the other bits. Setting a value with CFI=1
adds or modifies the 802.1Q header appropriately, setting the TCI field
to the field's new value (with the CFI bit masked out).
- `:tun_id`, `:tun_ipv4_src`, `:tun_ipv4_dst`. Modifying
any of these values modifies the corresponding tunnel header field used
for the packet's next tunnel encapsulation, if allowed by the
configuration of the output tunnel port.
A given nxm_header value may be used as 'src' or 'dst' only on a flow whose
nx_match satisfies its prerequisites. For example, NXM_OF_IP_TOS may be
used only if the flow's nx_match includes an nxm_entry that specifies
nxm_type=NXM_OF_ETH_TYPE, nxm_hasmask=0, and nxm_value=0x0800.
The switch will reject actions for which src_ofs+n_bits is greater than the
width of 'src' or dst_ofs+n_bits is greater than the width of 'dst' with
error type OFPET_BAD_ACTION, code OFPBAC_BAD_ARGUMENT.
This action behaves properly when 'src' overlaps with 'dst', that is, it
behaves as if 'src' were copied out to a temporary buffer, then the
temporary buffer copied to 'dst'.
"""
defstruct(
n_bits: 0,
src_offset: 0,
@ -11,44 +101,80 @@ defmodule Openflow.Action.NxRegMove do
@nxast 6
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
src_field = Keyword.get(options, :src_field)
dst_field = Keyword.get(options, :dst_field)
@type t :: %NxRegMove{
n_bits: 0..0xFFFF,
src_offset: 0..0xFFFF,
dst_offset: 0..0xFFFF,
src_field: atom(),
dst_field: atom()
}
@doc """
Creates a new reg_move action struct
## Options:
- n_bits: Number of bits
- src_offset: Starting bit offset in source
- dst_offset: Starting bit offset in destination
- src_field: oxm/nxm field name for source field
- dst_field: oxm/nxm field name for destination field
## Example
```elixir
# 1. move:NXM_OF_IN_PORT[]->NXM_OF_VLAN_TCI[]
iex> NxRegMove.new(src_field: :nx_in_port, :nx_vlan_tci)
# 2. move:NXM_NX_TUN_ID[40..57]->NXM_NX_REG1[0..17]
iex> NxRegMove.new(
iex> src_field: :tun_id,
iex> src_offset: 40,
iex> dst_field: :reg1
iex> dst_offset: 0,
iex> n_bits: 18
iex> )
```
"""
@spec new(
n_bits: 0..0xFFFF,
src_offset: 0..0xFFFF,
dst_offset: 0..0xFFFF,
src_field: atom(),
dst_field: atom()
) :: t()
def new(options \\ []) do
src_field = options[:src_field] || raise "src_field must be specified"
dst_field = options[:dst_field] || raise "dst_field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(dst_field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
src_ofs = Keyword.get(options, :src_offset, 0)
dst_ofs = Keyword.get(options, :dst_offset, 0)
%NxRegMove{
n_bits: n_bits,
src_offset: src_ofs,
dst_offset: dst_ofs,
n_bits: options[:n_bits] || default_n_bits,
src_offset: options[:src_offset] || 0,
dst_offset: options[:dst_offset] || 0,
src_field: src_field,
dst_field: dst_field
}
end
def to_binary(%NxRegMove{
n_bits: n_bits,
src_offset: src_ofs,
dst_offset: dst_ofs,
src_field: src_field,
dst_field: dst_field
}) do
src_field_bin = Openflow.Match.codec_header(src_field)
dst_field_bin = Openflow.Match.codec_header(dst_field)
@spec to_binary(t()) :: binary()
def to_binary(%NxRegMove{} = move) do
src_field_bin = Openflow.Match.codec_header(move.src_field)
dst_field_bin = Openflow.Match.codec_header(move.dst_field)
body =
<<n_bits::16, src_ofs::16, dst_ofs::16, src_field_bin::4-bytes, dst_field_bin::4-bytes>>
exp_body = <<@experimenter::32, @nxast::16, body::bytes>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
move.n_bits::16,
move.src_offset::16,
move.dst_offset::16,
src_field_bin::4-bytes,
dst_field_bin::4-bytes
>>)
end
@spec read(binary()) :: t()
def read(<<@experimenter::32, @nxast::16, body::bytes>>) do
<<n_bits::16, src_ofs::16, dst_ofs::16, src_field_bin::4-bytes, dst_field_bin::4-bytes>> =
body

View file

@ -1,24 +1,76 @@
defmodule Openflow.Action.NxResubmit do
@moduledoc """
Searches the flow table again, using a flow that is slightly modified from the original lookup:
Following the lookup, the original in_port is restored.
If the modified flow matched in the flow table, then the corresponding
actions are executed. Afterward, actions following NXAST_RESUBMIT in
the original set of actions, if any, are executed; any changes made to
the packet (e.g. changes to VLAN) by secondary actions persist when
those actions are executed, although the original in_port is restored.
"""
defstruct(in_port: :in_port)
@experimenter 0x00002320
@nxast 1
alias __MODULE__
alias Openflow.Action.Experimenter
@type t :: %{in_port: port_no()}
@type port_no ::
:max
| :in_port
| :table
| :normal
| :flood
| :all
| :controller
| :local
| :none
| 1..0xFFFF
@doc """
Creates a new nx_resubmit action struct
## Options:
- in_port: New in_port for checking flow table in the one of the `port_no()` type
## Note:
If the modified flow matchd in the flow table, then the corresponding actions are executed,\\
Afterward, actions following the resubmit in the original set of actions, if any, are executed;\\
any changes made to the packet by secondary actions persist when those actions are executed,
although the original in_port is restored
## Example:
```elixir
iex> %NxResubmit{in_port: :in_port} = NxResubmit.new()
iex> %NxResubmit{in_port: 1} = NxResubmit.new(1)
```
"""
@spec new(port_no()) :: t()
def new(in_port \\ :in_port) do
%NxResubmit{in_port: in_port}
end
@spec to_binary(t()) :: binary()
def to_binary(%NxResubmit{in_port: in_port}) do
in_port_int = Openflow.Utils.get_enum(in_port, :openflow10_port_no)
exp_body = <<@experimenter::32, @nxast::16, in_port_int::16, 0::size(4)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
in_port_int::16,
0::size(4)-unit(8)
>>)
end
@spec read(binary()) :: t()
def read(<<@experimenter::32, @nxast::16, in_port_int::16, _::size(4)-unit(8)>>) do
in_port = Openflow.Utils.get_enum(in_port_int, :openflow10_port_no)
%NxResubmit{in_port: in_port}

View file

@ -5,25 +5,31 @@ defmodule Openflow.Action.NxResubmitTable do
@nxast 14
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options \\ [])
def new(table_id) when is_atom(table_id) or is_integer(table_id) do
new(table_id: table_id)
end
def new(options) do
in_port = Keyword.get(options, :in_port, :in_port)
table_id = Keyword.get(options, :table_id, :all)
in_port = options[:in_port] || :in_port
table_id = options[:table_id] || :all
%NxResubmitTable{in_port: in_port, table_id: table_id}
end
def to_binary(%NxResubmitTable{in_port: in_port, table_id: table_id}) do
in_port_int = Openflow.Utils.get_enum(in_port, :openflow10_port_no)
table_id_int = Openflow.Utils.get_enum(table_id, :table_id)
exp_body = <<@experimenter::32, @nxast::16, in_port_int::16, table_id_int::8, 0::24>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxResubmitTable{} = resubmit_table) do
in_port_int = Openflow.Utils.get_enum(resubmit_table.in_port, :openflow10_port_no)
table_id_int = Openflow.Utils.get_enum(resubmit_table.table_id, :table_id)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
in_port_int::16,
table_id_int::8,
0::24
>>)
end
def read(

View file

@ -5,21 +5,31 @@ defmodule Openflow.Action.NxResubmitTableCt do
@nxast 44
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
in_port = Keyword.get(options, :in_port, :in_port)
table_id = Keyword.get(options, :table_id, :all)
def new(options \\ [])
def new(table_id) when is_atom(table_id) or is_integer(table_id) do
new(table_id: table_id)
end
def new(options) when is_list(options) do
in_port = options[:in_port] || :in_port
table_id = options[:table_id] || :all
%NxResubmitTableCt{in_port: in_port, table_id: table_id}
end
def to_binary(%NxResubmitTableCt{in_port: in_port, table_id: table_id}) do
in_port_int = Openflow.Utils.get_enum(in_port, :openflow10_port_no)
table_id_int = Openflow.Utils.get_enum(table_id, :table_id)
exp_body = <<@experimenter::32, @nxast::16, in_port_int::16, table_id_int::8, 0::24>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxResubmitTableCt{} = resubmit_table) do
in_port_int = Openflow.Utils.get_enum(resubmit_table.in_port, :openflow10_port_no)
table_id_int = Openflow.Utils.get_enum(resubmit_table.table_id, :table_id)
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
in_port_int::16,
table_id_int::8,
0::24
>>)
end
def read(

View file

@ -10,35 +10,40 @@ defmodule Openflow.Action.NxSample do
@nxast 29
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(
probability: 1..0xFFFF,
collector_set_id: 0..0xFFFFFFFF,
obs_domain_id: 0..0xFFFFFFFF,
obs_point_id: 0..0xFFFFFFFF
) :: %NxSample{
probability: 1..0xFFFF,
collector_set_id: 0..0xFFFFFFFF,
obs_domain_id: 0..0xFFFFFFFF,
obs_point_id: 0..0xFFFFFFFF
}
def new(options) do
probability = Keyword.get(options, :probability, 0)
collector_set_id = Keyword.get(options, :collector_set_id, 0)
obs_domain_id = Keyword.get(options, :obs_domain_id, 0)
obs_point_id = Keyword.get(options, :obs_point_id, 0)
(is_integer(options[:probability]) and options[:probability] > 0) ||
raise("probability must be greater than 0")
%NxSample{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id
probability: options[:probability],
collector_set_id: options[:collector_set_id] || 0,
obs_domain_id: options[:obs_domain_id] || 0,
obs_point_id: options[:obs_point_id] || 0
}
end
def to_binary(%NxSample{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id
}) do
exp_body =
<<@experimenter::32, @nxast::16, probability::16, collector_set_id::32, obs_domain_id::32,
obs_point_id::32>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxSample{} = sample) do
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
sample.probability::16,
sample.collector_set_id::32,
sample.obs_domain_id::32,
sample.obs_point_id::32
>>)
end
def read(

View file

@ -11,44 +11,44 @@ defmodule Openflow.Action.NxSample2 do
@nxast 38
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
probability = Keyword.get(options, :probability, 0)
collector_set_id = Keyword.get(options, :collector_set_id, 0)
obs_domain_id = Keyword.get(options, :obs_domain_id, 0)
obs_point_id = Keyword.get(options, :obs_point_id, 0)
sampling_port = Keyword.get(options, :sampling_port, 0)
def new(options \\ []) do
(is_integer(options[:probability]) and options[:probability] > 0) ||
raise("probability must be greater than 0")
%NxSample2{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id,
sampling_port: sampling_port
probability: options[:probability],
collector_set_id: options[:collector_set_id] || 0,
obs_domain_id: options[:obs_domain_id] || 0,
obs_point_id: options[:obs_point_id] || 0,
sampling_port: options[:sampling_port] || 0
}
end
def to_binary(%NxSample2{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id,
sampling_port: sampling_port
}) do
exp_body =
<<@experimenter::32, @nxast::16, probability::16, collector_set_id::32, obs_domain_id::32,
obs_point_id::32, sampling_port::16, 0::size(6)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
def to_binary(%NxSample2{} = sample) do
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
sample.probability::16,
sample.collector_set_id::32,
sample.obs_domain_id::32,
sample.obs_point_id::32,
sample.sampling_port::16,
0::size(6)-unit(8)
>>)
end
def read(
<<@experimenter::32, @nxast::16, probability::16, collector_set_id::32, obs_domain_id::32,
obs_point_id::32, sampling_port::16, 0::size(6)-unit(8)>>
) do
def read(<<
@experimenter::32,
@nxast::16,
probability::16,
collector_set_id::32,
obs_domain_id::32,
obs_point_id::32,
sampling_port::16,
_::size(6)-unit(8)
>>) do
%NxSample2{
probability: probability,
collector_set_id: collector_set_id,

View file

@ -12,43 +12,36 @@ defmodule Openflow.Action.NxSample3 do
@nxast 41
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
probability = Keyword.get(options, :probability, 0)
collector_set_id = Keyword.get(options, :collector_set_id, 0)
obs_domain_id = Keyword.get(options, :obs_domain_id, 0)
obs_point_id = Keyword.get(options, :obs_point_id, 0)
sampling_port = Keyword.get(options, :sampling_port, 0)
direction = Keyword.get(options, :direction, :default)
def new(options \\ []) do
(is_integer(options[:probability]) and options[:probability] > 0) ||
raise("probability must be greater than 0")
%NxSample3{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id,
sampling_port: sampling_port,
direction: direction
probability: options[:probability] || 0,
collector_set_id: options[:collector_set_id] || 0,
obs_domain_id: options[:obs_domain_id] || 0,
obs_point_id: options[:obs_point_id] || 0,
sampling_port: options[:sampling_port] || 0,
direction: options[:direction] || :default
}
end
def to_binary(%NxSample3{
probability: probability,
collector_set_id: collector_set_id,
obs_domain_id: obs_domain_id,
obs_point_id: obs_point_id,
sampling_port: sampling_port,
direction: direction
}) do
direction_int = Openflow.Enums.to_int(direction, :nx_action_sample_direction)
def to_binary(%NxSample3{} = sample) do
sample_direction_int = Openflow.Enums.to_int(sample.direction, :nx_action_sample_direction)
exp_body =
<<@experimenter::32, @nxast::16, probability::16, collector_set_id::32, obs_domain_id::32,
obs_point_id::32, sampling_port::16, direction_int::8, 0::size(5)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
sample.probability::16,
sample.collector_set_id::32,
sample.obs_domain_id::32,
sample.obs_point_id::32,
sample.sampling_port::16,
sample_direction_int::8,
0::size(5)-unit(8)
>>)
end
def read(

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxSetMplsLabel do
defstruct(label: 0)
@experimenter 0x00002320
@nxast 30
alias __MODULE__
def new(label) do
%NxSetMplsLabel{label: label}
end
def to_binary(%NxSetMplsLabel{label: label}) do
exp_body = <<@experimenter::32, @nxast::16, 0::16, label::32>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, _::16, label::32>>) do
%NxSetMplsLabel{label: label}
end
end

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxSetMplsTc do
defstruct(tc: 0)
@experimenter 0x00002320
@nxast 31
alias __MODULE__
def new(tc) do
%NxSetMplsTc{tc: tc}
end
def to_binary(%NxSetMplsTc{tc: tc}) do
exp_body = <<@experimenter::32, @nxast::16, tc::8, 0::size(5)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, tc::8, _::size(5)-unit(8)>>) do
%NxSetMplsTc{tc: tc}
end
end

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxSetMplsTtl do
defstruct(ttl: 0)
@experimenter 0x00002320
@nxast 25
alias __MODULE__
def new(ttl) do
%NxSetMplsTtl{ttl: ttl}
end
def to_binary(%NxSetMplsTtl{ttl: ttl}) do
exp_body = <<@experimenter::32, @nxast::16, ttl::8, 0::size(5)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, ttl::8, _::size(5)-unit(8)>>) do
%NxSetMplsTtl{ttl: ttl}
end
end

View file

@ -1,24 +0,0 @@
defmodule Openflow.Action.NxSetQueue do
defstruct(queue_id: 0)
@experimenter 0x00002320
@nxast 4
alias __MODULE__
def new(queue_id) do
%NxSetQueue{queue_id: queue_id}
end
def to_binary(%NxSetQueue{queue_id: queue_id}) do
exp_body = <<@experimenter::32, @nxast::16, 0::16, queue_id::32>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
end
def read(<<@experimenter::32, @nxast::16, 0::size(2)-unit(8), queue_id::32>>) do
%NxSetQueue{queue_id: queue_id}
end
end

View file

@ -1,23 +1,43 @@
defmodule Openflow.Action.NxSetTunnel do
@moduledoc """
Set encapsulating tunnel ID.
"""
defstruct(tunnel_id: 0)
@experimenter 0x00002320
@nxast 2
alias __MODULE__
alias Openflow.Action.Experimenter
@type tunnel_id :: 0..0xFFFFFFFF
@type t :: %NxSetTunnel{tunnel_id: tunnel_id()}
@doc """
Creates a new set_tunnel action struct
## Example:
```elixir
iex> %NxSetTunnel{tunnel_id: 1} = NxSetTunnel.new(1)
```
"""
@spec new(tunnel_id()) :: t()
def new(tunnel_id) do
%NxSetTunnel{tunnel_id: tunnel_id}
end
@spec to_binary(t()) :: binary()
def to_binary(%NxSetTunnel{tunnel_id: tunnel_id}) do
exp_body = <<@experimenter::32, @nxast::16, 0::16, tunnel_id::32>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::16,
tunnel_id::32
>>)
end
@spec read(binary()) :: t()
def read(<<@experimenter::32, @nxast::16, _::16, tunnel_id::32>>) do
%NxSetTunnel{tunnel_id: tunnel_id}
end

View file

@ -5,17 +5,22 @@ defmodule Openflow.Action.NxSetTunnel64 do
@nxast 9
alias __MODULE__
alias Openflow.Action.Experimenter
@spec new(tunnel_id :: 0..0xFFFFFFFFFFFFFFFF) :: %NxSetTunnel64{
tunnel_id: 0..0xFFFFFFFFFFFFFFFF
}
def new(tunnel_id) do
%NxSetTunnel64{tunnel_id: tunnel_id}
end
def to_binary(%NxSetTunnel64{tunnel_id: tunnel_id}) do
exp_body = <<@experimenter::32, @nxast::16, 0::size(6)-unit(8), tunnel_id::64>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::size(6)-unit(8),
tunnel_id::64
>>)
end
def read(<<@experimenter::32, @nxast::16, 0::size(6)-unit(8), tunnel_id::64>>) do

View file

@ -9,26 +9,30 @@ defmodule Openflow.Action.NxStackPop do
@nxast 28
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
field = Keyword.get(options, :field)
field = options[:field] || raise "field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
%NxStackPop{n_bits: n_bits, offset: ofs, field: field}
%NxStackPop{
n_bits: options[:n_bits] || default_n_bits,
offset: options[:offset] || 0,
field: field
}
end
def to_binary(%NxStackPop{n_bits: n_bits, offset: ofs, field: field}) do
field_bin = Openflow.Match.codec_header(field)
def to_binary(%NxStackPop{} = stack_pop) do
field_bin = Openflow.Match.codec_header(stack_pop.field)
exp_body =
<<@experimenter::32, @nxast::16, ofs::16, field_bin::4-bytes, n_bits::16,
0::size(6)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
stack_pop.offset::16,
field_bin::4-bytes,
stack_pop.n_bits::16,
0::size(6)-unit(8)
>>)
end
def read(

View file

@ -9,26 +9,30 @@ defmodule Openflow.Action.NxStackPush do
@nxast 27
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options) do
field = Keyword.get(options, :field)
field = options[:field] || raise "field must be specified"
default_n_bits = Openflow.Match.Field.n_bits_of(field)
n_bits = Keyword.get(options, :n_bits, default_n_bits)
ofs = Keyword.get(options, :offset, 0)
%NxStackPush{n_bits: n_bits, offset: ofs, field: field}
%NxStackPush{
n_bits: options[:n_bits] || default_n_bits,
offset: options[:offset] || 0,
field: field
}
end
def to_binary(%NxStackPush{n_bits: n_bits, offset: ofs, field: field}) do
field_bin = Openflow.Match.codec_header(field)
def to_binary(%NxStackPush{} = stack_pop) do
field_bin = Openflow.Match.codec_header(stack_pop.field)
exp_body =
<<@experimenter::32, @nxast::16, ofs::16, field_bin::4-bytes, n_bits::16,
0::size(6)-unit(8)>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
stack_pop.offset::16,
field_bin::4-bytes,
stack_pop.n_bits::16,
0::size(6)-unit(8)
>>)
end
def read(

View file

@ -8,25 +8,28 @@ defmodule Openflow.Action.NxWriteMetadata do
@nxast 22
alias __MODULE__
alias Openflow.Action.Experimenter
def new(options \\ [])
def new(metadata) when is_integer(metadata) do
new(metadata: metadata)
end
def new(options) when is_list(options) do
metadata = Keyword.get(options, :metadata, 0)
metadata_mask = Keyword.get(options, :metadata_mask, 0xFFFFFFFFFFFFFFFF)
metadata = options[:metadata] || raise "metadata must be specified"
metadata_mask = options[:metadata_mask] || 0xFFFFFFFFFFFFFFFF
%NxWriteMetadata{metadata: metadata, metadata_mask: metadata_mask}
end
def to_binary(%NxWriteMetadata{metadata: metadata, metadata_mask: metadata_mask}) do
exp_body =
<<@experimenter::32, @nxast::16, 0::size(6)-unit(8), metadata::64, metadata_mask::64>>
exp_body_size = byte_size(exp_body)
padding_length = Openflow.Utils.padding(4 + exp_body_size, 8)
length = 4 + exp_body_size + padding_length
<<0xFFFF::16, length::16, exp_body::bytes, 0::size(padding_length)-unit(8)>>
Experimenter.pack_exp_header(<<
@experimenter::32,
@nxast::16,
0::size(6)-unit(8),
metadata::64,
metadata_mask::64
>>)
end
def read(<<@experimenter::32, @nxast::16, _::size(6)-unit(8), metadata::64, metadata_mask::64>>) do

View file

@ -1,29 +1,67 @@
defmodule Openflow.Action.Output do
defstruct(
port_number: 0,
max_len: :no_buffer
)
@moduledoc """
Action for sends packets out `port_number`.
"""
defstruct port_number: 0,
max_len: :no_buffer
alias __MODULE__
@type port_no ::
0..0xFFFFFFFF
| :max
| :in_port
| :table
| :normal
| :flood
| :all
| :controller
| :local
| :any
@type max_len :: 0..0xFFFFFFFF | :no_buffer | :max
@type t :: %Output{port_number: port_no(), max_len: max_len()}
@spec ofpat() :: 0
def ofpat, do: 0
def new(port) when not is_list(port) do
new(port_number: port)
end
@doc """
Create a new output action structure
## Options:
- `port_number`: Output port
- `max_len`: Max length to send to controller
## Example
```elixir
iex> %Output{port_number: 1, max_len: :no_buffer} = Output.new(1)
```
"""
@spec new([port_number: port_no, max_len: max_len()] | port_no()) :: Output.t()
def new(options \\ [])
@spec new(port :: port_no()) :: Output.t()
def new(port) when is_atom(port) or is_integer(port),
do: new(port_number: port)
@spec new(options :: [port_number: port_no(), max_len: max_len()]) :: Output.t()
def new(options) when is_list(options) do
port_no = Keyword.get(options, :port_number)
max_len = Keyword.get(options, :max_len, :no_buffer)
port_no = options[:port_number] || raise "port_number must be specified"
max_len = options[:max_len] || :no_buffer
%Output{port_number: port_no, max_len: max_len}
end
@spec to_binary(Output.t()) :: <<_::16, _::_*8>>
def to_binary(%Output{port_number: port_no, max_len: max_len}) do
port_no_int = Openflow.Utils.get_enum(port_no, :openflow13_port_no)
max_len = Openflow.Utils.get_enum(max_len, :controller_max_len)
<<0::16, 16::16, port_no_int::32, max_len::16, 0::size(6)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: Output.t()
def read(<<0::16, 16::16, port_no_int::32, max_len::16, _pad::size(6)-unit(8)>>) do
port_no = Openflow.Utils.get_enum(port_no_int, :openflow13_port_no)
max_len = Openflow.Utils.get_enum(max_len, :controller_max_len)

View file

@ -1,18 +1,42 @@
defmodule Openflow.Action.PopMpls do
@moduledoc """
Pop the out MPLS label
note: The one of ETH__P_MPLS_* is needed to be specified to eth_type field
"""
defstruct(ethertype: 0x8847)
alias __MODULE__
@type t :: %PopMpls{}
@eth_p_mpls_uc 0x8847
@spec ofpat() :: 20
def ofpat, do: 20
def new(ethertype) do
@doc """
Create a new pop_mpls action struct
note: 0x8847(ETH_P_MPLS_UC) as default value.
```elixir
iex> %PopMpls{ethertype: 0x8847} = PopMpls.new()
```
"""
@spec new() :: t()
@spec new(ethertype :: 0..0xFFFF) :: t()
def new(ethertype \\ @eth_p_mpls_uc) do
%PopMpls{ethertype: ethertype}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%PopMpls{ethertype: ethertype}) do
<<20::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<20::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>) do
%PopMpls{ethertype: ethertype}
end

View file

@ -1,18 +1,35 @@
defmodule Openflow.Action.PopVlan do
@moduledoc """
Pop the outer VLAN tag.
"""
defstruct([])
alias __MODULE__
@type t() :: %PopVlan{}
@spec ofpat() :: 18
def ofpat, do: 18
@doc """
Create a new pop_vlan action struct
````elixir
iex> %PopVlan{} = PopVlan.new()
````
"""
@spec new() :: t()
def new do
%PopVlan{}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%PopVlan{}) do
<<18::16, 8::16, 0::size(4)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<18::16, 8::16, _::size(4)-unit(8)>>) do
%PopVlan{}
end

View file

@ -1,18 +1,37 @@
defmodule Openflow.Action.PushMpls do
@moduledoc """
Push a new MPLS label
"""
defstruct(ethertype: 0x8847)
@eth_p_mpls_uc 0x8847
alias __MODULE__
@type t :: %PushMpls{ethertype: 0..0xFFFF}
@spec ofpat() :: 19
def ofpat, do: 19
def new(ethertype) do
@doc """
Create a new push_mpls action struct\\
0x8847(ETH_P_MPLS_UC) as a default value.
iex> %PushMpls{ethertype: 0x8847} = PushMpls.new()
"""
@spec new() :: t()
@spec new(ethertype :: 0..0xFFFF) :: t()
def new(ethertype \\ @eth_p_mpls_uc) do
%PushMpls{ethertype: ethertype}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%PushMpls{ethertype: ethertype}) do
<<19::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<19::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>) do
%PushMpls{ethertype: ethertype}
end

View file

@ -1,18 +1,34 @@
defmodule Openflow.Action.PushVlan do
@moduledoc """
Push a new VLAN tag
"""
defstruct(ethertype: 0x8100)
alias __MODULE__
@type t :: %PushVlan{ethertype: 0..0xFFFF}
@spec ofpat() :: 17
def ofpat, do: 17
@doc """
Create a new push_vlan action struct.\\
0x8100 is a default value.
iex> %PushVlan{ethertype: 0x8100} = PushVlan.new()
"""
@spec new() :: t()
@spec new(ethertype :: 0..0xFFFF) :: t()
def new(ethertype \\ 0x8100) do
%PushVlan{ethertype: ethertype}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%PushVlan{ethertype: ethertype}) do
<<17::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<17::16, 8::16, ethertype::16, 0::size(2)-unit(8)>>) do
%PushVlan{ethertype: ethertype}
end

View file

@ -1,19 +1,81 @@
defmodule Openflow.Action.SetField do
@moduledoc """
Set a header field using OXM TLV format.
"""
defstruct(field: nil)
alias __MODULE__
@type t :: %SetField{field: Keyword.t()}
@set_field_size 8
def ofpat, do: 25
def new({_field, _value} = oxm_field) do
@doc """
Create a new set_field action struct
note: The following oxm(nxm)_header values are potentially acceptable as `field`:
- :tun_id
- :tun_ipv4_src
- :tun_ipv4_dst
- :tun_ipv6_src
- :tun_ipv6_dst
- :tun_flags
- :tun_gbp_id
- :tun_gbp_flags
- :tun_metadata{0..63}
- :in_port
- :pkt_mark
- :ct_mark
- :ct_label
- :reg{0..15}
- :xreg{0..8}
- :xxreg{0..4}
- :eth_src
- :eth_dst
- :vlan_tci
- :mpls_ttl
- :ip_src
- :ip_dst
- :ipv6_src
- :ipv6_dst
- :ipv6_label
- :ip_tos
- :ip_ecn
- :ip_ttl
- :arp_op
- :arp_spa
- :arp_tpa
- :arp_sha
- :arp_tha
- :tcp_src
- :tcp_dst
- :udp_src
- :udp_dst
- :icmp_type
- :icmp_code
- :icmpv6_type
- :icmpv6_code
- :nd_target
- :nd_sll
- :nd_tll
- :metadata
```elixir
iex> %SetField{field: [reg1: 10]} = SetField.new(reg1: 10)
```
"""
@spec new(Keyword.t()) :: t()
def new([{_field, _value}] = oxm_field) do
%SetField{field: oxm_field}
end
def to_binary(%SetField{field: {field, value}}) do
def to_binary(%SetField{field: field}) do
match_bin =
[{field, value}]
field
|> Openflow.Match.new()
|> Openflow.Match.to_binary()
@ -29,6 +91,6 @@ defmodule Openflow.Action.SetField do
match_len = 4 + 4 + flen
match_bin = <<1::16, match_len::16, match_field_bin::bytes>>
{[field | _], _rest} = Openflow.Match.read(match_bin)
%SetField{field: field}
%SetField{field: [field]}
end
end

View file

@ -1,18 +1,38 @@
defmodule Openflow.Action.SetMplsTtl do
@moduledoc """
Replace the existing MPLS TTL.\\
Only applies to the packets with an existing MPLS shim header.
"""
defstruct(ttl: 0)
alias __MODULE__
@type t :: %SetMplsTtl{ttl: 0..0xFF}
@spec ofpat() :: 15
def ofpat, do: 15
@doc """
Create a set_mpls_ttl action structure
## Example:
```elixir
iex> %SetMplsTtl{ttl: 64} = Openflow.Action.SetMplsTtl.new(64)
```
"""
@deprecated "OFPAT_SET_MPLS_TTL is deprecated in OpenFlow13, use SetField"
@spec new(0..0xFF) :: SetMplsTtl.t()
def new(ttl) do
%SetMplsTtl{ttl: ttl}
end
@spec to_binary(SetMplsTtl.t()) :: <<_::16, _::_*8>>
def to_binary(%SetMplsTtl{ttl: ttl}) do
<<15::16, 8::16, ttl::8, 0::size(3)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: SetMplsTtl.t()
def read(<<15::16, 8::16, ttl::8, _::size(3)-unit(8)>>) do
%SetMplsTtl{ttl: ttl}
end

View file

@ -1,18 +1,38 @@
defmodule Openflow.Action.SetNwTtl do
@moduledoc """
Set IP TTL
"""
defstruct(ttl: 0)
alias __MODULE__
@type t :: %SetNwTtl{ttl: 0..0xFF}
@spec ofpat() :: 23
def ofpat, do: 23
@doc """
Create a new set_nw_ttl action struct
## Options:
- IP TTL
```elixir
iex> %SetNwTtl{ttl: 64} = SetNwTtl.new(_ttl = 64)
```
"""
@spec new(ttl :: 0..0xFF) :: t()
def new(ttl) do
%SetNwTtl{ttl: ttl}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%SetNwTtl{ttl: ttl}) do
<<23::16, 8::16, ttl::8, 0::size(3)-unit(8)>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<23::16, 8::16, ttl::8, _::size(3)-unit(8)>>) do
%SetNwTtl{ttl: ttl}
end

View file

@ -1,18 +1,38 @@
defmodule Openflow.Action.SetQueue do
@moduledoc """
Set queue id when outputting to a port
"""
defstruct(id: 0)
alias __MODULE__
@type t :: %SetQueue{id: 0..0xFFFFFFFF}
@spec ofpat() :: 21
def ofpat, do: 21
@doc """
Create a new set_queue action struct
## Options:
- Queue id for the packets
```elixir
iex> %SetQueue{id: 1} = SetQueue.new(_id = 1)
```
"""
@spec new(id :: 0..0xFFFFFFFF) :: t()
def new(id) do
%SetQueue{id: id}
end
@spec to_binary(t()) :: <<_::16, _::_*8>>
def to_binary(%SetQueue{id: id}) do
<<21::16, 8::16, id::32>>
end
@spec read(<<_::16, _::_*8>>) :: t()
def read(<<21::16, 8::16, id::32>>) do
%SetQueue{id: id}
end

View file

@ -12,15 +12,9 @@ defmodule Openflow.Barrier.Reply do
def ofp_type, do: 21
def new do
%Reply{}
end
def new(xid \\ 0), do: %Reply{xid: xid}
def read(_) do
%Reply{}
end
def read(_), do: %Reply{}
def to_binary(%Reply{}) do
<<>>
end
def to_binary(%Reply{}), do: <<>>
end

View file

@ -10,17 +10,22 @@ defmodule Openflow.Barrier.Request do
alias __MODULE__
@type t :: %Request{
version: 4,
xid: 0..0xFFFFFFFF,
datapath_id: String.t() | nil,
aux_id: non_neg_integer() | nil
}
@spec ofp_type() :: 20
def ofp_type, do: 20
def new(xid \\ 0) do
%Request{xid: xid}
end
@spec new(xid :: 0..0xFFFFFFFF) :: t()
def new(xid \\ 0), do: %Request{xid: xid}
def read(_) do
%Request{}
end
@spec read(binary()) :: t()
def read(_), do: %Request{}
def to_binary(%Request{}) do
<<>>
end
@spec to_binary(t()) :: binary()
def to_binary(%Request{}), do: <<>>
end

Some files were not shown because too many files have changed in this diff Show more