diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d56ce6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/offwall/ +/target/ +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a87195a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,485 @@ +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "filetime" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fsevent" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fsevent-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "inotify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ipnetwork" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log-panics" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "notify" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "offwall" +version = "0.6.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.29.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log-panics 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-ini 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "simple_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syslog 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tls-api 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "tls-api-openssl 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.9.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.9.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rust-ini" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "same-file" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "simple_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syslog" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tls-api" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tls-api-openssl" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", + "tls-api 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unix_socket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcpkg" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" +"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum clap 2.29.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b8f59bcebcfe4269b09f71dab0da15b355c75916a8f975d3876ce81561893ee" +"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05" +"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" +"checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" +"checksum log-panics 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5fbc29c3d7647eacbd817798ea0bb1130ebbbbfbc221c5c67fc9b786c4c7e23" +"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" +"checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" +"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" +"checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" +"checksum notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5c3812da3098f210a0bb440f9c008471a031aa4c1de07a264fdd75456c95a4eb" +"checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" +"checksum openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "14ba54ac7d5a4eabd1d5f2c1fdeb7e7c14debfa669d94b983d01b465e767ba9e" +"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" +"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" +"checksum rust-ini 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22dab655e8122ccb15db25a56852ce62506f1486cdefd37e86371bf34ea8f601" +"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" +"checksum simple_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5a3b341928cec79e536fe62b75bfe2e35891a5e65801ebfbd2741dddf7d7fac" +"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" +"checksum syslog 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bbc9b0acde4f7c05fdc1cfb05239b8a53a66815dd86c67fee5aa9bfac5b4ed42" +"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" +"checksum tls-api 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0d20cc40863a888b09ae65aa8d95ad33e31a73bffc482d3958a53a4b4bb33873" +"checksum tls-api-openssl 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "97f5e5ed4fa358c61d00e2cc28b76f535be0ecd9f6cfb66ef997dabf333e4e60" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" +"checksum walkdir 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b167e9a4420d8dddb260e70c90a4a375a1e5691f21f70e715553da87b6c2503a" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0673242 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "offwall" +version = "0.6.0" +authors = ["Bastian Germann"] +license = "GPL-3.0+" +description = "A simple, CSV-reading OpenFlow controller dedicated to firewall bypassing" + +[features] +tls = ["tls-api-openssl"] + +[dependencies] +byteorder = "1.1" +notify = "4.0" +ipnetwork = "0.12" +log = "0.3" +log-panics = "1.2" +simple_logger = "0.4" +rust-ini = "0.10" +rand = "0.4" +tls-api = "0.1" +tls-api-openssl = { version = "0.1", optional = true } + +[dependencies.clap] +version = "2" +default-features = false + +[target.'cfg(unix)'.dependencies] +libc = "0.2" +syslog = "3.3.0" diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..4680356 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,40 @@ +# Building OFFWall + +OFFWall is provided by the cargo crate offwall. +Rust version 1.22.0 is minimally required to build it. +Building the crate on a system that has rustc and cargo available is as easy as `cargo build`. +Installing to the local cargo path takes a `cargo install`. + +The crate has one optional feature: tls. +This feature needs the OpenSSL or LibreSSL library installed on the system. +For more information please consult the [openssl crate's documentation](https://crates.io/crates/openssl). +If you want to enable TLS, please use the `--features tls` option appended to the aforementioned commands. + +## Prerequisites for cross compilation + +The production target system for OFFWall is Solaris 10 (SPARC64). +A compatible cross linker is required to build, which is provided by the `cross` crate. +It also includes an OpenSSL installation, so you do not have to care about installing it. + +You should use a x86_64 GNU/Linux build system with cross. +It depends on the `rustup` toolchain manager and `docker`. +Be sure to add the building system user to the docker group. + +cross is installed via `cargo install cross`. + +## Packaging for Solaris 10 + +It is assumed that you have cross installed and the docker daemon is running. +The Heirloom Packaging Tools and a make implementation are also required. +At least make, pkgmk, rustup, docker and cross have to be in $PATH. +Build the Solaris 10 SVR4 package by simply running `make`. +You will find the package at the new directory offwall. +You can `make offwall.pkg` a container with pkgtrans available. + +## Installing on Solaris 10 + +You can install the package with pkgadd. Maybe you get a checksum error, +because the upstream Heirloom Packaging Tools depend on a long's size +being 32 bit for computing the checksums. You should install a 32-bit version. +If that is not possible, pkgadd has an undocumented -C flag to deactivate checksum checks. +Or you can [patch](https://github.com/eunuchs/heirloom-project/pull/1) and build from source. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2fb2e74 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5755f56 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CARGO=cross +TARGET=sparcv9-sun-solaris + +offwall: build + pkgmk -o -d . -f pkg/prototype + +offwall.pkg: offwall + pkgtrans . $@ $< + +build: + $(CARGO) build --release --all-features --target $(TARGET) + +clean: + $(CARGO) clean + rm -rf offwall offwall.pkg diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba0190b --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# OFFWall + +OFFWall is an OpenFlow 1.3 compatible controller dedicated to firewall bypassing via CSV files. +It only implements the features that are needed to proactively install flow entries onto a connected switch. + +This Rust software project is part of Bastian Germann's Master thesis, which explains it in detail: +Implementation of a reduced OpenFlow Controller for firewall bypassing in practice. + +You can find information about compiling and packaging in [INSTALL.md](INSTALL.md). + +## License + +Copyright (C) 2017 Bastian Germann + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..04d6354 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["OpenFlow", "IPv4"] diff --git a/pkg/depend b/pkg/depend new file mode 100644 index 0000000..121a3b7 --- /dev/null +++ b/pkg/depend @@ -0,0 +1 @@ +P CSWlibgcc-s1 libgcc_s1 - The GNU Compiler Collection, libgcc_s.so.1 diff --git a/pkg/etc/offwall.csv b/pkg/etc/offwall.csv new file mode 100644 index 0000000..da3ebd4 --- /dev/null +++ b/pkg/etc/offwall.csv @@ -0,0 +1,3 @@ +# Please read offwall.csv(4) on how to add firewall +# bypass rules line by line with the following 5 values: +# src_cidr; src_port; dst_cidr; dst_port; proto diff --git a/pkg/etc/offwall.ini b/pkg/etc/offwall.ini new file mode 100644 index 0000000..36a21f4 --- /dev/null +++ b/pkg/etc/offwall.ini @@ -0,0 +1,31 @@ +; Example offwall.ini file. +; Please also read offwall.ini(4). + +; [Connection] +; Default: +; uri=tcp:127.0.0.1:6633 + +; An example TLS connection setup +; (may not be available depending on the compiler options) +; uri=tls:127.0.0.1:6633 +; pkcs12=/etc/offwall.p12 +; passwd=s3cr3t + +; [Table] +; Default: +; id=0 + +[Networks] +; There is no default for the inside network's IP range. +; You have to provide it manually. + +; inside=192.0.2.0/28 + +[Ports] +; There is no default for the OpenFlow port numbers. +; You have to provide them manually. + +; inside=1 +; fw_in=2 +; fw_out=3 +; outside=4 diff --git a/pkg/man/offwall.1 b/pkg/man/offwall.1 new file mode 100644 index 0000000..fedfde9 --- /dev/null +++ b/pkg/man/offwall.1 @@ -0,0 +1,47 @@ +.TH OFFWALL 1 +.SH NAME +offwall \- A simple, CSV-reading OpenFlow controller dedicated to firewall bypassing +.SH SYNOPSIS +.B offwall +[\fB\-h\fR] [\fB\-s\fR] [\fB\-v\fR ...] [\fB\-V\fR] [\fB\-c\fR \fIini\fR] \fIcsv\fR +.SH DESCRIPTION +OFFWall is an OpenFlow 1.3 compatible controller dedicated to firewall bypassing via CSV files. +It only implements the features that are needed to proactively install flow entries onto one connected switch. +.PP +The flow entries are generated from the given \fIcsv\fR file that is expected to have \fBoffwall.csv\fR(4) syntax. +The file is watched for changes. +When it is changed, the flow entries are modified accordingly. +.SH OPTIONS +.TP +\fB\-h\fR, \fB\-\-help\fR +Prints help information. +.TP +\fB\-d\fR, \fB\-\-daemonize\fR +Runs the process as daemon. +.TP +\fB\-s\fR, \fB\-\-syslog\fR +Logs via syslog. +If this is not specified, the program prints to stdout. +.TP +\fB\-v\fR +Repeat this option up to 4 times to set the level of the log's verbosity. +You cannot disable error messages. +.TP +\fB\-V\fR, \fB\-\-version\fR +Prints version information. +.TP +\fB\-c\fR, \fB\-\-conf\fR \fIini\fR +The \fIini\fR configuration file expects a file with \fBoffwall.ini\fR(4) syntax. +Defaults to \fI/etc/offwall.ini\fR. +.SH ENVIRONMENT +.TP +.B RUST_BACKTRACE +If set to a value different from "0" and the program panics, a backtrace is produced. +.SH FILES +.TP +.I /etc/offwall.ini +The default configuration file with \fBoffwall.ini\fR(4) syntax +.SH "SEE ALSO" +.BR offwall.ini (4), +.BR offwall.csv (4), +.BR syslog (3) diff --git a/pkg/man/offwall.csv.4 b/pkg/man/offwall.csv.4 new file mode 100644 index 0000000..d3cda13 --- /dev/null +++ b/pkg/man/offwall.csv.4 @@ -0,0 +1,45 @@ +.TH OFFWALL.CSV 4 +.SH NAME +offwall.csv \- The bypass rules file for OFFWall +.SH DESCRIPTION +The offwall.csv file conforms to the comma-separated values (CSV) format. +OFFWall is supposed to read a file with this syntax, which expresses the firewall bypass rules that are to be installed as flow entries on a connected OpenFlow switch. +.PP +You can write comments in lines beginning with a # character. +The separation character to be used is the semicolon (;). +Whitespace surrounding a value is ignored. +.PP +Each line has to have exactly five values which are used to match an incoming IPv4 packet. +The meaning of the values is identified by position: +.I src_cidr; src_port; dst_cidr; dst_port; proto +.TP +.I src_cidr +An IPv4 address range in CIDR notation that is matched against the source address. +.TP +.I src_port +An unsigned 16 bit integer that is matched against the TCP or UDP source port. +If proto is ICMP this has to be a wildcard. +.TP +.I dst_cidr +An IPv4 address range in CIDR notation that is matched against the destination address. +.TP +.I dst_port +An unsigned 16 bit integer that is matched against the TCP or UDP destination port. +If proto is ICMP this has to be a wildcard. +.TP +.I proto +May be one of TCP, UDP, or ICMP. +.PP +Any field may be a wildcard (*), but at least one has to have a value. +A wildcard means exclusion from the match. +If an IPv4 packet matches all fields it bypasses the firewall in both inbound and outbound directions. +.SH EXAMPLES +.nf +# src_cidr; src_port; dst_cidr; dst_port; proto +192.0.2.0/24 ; *; 192.0.2.10/32; 80; TCP +192.0.2.0/24 ; 25; 192.0.2.0/30 ; 25; TCP +203.0.113.102/32; 137; 192.0.2.15/32; *; UDP +203.0.113.102/32; *; 192.0.2.15/32; *; ICMP +.fi +.SH "SEE ALSO" +.BR OFFWall (1) diff --git a/pkg/man/offwall.ini.4 b/pkg/man/offwall.ini.4 new file mode 100644 index 0000000..663b4b5 --- /dev/null +++ b/pkg/man/offwall.ini.4 @@ -0,0 +1,85 @@ +.TH OFFWALL.INI 4 +.SH NAME +OFFWall.ini \- The general configuration file for OFFWall +.SH DESCRIPTION +The OFFWall.ini file conforms to the informal INI format. +It is read by OFFWall and expresses its general configuration. +.PP +You can write comments in lines beginning with a ; character. +The file consists of [Sections] of \fIkey\fR=value pairs: +.PP +.B [Connection] +.PP +If the section is not available it defaults to uri=tcp:127.0.0.1:6633. +.TP +.I uri +Expects an OpenFlow Connection URI which consists of protocol:address:port. +The protocol part can be tcp or tls (if TLS support is available). +The address part is an IPv4 or IPv6 address. +The port part is an optional TCP port and defaults to 6633. +.TP +.I pkcs12 +If uri's protocol is tls this has to be the path to a PKCS #12 bundle file with a certificate, its chain of trust, and its private key. +.TP +.I passwd +The password for pkcs12's private key. +.PP +.B [Table] +.PP +If the section is not available it defaults to id=0. +.TP +.I id +The OpenFlow Table ID where OFFWall stores its flow entries. +Expects an unsigned 8 bit integer. +.PP +.B [Ports] +.PP +This section holds four key=value pairs of the same kind. +The value is an unsigned 32 bit OpenFlow switch port number. +The key can be one of the following: +.TP +.I inside +Identifies the port to the inside network. +.TP +.I fw_in +Identifies the port to the firewall's connection with the inside network. +.TP +.I fw_out +Identifies the port to the firewall's connection with the outside network. +.TP +.I outside +Identifies the port to the outside network. +.PP +.B [Networks] +.TP +.I inside +The inside network's IP range in CIDR notation. +The OpenFlow matches' inbound and outbound directions are derived from this information. +.SH EXAMPLES +.nf +[Connection] +uri=tcp:192.0.2.1:6633 + +; A TLS connection setup: +; uri=tls:192.0.2.1:6633 +; pkcs12=/etc/offwall.p12 +; passwd=s3cr3t + +[Table] +id=0 + +[Ports] +inside=1 +fw_in=2 +fw_out=3 +outside=4 + +[Networks] +inside=192.0.2.0/28 +.fi +.SH FILES +.TP +.I /etc/offwall.ini +The default configuration file with this syntax +.SH "SEE ALSO" +.BR offwall (1) diff --git a/pkg/pkginfo b/pkg/pkginfo new file mode 100644 index 0000000..eb8e1e6 --- /dev/null +++ b/pkg/pkginfo @@ -0,0 +1,7 @@ +ARCH="sparc" +CATEGORY="application" +NAME="The OFFWall OpenFlow controller for firewall bypassing" +PKG="offwall" +VERSION="0.6.0" +CLASSES=manifest preserve none +VENDOR="Bastian Germann" diff --git a/pkg/prototype b/pkg/prototype new file mode 100644 index 0000000..20c478c --- /dev/null +++ b/pkg/prototype @@ -0,0 +1,28 @@ +i pkginfo +i depend +i copyright=../LICENSE.md +!default 0755 root sys +d none /etc +d none /opt +d none /usr +d none /usr/share +d none /var +d none /var/svc +d none /var/svc/manifest +d none /var/svc/manifest/application +!default 0755 root bin +d none /lib +d none /lib/svc +d none /lib/svc/method +d none /usr/share/man +d none /usr/share/man/man1 +d none /usr/share/man/man4 +f none /opt/offwall=../target/sparcv9-sun-solaris/release/offwall +!default 0644 root bin +f none /usr/share/man/man1/offwall.1=man/offwall.1 +f none /usr/share/man/man4/offwall.csv.4=man/offwall.csv.4 +f none /usr/share/man/man4/offwall.ini.4=man/offwall.ini.4 +f preserve /etc/offwall.csv=etc/offwall.csv +f preserve /etc/offwall.ini=etc/offwall.ini +f none /lib/svc/method/offwall=svc/offwall.sh 0555 root bin +f manifest /var/svc/manifest/application/offwall.xml=svc/offwall.xml 0444 root sys diff --git a/pkg/svc/offwall.sh b/pkg/svc/offwall.sh new file mode 100755 index 0000000..1c3e426 --- /dev/null +++ b/pkg/svc/offwall.sh @@ -0,0 +1,62 @@ +#!/sbin/sh + +. /lib/svc/share/smf_include.sh + +if [ -z $SMF_FMRI ]; then + echo "SMF framework variables are not initialized." + exit $SMF_EXIT_ERR +fi + +PIDFILE=`svcprop -p offwall/pidfile $SMF_FMRI` +if [ -z $PIDFILE ]; then + echo "offwall/pidfile property not set" + exit $SMF_EXIT_ERR_CONFIG +fi + +CSV=`svcprop -p offwall/csv $SMF_FMRI` +if [ -z $CSV ]; then + echo "offwall/csv property not set" + exit $SMF_EXIT_ERR_CONFIG +fi + +LOG=`svcprop -p offwall/loglevel $SMF_FMRI` +if [ -z $LOG ]; then + echo "offwall/loglevel property not set" + exit $SMF_EXIT_ERR_CONFIG +fi + +LOGV= +if [ $LOG -ne 0 ]; then + i=1 + while [ $i -le $LOG ]; do + LOGV=v$LOGV + i=`expr $i + 1` + done + LOGV=-$LOGV +fi + +case "$1" in +'start') + LD_LIBRARY_PATH=/opt/csw/lib/64 /opt/offwall -p $PIDFILE -s $LOGV $CSV + while [ ! -f "$PIDFILE" ]; do + sleep 1 + done + ;; +'stop') + if [ -f "$PIDFILE" ]; then + read PID <$PIDFILE + ps -p $PID -o comm= | grep /opt/offwall + if [ $? -ne 0 ]; then + exit $SMF_EXIT_ERR + fi + kill $PID + rm $PIDFILE + fi + ;; +*) + echo "Usage: $0 " + exit $SMF_EXIT_ERR + ;; +esac + +exit $SMF_EXIT_OK diff --git a/pkg/svc/offwall.xml b/pkg/svc/offwall.xml new file mode 100644 index 0000000..b136ee6 --- /dev/null +++ b/pkg/svc/offwall.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bypass_csv.rs b/src/bypass_csv.rs new file mode 100644 index 0000000..94baa34 --- /dev/null +++ b/src/bypass_csv.rs @@ -0,0 +1,293 @@ +/*! +A CSV parser for files with firewall bypass rules. + +# Syntax + +The CSV is separated by semicolons. +Empty lines and lines beginning with `#` are ignored. +You can place a `*` if you want to express a wildcard. +A record consisting only of wildcards is not allowed. +Whitespaces surrounding the values are ignored. + +Each line consists of a 5-tupel describing a connection +that is to bypass, e.g. + +```csv +# src_cidr ;src_port;dst_cidr ;dst_port;proto +192.0.2.0/24;* ;192.0.2.10/32;80 ;TCP +``` + +You have to use IPv4 CIDR suffix notation. +The IP Protocol (proto) value can be TCP, UDP or `*`. +If it is `*`, the port numbers also have to be `*`. +*/ + +use ipnetwork::{Ipv4Network, IpNetworkError}; + +use std::collections::HashSet; +use std::convert::From; +use std::error; +use std::fmt; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::str::FromStr; + +/// The CSV delimiter. +const DELIMITER: char = ';'; + +/// The char introducing a line comment. +const COMMENT: char = '#'; + +#[derive(Debug)] +pub enum Error { + ValueCount(String), + InvalidCidr(IpNetworkError, String), + InvalidPortNumber(String), + InvalidProtocol(String), + OnlyWildcards(String), + PortWithProtocolWildcard(String), + PortWithIcmp(String), + NotMatchingInsideNet(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::ValueCount(ref s) => { + write!(f, "The following line does not have exactly 5 values: {}", s) + } + Error::InvalidCidr(ref e, ref s) => { + write!(f, "{} -- Violating line: {}", e, s) + } + Error::InvalidPortNumber(ref p) => { + write!(f, "{} is an invalid port number.", p) + } + Error::InvalidProtocol(ref p) => { + write!(f, "{} is an invalid protocol.", p) + } + Error::OnlyWildcards(ref s) => { + write!(f, "The following line only contains wildcards, which would overwrite default rules: {}", s) + } + Error::PortWithProtocolWildcard(ref s) => { + write!(f, "The following line contains at least one port number with protocol being a wildcard: {}", s) + } + Error::PortWithIcmp(ref s) => { + write!(f, "The following line contains at least one port number with ICMP: {}", s) + } + Error::NotMatchingInsideNet(ref s) => { + write!(f, "The following line does not contain an IP range that is in the configured inside net: {}", s) + } + } + } +} + +impl From for io::Error { + fn from(e: Error) -> Self { + io::Error::new(io::ErrorKind::InvalidData, e) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + "bypass_csv parser error" + } +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] +pub enum Direction { + Inside, + Outside, +} + +/// The IP protocols that are allowed. +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] +pub enum IpProtocol { + Icmp = 1, + Tcp = 6, + Udp = 17, +} + +/// Represents one record of the bypass rules. +/// A member wildcard is represented as None value. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct BypassRecord { + /// The source IP address + pub src_ip: Option, + /// The source port + pub src_port: Option, + /// The destination IP address + pub dst_ip: Option, + /// The destination port + pub dst_port: Option, + /// The IP protocol + pub proto: Option, + /// The direction. The switch's input/output ports should be derived from it. + pub direction: Direction, +} + +impl BypassRecord { + fn reverse_direction(&self) -> BypassRecord { + let direction = match self.direction { + Direction::Inside => Direction::Outside, + Direction::Outside => Direction::Inside, + }; + BypassRecord { + src_ip: self.dst_ip, + src_port: self.dst_port, + dst_ip: self.src_ip, + dst_port: self.src_port, + proto: self.proto, + direction: direction, + } + } +} + +fn parse_port_number(ps: &str) -> Result { + ps.parse().map_err(|_| Error::InvalidPortNumber(ps.to_string())) +} + +fn parse_protocol(ps: &str) -> Result { + match ps { + "ICMP" | "icmp" => Ok(IpProtocol::Icmp), + "UDP" | "udp" => Ok(IpProtocol::Udp), + "TCP" | "tcp" => Ok(IpProtocol::Tcp), + _ => Err(Error::InvalidProtocol(ps.to_string())), + } +} + +pub struct CsvParser { + pub path: String, + inside_net: Ipv4Network, +} + +impl CsvParser { + pub fn new(path: String, inside_net: Ipv4Network) -> CsvParser { + CsvParser{ + path: path, + inside_net: inside_net, + } + } + + /// Checks for `ip_net` to be in `self.inside_net` + fn in_inside_net(&self, ip_net: Option) -> bool { + ip_net.map_or(false, |ip| self.inside_net.contains(ip.network())) + } + + /// Parses one CSV line and validates it semantically. + /// src_ip or dst_ip has to be in `inside_net`. + /// Dependent on this the output_port is set. + /// The corresponding `BypassRecord` and its reverse are returned. + /// + /// # Examples + /// + /// ``` + /// let p = parse_line("# comment"); + /// assert_eq!(Ok(None)); + /// ``` + fn parse_line(&self, line: String) -> Result, Error> { + if line.is_empty() || line.starts_with(COMMENT) { + return Ok(Vec::with_capacity(0)); + } + + let mut csv_elems = vec![]; + for item in line.split(DELIMITER) { + let trimmed = match item.trim() { + // check wildcard + "*" => None, + trm => Some(trm), + }; + csv_elems.push(trimmed); + } + + // check 5-tupel + if csv_elems.len() != 5 { + return Err(Error::ValueCount(line.to_string())); + } + + let src_port = match csv_elems[1] { + Some(p) => Some(parse_port_number(p)?), + _ => None, + }; + let dst_port = match csv_elems[3] { + Some(p) => Some(parse_port_number(p)?), + _ => None, + }; + + let src_ip = match csv_elems[0] { + Some(ip) => Some(Ipv4Network::from_str(ip).map_err( + |e| Error::InvalidCidr(e, line.to_string()) + )?), + _ => None, + }; + let dst_ip = match csv_elems[2] { + Some(ip) => Some(Ipv4Network::from_str(ip).map_err( + |e| Error::InvalidCidr(e, line.to_string()) + )?), + _ => None, + }; + + let proto = match csv_elems[4] { + Some(proto) => Some(parse_protocol(proto)?), + _ => None, + }; + + // check for semantic errors + if src_ip == None && src_port == None && + dst_ip == None && dst_port == None && proto == None { + return Err(Error::OnlyWildcards(line.to_string())); + } + if (src_port != None || dst_port != None) && proto == None { + return Err(Error::PortWithProtocolWildcard(line.to_string())); + } + if (src_port != None || dst_port != None) && proto == Some(IpProtocol::Icmp) { + return Err(Error::PortWithIcmp(line.to_string())); + } + let direction = if self.in_inside_net(src_ip) { + Direction::Outside + } else if self.in_inside_net(dst_ip) { + Direction::Inside + } else { + return Err(Error::NotMatchingInsideNet(line.to_string())); + }; + + let rec = BypassRecord { + src_ip: src_ip, + src_port: src_port, + dst_ip: dst_ip, + dst_port: dst_port, + proto: proto, + direction: direction, + }; + let rec_reverse = rec.reverse_direction(); + + debug!("Got {:?}", rec); + debug!("Got {:?}", rec_reverse); + + Ok(vec![rec, rec_reverse]) + } + + /// Parses a CSV file and returns its records + pub fn parse_file(&self) -> io::Result> { + + info!("Reading CSV file {}", self.path); + + let file = File::open(&self.path).map_err(|e| + io::Error::new(e.kind(), format!("Unable to open `{:?}`: {}", self.path, e)) + )?; + let reader = io::BufReader::new(file); + + let mut bypass_records = HashSet::new(); + + for line_res in reader.lines() { + let line = line_res?; + let records = self.parse_line(line)?; + for rec in records { + bypass_records.insert(rec); + } + } + + Ok(bypass_records) + } + +} diff --git a/src/conf.rs b/src/conf.rs new file mode 100644 index 0000000..1f007fb --- /dev/null +++ b/src/conf.rs @@ -0,0 +1,407 @@ +/*! +A parser for an INI file with the following structure: + +```ini +[Connection] +uri=tcp:192.0.2.1:6633 + +; A TLS connection setup: +; uri=tls:192.0.2.1:6633 +; pkcs12=/etc/offwall.p12 +; passwd=s3cr3t + +[Table] +id=0 + +[Networks] +inside=192.0.2.0/28 + +[Ports] +inside=1 +fw_in=2 +fw_out=3 +outside=4 +``` +*/ + +use bypass_csv::Direction; +use openflow::messages::OFP_TCP_PORT; +use openflow::messages::OFPP_MAX; + +use ini::Ini; +use ini::ini; + +use ipnetwork::{Ipv4Network, IpNetworkError}; + +use std::convert::From; +use std::default::Default; +use std::error; +use std::fmt; +use std::fs::File; +use std::io; +use std::io::Read; +use std::iter::IntoIterator; +use std::net::*; +use std::num::ParseIntError; +use std::str::FromStr; +use std::vec; + +use tls_api; +use tls_api::TlsAcceptorBuilder; + +#[cfg(feature = "tls")] +use tls_api_openssl; + +const PORTS_SECTION: &str = "Ports"; +const INSIDE_KEY: &str = "inside"; +const FW_IN_KEY: &str = "fw_in"; +const FW_OUT_KEY: &str = "fw_out"; +const OUTSIDE_KEY: &str = "outside"; + +const CONN_SECTION: &str = "Connection"; +const URI_KEY: &str = "uri"; +const P12_KEY: &str = "pkcs12"; +const PASS_KEY: &str = "passwd"; + +const NET_SECTION: &str = "Networks"; + +const TABLE_SECTION: &str = "Table"; +const ID_KEY: &str = "id"; + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + Ini(ini::Error), + ParseTableId(ParseIntError), + ParseSwitchPort(ParseIntError), + InvalidSwitchPortNo(String), + InvalidCidr(IpNetworkError, String), + MissingSection(&'static str), + MissingEntry(&'static str, &'static str), + InvalidUri, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Io(ref e) => { + write!(f, "{}", e) + } + Error::Ini(ref e) => { + write!(f, "{}", e) + } + Error::ParseTableId(ref e) => { + write!(f, "Error on trying to parse the OpenFlow Table ID: {}", e) + } + Error::ParseSwitchPort(ref e) => { + write!(f, "Error on trying to parse a switch port number: {}", e) + } + Error::InvalidSwitchPortNo(ref p) => { + write!(f, "Switch port number {} is invalid", p) + } + Error::InvalidCidr(ref e, ref s) => { + write!(f, "Error on trying to parse '{}' as IP CIDR: {}", s, e) + } + Error::MissingSection(s) => { + write!(f, "The INI file does not have a [{}] section", s) + } + Error::MissingEntry(s, k) => { + write!(f, "The INI [{}] section does not have a '{}' key", s, k) + } + Error::InvalidUri => { + write!(f, "The OpenFlow Connection URI from INI file is invalid") + } + } + } +} + +impl From for io::Error { + fn from(e: Error) -> Self { + match e { + Error::Io(ioe) => ioe, + _ => io::Error::new(io::ErrorKind::InvalidData, e), + } + } +} +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(e) + } +} +impl From for Error { + fn from(e: ParseIntError) -> Self { + Error::ParseSwitchPort(e) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + "INI configuration parser error" + } +} + +#[derive(Debug)] +pub struct OfPort { + pub of_port: u32, +} + +/// Represents the ports of the switch that +/// are subject to the firewall bypassing +#[derive(Debug)] +pub struct Ports { + pub inside: OfPort, + pub fw_in: OfPort, + pub fw_out: OfPort, + pub outside: OfPort, +} + +#[derive(Debug)] +pub struct OfTable { + pub id: u8, +} + +#[derive(Debug, PartialEq)] +enum ConnectionProtocol { + Tcp, + #[cfg(feature = "tls")] + Tls, +} +impl FromStr for ConnectionProtocol { + type Err = Error; + + fn from_str(proto: &str) -> Result { + match proto { + "tcp" => Ok(ConnectionProtocol::Tcp), + #[cfg(feature = "tls")] + "tls" => Ok(ConnectionProtocol::Tls), + _ => Err(Error::InvalidUri), + } + } +} + +trait Section { + type S; + + fn from_ini(conf: &Ini) -> Result; +} + +#[derive(Debug)] +pub struct OfConnection { + proto: ConnectionProtocol, + pub socket: SocketAddr, + pkcs12: Option<(Vec, String)>, +} +#[cfg(feature = "tls")] +impl OfConnection { + pub fn tls_acceptor (&self) -> tls_api::Result> { + Ok(match self.pkcs12 { + Some(ref p12) => Some( + tls_api_openssl::TlsAcceptorBuilder::from_pkcs12( + &p12.0, &p12.1 + )?.build()? + ), + _ => None + }) + } +} +impl Section for OfConnection { + type S = OfConnection; + + fn from_ini(conf: &Ini) -> Result { + debug!("Reading [{}] section", CONN_SECTION); + + match conf.section(Some(CONN_SECTION.to_owned())) { + Some(conn_section) => { + let uri = conn_section.get(URI_KEY) + .ok_or(Error::MissingEntry(CONN_SECTION, URI_KEY))?; + let mut conn = OfConnection::from_str(uri)?; + + #[cfg(feature = "tls")] { + if conn.proto == ConnectionProtocol::Tls { + let path = conn_section.get(P12_KEY) + .ok_or(Error::MissingEntry(CONN_SECTION, P12_KEY))?; + let mut p12 = vec![]; + File::open(path)?.read_to_end(&mut p12)?; + let passwd = conn_section.get(PASS_KEY) + .ok_or(Error::MissingEntry(CONN_SECTION, PASS_KEY))?; + conn.pkcs12 = Some((p12, passwd.to_owned())); + } + } + Ok(conn) + } + _ => Ok(OfConnection::default()) + } + } +} + +impl FromStr for OfConnection { + type Err = Error; + + fn from_str(conn: &str) -> Result { + let def_port = OFP_TCP_PORT.to_string(); + let mut conn_split: Vec<_> = conn.split(':').collect(); + if conn_split.len() == 2 { + conn_split.push(&def_port); + } + if conn_split.len() == 3 { + let joined = &format!("{}:{}", conn_split[1], conn_split[2]); + if let Ok(socket) = SocketAddr::from_str(joined) { + let connection = OfConnection{ + proto: ConnectionProtocol::from_str(conn_split[0])?, + socket: socket, + pkcs12: None, + }; + debug!("Got {:?}", connection); + return Ok(connection); + } + } + Err(Error::InvalidUri) + } +} + +impl Default for OfConnection { + fn default() -> Self { + let socket_v4 = SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), OFP_TCP_PORT); + OfConnection { + proto: ConnectionProtocol::Tcp, + socket: SocketAddr::V4(socket_v4), + pkcs12: None, + } + } +} + +impl FromStr for OfPort { + type Err = Error; + + fn from_str(port: &str) -> Result { + let port_no: u32 = port.parse()?; + if 0 == port_no || port_no > OFPP_MAX { + return Err(Error::InvalidSwitchPortNo(port_no.to_string())); + } + + Ok(OfPort { + of_port: port_no, + }) + } +} + +impl Section for Ports { + type S = Ports; + + fn from_ini(conf: &Ini) -> Result { + debug!("Reading [{}] section", PORTS_SECTION); + + let ports_section = conf.section(Some(PORTS_SECTION.to_owned())) + .ok_or(Error::MissingSection(PORTS_SECTION))?; + + let inside = ports_section.get(INSIDE_KEY) + .ok_or(Error::MissingEntry(PORTS_SECTION, INSIDE_KEY))?; + let fw_in = ports_section.get(FW_IN_KEY) + .ok_or(Error::MissingEntry(PORTS_SECTION, FW_IN_KEY))?; + let fw_out = ports_section.get(FW_OUT_KEY) + .ok_or(Error::MissingEntry(PORTS_SECTION, FW_OUT_KEY))?; + let outside = ports_section.get(OUTSIDE_KEY) + .ok_or(Error::MissingEntry(PORTS_SECTION, OUTSIDE_KEY))?; + + // the port values are trimmed, so try to parse directly + let ports = Ports { + inside: OfPort::from_str(inside)?, + fw_in: OfPort::from_str(fw_in)?, + fw_out: OfPort::from_str(fw_out)?, + outside: OfPort::from_str(outside)?, + }; + + debug!("Got {:?}", ports); + Ok(ports) + } +} + +impl Ports { + pub fn in_out_from_direction(&self, dir: Direction) -> (&OfPort, &OfPort) { + match dir { + Direction::Inside => (&self.outside, &self.inside), + Direction::Outside => (&self.inside, &self.outside) + } + } +} + +impl<'a> IntoIterator for &'a Ports { + type Item = &'a OfPort; + type IntoIter = vec::IntoIter<&'a OfPort>; + + fn into_iter(self) -> Self::IntoIter { + let v = vec![&self.inside, &self.fw_in, &self.fw_out, &self.outside]; + v.into_iter() + } +} + +impl Section for Ipv4Network { + type S = Ipv4Network; + + fn from_ini(conf: &Ini) -> Result { + debug!("Reading [{}] section", NET_SECTION); + + let net_section = conf.section(Some(NET_SECTION.to_owned())) + .ok_or(Error::MissingSection(NET_SECTION))?; + + let inside = net_section.get(INSIDE_KEY) + .ok_or(Error::MissingEntry(NET_SECTION, INSIDE_KEY))?; + + let net = Ipv4Network::from_str(inside).map_err( + |e| Error::InvalidCidr(e, inside.to_string()) + )?; + + debug!("Got {:?}", net); + Ok(net) + } +} + +impl Section for OfTable { + type S = OfTable; + + fn from_ini(conf: &Ini) -> Result { + debug!("Reading [{}] section", TABLE_SECTION); + + let table = match conf.section(Some(TABLE_SECTION.to_owned())) { + Some(table_section) => { + let id = table_section.get(ID_KEY) + .ok_or(Error::MissingEntry(TABLE_SECTION, ID_KEY))?; + + Ok(OfTable { + id: id.parse().map_err(Error::ParseTableId)? + }) + } + + _ => Ok(OfTable::default()) + }; + + debug!("Got {:?}", table); + table + } +} + +impl Default for OfTable { + fn default() -> Self { + OfTable { + id: 0, + } + } +} + +pub fn parse_file(path: &str) -> Result<(OfConnection, OfTable, Ports, Ipv4Network), Error> { + info!("Reading INI file {}", path); + + let conf = match Ini::load_from_file(path) { + Ok(i) => i, + Err(e) => { + return Err(Error::Ini(e)); + } + }; + + Ok(( + OfConnection::from_ini(&conf)?, + OfTable::from_ini(&conf)?, + Ports::from_ini(&conf)?, + Ipv4Network::from_ini(&conf)? + )) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..30e4d6b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,184 @@ +/*! +A simple, CSV-reading OpenFlow controller dedicated to firewall bypassing. + +The controller supports exactly one switch. +You can use mininet as a test switch. +To spawn an instance with 4 ports you can run: + +```sh +# mn --controller remote,port=6653 --topo single,4 --switch ovs,protocols=OpenFlow13 +``` +*/ + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate log; +extern crate log_panics; +extern crate simple_logger; +extern crate byteorder; +extern crate ini; +extern crate notify; +extern crate ipnetwork; +extern crate rand; + +extern crate tls_api; +#[cfg(feature = "tls")] +extern crate tls_api_openssl; + +#[cfg(unix)] +extern crate syslog; +#[cfg(unix)] +extern crate libc; + +mod bypass_csv; +mod openflow; +mod conf; + +use bypass_csv::BypassRecord; +use bypass_csv::CsvParser; + +use notify::{Watcher, RecursiveMode, DebouncedEvent}; + +use openflow::OfController; + +use std::cell::RefCell; +use std::collections::HashSet; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::net; +use std::process::exit; +use std::sync::mpsc; +use std::sync::mpsc::{Sender, Receiver}; +use std::thread; +use std::time::Duration; + +const DEFAULT_CONFIG_PATH: &str = "/etc/offwall.ini"; +const NOTIFY_SECONDS: u64 = 2; + +/// Registers a file as notify target. +/// If the registering fails, the file is tried to be reregistered infinitely. +fn register_bypass_file(parser: &CsvParser, record_tx: &Sender>) { + loop { + let (tx, rx) = mpsc::channel(); + if let Ok(mut watcher) = notify::watcher(tx, Duration::from_secs(NOTIFY_SECONDS)) { + if watcher.watch(&parser.path, RecursiveMode::NonRecursive).is_ok() { + info!("Watching file {}", parser.path); + match handle_file_events(&rx, parser, record_tx) { + Ok(_) => warn!("notify watch removed"), + Err(e) => error!("{}", e), + } + } + } + } +} + +/// Reads inode events and parses the corresponding file. +/// If the inode is (re)moved, it is unregistered from notify. +/// On unregistering, which can also be caused by deleting, +/// unmounting etc., the causing event's mask is returned. +fn handle_file_events( + rx: &Receiver, + parser: &CsvParser, + tx: &Sender>, +) -> notify::Result<()> { + loop { + match rx.recv().expect("inter-thread communication failed") { + DebouncedEvent::NoticeRemove(_) | DebouncedEvent::Remove(_) => { + return Ok(()); + } + DebouncedEvent::Error(error, _) => { + return Err(error); + } + _ => { + match parser.parse_file() { + Ok(recs) => tx.send(recs).expect("inter-thread communication failed"), + Err(io_err) => { + return Err(notify::Error::Io(io_err)); + } + } + } + } + } +} + +/// Reads command line arguments and calls the corresponding functions. +fn handle_cli_args() -> io::Result<()> { + #[cfg(unix)] + let unix_opts = + "-p, --pid [file] 'Daemonizes the process and writes a PID file' + -s, --syslog 'Logs via syslog' + "; + #[cfg(not(unix))] + let unix_opts = ""; + + let usage = &format!( + "{}-v... 'Repeat to set the level of verbosity' + -c, --conf [ini] 'The INI configuration file. Default: {}' + 'The CSV file with firewall bypass rules'" + , unix_opts, DEFAULT_CONFIG_PATH); + let matches = app_from_crate!().args_from_usage(usage).get_matches(); + + let log_lvl = match matches.occurrences_of("v") { + 0 => log::LogLevel::Error, + 1 => log::LogLevel::Warn, + 2 => log::LogLevel::Info, + 3 => log::LogLevel::Debug, + _ => log::LogLevel::Trace, + }; + + if matches.is_present("syslog") { + let app_name = Some(crate_name!()); + let filter = log_lvl.to_log_level_filter(); + #[cfg(unix)] + syslog::init(syslog::Facility::LOG_USER, filter, app_name).expect("error on logging initialization"); + log_panics::init(); + } else { + simple_logger::init_with_level(log_lvl).expect("error on logging initialization"); + } + + let csv_path = matches.value_of("csv").expect("required csv argument").to_string(); + let conf_path = matches.value_of("conf").unwrap_or(DEFAULT_CONFIG_PATH); + + let (conn, table, ports, inside_net) = conf::parse_file(conf_path)?; + let csv_parser = CsvParser::new(csv_path, inside_net); + + #[cfg(unix)] { + if matches.is_present("pid") { + let pid = unsafe { libc::fork() }; + if pid < 0 { + return Err(io::Error::last_os_error()); + } else if pid > 0 { + // exit the parent process + exit(0); + } + let pid_path = matches.value_of("pid").expect("pid file path missing"); + let mut file = File::create(pid_path)?; + write!(file, "{}", pid)?; + } + } + + // first file read that terminates the program on errors + let records = RefCell::new(csv_parser.parse_file()?); + + let listen_socket = net::TcpListener::bind(conn.socket)?; + info!("Listening on {}", listen_socket.local_addr()?); + + let (tx, rx) = mpsc::channel(); + thread::spawn(move || register_bypass_file(&csv_parser, &tx)); + + loop { + if let Err(e) = OfController::run(&rx, &listen_socket, &conn, &table, &ports, &records) { + error!("retry connection on error: {}", e); + } + } +} + +/// Entry function with top level error handling. +fn main() { + if let Err(e) = handle_cli_args() { + error!("{}", e); + exit(1); + } +} diff --git a/src/openflow/error.rs b/src/openflow/error.rs new file mode 100644 index 0000000..4901fb3 --- /dev/null +++ b/src/openflow/error.rs @@ -0,0 +1,32 @@ +use openflow::messages::*; +use std::error; +use std::fmt; +use std::io; +use std::result; + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + BadRequest(OfpBadRequestCode, Vec), + HelloFailed, +} + +impl error::Error for Error { + fn description(&self) -> &str { + "Deserialization error" + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(e) + } +} + +pub type Result = result::Result; diff --git a/src/openflow/messages/deserialize.rs b/src/openflow/messages/deserialize.rs new file mode 100644 index 0000000..42c3293 --- /dev/null +++ b/src/openflow/messages/deserialize.rs @@ -0,0 +1,145 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use openflow::error::*; +use openflow::messages::*; + +use std::io; +use std::mem::size_of; + +/// To be implemented by all OpenFlow message parts that are received. +pub trait Deserialize { + + /// The type to deserialize + type R; + + /// Deserialize the bytes buffer + /// Fails on providing a too small or too large buffer + fn deserialize(bytes: Vec) -> Result { + if Self::min_length() > bytes.len() || + Self::max_length() < bytes.len() { + return Err(Error::BadRequest(OfpBadRequestCode::BadLen, bytes)); + } + Self::deserialize_len_ok(bytes) + } + + /// Deserializes the byte buffer + /// Implementers can rely on the bytes buffer's size to be greater or equal Self::min_length() + fn deserialize_len_ok(bytes: Vec) -> Result; + + /// The minimum length of the message part in bytes + /// If Self::R contains dynamically sized fields, + /// you probably have to override this implementation. + fn min_length() -> usize { + size_of::() + } + + /// The maximum length of the message part in bytes + /// May not return a value greater than 0xFFF7 + /// If Self::R is fixed size, you probably have to + /// override this implementation. + fn max_length() -> usize { + 0xffff - OfpHeader::header_length() + } +} + +impl OfpHeader { + pub fn deserialize(bytes: &[u8; 8]) -> OfpHeader { + OfpHeader{ + version: bytes[0], + typ: bytes[1], + length: NetworkEndian::read_u16(&bytes[2..4]), + xid: NetworkEndian::read_u32(&bytes[4..]), + } + } +} + +impl Deserialize for OfpEchoRequest { + type R = OfpEchoRequest; + + fn deserialize_len_ok(bytes: Vec) -> Result { + Ok(OfpEchoRequest{ + arbitrary: bytes + }) + } + + fn min_length() -> usize { + 0 + } +} + +impl Deserialize for OfpSwitchFeatures { + type R = OfpSwitchFeatures; + + fn deserialize_len_ok(bytes: Vec) -> Result { + Ok(OfpSwitchFeatures{ + datapath_id: NetworkEndian::read_u64(&bytes[0..8]), + n_buffers: NetworkEndian::read_u32(&bytes[8..12]), + n_tables: bytes[12], + auxiliary_id: bytes[13], + pad: [bytes[14], bytes[15]], + capabilities: NetworkEndian::read_u32(&bytes[16..20]), + reserved: NetworkEndian::read_u32(&bytes[20..]), + }) + } + + fn max_length() -> usize { + 24 + } +} + +impl Deserialize for OfpErrorMsg { + type R = OfpErrorMsg; + + fn deserialize_len_ok(bytes: Vec) -> Result { + let typ = NetworkEndian::read_u16(&bytes[0..2]); + let code = NetworkEndian::read_u16(&bytes[2..4]); + Ok(OfpErrorMsg{ + typ: typ, code: code, data: bytes[4..].to_vec(), + }) + } + + fn min_length() -> usize { + 4 + } +} + +impl OfpErrorMsg { + /// Fail if the error message is OfpErrorType::BadAction + /// with OfpBadActionCode::BadOutPort + pub fn fail_on_bad_port(&self) -> io::Result<()> { + if self.typ == OfpErrorType::BadAction as u16 && + self.code == OfpBadActionCode::BadOutPort as u16 { + + // This error is caused by an `OfpFlowMod`. + // Deserialize its `out_port` field + let msg = if self.data.len() < 40 { + "A configured switch port number does not exist.".to_owned() + } else { + let out_port = NetworkEndian::read_u32(&self.data[36..40]); + format!("Switch port number {} does not exist.", out_port) + }; + Err(io::Error::new(io::ErrorKind::BrokenPipe, msg)) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn header_deserialization() { + let expected = OfpHeader{version: 3, typ: 1, length: 0x5234, xid: 0x12345678}; + let bytes = [3, 1, 0x52, 0x34, 0x12, 0x34, 0x56, 0x78]; + assert_eq!(expected, OfpHeader::deserialize(&bytes)); + } + + #[test] + fn min_lengths() { + assert_eq!(0, OfpEchoRequest::min_length()); + assert_eq!(24, OfpSwitchFeatures::min_length()); + assert_eq!(24, OfpSwitchFeatures::max_length()); + assert_eq!(4, OfpErrorMsg::min_length()); + } +} diff --git a/src/openflow/messages/mod.rs b/src/openflow/messages/mod.rs new file mode 100644 index 0000000..090815e --- /dev/null +++ b/src/openflow/messages/mod.rs @@ -0,0 +1,542 @@ +pub mod deserialize; +pub mod serialize; + +use byteorder::{ByteOrder, NetworkEndian}; +use bypass_csv::IpProtocol; +use ipnetwork::Ipv4Network; +use std::fmt; + +pub enum ProtocolEndpoint { + Src, + Dst, +} + +impl OfpErrorMsg { + fn first_64_bytes(header: &[u8], body: &[u8]) -> Vec { + let mut buf = vec![]; + buf.extend_from_slice(header); + let target_length = 64 - header.len(); + let shrunk_body = if body.len() < target_length { + body + } else { + &body[0..target_length] + }; + buf.extend_from_slice(shrunk_body); + buf + } + + pub fn new_hello_failed() -> OfpErrorMsg { + OfpErrorMsg{ + typ: OfpErrorType::HelloFailed as u16, + code: OfpHelloFailedCode::Incompatible as u16, + data: vec![], + } + } + + pub fn new_bad_request(code: OfpBadRequestCode, header: &[u8], body: &[u8]) -> OfpErrorMsg { + OfpErrorMsg{ + typ: OfpErrorType::BadRequest as u16, + code: code as u16, + data: Self::first_64_bytes(header, body), + } + } + pub fn check_table_full(&self) -> bool { + self.typ == OfpErrorType::FlowModFailed as u16 && + self.code == OfpFlowModFailedCode::TableFull as u16 + } +} + +impl fmt::Display for OfpErrorMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let t = self.typ; + let typ = + if t == OfpErrorType::HelloFailed as u16 {OfpErrorType::HelloFailed} + else if t == OfpErrorType::BadRequest as u16 {OfpErrorType::BadRequest} + else if t == OfpErrorType::BadAction as u16 {OfpErrorType::BadAction} + else if t == OfpErrorType::BadInstruction as u16 {OfpErrorType::BadInstruction} + else if t == OfpErrorType::BadMatch as u16 {OfpErrorType::BadMatch} + else if t == OfpErrorType::FlowModFailed as u16 {OfpErrorType::FlowModFailed} + else if t == OfpErrorType::Experimenter as u16 {OfpErrorType::Experimenter} + else {return write!(f, "OpenFlow Error: type({}), code({})", self.typ, self.code);} + ; + write!(f, "OpenFlow Error: {:?}, code({})", typ, self.code) + } +} + +#[derive(Debug)] +pub struct OfpEchoRequest { + pub arbitrary: Vec, +} + +#[derive(Debug)] +pub struct OfpEchoReply { + pub arbitrary: Vec, +} + +#[derive(Debug, Clone)] +pub struct OfpOxmTlv { + /// Header class + class: OfpOxmClass, + /// Header field + field: OxmOfbMatchFields, + /// Header hasmask + hasmask: bool, + /// Body + body: Vec, +} + +/* This is based on the openflow.h from OpenFlow Switch Specification 1.3.5. + * + * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior University + * Copyright (c) 2011, 2012 Open Networking Foundation + * + * We are making the OpenFlow specification and associated documentation + * (Software) available for public use and benefit with the expectation + * that others will use, modify and enhance the Software and contribute + * those enhancements back to the community. However, since we would + * like to make the Software available for broadest use, with as few + * restrictions as possible permission is hereby granted, free of + * charge, to any person obtaining a copy of this Software to deal in + * the Software under the copyrights 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. + * + * The name and trademarks of copyright holder(s) may NOT be used in + * advertising or publicity pertaining to the Software or any + * derivatives without specific, written prior permission. + */ + +/// Version number: +/// OpenFlow versions released: 0x01 = 1.0 ; 0x02 = 1.1 ; 0x03 = 1.2; 0x04 = 1.3 +/// The most significant bit in the version field is reserved and must be set to zero. +pub const OFP_VERSION: u8 = 0x04; + +/// Official IANA registered port for OpenFlow. +pub const OFP_TCP_PORT: u16 = 6653; + +/// Maximum number of physical and logical switch ports. Ports are numbered starting from 1. +pub const OFPP_MAX: u32 = 0xffff_ff00; +/// Special value used in some requests when no port is specified (i.e. wildcarded). +pub const OFPP_ANY: u32 = 0xffff_ffff; + +pub enum OfpType { +/* Immutable messages. */ + + /// Symmetric message + Hello = 0, + /// Symmetric message + Error = 1, + /// Symmetric message + EchoRequest = 2, + /// Symmetric message + EchoReply = 3, + +/* Switch configuration messages. */ + + /// Controller/switch message + FeaturesRequest = 5, + /// Controller/switch message + FeaturesReply = 6, + +/* Asynchronous messages. */ + + /// Async message + PortStatus = 12, + +/* Controller command messages. */ + + /// Controller/switch message + FlowMod = 14, + +/* Asynchronous message configuration. */ + + /// Controller/switch message + SetAsync = 28, +} + +/// Header on all OpenFlow packets. +#[derive(Debug, PartialEq)] +pub struct OfpHeader { + /// OFP_VERSION. + pub version: u8, + /// This packet's OfpType. + pub typ: u8, + /// This packet's length including this OfpHeader. + length: u16, + /// Transaction id associated with this packet. + /// Replies use the same id as was in the request + /// to facilitate pairing. + pub xid: u32, +} + +/// Switch features. +#[derive(Debug, PartialEq)] +pub struct OfpSwitchFeatures { + /// Datapath unique ID. The lower 48-bits are for + /// a MAC address, while the upper 16-bits are + /// implementer-defined. + pub datapath_id: u64, + /// Max packets buffered at once. + n_buffers: u32, + /// Number of tables supported by datapath. + n_tables: u8, + /// Identify auxiliary connections + auxiliary_id: u8, + /// Align to 64-bits. + pad: [u8;2], + +/* Features. */ + + /// Bitmap of support OfpCapabilities. + capabilities: u32, + reserved: u32, +} + +/* ## -------------------------- ## */ +/* ## OpenFlow Extensible Match. ## */ +/* ## -------------------------- ## */ + +/// The match type indicates the match structure (set of fields that compose the +/// match) in use. The match type is placed in the type field at the beginning +/// of all match structures. The "OpenFlow Extensible Match" type corresponds +/// to OXM TLV format described below and must be supported by all OpenFlow +/// switches. Extensions that define other match types may be published on the +/// ONF wiki. Support for extensions is optional. +pub enum OfpMatchType { + /// OpenFlow Extensible Match + Oxm = 1, +} + +/// Fields to match against flows +#[derive(Debug, Clone)] +pub struct OfpMatch { + /// One of OfpMatchType + typ: u16, + // length(): Length of OfpMatch (excluding padding) + /* Followed by: + * - Exactly (length - 4) (possibly 0) bytes containing OXM TLVs, then + * - Exactly ((length + 7)/8*8 - length) (between 0 and 7) bytes of + * all-zero bytes + * In summary, OfpMatch is padded as needed, to make its overall size + * a multiple of 8, to preserve alignment in structures using it. + */ + /// 0 or more OXM match fields + oxm_fields: Vec, + // Zero bytes - see above for sizing +} + +/// Construction of an OXM TLV. +impl OfpOxmTlv { + fn new(field: OxmOfbMatchFields, hasmask: bool, body: Vec) -> OfpOxmTlv { + OfpOxmTlv { + class: OfpOxmClass::OpenflowBasic, + hasmask: hasmask, + field: field, + body: body, + } + } + + /// OpenFlow port on which the packet was received. + /// May be a physical port, a logical port, or the reserved port OFPP_LOCAL + /// Prereqs: None. + /// Format: 32-bit integer in network byte order. + pub fn new_in_port(in_port: u32) -> OfpOxmTlv { + let mut port_bytes = vec![0;4]; + NetworkEndian::write_u32(&mut port_bytes, in_port); + OfpOxmTlv::new(OxmOfbMatchFields::InPort, false, port_bytes) + } + + /// Packet's Ethernet type. + /// Prereqs: None. + /// Format: 16-bit integer in network byte order. + pub fn new_eth_type_ipv4() -> OfpOxmTlv { + OfpOxmTlv::new(OxmOfbMatchFields::EthType, false, vec![8, 0]) + } + + /// The "protocol" byte in the IP header. + /// Prereqs: OxmOfbMatchFields::EthType must be either 0x0800 or 0x86dd. + /// Format: 8-bit integer. + pub fn new_ip_proto(proto: &IpProtocol) -> OfpOxmTlv { + OfpOxmTlv::new(OxmOfbMatchFields::IpProto, false, vec![*proto as u8]) + } + + /// The source or destination address in the IP header. + /// Prereqs: OxmOfbMatchFields::EthType must match 0x0800 exactly. + /// Format: 32-bit integer in network byte order. + /// Masking: Arbitrary masks. + pub fn new_ipv4(cidr: &Ipv4Network, endpoint: &ProtocolEndpoint) -> OfpOxmTlv { + let direction = match *endpoint { + ProtocolEndpoint::Src => OxmOfbMatchFields::Ipv4Src, + ProtocolEndpoint::Dst => OxmOfbMatchFields::Ipv4Dst, + }; + let mut oxm_val_and_mask = vec![]; + oxm_val_and_mask.extend_from_slice(&cidr.network().octets()); + oxm_val_and_mask.extend_from_slice(&cidr.mask().octets()); + OfpOxmTlv::new(direction, true, oxm_val_and_mask) + } + + /// The source or destination port in the TCP/UDP header. + /// Prereqs: + /// OxmOfbMatchFields::EthType must be either 0x0800 or 0x86dd. + /// OxmOfbMatchFields::IpProto must match 6 or 17 exactly. + /// Format: 16-bit integer in network byte order. + pub fn new_port(proto: &IpProtocol, port: u16, endpoint: &ProtocolEndpoint) -> Option { + let field = match *proto { + IpProtocol::Tcp => match *endpoint { + ProtocolEndpoint::Src => OxmOfbMatchFields::TcpSrc, + ProtocolEndpoint::Dst => OxmOfbMatchFields::TcpDst, + }, + IpProtocol::Udp => match *endpoint { + ProtocolEndpoint::Src => OxmOfbMatchFields::UdpSrc, + ProtocolEndpoint::Dst => OxmOfbMatchFields::UdpDst, + }, + _ => return None, + }; + let mut port_bytes = vec![0;2]; + NetworkEndian::write_u16(&mut port_bytes, port); + Some(OfpOxmTlv::new(field, false, port_bytes)) + } + +} + +/// OXM Class IDs. +/// The high order bit differentiate reserved classes from member classes. +/// Classes 0x0000 to 0x7FFF are member classes, allocated by ONF. +/// Classes 0x8000 to 0xFFFE are reserved classes, reserved for standardisation. +#[derive(Debug, Clone, Copy)] +enum OfpOxmClass { + /// Basic class for OpenFlow + OpenflowBasic = 0x8000, +} + +/// OXM Flow match field types for OpenFlow basic class. +#[derive(Debug, Clone, Copy)] +enum OxmOfbMatchFields { + /// Switch input port. + InPort = 0, + /// Ethernet frame type. + EthType = 5, + /// IP protocol. + IpProto = 10, + /// IPv4 source address. + Ipv4Src = 11, + /// IPv4 destination address. + Ipv4Dst = 12, + /// TCP source port. + TcpSrc = 13, + /// TCP destination port. + TcpDst = 14, + /// UDP source port. + UdpSrc = 15, + /// UDP destination port. + UdpDst = 16, +} + +/// Values for 'type' in `OfpErrorMsg`. These values are immutable: they will +/// not change in future versions of the protocol (although new values may be added). +#[derive(Debug)] +pub enum OfpErrorType { + /// Hello protocol failed. + HelloFailed = 0, + /// Request was not understood. + BadRequest = 1, + /// Error in action description. + BadAction = 2, + /// Error in instruction list. + BadInstruction = 3, + /// Error in match. + BadMatch = 4, + /// Problem modifying flow entry. + FlowModFailed = 5, + /// Experimenter error messages. + Experimenter = 0xffff, +} + +/// `OfpErrorMsg` 'code' values for `OfpErrorType::HelloFailed`. +/// 'data' contains an ASCII text string that may give failure details. +pub enum OfpHelloFailedCode { + /// No compatible version. + Incompatible = 0, +} + +/// `OfpErrorMsg` 'code' values for `OfpErrorType::BadRequest`. +/// 'data' contains at least the first 64 bytes of the failed request. +#[derive(Debug)] +pub enum OfpBadRequestCode { + /// ofp_header.version not supported. + BadVersion = 0, + /// ofp_header.type not supported. + BadType = 1, + /// Wrong request length for type. + BadLen = 6, +} + +/* ## ----------------- ## */ +/* ## OpenFlow Actions. ## */ +/* ## ----------------- ## */ + +pub enum OfpActionType { + /// Output to switch port. + Output = 0, +} + +/// Action structure for `OfpActionType::Output`, which sends packets out 'port'. +/// A `max_len` of zero means no bytes of the packet should be sent to the controller. +#[derive(Debug)] +pub struct OfpActionOutput { + /// OfpActionType::Output. + typ: u16, + /// Length is 16. The length includes the header and + /// any padding used to make the action 64-bit aligned. + len: u16, + /// Output port. + port: u32, + /// Max length to send to controller. + max_len: u16, + /// Pad to 64 bits. + pad: [u8; 6], +} + +/* ## ---------------------- ## */ +/* ## OpenFlow Instructions. ## */ +/* ## ---------------------- ## */ + +pub enum OfpInstructionType { + /// Applies the action(s) immediately + ApplyActions = 4, +} + +/// Instruction structure for `OfpInstructionType::ApplyActions` +#[derive(Debug)] +pub struct OfpInstructionActions { + /// One of OfpInstructionType + typ: u16, + // len: Length of this struct in bytes. The length includes the header + // and any padding used to make the instruction 64-bit aligned. + /// Align to 64-bits + pad: [u8; 4], + /// 0 or more actions associated with `OfpInstructionType::ApplyActions` + actions: Vec, +} + + +/* ## --------------------------- ## */ +/* ## OpenFlow Flow Modification. ## */ +/* ## --------------------------- ## */ + +#[derive(Clone, Copy)] +pub enum OfpFlowModCommand { + /// New flow. + Add = 0, + /// Delete entry strictly matching wildcards and priority. + DeleteStrict = 4, +} + +/// Value used in `idle_timeout` and `hard_timeout` to indicate that the entry is permanent. +pub const OFP_FLOW_PERMANENT : u16 = 0; + +/// By default, choose a priority in the middle. +pub const OFP_DEFAULT_PRIORITY : u16 = 0x8000; + +/// Flow setup and teardown (controller -> datapath). +#[derive(Debug)] +pub struct OfpFlowMod { + /// Opaque controller-issued identifier. + cookie: u64, + /// Mask used to restrict the cookie bits + /// that must match when the command is + /// OfpFlowModCommand::Modify* or OfpFlowModCommand::Delete*. + /// A value of 0 indicates no restriction. + cookie_mask: u64, + /// ID of the table to put the flow in. + /// For OfpFlowModCommand::Delete* commands, + /// OFPTT_ALL can also be used to delete + /// matching flows from all tables. + table_id: u8, + /// One of OfpFlowModCommand. + command: u8, + /// Idle time before discarding (seconds). + idle_timeout: u16, + /// Max time before discarding (seconds). + hard_timeout: u16, + /// Priority level of flow entry. + priority: u16, + /// Buffered packet to apply to, or + /// OFP_NO_BUFFER. + /// Not meaningful for OfpFlowModCommand::Delete*. + buffer_id: u32, + /// For OfpFlowModCommand::Delete* commands, require + /// matching entries to include this as an + /// output port. A value of OfpPortNo::Any + /// indicates no restriction. + out_port: u32, + /// For OfpFlowModCommand::Delete* commands, require + /// matching entries to include this as an + /// output group. A value of OfpPortNo::Any + /// indicates no restriction. + out_group: u32, + /// Bitmap of OfpFlowModFlags. + flags: u16, + pad: [u8; 2], + /// Fields to match. Variable size. + match_field: OfpMatch, + + /* The variable size and padded match is always followed by instructions. */ + + /// Instruction set - 0 or more. + /// The length of the instruction + /// set is inferred from the + /// length field in the header. + instructions: Vec, +} + +pub const OFP_NO_BUFFER : u32 = 0xffff_ffff; + +/// `OfpErrorMsg` 'code' values for `OfpErrorType::BadAction`. +/// 'data' contains at least the first 64 bytes of the failed request. +pub enum OfpBadActionCode { + /// Poblem validating output port + BadOutPort = 4, +} + +/// `OfpErrorMsg` 'code' values for `OfpErrorType::FlowModFailed`. +/// 'data' contains at least the first 64 bytes of the failed request. +#[derive(Debug)] +pub enum OfpFlowModFailedCode { + /// Flow not added because table was full. + TableFull = 1, +} + +/// Error message (datapath -> controller). +#[derive(Debug)] +pub struct OfpErrorMsg { + typ: u16, + code: u16, + /// Variable-length data. Interpreted based on the type and code. No padding. + data: Vec, +} + +/// Asynchronous message configuration. +#[derive(Default)] +pub struct OfpAsyncConfig { + /// Bitmasks of OFPR_* values. + packet_in_mask: [u32; 2], + /// Bitmasks of OFPPR_* values. + port_status_mask: [u32; 2], + /// Bitmasks of OFPRR_* values. + flow_removed_mask: [u32; 2], +} diff --git a/src/openflow/messages/serialize.rs b/src/openflow/messages/serialize.rs new file mode 100644 index 0000000..789c16b --- /dev/null +++ b/src/openflow/messages/serialize.rs @@ -0,0 +1,332 @@ +use byteorder::{NetworkEndian, WriteBytesExt}; +use bypass_csv::BypassRecord; +use openflow::messages::*; +use std::convert::From; +use std::io; +use std::io::Write; +use std::mem::size_of; + +impl OfpHeader { + pub fn new(typ: OfpType, xid: u32) -> OfpHeader { + OfpHeader{ + version: OFP_VERSION, + typ: typ as u8, + length: OfpHeader::header_length() as u16, + xid: xid, + } + } + + pub fn header_length() -> usize { + size_of::() + } + + pub fn body_length(&self) -> usize { + self.length as usize - OfpHeader::header_length() + } + + pub fn serialize(&self, stream: &mut S) -> io::Result<()> { + stream.write_all(&[self.version, self.typ])?; + stream.write_u16::(self.length)?; + stream.write_u32::(self.xid) + } +} + +impl OfpMatch { + /// Constructs an empty match. + pub fn new() -> OfpMatch { + OfpMatch { + typ: OfpMatchType::Oxm as u16, + oxm_fields: vec![], + } + } + + /// Adds a single match field to the match. + pub fn add_tlv(&mut self, oxm_tlv: OfpOxmTlv) -> &mut OfpMatch { + self.oxm_fields.push(oxm_tlv); + self + } + + /// Length of OfpMatch (excluding padding) + fn length(&self) -> usize { + let mut length = 4; + for oxm in &self.oxm_fields { + length += oxm.length(); + } + length + } + + /// Padding of OfpMatch + fn pad_len(&self) -> usize { + let len = self.length(); + (len + 7)/8*8 - len + } + + pub fn serialize(&self, stream: &mut S) -> io::Result<()> { + stream.write_u16::(self.typ)?; + stream.write_u16::(self.length() as u16)?; + for oxm in &self.oxm_fields { + oxm.serialize(stream)?; + } + // make its overall size a multiple of 8; fill with zeros + stream.write_all(&vec![0; self.pad_len()]) + } +} + +impl<'a> From<&'a BypassRecord> for OfpMatch { + fn from(rec: &'a BypassRecord) -> Self { + let mut mat = OfpMatch::new(); + mat.add_tlv(OfpOxmTlv::new_eth_type_ipv4()); + if let Some(ref ip) = rec.src_ip { + let src_ip = OfpOxmTlv::new_ipv4(ip, &ProtocolEndpoint::Src); + mat.add_tlv(src_ip); + } + if let Some(ref ip) = rec.dst_ip { + let dst_ip = OfpOxmTlv::new_ipv4(ip, &ProtocolEndpoint::Dst); + mat.add_tlv(dst_ip); + } + if let Some(ref proto) = rec.proto { + let oxm_proto = OfpOxmTlv::new_ip_proto(proto); + mat.add_tlv(oxm_proto); + if let Some(port) = rec.src_port { + if let Some(oxm_port) = OfpOxmTlv::new_port(proto, port, &ProtocolEndpoint::Src) { + mat.add_tlv(oxm_port); + } + } + if let Some(port) = rec.dst_port { + if let Some(oxm_port) = OfpOxmTlv::new_port(proto, port, &ProtocolEndpoint::Dst) { + mat.add_tlv(oxm_port); + } + } + } + mat + } +} + +impl OfpOxmTlv { + fn length(&self) -> usize { + 4 + self.body.len() as usize + } + + pub fn serialize(&self, stream: &mut S) -> io::Result<()> { + let class = self.class as u32; + let hasmask_u32 = if self.hasmask {1} else {0}; + let header = ((class) << 16) | ((self.field as u32) << 9) | (hasmask_u32 << 8) | self.body.len() as u32; + stream.write_u32::(header)?; + stream.write_all(&self.body) + } +} + +impl OfpActionOutput { + pub fn new(port: u32) -> OfpActionOutput { + OfpActionOutput { + typ: OfpActionType::Output as u16, + len: size_of::() as u16, + port: port, + max_len: 0, + pad: [0; 6], + } + } + + pub fn serialize(&self, stream: &mut S) -> io::Result<()> { + stream.write_u16::(self.typ)?; + stream.write_u16::(self.len)?; + stream.write_u32::(self.port)?; + stream.write_u16::(self.max_len)?; + stream.write_all(&self.pad) + } +} + +impl OfpInstructionActions { + pub fn new(actions: Vec) -> OfpInstructionActions { + OfpInstructionActions { + typ: OfpInstructionType::ApplyActions as u16, + pad: [0;4], + actions: actions, + } + } + + pub fn serialize(&self, stream: &mut S) -> io::Result<()> { + let actions = &mut vec![]; + for action in &self.actions { + action.serialize(actions)?; + } + stream.write_u16::(self.typ)?; + stream.write_u16::(8 + actions.len() as u16)?; + stream.write_all(&self.pad)?; + stream.write_all(actions) + } +} + +impl OfpFlowMod { + pub fn new( + command: OfpFlowModCommand, + table_id: u8, + priority: u16, + out_port: u32, + match_field: OfpMatch, + instructions: Vec + ) -> OfpFlowMod { + OfpFlowMod { + cookie: 0, + cookie_mask: 0, + table_id: table_id, + command: command as u8, + idle_timeout: OFP_FLOW_PERMANENT, + hard_timeout: OFP_FLOW_PERMANENT, + priority: priority, + buffer_id: OFP_NO_BUFFER, + out_port: out_port, + out_group: OFPP_ANY, + flags: 0, + pad: [0; 2], + match_field: match_field, + instructions: instructions, + } + } +} + +/// An OpenFlow packet. Must be implemented for all OpenFlow messsages that are sent. +pub trait OfpPacket { + fn header(&self, body_length: usize, xid: u32) -> OfpHeader { + OfpHeader { + version: OFP_VERSION, + typ: Self::typ() as u8, + length: (OfpHeader::header_length() + body_length) as u16, + xid: xid, + } + } + + fn typ() -> OfpType; + + /// Serializes this packet with network byte order. + /// The xid is used as its header's transaction id. + fn serialize(&self, stream: &mut S, xid: u32) -> io::Result<()> { + let mut body = vec![]; + self.serialize_body(&mut body)?; + let header = self.header(body.len(), xid); + header.serialize(stream)?; + stream.write_all(&body) + } + + fn serialize_body(&self, stream: &mut S) -> io::Result<()>; +} + +impl OfpPacket for OfpEchoReply { + + fn typ() -> OfpType { + OfpType::EchoReply + } + + fn serialize_body(&self, stream: &mut S) -> io::Result<()> { + stream.write_all(&self.arbitrary) + } +} + +impl OfpPacket for OfpErrorMsg { + + fn typ() -> OfpType { + OfpType::Error + } + + fn serialize_body(&self, stream: &mut S) -> io::Result<()> { + stream.write_u16::(self.typ)?; + stream.write_u16::(self.code)?; + stream.write_all(&self.data) + } +} + +impl OfpPacket for OfpFlowMod { + + fn typ() -> OfpType { + OfpType::FlowMod + } + + fn serialize_body(&self, stream: &mut S) -> io::Result<()> { + stream.write_u64::(self.cookie)?; + stream.write_u64::(self.cookie_mask)?; + stream.write_all(&[self.table_id, self.command])?; + stream.write_u16::(self.idle_timeout)?; + stream.write_u16::(self.hard_timeout)?; + stream.write_u16::(self.priority)?; + stream.write_u32::(self.buffer_id)?; + stream.write_u32::(self.out_port)?; + stream.write_u32::(self.out_group)?; + stream.write_u16::(self.flags)?; + stream.write_all(&self.pad)?; + self.match_field.serialize(stream)?; + for instr in &self.instructions { + instr.serialize(stream)?; + } + Ok(()) + } +} + +impl OfpPacket for OfpAsyncConfig { + + fn typ() -> OfpType { + OfpType::SetAsync + } + + fn serialize_body(&self, stream: &mut S) -> io::Result<()> { + stream.write_u32::(self.packet_in_mask[0])?; + stream.write_u32::(self.packet_in_mask[1])?; + stream.write_u32::(self.port_status_mask[0])?; + stream.write_u32::(self.port_status_mask[1])?; + stream.write_u32::(self.flow_removed_mask[0])?; + stream.write_u32::(self.flow_removed_mask[1]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn echo_reply_header() { + let xid = 42; + let expected = OfpHeader{ + version: 4, typ: 3, length: 8, xid: xid + }; + let testee = OfpEchoReply{arbitrary: vec![]}; + assert_eq!(expected, testee.header(0, xid)); + } + + #[test] + fn echo_reply_body_serialization() { + let arbitrary = vec![1,2,3,4]; + let testee = OfpEchoReply{arbitrary: arbitrary}; + let mut ser = vec![]; + testee.serialize_body(&mut ser).unwrap(); + assert_eq!(vec![1,2,3,4], ser); + assert_eq!(12, testee.header(ser.len(), 1).length); + } + + #[test] + fn oxm_tlv_serialization() { + let testee = OfpOxmTlv::new_in_port(0x11223344); + assert_eq!(8, testee.length()); + assert_eq!(vec![0x11,0x22,0x33,0x44], testee.body); + } + + #[test] + fn match_serialization() { + let tlv = OfpOxmTlv::new_in_port(1); + let mut testee = OfpMatch::new(); + testee.add_tlv(tlv); + assert_eq!(12, testee.length()); + assert_eq!(4, testee.pad_len()); + let mut ser = vec![]; + testee.serialize(&mut ser).unwrap(); + assert_eq!(16, ser.len()); + } + + #[test] + fn action_output_serialization() { + let testee = OfpActionOutput::new(0x11223344); + let mut ser = vec![]; + testee.serialize(&mut ser).unwrap(); + assert_eq!(16, ser.len()); + assert_eq!(vec![0,0,0,16,0x11,0x22,0x33,0x44,0,0,0,0,0,0,0,0], ser); + } + +} diff --git a/src/openflow/mod.rs b/src/openflow/mod.rs new file mode 100644 index 0000000..0fee986 --- /dev/null +++ b/src/openflow/mod.rs @@ -0,0 +1,307 @@ +/*! +Implements an OpenFlow Controller with protocol version 0x04 compatibility. +It provides only a small subset of the OpenFlow features to push some rules +to a switch proactively. +*/ + +pub mod error; +pub mod messages; + +use bypass_csv::BypassRecord; +use conf::OfConnection; +use conf::OfPort; +use conf::OfTable; +use conf::Ports; + +use openflow::error::*; +use openflow::messages::*; +use openflow::messages::deserialize::*; +use openflow::messages::serialize::*; + +use rand; + +use std::cell::RefCell; +use std::collections::HashSet; +use std::io; +use std::io::{Read, Write}; +use std::ops::Sub; +use std::net::{TcpStream, TcpListener}; +use std::sync::mpsc::{Receiver, TryRecvError}; +use std::time::{Duration, Instant}; + +use tls_api; +use tls_api::{TlsAcceptor, TlsStream}; + +const BASIC_FLOW_PRIORITY: u16 = 1; +const FLOW_REFRESH_SECS: u64 = 3600; + +fn gen_xid() -> u32 { + let xid = rand::random(); + trace!("Using xid {} for the outgoing message", xid); + xid +} +#[derive(Debug)] +enum Stream { + Tcp(TcpStream), + Tls(TlsStream), +} +impl Stream { + fn from(connection: &OfConnection, stream: TcpStream) -> tls_api::Result { + #[cfg(feature = "tls")] { + if let Some(acc) = connection.tls_acceptor()? { + return match acc.accept(stream) { + Ok(s) => Ok(Stream::Tls(s)), + Err(tls_api::HandshakeError::Failure(e)) => { + Err(e) + } + Err(tls_api::HandshakeError::Interrupted(_)) => { + Err(tls_api::Error::new_other("TLS stream was interrupted")) + } + } + } + } + Ok(Stream::Tcp(stream)) + } +} +impl Read for Stream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match *self { + Stream::Tcp(ref mut s) => s.read(buf), + Stream::Tls(ref mut s) => s.read(buf), + } + } +} +impl Write for Stream { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + Stream::Tcp(ref mut s) => s.write(buf), + Stream::Tls(ref mut s) => s.write(buf), + } + } + fn flush(&mut self) -> io::Result<()> { + match *self { + Stream::Tcp(ref mut s) => s.flush(), + Stream::Tls(ref mut s) => s.flush(), + } + } +} + +#[derive(Debug)] +pub struct OfController<'a> { + table: &'a OfTable, + ports: &'a Ports, + stream: Stream, + hello_received: bool, + records: &'a RefCell>, + rx: &'a Receiver>, + refresh_timer: Instant, +} + +impl<'a> OfController<'a> { + + fn send_flow_mod( + &mut self, + cmd: OfpFlowModCommand, + mut match_field: OfpMatch, + input_port: &OfPort, + output_port: &OfPort, + prio: u16 + ) -> io::Result<()> { + let in_tlv = OfpOxmTlv::new_in_port(input_port.of_port); + match_field.add_tlv(in_tlv); + + let output = OfpActionOutput::new(output_port.of_port); + let instr = vec![OfpInstructionActions::new(vec![output])]; + + let flow_mod = OfpFlowMod::new(cmd, self.table.id, prio, output_port.of_port, match_field, instr); + + trace!("Outgoing message: {:?}", flow_mod); + flow_mod.serialize(&mut self.stream, gen_xid()) + } + + fn add_basic_flow_mods(&mut self) -> io::Result<()> { + let p = self.ports; + let cmd = OfpFlowModCommand::Add; + let prio = BASIC_FLOW_PRIORITY; + self.send_flow_mod(cmd, OfpMatch::new(), &p.inside, &p.fw_in, prio)?; + self.send_flow_mod(cmd, OfpMatch::new(), &p.fw_in, &p.inside, prio)?; + self.send_flow_mod(cmd, OfpMatch::new(), &p.outside, &p.fw_out, prio)?; + self.send_flow_mod(cmd, OfpMatch::new(), &p.fw_out, &p.outside, prio) + } + + fn send_bypass_flow_mods( + &mut self, + cmd: OfpFlowModCommand, + records: &HashSet + ) -> io::Result<()> { + for rec in records { + let mat = OfpMatch::from(rec); + let (input, output) = self.ports.in_out_from_direction(rec.direction); + self.send_flow_mod(cmd, mat, input, output, OFP_DEFAULT_PRIORITY)?; + } + Ok(()) + } + + fn handle_ofp_message(&mut self, header: &OfpHeader) -> Result<()> { + debug!("Incoming message: {:?}", header); + + // Read the body + let mut buf = vec![0; header.body_length()]; + self.stream.read_exact(&mut buf)?; + + // Process the message + let t = header.typ; + if t == OfpType::Hello as u8 { + // simple version discovery + if header.version < OFP_VERSION { + return Err(Error::HelloFailed); + } + self.hello_received = true; + let req = OfpHeader::new(OfpType::FeaturesRequest, gen_xid()); + req.serialize(&mut self.stream)?; + } + else if !self.hello_received || header.version != OFP_VERSION { + return Err(Error::BadRequest(OfpBadRequestCode::BadVersion, buf)); + } + else if t == OfpType::EchoRequest as u8 { + // The EchoReply takes the same body byte stream as the EchoRequest + let req = OfpEchoRequest::deserialize(buf)?; + let rep = OfpEchoReply{arbitrary: req.arbitrary}; + rep.serialize(&mut self.stream, header.xid)?; + } + else if t == OfpType::FeaturesReply as u8 { + let features = OfpSwitchFeatures::deserialize(buf)?; + let datapath_id = features.datapath_id; + info!("The connected switch identified itself with datapath id {}", datapath_id); + + // unsubscribe from all messages + let async_conf = OfpAsyncConfig::default(); + async_conf.serialize(&mut self.stream, gen_xid())?; + + self.add_basic_flow_mods()?; + + self.send_bypass_flow_mods(OfpFlowModCommand::Add, &self.records.borrow())?; + } + else if t == OfpType::PortStatus as u8 { + // Ignore. Can be received before unsubscribing via OfpType::SetAsync. + trace!("Ignoring Port Status Message"); + } + else if t == OfpType::Error as u8 { + let error = OfpErrorMsg::deserialize(buf)?; + error.fail_on_bad_port()?; + if error.check_table_full() { + error!( + "Table 0 does not have enough free memory for a new Flow. {} {}", + "The implementation could be changed to allow for using more tables.", + error + ); + } else { + error!("Unexpected {}", error); + debug!("Full error message: {:?}", error); + } + } + else { + debug!("Cannot interpret message of type {}. Full message body: {:?}", header.typ, buf); + return Err(Error::BadRequest(OfpBadRequestCode::BadType, buf)); + } + Ok(()) + } + + /// Checks the inter-thread channel for new messages, + /// constructs and sends a flow mod for each new/changed `BypassRecord`. + /// Refreshes all flow entries each hour. + fn handle_bypass_records(&mut self) -> io::Result<()> { + match self.rx.try_recv() { + Ok(new_records) => { + + let del_set = self.records.borrow().sub(&new_records); + self.send_bypass_flow_mods(OfpFlowModCommand::DeleteStrict, &del_set)?; + for to_delete in del_set { + self.records.borrow_mut().remove(&to_delete); + } + + let add_set = new_records.sub(&self.records.borrow()); + self.send_bypass_flow_mods(OfpFlowModCommand::Add, &add_set)?; + for to_add in add_set { + self.records.borrow_mut().insert(to_add); + } + } + Err(TryRecvError::Empty) => {} + Err(TryRecvError::Disconnected) => { + panic!("inter-thread communication failed"); + } + }; + if self.refresh_timer.elapsed() > Duration::from_secs(FLOW_REFRESH_SECS) { + self.send_bypass_flow_mods(OfpFlowModCommand::Add, &self.records.borrow())?; + self.refresh_timer = Instant::now(); + } + Ok(()) + } + + fn handle_of_errors(&mut self, error: Error, header: &OfpHeader, header_buf: &[u8]) -> io::Result<()> { + let err_msg = match error { + Error::Io(e) => return Err(e), + Error::HelloFailed => { + let msg = format!("The connected switch supports only OpenFlow protocol version {:x}", header.version); + let err = OfpErrorMsg::new_hello_failed(); + err.serialize(&mut self.stream, header.xid)?; + return Err(io::Error::new(io::ErrorKind::BrokenPipe, msg)); + } + Error::BadRequest(code, buf) => { + OfpErrorMsg::new_bad_request(code, header_buf, &buf) + } + + }; + debug!("Outgoing error message: {:?}", err_msg); + err_msg.serialize(&mut self.stream, header.xid) + } + + /// Manages the lifetime of a controller by accepting a TCP connection, + /// sending a Hello message and handling incoming messages both from + /// network and inter-thread channel. + /// Is an implicit factory for OfController instances. + pub fn run( + rx: &Receiver>, + listener: &TcpListener, + connection: &OfConnection, + table: &OfTable, + ports: &Ports, + records: &RefCell>, + ) -> tls_api::Result<()> { + + let (stream, addr) = listener.accept()?; + info!("connection from {}", addr); + + let stream = Stream::from(connection, stream)?; + + let mut ctrl = OfController { + table: table, + ports: ports, + stream: stream, + hello_received: false, + records: records, + rx: rx, + refresh_timer: Instant::now(), + }; + + // Send a Hello + // Rely on the simple version: If one Hello is empty, + // the smaller OfpHeader::version is agreed upon + let hello = OfpHeader::new(OfpType::Hello, gen_xid()); + hello.serialize(&mut ctrl.stream)?; + + loop { + // Read the header + let mut hbuf = [0; 8]; + ctrl.stream.read_exact(&mut hbuf)?; + let header = OfpHeader::deserialize(&hbuf); + let msg_res = ctrl.handle_ofp_message(&header); + if msg_res.is_err() { + ctrl.handle_of_errors(msg_res.unwrap_err(), &header, &hbuf)?; + } + if ctrl.hello_received { + ctrl.handle_bypass_records()?; + } + } + } +}