Commit 0d2ea98f authored by Nawasan Wisitsingkhon's avatar Nawasan Wisitsingkhon

read code

parents
target
Cargo.lock
*.log
language: rust
rust:
- stable
- beta
- nightly
after_success: |
[ "$TRAVIS_RUST_VERSION" = stable ] &&
[ "$TRAVIS_BRANCH" = master ] &&
[ "$TRAVIS_PULL_REQUEST" = false ] &&
cargo doc &&
echo "<meta http-equiv=refresh content=0;url=`echo $TRAVIS_REPO_SLUG | cut -d '/' -f 2`/index.html>" > target/doc/index.html &&
curl https://raw.githubusercontent.com/kmcallister/travis-doc-upload/master/travis-doc-upload.sh | sh
[package]
name = "rust_ofp"
version = "0.2.1"
authors = ["Sam Baxter <sbaxter@cs.umass.edu>"]
description = "Rust OpenFlow 0x01 Protocol and Controller Framework"
documentation = "https://baxtersa.github.io/rust_ofp/docs"
repository = "https://github.com/baxtersa/rust_ofp"
readme = "README.md"
keywords = ["SDN", "Networking", "OpenFlow"]
license = "MIT"
exclude = [
"scripts/*",
".travis.yml",
".gitignore",
]
[lib]
name = "rust_ofp"
path = "src/lib.rs"
[[bin]]
name = "rust_ofp_controller"
path = "src/main.rs"
[dependencies]
byteorder = "1.0.0"
hex-literal = "0.4.1"
md5 = "0.7.0"
MIT License
Copyright (c) 2016 Sam Baxter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
rust_ofp [![Build Status](https://travis-ci.org/baxtersa/rust_ofp.svg?branch=master)](https://travis-ci.org/baxtersa/rust_ofp) [![](http://meritbadge.herokuapp.com/rust_ofp)](https://crates.io/crates/rust_ofp)
===
OpenFlow 1.0 protocol and controller in Rust.
---
`rust_ofp` aims to implement the OpenFlow1.0 protocol, for purposes of prototyping SDN systems in Rust. In the future, this may grow to support other OpenFlow specifications (namely 1.3), and others protocols entirely.
I'm drawing heavily on inspiration and code structure from the [frenetic-lang](https://github.com/frenetic-lang) project, due to my familiarity with it. I hope that Rust will enable a more natural implementation of the low-level protocol than OCaml + CStructs, and true parallelism will allow for higher controller performance and a simpler event loop.
See my blog post on the crate [here](http://baxtersa.github.io/2016/12/30/rust-openflow-0x01.html)!
Building
---
`rust_ofp` is composed of a Rust library implementing the OpenFlow 1.0 protocol, and a `rust_ofp_controller` binary that currently acts as little more than an echo server for SDN configurations. It can be built and run by the normal means for Rust projects.
```bash
cd path/to/rust_ofp
cargo build
cargo run
```
Testing
---
I'm performing all correctness evaluation in [mininet](https://mininet.org) for the time being. Mininet offers quick feedback, as much scalability as I need for now, and should properly support OpenFlow 1.0 (and other protocols). There is no reason correctness in mininet shouldn't transfer to physical hardware as well, and maybe one day I'll get around to testing out that hypothesis.
Anyway, testing the controller binary is pretty straightforward, assuming mininet is installed.
In one terminal
```bash
cd path/to/rust_ofp
cargo run
```
In another terminal
```bash
sudo mn --controller=remote
```
The terminal running `rust_ofp_controller` will occasionally print some things out, logging a rough idea of the types of messages it receives, and behaviors it performs.
The mininet terminal should launch an interactive shell with the typical mininet utilities.
Currently, the test executable for `rust_ofp` implements MAC learning, so as hosts ping eachother, the controller installs forwarding rules on switches rather than routing all packets through the controller.
Documentation
---
Travis CI automatically uploads source documentation generated by `cargo doc`.
- [`rust_ofp`](https://baxtersa.github.io/rust_ofp/docs)
- [`rust_ofp_controller`](https://baxtersa.github.io/rust_ofp/docs/rust_ofp_controller)
ToDo
---
Some parts of the OpenFlow 1.0 standard remain unimplemented. Notably, `rust_ofp` does not currently implement the following message codes:
- `OFPT_VENDOR`
- `OFPT_GET_CONFIG_REQUEST/OFPT_GET_CONFIG_REPLY`
- `OFPT_SET_CONFIG`
- `OFPT_PORT_MOD`
- `OFPT_STATS_REQUEST/OFPT_STATS_REPLY`
- `OFPT_QUEUE_GET_CONFIG_REQUEST/OFPT_QUEUE_GET_CONFIG_REPLY`
The current controller executable is a minimal wrapper around the protocol that doesn't dynamically handle any rule configuration. Future goals for the controller include:
- A GTK GUI for configuring and querying an SDN dynamically.
- A compiler from some higher-level abstraction to install a forwarding policy other than a global `DROP` on launch.
- [Consistent Updates](http://www.cs.cornell.edu/~jnfoster/papers/frenetic-consistent-updates.pdf) for dynamically updating an SDN's global forwarding policy.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
PROJECT_NAME=docs
DOCS_REPO=baxtersa/rust_ofp.git
SSH_KEY_TRAVIS_ID=6b1a22a7fe81
\ No newline at end of file
/// Set bit `bit` of `x` on if `toggle` is true, otherwise off.
pub fn bit(bit: u64, x: u64, toggle: bool) -> u64 {
if toggle {
x | (1 << bit)
} else {
x & !(1 << bit)
}
}
/// Test whether bit `bit` of `x` is set.
pub fn test_bit(bit: u64, x: u64) -> bool {
(x >> bit) & 1 == 1
}
use std::collections::HashMap;
use std::net::TcpStream;
use rust_ofp::ofp_controller::openflow0x01::OF0x01Controller;
use rust_ofp::openflow0x01::{Action, PacketIn, PacketOut, Pattern, PseudoPort, SwitchFeatures};
use rust_ofp::openflow0x01::message::{add_flow, parse_payload};
/// Implements L2 learning switch functionality. Switches forward packets to the
/// learning controller, which will examine the packet and learn the source-port
/// mapping. If the controller already knows the destination location, it pushes
/// a flow entry down to the switch that matches traffic between the packet's
/// source and destination.
///
/// Abstractly, a learning switch can be thought of in terms of two logically
/// distinct components.
///
/// - A _Learning Module_ that builds a map from host MAC addresses to the
/// switch port on which they are connected.
///
/// - A _Routing Module_ that performs traffic routing. If the switch receives
/// a packet which the learning module has learned of the destination location,
/// it forwards the packet directly on the associated port. If the location of
/// the destination is unknown, it floods the packet out all ports.
pub struct LearningSwitch {
known_hosts: HashMap<u64, u16>,
}
impl LearningSwitch {
fn learning_packet_in(&mut self, pkt: &PacketIn) {
let pk = parse_payload(&pkt.input_payload);
self.known_hosts.insert(pk.dl_src, pkt.port);
}
fn routing_packet_in(&mut self, sw: u64, pkt: PacketIn, stream: &mut TcpStream) {
let pk = parse_payload(&pkt.input_payload);
let pkt_dst = pk.dl_dst;
let pkt_src = pk.dl_src;
// ดึง port ที่เก็บไว้ โดยใส่ mac ปลายทาง
let out_port = self.known_hosts.get(&pkt_dst);
match out_port {
Some(p) => {
let src_port = pkt.port;
let mut src_dst_match = Pattern::match_all();
src_dst_match.dl_dst = Some(pkt_dst);
src_dst_match.dl_src = Some(pkt_src);
let mut dst_src_match = Pattern::match_all();
dst_src_match.dl_dst = Some(pkt_src);
dst_src_match.dl_src = Some(pkt_dst);
println!("Installing rule for host {:?} to {:?}.", pkt_src, pkt_dst);
let actions = vec![Action::Output(PseudoPort::PhysicalPort(*p))];
Self::send_flow_mod(sw, 0, add_flow(10, src_dst_match, actions), stream);
println!("Installing rule for host {:?} to {:?}.", pkt_dst, pkt_src);
let actions = vec![Action::Output(PseudoPort::PhysicalPort(src_port))];
Self::send_flow_mod(sw, 0, add_flow(10, dst_src_match, actions), stream);
let pkt_out = PacketOut {
output_payload: pkt.input_payload,
port_id: None,
apply_actions: vec![Action::Output(PseudoPort::PhysicalPort(*p))],
};
Self::send_packet_out(sw, 0, pkt_out, stream)
}
None => {
println!("Flooding to {:?}", pkt_dst);
let pkt_out = PacketOut {
output_payload: pkt.input_payload,
port_id: None,
apply_actions: vec![Action::Output(PseudoPort::AllPorts)],
};
Self::send_packet_out(sw, 0, pkt_out, stream)
}
}
}
}
impl OF0x01Controller for LearningSwitch {
fn new() -> LearningSwitch {
LearningSwitch { known_hosts: HashMap::new() }
}
fn switch_connected(&mut self, _: u64, _: SwitchFeatures, _: &mut TcpStream) {}
fn switch_disconnected(&mut self, _: u64) {}
fn packet_in(&mut self, sw: u64, _: u32, pkt: PacketIn, stream: &mut TcpStream) {
self.learning_packet_in(&pkt);
self.routing_packet_in(sw, pkt, stream);
}
}
#![crate_name = "rust_ofp"]
#![crate_type = "lib"]
extern crate byteorder;
pub mod learning_switch;
mod bits;
pub mod ofp_controller;
pub mod ofp_header;
pub mod ofp_message;
pub mod openflow0x01;
pub mod packet;
mod rust_ofp {
pub use super::*;
}
use std::io::{BufRead, Cursor, Error, Read, Write};
use std::net::{TcpListener, TcpStream};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use rust_ofp::learning_switch::LearningSwitch;
use rust_ofp::ofp_controller::OfpController;
extern crate byteorder;
extern crate rust_ofp;
struct packetIn {
buf_id: Option<i32>,
payload: Vec<u8>,
total_len: u16,
port: u16,
reason: u8,
}
fn packetin_payload(buf:&mut Vec<u8>) -> Result<packetIn, Error> {
let mut packet = Cursor::new(buf.to_vec());
let buf_id = match packet.read_i32::<BigEndian>()? {
-1 => None,
n => Some(n),
};
let total_len = packet.read_u16::<BigEndian>()?;
let port = packet.read_u16::<BigEndian>()?;
let reason = packet.read_u8()?;
packet.consume(1);
let payload = packet.fill_buf()?.to_vec();
Ok(packetIn {
buf_id: buf_id,
payload: payload,
port: port,
reason: reason,
total_len: total_len,
})
}
fn send_hello(stream: &mut TcpStream) {
let mut buf: Vec<u8> = Vec::new();
buf.write_u8(1).unwrap();
buf.write_u8(0).unwrap();
buf.write_u16::<BigEndian>(8).unwrap();
buf.write_u32::<BigEndian>(0).unwrap();
println!("send: {:?}", buf);
stream.write_all(&buf).unwrap();
}
fn feture_req(stream: &mut TcpStream, xid: u32) -> Result<(), std::io::Error> {
let mut buf: Vec<u8> = Vec::new();
buf.write_u8(1)?;
buf.write_u8(5)?;
buf.write_u16::<BigEndian>(8)?;
buf.write_u32::<BigEndian>(xid)?;
println!("send: {:?}", buf);
stream.write_all(&buf)?;
Ok(())
}
fn main() -> Result<(), std::io::Error> {
let listener = TcpListener::bind(("127.0.0.1", 6633)).unwrap();
let mut buf = vec![0u8; 8];
for stream in listener.incoming() {
println!("{:?}", stream);
match stream {
Ok(mut stream) => {
// std::thread::spawn(move || {
println!("=================== connection =======================");
// LearningSwitch::handle_client_connected(&mut stream);
// continue;
// after tcp is connected, it will send hello message
send_hello(&mut stream);
// loop for receive data
loop {
// first receive with Openflow header 64 bit to buf
let res = stream.read(&mut buf);
match res {
Ok(v) if v > 0 => {
let mut packet = Cursor::new(buf.to_vec());
println!("buf: {:?}", packet);
// split data from header
let _version = packet.read_u8().unwrap();
let message = packet.read_u8().unwrap();
// length is only payload size to receive
let length = packet.read_u16::<BigEndian>().unwrap() - 8;
let xid = packet.read_u32::<BigEndian>().unwrap();
// message_body is var to receive payload if it has
// and assign size by length
let mut message_body = vec![0u8; length as usize];
stream.read(&mut message_body)?;
match message {
// 0 is Hello message
0 => {
// after get Hello, send fetureReq
feture_req(&mut stream, xid)?;
}
10 => {
let payload = packetin_payload(&mut buf)?;
}
_ => {
println!("others message");
}
}
}
Ok(_) | Err(_) => break,
}
}
println!("======================================================");
// });
}
Err(_) => {
// connection failed
panic!("Connection failed")
}
}
}
Ok(())
}
use std::net::TcpStream;
use rust_ofp::ofp_message::OfpMessage;
/// OpenFlow Controller
///
/// Version-agnostic API for implementing an OpenFlow controller.
pub trait OfpController {
/// OpenFlow message type supporting the same protocol version as the controller.
type Message: OfpMessage;
/// Send a message to the node associated with the given `TcpStream`.
fn send_message(u32, Self::Message, &mut TcpStream);
/// Perform handshake and begin loop reading incoming messages from client stream.
fn handle_client_connected(&mut TcpStream);
}
pub mod openflow0x01 {
use super::*;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::mem::size_of_val;
use std::io::{Write, Read};
use std::marker::PhantomData;
use std::net::TcpStream;
use rust_ofp::ofp_header::OfpHeader;
use rust_ofp::ofp_message::OfpMessage;
use rust_ofp::openflow0x01::{FlowMod, PacketIn, PacketOut, SwitchFeatures};
use rust_ofp::openflow0x01::message::Message;
#[derive(Debug)]
struct ThreadState<Cntl> {
switch_id: Option<u64>,
phantom: PhantomData<Cntl>,
}
impl<Cntl: OF0x01Controller> ThreadState<Cntl> {
fn process_message(&mut self,
cntl: &mut Cntl,
xid: u32,
msg: Message,
stream: &mut TcpStream) {
match msg {
Message::Hello => Cntl::send_message(xid, Message::FeaturesReq, stream),
Message::Error(err) => println!("Error: {:?}", err),
Message::EchoRequest(bytes) => {
Cntl::send_message(xid, Message::EchoReply(bytes), stream)
}
Message::EchoReply(_) => (),
Message::FeaturesReq => (),
Message::FeaturesReply(feats) => {
if self.switch_id.is_some() {
panic!("Switch connection already received.")
}
self.switch_id = Some(feats.datapath_id);
Cntl::switch_connected(cntl, feats.datapath_id, feats, stream)
}
Message::FlowMod(_) => (),
Message::PacketIn(pkt) => {
Cntl::packet_in(cntl, self.switch_id.unwrap(), xid, pkt, stream)
}
Message::FlowRemoved(_) |
Message::PortStatus(_) |
Message::PacketOut(_) |
Message::BarrierRequest |
Message::BarrierReply => (),
}
}
fn switch_disconnected(&self, cntl: &mut Cntl) {
Cntl::switch_disconnected(cntl, self.switch_id.unwrap())
}
}
/// OpenFlow0x01 Controller API
///
/// OpenFlow 1.0-specific API for communicating between a controller and the dataplane.
pub trait OF0x01Controller: OfpController<Message = Message> {
/// Create a new Controller.
fn new() -> Self;
/// Callback invoked with `sw` when a switch with identifier `sw` connects to
/// the controller.
fn switch_connected(&mut self, sw: u64, feats: SwitchFeatures, stream: &mut TcpStream);
/// Callback invoked with `sw` when a switch with identifier `sw` disconnects
/// from the controller.
fn switch_disconnected(&mut self, sw: u64);
/// Callback invoked when a packet `pkt` with transaction ID `xid` from
/// switch `sw` arrives at the controller.
fn packet_in(&mut self, sw: u64, xid: u32, pkt: PacketIn, stream: &mut TcpStream);
/// Send packet `pkt` with transaction ID `xid` to switch `sw` from the controller.
fn send_packet_out(_: u64, xid: u32, pkt: PacketOut, stream: &mut TcpStream) {
Self::send_message(xid, Message::PacketOut(pkt), stream)
}
/// Send flowmod `flow` with transaction ID `xid` to switch `sw` from the controller.
fn send_flow_mod(_: u64, xid: u32, flow: FlowMod, stream: &mut TcpStream) {
Self::send_message(xid, Message::FlowMod(flow), stream)
}
/// Send barrier request with transaction ID `xid` to switch `sw` from the controller.
/// Guarantees switch `sw` processes messages prior to barrier before messages after.
fn send_barrier_request(_: u64, xid: u32, stream: &mut TcpStream) {
Self::send_message(xid, Message::BarrierRequest, stream)
}
}
impl<Controller: OF0x01Controller> OfpController for Controller {
type Message = Message;
fn send_message(xid: u32, message: Message, writer: &mut TcpStream) {
let raw_msg = Message::marshal(xid, message);
println!("send : {}bytes : {:?}", raw_msg.len(), raw_msg);
writer.write_all(&raw_msg).unwrap()
}
fn handle_client_connected(stream: &mut TcpStream) {
let mut cntl = Controller::new();
Controller::send_message(0, Message::Hello, stream);
let mut buf = [0u8; 8];
let mut thread_state = ThreadState::<Self> {
switch_id: None,
phantom: PhantomData,
};
loop {
let res = stream.read(&mut buf);
println!("get : {}bytes : {:?}", size_of_val(&buf), buf);
match res {
Ok(num_bytes) if num_bytes > 0 => {
let header = OfpHeader::parse(buf);
// ขนาดทั้งหมด ลบด้วย ขนาดของ header
let message_len = header.length() - OfpHeader::size();
let mut message_buf = vec![0; message_len];
// อ่านข้อมูลที่เหลือ จาก tcp โดยที่ การอ่านครั้งแรกคือ Header ขนาด 64 bytes
let _ = stream.read(&mut message_buf);
println!("data: {:?}", message_buf);
// message::parse สำหรับคำนวณว่าต้องตอบกลับเป็น message อะไร
// let mut hash = DefaultHasher::new();
// message_buf.hash(&mut hash);
// println!("data: {}", hash.finish());
let (xid, body) = Message::parse(&header, &message_buf);
thread_state.process_message(&mut cntl, xid, body, stream)
}
Ok(_) => {
println!("Connection closed reading header.");
break;
}
Err(e) => {
println!("{}", e);
thread_state.switch_disconnected(&mut cntl)
}
}
}
}
}
}
use std::io::Cursor;
use std::mem::{size_of, transmute};
use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt};
use rust_ofp::openflow0x01::MsgCode;
/// OpenFlow Header
///
/// The first fields of every OpenFlow message, no matter the protocol version.
/// This is parsed to determine version and length of the remaining message, so that
/// it can be properly handled.
#[repr(packed)]
pub struct OfpHeader {
version: u8,
typ: u8,
length: u16,
xid: u32,
}
impl OfpHeader {
/// Create an `OfpHeader` out of the arguments.
pub fn new(version: u8, typ: u8, length: u16, xid: u32) -> OfpHeader {
OfpHeader {
version: version,
typ: typ,
length: length,
xid: xid,
}
}
/// Return the byte-size of an `OfpHeader`.
pub fn size() -> usize {
size_of::<OfpHeader>()
}
/// Fills a message buffer with the header fields of an `OfpHeader`.
pub fn marshal(bytes: &mut Vec<u8>, header: OfpHeader) {
bytes.write_u8(header.version()).unwrap();
bytes.write_u8(header.type_code() as u8).unwrap();
bytes.write_u16::<BigEndian>(header.length() as u16).unwrap();
bytes.write_u32::<BigEndian>(header.xid()).unwrap();
}
/// Takes a message buffer (sized for an `OfpHeader`) and returns an `OfpHeader`.
pub fn parse(buf: [u8; 8]) -> Self {
let mut bytes = Cursor::new(buf.to_vec());
OfpHeader {
version: bytes.read_u8().unwrap(),
typ: bytes.read_u8().unwrap(),
length: bytes.read_u16::<BigEndian>().unwrap(),
xid: bytes.read_u32::<BigEndian>().unwrap(),
}
}
/// Return the `version` field of a header.
pub fn version(&self) -> u8 {
self.version
}
/// Return the OpenFlow message type code of a header.
/// # Safety
///
/// The `typ` field of the `self` header is expected to be a `u8` within the
/// defined range of the `MsgCode` enum.
pub fn type_code(&self) -> MsgCode {
unsafe { transmute(self.typ) }
}
/// Return the `length` field of a header. Includes the length of the header itself.
pub fn length(&self) -> usize {
self.length as usize
}
/// Return the `xid` field of a header, the transaction id associated with this packet.
/// Replies use the same id to facilitate pairing.
pub fn xid(&self) -> u32 {
self.xid
}
}
use ofp_header::OfpHeader;
/// OpenFlow Message
///
/// Version-agnostic API for handling OpenFlow messages at the byte-buffer level.
pub trait OfpMessage {
/// Return the byte-size of an `OfpMessage`.
fn size_of(&Self) -> usize;
/// Create an `OfpHeader` for the given transaction id and OpenFlow message.
fn header_of(u32, &Self) -> OfpHeader;
/// Return a marshaled buffer containing an OpenFlow header and the message `msg`.
fn marshal(u32, Self) -> Vec<u8>;
/// Returns a pair `(u32, OfpMessage)` of the transaction id and OpenFlow message parsed from
/// the given OpenFlow header `header`, and buffer `buf`.
fn parse(&OfpHeader, &[u8]) -> (u32, Self);
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment