Repository: superkkt/cherry
Branch: master
Commit: ea98d7c26dbc
Files: 698
Total size: 6.1 MB
Directory structure:
gitextract_s279x8yc/
├── .gitlab-ci.yml
├── Changelog
├── Dockerfile
├── Godeps/
│ ├── Godeps.json
│ └── Readme
├── Gopkg.toml
├── LICENSE
├── README.md
├── api/
│ ├── core/
│ │ └── api.go
│ ├── response.go
│ ├── server.go
│ └── ui/
│ ├── api.go
│ ├── category.go
│ ├── component.go
│ ├── group.go
│ ├── host.go
│ ├── ip.go
│ ├── log.go
│ ├── network.go
│ ├── session.go
│ ├── switch.go
│ ├── user.go
│ └── vip.go
├── cmd/
│ ├── cherry/
│ │ ├── cherry.yaml
│ │ └── main.go
│ └── walnut/
│ ├── main.go
│ ├── sdk.go
│ └── walnut.yaml
├── database/
│ ├── mysql.go
│ ├── mysql_schema.sql
│ └── random.go
├── election/
│ └── election.go
├── graph/
│ ├── graph.go
│ └── graph_test.go
├── ldap/
│ ├── client.go
│ └── conn.go
├── log/
│ └── syslog.go
├── network/
│ ├── controller.go
│ ├── device.go
│ ├── error.go
│ ├── flow_cache.go
│ ├── link.go
│ ├── node.go
│ ├── of10_session.go
│ ├── of13_session.go
│ ├── port.go
│ ├── reserve.go
│ ├── reserve_test.go
│ ├── session.go
│ └── topology.go
├── northbound/
│ ├── app/
│ │ ├── announcer/
│ │ │ ├── announcer.go
│ │ │ └── backoff.go
│ │ ├── dhcp/
│ │ │ └── dhcp.go
│ │ ├── discovery/
│ │ │ └── discovery.go
│ │ ├── l2switch/
│ │ │ ├── storm_controller.go
│ │ │ ├── storm_controller_test.go
│ │ │ └── switch.go
│ │ ├── monitor/
│ │ │ ├── monitor.go
│ │ │ └── sendmail.go
│ │ ├── processor.go
│ │ ├── proxyarp/
│ │ │ ├── arp.go
│ │ │ └── error.go
│ │ └── virtualip/
│ │ └── virtualip.go
│ └── manager.go
├── openflow/
│ ├── action.go
│ ├── barrier.go
│ ├── config.go
│ ├── const.go
│ ├── description.go
│ ├── echo.go
│ ├── error.go
│ ├── factory.go
│ ├── features.go
│ ├── flow_mod.go
│ ├── flow_removed.go
│ ├── flow_stats.go
│ ├── hello.go
│ ├── instruction.go
│ ├── match.go
│ ├── message.go
│ ├── of10/
│ │ ├── action.go
│ │ ├── barrier.go
│ │ ├── config.go
│ │ ├── const.go
│ │ ├── description.go
│ │ ├── echo.go
│ │ ├── factory.go
│ │ ├── features.go
│ │ ├── flow_mod.go
│ │ ├── flow_removed.go
│ │ ├── flow_stats.go
│ │ ├── hello.go
│ │ ├── instruction.go
│ │ ├── match.go
│ │ ├── packet_in.go
│ │ ├── packet_out.go
│ │ ├── port.go
│ │ ├── port_status.go
│ │ └── queue.go
│ ├── of13/
│ │ ├── action.go
│ │ ├── barrier.go
│ │ ├── config.go
│ │ ├── const.go
│ │ ├── echo.go
│ │ ├── factory.go
│ │ ├── features.go
│ │ ├── flow_mod.go
│ │ ├── flow_removed.go
│ │ ├── hello.go
│ │ ├── instruction.go
│ │ ├── match.go
│ │ ├── multipart_description.go
│ │ ├── multipart_flow_stats.go
│ │ ├── multipart_port_description.go
│ │ ├── multipart_table_features.go
│ │ ├── packet_in.go
│ │ ├── packet_out.go
│ │ ├── port.go
│ │ ├── port_status.go
│ │ └── queue.go
│ ├── packet_in.go
│ ├── packet_out.go
│ ├── port.go
│ ├── port_description.go
│ ├── port_status.go
│ ├── queue.go
│ ├── table_features.go
│ └── transceiver/
│ ├── stream.go
│ └── transceiver.go
├── protocol/
│ ├── arp.go
│ ├── checksum.go
│ ├── dhcp.go
│ ├── dhcp_test.go
│ ├── ethernet.go
│ ├── icmp.go
│ ├── ipv4.go
│ ├── lldp.go
│ ├── tcp.go
│ └── udp.go
├── vendor/
│ ├── github.com/
│ │ ├── ant0ine/
│ │ │ └── go-json-rest/
│ │ │ ├── LICENSE
│ │ │ └── rest/
│ │ │ ├── access_log_apache.go
│ │ │ ├── access_log_json.go
│ │ │ ├── api.go
│ │ │ ├── auth_basic.go
│ │ │ ├── content_type_checker.go
│ │ │ ├── cors.go
│ │ │ ├── doc.go
│ │ │ ├── gzip.go
│ │ │ ├── if.go
│ │ │ ├── json_indent.go
│ │ │ ├── jsonp.go
│ │ │ ├── middleware.go
│ │ │ ├── powered_by.go
│ │ │ ├── recorder.go
│ │ │ ├── recover.go
│ │ │ ├── request.go
│ │ │ ├── response.go
│ │ │ ├── route.go
│ │ │ ├── router.go
│ │ │ ├── status.go
│ │ │ ├── timer.go
│ │ │ └── trie/
│ │ │ └── impl.go
│ │ ├── boombuler/
│ │ │ └── barcode/
│ │ │ ├── .gitignore
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── barcode.go
│ │ │ ├── qr/
│ │ │ │ ├── alphanumeric.go
│ │ │ │ ├── automatic.go
│ │ │ │ ├── blocks.go
│ │ │ │ ├── encoder.go
│ │ │ │ ├── errorcorrection.go
│ │ │ │ ├── numeric.go
│ │ │ │ ├── qrcode.go
│ │ │ │ ├── unicode.go
│ │ │ │ └── versioninfo.go
│ │ │ ├── scaledbarcode.go
│ │ │ └── utils/
│ │ │ ├── base1dcode.go
│ │ │ ├── bitlist.go
│ │ │ ├── galoisfield.go
│ │ │ ├── gfpoly.go
│ │ │ ├── reedsolomon.go
│ │ │ └── runeint.go
│ │ ├── davecgh/
│ │ │ └── go-spew/
│ │ │ ├── LICENSE
│ │ │ └── spew/
│ │ │ ├── bypass.go
│ │ │ ├── bypasssafe.go
│ │ │ ├── common.go
│ │ │ ├── config.go
│ │ │ ├── doc.go
│ │ │ ├── dump.go
│ │ │ ├── format.go
│ │ │ └── spew.go
│ │ ├── fsnotify/
│ │ │ └── fsnotify/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── AUTHORS
│ │ │ ├── CHANGELOG.md
│ │ │ ├── CONTRIBUTING.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── fen.go
│ │ │ ├── fsnotify.go
│ │ │ ├── inotify.go
│ │ │ ├── inotify_poller.go
│ │ │ ├── kqueue.go
│ │ │ ├── open_mode_bsd.go
│ │ │ ├── open_mode_darwin.go
│ │ │ └── windows.go
│ │ ├── go-sql-driver/
│ │ │ └── mysql/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── AUTHORS
│ │ │ ├── CHANGELOG.md
│ │ │ ├── CONTRIBUTING.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── appengine.go
│ │ │ ├── buffer.go
│ │ │ ├── collations.go
│ │ │ ├── connection.go
│ │ │ ├── connection_go18.go
│ │ │ ├── const.go
│ │ │ ├── driver.go
│ │ │ ├── dsn.go
│ │ │ ├── errors.go
│ │ │ ├── fields.go
│ │ │ ├── infile.go
│ │ │ ├── packets.go
│ │ │ ├── result.go
│ │ │ ├── rows.go
│ │ │ ├── statement.go
│ │ │ ├── transaction.go
│ │ │ ├── utils.go
│ │ │ ├── utils_go17.go
│ │ │ └── utils_go18.go
│ │ ├── google/
│ │ │ └── go-cmp/
│ │ │ ├── LICENSE
│ │ │ └── cmp/
│ │ │ ├── cmpopts/
│ │ │ │ ├── equate.go
│ │ │ │ ├── ignore.go
│ │ │ │ ├── sort.go
│ │ │ │ ├── struct_filter.go
│ │ │ │ └── xform.go
│ │ │ ├── compare.go
│ │ │ ├── export_panic.go
│ │ │ ├── export_unsafe.go
│ │ │ ├── internal/
│ │ │ │ ├── diff/
│ │ │ │ │ ├── debug_disable.go
│ │ │ │ │ ├── debug_enable.go
│ │ │ │ │ └── diff.go
│ │ │ │ ├── flags/
│ │ │ │ │ ├── flags.go
│ │ │ │ │ ├── toolchain_legacy.go
│ │ │ │ │ └── toolchain_recent.go
│ │ │ │ ├── function/
│ │ │ │ │ └── func.go
│ │ │ │ └── value/
│ │ │ │ ├── pointer_purego.go
│ │ │ │ ├── pointer_unsafe.go
│ │ │ │ ├── sort.go
│ │ │ │ └── zero.go
│ │ │ ├── options.go
│ │ │ ├── path.go
│ │ │ ├── report.go
│ │ │ ├── report_compare.go
│ │ │ ├── report_reflect.go
│ │ │ ├── report_slices.go
│ │ │ ├── report_text.go
│ │ │ └── report_value.go
│ │ ├── hashicorp/
│ │ │ ├── golang-lru/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── 2q.go
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── arc.go
│ │ │ │ ├── lru.go
│ │ │ │ └── simplelru/
│ │ │ │ └── lru.go
│ │ │ └── hcl/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── Makefile
│ │ │ ├── README.md
│ │ │ ├── appveyor.yml
│ │ │ ├── decoder.go
│ │ │ ├── hcl/
│ │ │ │ ├── ast/
│ │ │ │ │ ├── ast.go
│ │ │ │ │ └── walk.go
│ │ │ │ ├── parser/
│ │ │ │ │ ├── error.go
│ │ │ │ │ └── parser.go
│ │ │ │ ├── scanner/
│ │ │ │ │ └── scanner.go
│ │ │ │ ├── strconv/
│ │ │ │ │ └── quote.go
│ │ │ │ └── token/
│ │ │ │ ├── position.go
│ │ │ │ └── token.go
│ │ │ ├── hcl.go
│ │ │ ├── json/
│ │ │ │ ├── parser/
│ │ │ │ │ ├── flatten.go
│ │ │ │ │ └── parser.go
│ │ │ │ ├── scanner/
│ │ │ │ │ └── scanner.go
│ │ │ │ └── token/
│ │ │ │ ├── position.go
│ │ │ │ └── token.go
│ │ │ ├── lex.go
│ │ │ └── parse.go
│ │ ├── magiconair/
│ │ │ └── properties/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── CHANGELOG.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── _third_party/
│ │ │ │ └── gopkg.in/
│ │ │ │ └── check.v1/
│ │ │ │ └── LICENSE
│ │ │ ├── decode.go
│ │ │ ├── doc.go
│ │ │ ├── integrate.go
│ │ │ ├── lex.go
│ │ │ ├── load.go
│ │ │ ├── parser.go
│ │ │ ├── properties.go
│ │ │ └── rangecheck.go
│ │ ├── mitchellh/
│ │ │ └── mapstructure/
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── decode_hooks.go
│ │ │ ├── error.go
│ │ │ └── mapstructure.go
│ │ ├── pelletier/
│ │ │ ├── go-buffruneio/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── .travis.yml
│ │ │ │ ├── README.md
│ │ │ │ └── buffruneio.go
│ │ │ └── go-toml/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── clean.sh
│ │ │ ├── doc.go
│ │ │ ├── example-crlf.toml
│ │ │ ├── example.toml
│ │ │ ├── keysparsing.go
│ │ │ ├── lexer.go
│ │ │ ├── match.go
│ │ │ ├── parser.go
│ │ │ ├── position.go
│ │ │ ├── query.go
│ │ │ ├── querylexer.go
│ │ │ ├── queryparser.go
│ │ │ ├── test.sh
│ │ │ ├── token.go
│ │ │ ├── toml.go
│ │ │ └── tomltree_conversions.go
│ │ ├── pkg/
│ │ │ └── errors/
│ │ │ ├── .gitignore
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── appveyor.yml
│ │ │ ├── errors.go
│ │ │ └── stack.go
│ │ ├── pquerna/
│ │ │ └── otp/
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── NOTICE
│ │ │ ├── README.md
│ │ │ ├── doc.go
│ │ │ ├── hotp/
│ │ │ │ └── hotp.go
│ │ │ ├── otp.go
│ │ │ └── totp/
│ │ │ └── totp.go
│ │ ├── spf13/
│ │ │ ├── afero/
│ │ │ │ ├── .travis.yml
│ │ │ │ ├── LICENSE.txt
│ │ │ │ ├── README.md
│ │ │ │ ├── afero.go
│ │ │ │ ├── appveyor.yml
│ │ │ │ ├── basepath.go
│ │ │ │ ├── cacheOnReadFs.go
│ │ │ │ ├── const_bsds.go
│ │ │ │ ├── const_win_unix.go
│ │ │ │ ├── copyOnWriteFs.go
│ │ │ │ ├── httpFs.go
│ │ │ │ ├── ioutil.go
│ │ │ │ ├── mem/
│ │ │ │ │ ├── dir.go
│ │ │ │ │ ├── dirmap.go
│ │ │ │ │ └── file.go
│ │ │ │ ├── memmap.go
│ │ │ │ ├── memradix.go
│ │ │ │ ├── os.go
│ │ │ │ ├── path.go
│ │ │ │ ├── readonlyfs.go
│ │ │ │ ├── regexpfs.go
│ │ │ │ ├── unionFile.go
│ │ │ │ └── util.go
│ │ │ ├── cast/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── cast.go
│ │ │ │ └── caste.go
│ │ │ ├── jwalterweatherman/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ └── thatswhyyoualwaysleaveanote.go
│ │ │ └── pflag/
│ │ │ ├── .travis.yml
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── bool.go
│ │ │ ├── count.go
│ │ │ ├── duration.go
│ │ │ ├── flag.go
│ │ │ ├── float32.go
│ │ │ ├── float64.go
│ │ │ ├── golangflag.go
│ │ │ ├── int.go
│ │ │ ├── int32.go
│ │ │ ├── int64.go
│ │ │ ├── int8.go
│ │ │ ├── int_slice.go
│ │ │ ├── ip.go
│ │ │ ├── ipmask.go
│ │ │ ├── ipnet.go
│ │ │ ├── string.go
│ │ │ ├── string_slice.go
│ │ │ ├── uint.go
│ │ │ ├── uint16.go
│ │ │ ├── uint32.go
│ │ │ ├── uint64.go
│ │ │ └── uint8.go
│ │ └── superkkt/
│ │ ├── go-logging/
│ │ │ ├── .travis.yml
│ │ │ ├── CHANGELOG.md
│ │ │ ├── CONTRIBUTORS
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── backend.go
│ │ │ ├── format.go
│ │ │ ├── level.go
│ │ │ ├── log_nix.go
│ │ │ ├── log_windows.go
│ │ │ ├── logger.go
│ │ │ ├── memory.go
│ │ │ ├── multi.go
│ │ │ ├── syslog.go
│ │ │ └── syslog_fallback.go
│ │ └── viper/
│ │ ├── .gitignore
│ │ ├── .travis.yml
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── flags.go
│ │ ├── nohup.out
│ │ ├── util.go
│ │ └── viper.go
│ ├── golang.org/
│ │ └── x/
│ │ ├── sys/
│ │ │ ├── AUTHORS
│ │ │ ├── CONTRIBUTORS
│ │ │ ├── LICENSE
│ │ │ ├── PATENTS
│ │ │ └── unix/
│ │ │ ├── .gitignore
│ │ │ ├── asm.s
│ │ │ ├── asm_darwin_386.s
│ │ │ ├── asm_darwin_amd64.s
│ │ │ ├── asm_darwin_arm.s
│ │ │ ├── asm_darwin_arm64.s
│ │ │ ├── asm_dragonfly_amd64.s
│ │ │ ├── asm_freebsd_386.s
│ │ │ ├── asm_freebsd_amd64.s
│ │ │ ├── asm_freebsd_arm.s
│ │ │ ├── asm_linux_386.s
│ │ │ ├── asm_linux_amd64.s
│ │ │ ├── asm_linux_arm.s
│ │ │ ├── asm_linux_arm64.s
│ │ │ ├── asm_linux_mips64x.s
│ │ │ ├── asm_linux_ppc64x.s
│ │ │ ├── asm_linux_s390x.s
│ │ │ ├── asm_netbsd_386.s
│ │ │ ├── asm_netbsd_amd64.s
│ │ │ ├── asm_netbsd_arm.s
│ │ │ ├── asm_openbsd_386.s
│ │ │ ├── asm_openbsd_amd64.s
│ │ │ ├── asm_solaris_amd64.s
│ │ │ ├── bluetooth_linux.go
│ │ │ ├── constants.go
│ │ │ ├── env_unix.go
│ │ │ ├── env_unset.go
│ │ │ ├── flock.go
│ │ │ ├── flock_linux_32bit.go
│ │ │ ├── gccgo.go
│ │ │ ├── gccgo_c.c
│ │ │ ├── gccgo_linux_amd64.go
│ │ │ ├── mkall.sh
│ │ │ ├── mkerrors.sh
│ │ │ ├── mkpost.go
│ │ │ ├── mksyscall.pl
│ │ │ ├── mksyscall_solaris.pl
│ │ │ ├── mksysctl_openbsd.pl
│ │ │ ├── mksysnum_darwin.pl
│ │ │ ├── mksysnum_dragonfly.pl
│ │ │ ├── mksysnum_freebsd.pl
│ │ │ ├── mksysnum_linux.pl
│ │ │ ├── mksysnum_netbsd.pl
│ │ │ ├── mksysnum_openbsd.pl
│ │ │ ├── race.go
│ │ │ ├── race0.go
│ │ │ ├── sockcmsg_linux.go
│ │ │ ├── sockcmsg_unix.go
│ │ │ ├── str.go
│ │ │ ├── syscall.go
│ │ │ ├── syscall_bsd.go
│ │ │ ├── syscall_darwin.go
│ │ │ ├── syscall_darwin_386.go
│ │ │ ├── syscall_darwin_amd64.go
│ │ │ ├── syscall_darwin_arm.go
│ │ │ ├── syscall_darwin_arm64.go
│ │ │ ├── syscall_dragonfly.go
│ │ │ ├── syscall_dragonfly_amd64.go
│ │ │ ├── syscall_freebsd.go
│ │ │ ├── syscall_freebsd_386.go
│ │ │ ├── syscall_freebsd_amd64.go
│ │ │ ├── syscall_freebsd_arm.go
│ │ │ ├── syscall_linux.go
│ │ │ ├── syscall_linux_386.go
│ │ │ ├── syscall_linux_amd64.go
│ │ │ ├── syscall_linux_arm.go
│ │ │ ├── syscall_linux_arm64.go
│ │ │ ├── syscall_linux_mips64x.go
│ │ │ ├── syscall_linux_ppc64x.go
│ │ │ ├── syscall_linux_s390x.go
│ │ │ ├── syscall_netbsd.go
│ │ │ ├── syscall_netbsd_386.go
│ │ │ ├── syscall_netbsd_amd64.go
│ │ │ ├── syscall_netbsd_arm.go
│ │ │ ├── syscall_no_getwd.go
│ │ │ ├── syscall_openbsd.go
│ │ │ ├── syscall_openbsd_386.go
│ │ │ ├── syscall_openbsd_amd64.go
│ │ │ ├── syscall_solaris.go
│ │ │ ├── syscall_solaris_amd64.go
│ │ │ ├── syscall_unix.go
│ │ │ ├── types_darwin.go
│ │ │ ├── types_dragonfly.go
│ │ │ ├── types_freebsd.go
│ │ │ ├── types_linux.go
│ │ │ ├── types_netbsd.go
│ │ │ ├── types_openbsd.go
│ │ │ ├── types_solaris.go
│ │ │ ├── zerrors_darwin_386.go
│ │ │ ├── zerrors_darwin_amd64.go
│ │ │ ├── zerrors_darwin_arm.go
│ │ │ ├── zerrors_darwin_arm64.go
│ │ │ ├── zerrors_dragonfly_amd64.go
│ │ │ ├── zerrors_freebsd_386.go
│ │ │ ├── zerrors_freebsd_amd64.go
│ │ │ ├── zerrors_freebsd_arm.go
│ │ │ ├── zerrors_linux_386.go
│ │ │ ├── zerrors_linux_amd64.go
│ │ │ ├── zerrors_linux_arm.go
│ │ │ ├── zerrors_linux_arm64.go
│ │ │ ├── zerrors_linux_mips64.go
│ │ │ ├── zerrors_linux_mips64le.go
│ │ │ ├── zerrors_linux_ppc64.go
│ │ │ ├── zerrors_linux_ppc64le.go
│ │ │ ├── zerrors_linux_s390x.go
│ │ │ ├── zerrors_netbsd_386.go
│ │ │ ├── zerrors_netbsd_amd64.go
│ │ │ ├── zerrors_netbsd_arm.go
│ │ │ ├── zerrors_openbsd_386.go
│ │ │ ├── zerrors_openbsd_amd64.go
│ │ │ ├── zerrors_solaris_amd64.go
│ │ │ ├── zsyscall_darwin_386.go
│ │ │ ├── zsyscall_darwin_amd64.go
│ │ │ ├── zsyscall_darwin_arm.go
│ │ │ ├── zsyscall_darwin_arm64.go
│ │ │ ├── zsyscall_dragonfly_amd64.go
│ │ │ ├── zsyscall_freebsd_386.go
│ │ │ ├── zsyscall_freebsd_amd64.go
│ │ │ ├── zsyscall_freebsd_arm.go
│ │ │ ├── zsyscall_linux_386.go
│ │ │ ├── zsyscall_linux_amd64.go
│ │ │ ├── zsyscall_linux_arm.go
│ │ │ ├── zsyscall_linux_arm64.go
│ │ │ ├── zsyscall_linux_mips64.go
│ │ │ ├── zsyscall_linux_mips64le.go
│ │ │ ├── zsyscall_linux_ppc64.go
│ │ │ ├── zsyscall_linux_ppc64le.go
│ │ │ ├── zsyscall_linux_s390x.go
│ │ │ ├── zsyscall_netbsd_386.go
│ │ │ ├── zsyscall_netbsd_amd64.go
│ │ │ ├── zsyscall_netbsd_arm.go
│ │ │ ├── zsyscall_openbsd_386.go
│ │ │ ├── zsyscall_openbsd_amd64.go
│ │ │ ├── zsyscall_solaris_amd64.go
│ │ │ ├── zsysctl_openbsd.go
│ │ │ ├── zsysnum_darwin_386.go
│ │ │ ├── zsysnum_darwin_amd64.go
│ │ │ ├── zsysnum_darwin_arm.go
│ │ │ ├── zsysnum_darwin_arm64.go
│ │ │ ├── zsysnum_dragonfly_amd64.go
│ │ │ ├── zsysnum_freebsd_386.go
│ │ │ ├── zsysnum_freebsd_amd64.go
│ │ │ ├── zsysnum_freebsd_arm.go
│ │ │ ├── zsysnum_linux_386.go
│ │ │ ├── zsysnum_linux_amd64.go
│ │ │ ├── zsysnum_linux_arm.go
│ │ │ ├── zsysnum_linux_arm64.go
│ │ │ ├── zsysnum_linux_mips64.go
│ │ │ ├── zsysnum_linux_mips64le.go
│ │ │ ├── zsysnum_linux_ppc64.go
│ │ │ ├── zsysnum_linux_ppc64le.go
│ │ │ ├── zsysnum_linux_s390x.go
│ │ │ ├── zsysnum_netbsd_386.go
│ │ │ ├── zsysnum_netbsd_amd64.go
│ │ │ ├── zsysnum_netbsd_arm.go
│ │ │ ├── zsysnum_openbsd_386.go
│ │ │ ├── zsysnum_openbsd_amd64.go
│ │ │ ├── zsysnum_solaris_amd64.go
│ │ │ ├── ztypes_darwin_386.go
│ │ │ ├── ztypes_darwin_amd64.go
│ │ │ ├── ztypes_darwin_arm.go
│ │ │ ├── ztypes_darwin_arm64.go
│ │ │ ├── ztypes_dragonfly_amd64.go
│ │ │ ├── ztypes_freebsd_386.go
│ │ │ ├── ztypes_freebsd_amd64.go
│ │ │ ├── ztypes_freebsd_arm.go
│ │ │ ├── ztypes_linux_386.go
│ │ │ ├── ztypes_linux_amd64.go
│ │ │ ├── ztypes_linux_arm.go
│ │ │ ├── ztypes_linux_arm64.go
│ │ │ ├── ztypes_linux_mips64.go
│ │ │ ├── ztypes_linux_mips64le.go
│ │ │ ├── ztypes_linux_ppc64.go
│ │ │ ├── ztypes_linux_ppc64le.go
│ │ │ ├── ztypes_linux_s390x.go
│ │ │ ├── ztypes_netbsd_386.go
│ │ │ ├── ztypes_netbsd_amd64.go
│ │ │ ├── ztypes_netbsd_arm.go
│ │ │ ├── ztypes_openbsd_386.go
│ │ │ ├── ztypes_openbsd_amd64.go
│ │ │ └── ztypes_solaris_amd64.go
│ │ └── text/
│ │ ├── AUTHORS
│ │ ├── CONTRIBUTORS
│ │ ├── LICENSE
│ │ ├── PATENTS
│ │ ├── internal/
│ │ │ ├── gen/
│ │ │ │ ├── code.go
│ │ │ │ └── gen.go
│ │ │ ├── triegen/
│ │ │ │ ├── compact.go
│ │ │ │ ├── print.go
│ │ │ │ └── triegen.go
│ │ │ └── ucd/
│ │ │ └── ucd.go
│ │ ├── transform/
│ │ │ └── transform.go
│ │ └── unicode/
│ │ ├── cldr/
│ │ │ ├── base.go
│ │ │ ├── cldr.go
│ │ │ ├── collate.go
│ │ │ ├── decode.go
│ │ │ ├── makexml.go
│ │ │ ├── resolve.go
│ │ │ ├── slice.go
│ │ │ └── xml.go
│ │ └── norm/
│ │ ├── composition.go
│ │ ├── forminfo.go
│ │ ├── input.go
│ │ ├── iter.go
│ │ ├── maketables.go
│ │ ├── normalize.go
│ │ ├── readwriter.go
│ │ ├── tables.go
│ │ ├── transform.go
│ │ ├── trie.go
│ │ └── triegen.go
│ ├── google.golang.org/
│ │ └── appengine/
│ │ ├── LICENSE
│ │ └── cloudsql/
│ │ ├── cloudsql.go
│ │ ├── cloudsql_classic.go
│ │ └── cloudsql_vm.go
│ └── gopkg.in/
│ ├── asn1-ber.v1/
│ │ ├── .travis.yml
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── ber.go
│ │ ├── content_int.go
│ │ ├── header.go
│ │ ├── identifier.go
│ │ ├── length.go
│ │ └── util.go
│ ├── ldap.v3/
│ │ ├── .gitignore
│ │ ├── .travis.yml
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── README.md
│ │ ├── add.go
│ │ ├── bind.go
│ │ ├── client.go
│ │ ├── compare.go
│ │ ├── conn.go
│ │ ├── control.go
│ │ ├── debug.go
│ │ ├── del.go
│ │ ├── dn.go
│ │ ├── doc.go
│ │ ├── error.go
│ │ ├── filter.go
│ │ ├── ldap.go
│ │ ├── moddn.go
│ │ ├── modify.go
│ │ ├── passwdmodify.go
│ │ └── search.go
│ └── yaml.v2/
│ ├── .travis.yml
│ ├── LICENSE
│ ├── LICENSE.libyaml
│ ├── README.md
│ ├── apic.go
│ ├── decode.go
│ ├── emitterc.go
│ ├── encode.go
│ ├── parserc.go
│ ├── readerc.go
│ ├── resolve.go
│ ├── scannerc.go
│ ├── sorter.go
│ ├── writerc.go
│ ├── yaml.go
│ ├── yamlh.go
│ └── yamlprivateh.go
└── version.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitlab-ci.yml
================================================
image: golang:1.12
before_script:
- mkdir -p /go/src/github.com/superkkt
- cp -rp /builds/superkkt/cherry /go/src/github.com/superkkt
stages:
- build
- test
build_all:
stage: build
script:
- cd /go/src/github.com/superkkt/cherry
- go build $(go list ./... | grep -v /vendor/)
test_all:
stage: test
script:
- cd /go/src/github.com/superkkt/cherry
- go test $(go list ./... | grep -v /vendor/)
================================================
FILE: Changelog
================================================
* v0.14.2 (2019-09-20)
3faead4 Fix the unexpected master change (See #44)
* v0.14.1 (2019-09-03)
b321987 Remove password plain text log (See #43)
* v0.14.0 (2019-08-02)
fad9247 Change ui server middleware order (See #42)
e02444b Change name of host table timestamp column (See #41)
7ee607a Revert "Set hard-timeout to clear stale flows"
71199c6 Change data column type in log table (See #38)
3b8fe3e Apply LDAP (See #31)
715f48f Change validation of input length (See #36)
6a81a12 Change timestamp column option in host table (See #37)
9cf45b8 Apply TOTP (See #30)
6f28b01 Prevent gateway from being set as reserved IP (See #35)
4faeba4 Change host update API (See #26)
efaab76 Change IP structure (See #27)
8f0eb70 Add get host API (See #34)
040d871 Add gateway within network (See #25)
501c3d3 Add network search (See #24)
8740e05 Add spec feature within host (See #29)
2e8ed92 Add Component UI API (See #28)
3f301a2 Add Category UI API (See #23)
80606bb Add Log UI API (See #18)
519ecce Fix a bug to add an invalid IP address to the database when adding a network (See #33)
c4c6fdf NULL MAC address to disconnect a host from the network (See #32)
3504fd6 Dedicated IP address for ARP host discovery (See #12)
cb92e4a Use reserved IP addresses (TEST-NET-1) for DHCP and ARP discovery
79d95da Validate TCP and UDP packet lengths
a5415ba Fix the broken UDP checksum
6ce8b01 Implement a DHCP server
c01b487 refactoring: apply the custom response writer to the UI API
cf82a97 revise
abf5e1a refactoring: apply pagination with structure
fbf94f0 refactoring: ui transaction returns instance reference
936f21d Add Host List API (See #20)
74ff82e fix add, update host
9e013c9 refactoring: call database logics in a closure
b9b85d4 refactoring: apply the custom response writer to the core API
726f009 refactoring: custom response writer with logging
58362ed Add Host UI API (See #20)
c31fdaa Add VIP UI API (See #19)
a13a7dc Refactoring: re-organize the source tree structure of the API package
5667f06 Add Switch UI API (See #14)
0090f4b Add Network UI API (See #15)
1729ffa Add Host group UI API (See #17)
4e4188f Add User UI API (See #16)
b10bfa7 Add extra comment for time interval of the flow manager
a4aaac9 Set hard-timeout to clear stale flows
0c939a7 ARP announcer (See #22)
cd32572 Devide UI API server (See #13)
9af1b5b Fix the broken test case
e2e8add Replace godep with dep to vendor libraries
9cddf5f Add extra debug logs for broadcast packets
b1e6584 Update the buffered socket stream to avoid a potential internal buffer corruption
ef776e5 Add the GitLab CI
f4f235f Fix a typo
1fa1d05 Fix broken test cases
146b172 Fix the IP conflict by ARP probes (See #12)
* v0.13.5 (2018-05-15)
06fe675 Discovery: workaround for old OSes that have a broken TCP/IP stack (See #6)
* v0.13.4 (2018-04-22)
4767d4c Fix the node discovery bug (See #5)
* v0.13.3 (2018-04-22)
619eefc Revert "Decrease the PACKET_IN read buffer size"
* v0.13.2 (2018-04-20)
05f3fc0 Adjust log levels
412988e Use string parameters in the YAML config file
1114081 MySQL: validate the cluster address
fe7f912 Update the MySQL driver
c3b6288 mysql: new cluster dialer and query function that pass Tx instead of DB
a760e5e Ignore the WRITE fsnotify operation to avoid reading empty config
d4d48f7 Increase the flow idle timeout
df3e5ed Make a switch drops all incoming packets (except ARP and LLDP) for a while on start up
0bfdb5b Sort networks by address
3809a8c Re-ordering DB tables in the MySQL schema
9eb8066 Decrease the ARP probe interval
480d6b5 Fix missing time interval change
7cfdccb Decrease the flow manager interval
6f8780c Add extra comments
151ab37 Decrease the device explorer interval to 1 minute
0e11b0d Update a debug message
1fc910c Decrease ARP sender's loop interval to 1.5 sec.
5472f62 Add a comment for time buffering
750627a Drop the some PACKET_INs for proper device setup on start up
0670200 Drop the packets before ethernet parsing if the device is not yet ready
e7d0e91 Decrease ARP sender's loop interval
3f3fbc3 Send ARP probes immediately on start up for fast discovery
a77d304 Fix aaa0e4fd797 as OF1.0 does not support the cookie mask
5ade735 Deny the API request if we are not the master controller
5e2633e Keep the ARP flow unless the switch device reconnects
aaa0e4f Flow cache to avoid duplicate and repeated flow installation (See #1)
f8bb64f Minor refactoring
109e393 Refactoring of the switch device negotiation procedure
c527c28 Add extra debug messages
e8d4b50 Decrease the timeout for a staled host
6d42974 Decrease the PACKET_IN read buffer size
78f098a Use FLOW_ADD instead of FLOW_MODIFY for a broken switch device (i.e., Dell S3148) (See #1)
7770802 Log DPID when we get an openflow error packet
018b460 Ignore the invalid echo responses
* v0.13.1 (Sat Aug 05 16:45:00 2017 +0900)
075e31c Ignore the ARP reply received from an edge among switches
* v0.13.0 (Fri Aug 04 23:37:48 2017 +0900)
fe412ce Remove duplicated link down check
5bfbf68 Decrease the flow manager interval to 1 minute
0130939 Flow manager to keep the flows update
b92618c Fix the deadlock between the topology and the device
698ce6a Minor refactoring
7a88c77 Set idle and hard flow timeouts from the config
ae4e791 Log flow removed timestamp
13ba29f Remove all the flow histories on the device down event
fd8b1a4 Warning log on the port down event
72b66c2 Backoff-delayed ARP announcer for all the registered hosts
fbc8759 Backoff-delayed ARP announcer
0509b0c Invalidate the ARP caches when a host is removed
9d0500e Fix the broken VIP toggling by repeated toggles in a moment
d468086 Minor refactoring of README
f7cee84 Update README for the new configuration file
ae3ee97 Fix the broken VIP toggling by the Discovery module
ad5b2d9 Revert a weird patch 601ac8055
ce25c01 Minor refactoring by function renaming
daaeaed Log switch device up/down events
a34bdce Fix the broken Dockerfile
ee8f7a4 Flow history on the database
e46bce5 Adjust switch port numbers based on its first printed port number
b8eb6e9 Dynamic config reloading
0a3a53f Increase log level of toggling VIP addresses
5841fd6 Remove the old Golang X Context package
9c62ce2 Fix the unintended package import
a014cee Adjust log message level
38bbac7 Implement automatic master election
0be0c21 New host stale field instead of its UNIX timestamp
584e230 Minor refactoring
31e2491 Adjust loop intervals
4acb3e3 Remove useless comment
a5b152e Periodic switch port scanning
bef4a14 Minor refactoring for the device expolorer interval
aee62af Periodically send LLDPs to keep the network topology updated
a730b7e Dynamic discovery of physical host locations using the ARP probe
67911b1 Fix unexpected foreign key error from deleting a network address
bba56c1 Add debug messages for ECHO_REQUEST and ECHO_REPLY packets
e0b7e53 Decrease the capacity of the reader channel
* v0.12.1 (Wed Aug 31 15:06:31 2016 +0900)
3d35bfb Log the number of unread remaining packets in the reader channel
e1cfa8b Ignore soft errors from OpenFlow EchoReply
6d6e729 Fix the incorrect log levels of the config file
* v0.12.0 (Sun Aug 28 18:57:08 2016 +0900)
46a636c Fix the bug caused by unnecessary protocol vercion checking
e40cda8 Run the packet reader thread in the OpenFlow transceiver
6a16954 Rename the openflow/trans package to openflow/transceiver
14fd96d Replace the custom logger with the go-logging library
b5ca266 Reorganize the package structure
9f48eb6 Adopt a temporary error to ignore soft errors
b522598 Vendoring by Godeps
a107e1f Do not support multiple DB hosts and set the wait_time MySQL variable to 120s
* v0.11.0 (Mon Jun 6 15:42:29 2016 +0900)
4599801 (HEAD, tag: v0.11.0, origin/v0.11, origin/master, origin/HEAD, v0.11, master) Bump version to 0.11.0
601ac80 Fix the duplicated DPID by disconnecting the previous main connection (see #8313)
2cb100e Vendoring all libraries
21b7e2f Update Changelog and LICENSE
4797aa3 Change version to 0.11-RC3
6700906 Send the alarm email asynchronously (see #6547)
c21420a Change version to 0.11-RC2
25ac489 Enable the monitor app (see #6547)
f26e497 Change version to 0.11-RC
29f152a Implement notification when a device is up and down (see #6547)
* v0.10.0 (Sat Oct 24 17:57:16 2015 +0900)
18fb269 (tag: v0.10.0, origin/v0.10) Change version to 0.10-RC
87b02be Implement a broadcast storm controller (see #6534)
59c98c0 Add version flag
* v0.9.1 (Tue Oct 20 22:28:58 2015 +0900)
9a00866 (tag: v0.9.1, origin/v0.9) Implement a flow cache to avoid duplicated flow installation
4299780 Remove hard timeout of a L2 switch's flow
07ee1e2 Add the version flag
* v0.9.0 (Fri Oct 9 00:34:00 2015 +0900)
35a345c (tag: v0.9.0) Update README.md
1650f6a Update README.md
3310bcc Add Changelog
2933299 Add origin header
3ee079f Improve VIP toggling logs
d305027 Remove useless modules
b66bcb3 Remove foreign key delete cascade as MySQL does not call triggers on the cascding
e471dbc Implement manual toggle VIP active/standby hosts (see #6109)
38f8be4 Drop useless removing flows when removeVIP API is called
211df46 Add VIP toggle log messages
a2ea75e Fix incorrect query string
4f383e3 Add VIPParam validation
db7db86 Minor refactoring
d24b64d Implement floating virtual IP (see #6109)
34c7956 Improve MAC address handling of the REST module
e0eaced Fix incorrect IP and MAC parser and add log messages
7c762a2 Fix decoding MAC address routine that results in panic
5ef7ab8 Implement host list, add, and remove REST APIs
c2a5f0f Refactoring of REST APIs
2aa69f3 Implement network list, add, and delete APIs via REST
66469a1 Change default REST port number from 8080 to 7070
06b7fad Implement switch list, add, and delete APIs via REST
91b9b28 Do not return error if we cannot find a neighbor device or its port to avoid unexpected device disconnection
a82d8df Send barrier reqeust after setting ARP sender flow
1a88dd4 Remove incorrect log message of ProxyARP
4d42ee8 Improve ARP announcement checker
6c732c3 Allow database connection error if there is at least one avaliable server on the database pool (see #6236)
22e6838 Log malicious ARP packet whose ethernet destination MAC is not the broadcast address
1570025 Refactoring ARP sender flow routines
f82246b Add a permanent flow for ARP packets to forward them to the controller
545625b Improve DB index
259e06d Improve DB indices
4a81851 Fix incorrect unique keys of host DB table
5558f15 Make DB DELETE to be cascaded
b03ea16 Add host description field to the database schema
4d7fcbf Implement multiple database connections (see #6115)
b44a7b6 Check ARP announcement before ARP operation (see #6109)
0ba70d7 Drop ARP announcement packets (see #6109)
3d31799 Fix typo
ac588a8 Implement ARP packet description
ceb047a Remove useless assigning of return values (see #6104)
c7935d6 Implement MySQL deadlock protection (see #6104)
4959db7 Add OFPFF_CHECK_OVERLAP flag to FLOW_MOD (see #6108)
1037c73 Change host DB table schema
253cf47 Remove flows when port down event is detected (see #6108)
90da465 Implement fixed host location (see #6103)
3827e3d Clear all learned MAC addresses when the topology is updated (see #6102)
d5e0bde Fix incorrect Deadline interface
0832255 Remove ARP announcement routines from the ProxyARP module
691a2db Remove the router module
9676380 Implement VLAN ID of the OpenFlow action
94dd8e6 Add the router module
fd3eb21 Implement per-device host database in the topology
9ac9595 Remove the router northbound application
ecd84f5 Implement InPort output action
136ee97 Fix missing IP ToS wildcard
6ced806 Fix incorrect IPv4 address handling
7961ab5 Implement SetEnqueue for QoS in the router application (see #5950)
cf3eef5 Implement QueueGetConfigRequest (see #5950)
2844e19 Checks northbound applications' dependencies among them (see #6001)
d2613f6 Minor typo changes
eafc9b1 Remove useless comments
299f581 Remove OnFlowRemoved callback handler from northbound applications
4863b61 Change OF action's output type from map to slice
dbab7c4 Drop an incoming packet heading to a private address
e49fb98 Add SIGHUP handler to northbound applications and extra debugging messages
2bf9592 Drop an incoming packet heading to link-local address
05db1b0 Add extra debugging messages
9c42639 Fix missing flow reverse
10df6b1 Add verbose debugging messages
ba0ed3e Implement HUP signal handler to show current daemon status (see #5974)
386fa87 Refactoring of Dockerfile and entrypoint script
240ad0a Update README.md
7858b41 Update README.md
1ac3427 Update README.md
e708323 Update README.md
a389236 Update README.md
eaf16e0 Update README.md
5412891 Remove MySQL from Dockerfile
8b733aa Remove SIGHUP from the Docker entrypoint script
d536fa5 Update README.md
3eb59ea Update README.md
8c5308c Update README.md
7473a51 Update README.md
3d25588 Update README.md
3e94b80 Add Dockerfile
cbdbe46 Remove internal prefixes of import paths
da3f8c1 Implement ICMP echo reply
9225b3c Update README.md
062645b Change MAC address type of the database schema
529b1aa Separate database module
65aea0a Preventing IP spoofing
9e9d0a7 Preventing IP spoofing
* v0.8.0 (Thu Jul 2 23:43:09 2015 +0900)
d42c3d5 Router's outgoing packet handler (see #5084)
548de57 Router's incoming packet handler (see #5084)
593060b Update README.md
d730618 Update README.md
d19a1fc Update copyright
b4ccf88 Update copyright
43eaee1 Update README.md
3cc461f Implement custom log writer that supports log level filter
9ce0b7f Update README.md
5321096 Update README.md
c281b64 Update README.md
1cbdc63 Move the LICENSE file
7af5145 Create README.md
24d6324 Default config to use only L2Switch
6568a71 Replace git.sds.co.kr with github.com
f6550bd Add GPLv2 License
bd771ee Fix duplicated packet problem
a85ec70 Implement ProxyARP application (see #5084)
ae8751c Minor refactoring
9e4d7a4 Implement network event listener (see #5084)
e7be8db Getting OpenFlow factory from a device and cleanup flows when a device is disconnected (see #5084)
6369c0d Refactoring of the L2 switch application (see #5634)
ccf4648 Implement north-bound L2 switch applicaion (#5634)
797986b Implement north-bound application layout (see #5634)
8039ed5 Implement path finding (see #5634)
2d9ac36 Implement LLDP timer (see #5634)
96638bd Remove debugging messages
920a429 Refactoring package structure (see #5634)
ff0c33a Remove comment out for testing
7b30eb0 Refactoring auxiliary connections (see #5634)
c004856 Refactoring network packages (see #5634)
c97a99b Refactoring of the controller package (see #5634)
f6624bf Change the number of goroutines that can be executed simultaneously
4d9434b Refactoring of the OpenFlow package (see #5634)
c63ed26 Fix host DB consistency (see #5510)
6a55b26 Implement ARP announcement (see #5084)
3b8c0d5 Implement event listener (see #5084)
b8e7c93 Implement event listener (see #5084)
4963470 Remove L3 switch application (see #5084)
2c32400 Implement ProxyARP (see #5084)
b71e288 Change package name of device to controller
079cda9 Minor refactoring
89af5b1 Use both hardware and software flow tables of HP-2920G (see #5084)
46c62ea Minor refactoring
008630c Split L2 and L3 switches (see #5084)
31ccf27 Implement configuration of plugin applications (see #5084)
0490fba Implement UDP protocol (see #5454)
b2cdd8a Implement TCP protocol (see #5454)
bf85523 Fix incorrect netmask on the flow rule of L2 MAC learning switch (see #5453)
e67d097 Improve switch compatibilities (see #5453)
bf8ebff Minor refactoring of VirtualRouter
82f9db8 Add ethernet parsing routine for a IEEE 802.1Q-tagged frame (see #5454)
f8dbd02 Implment ICMP Echo protocol (see #5454)
4ccac40 Implement IPv4 protocol (see #5454)
2229248 Implement ARP protocol (see #5454)
8675846 Implement L2 switch application on OF1.0 (see #5388)
0dfcdf0 Fix abnormal broadcast when src and dst nodes are on a same switch (see #5448)
8b26a71 Fix duplicated packet problem (see #5447)
40e16f9 Implement custom routines for HP-2920 (see #5388)
0df3f20 Implement a packet processor (see #5388)
19e5e85 Implement a L2 MAC learning switch module (see #5388)
90b40da Fix port modification bugs (see #5086)
0b85e51 Fix abnormal broadcast storm (see #5086)
ee7c554 Implement L2 switch application on OF1.3 (see #5388)
2c20909 Minor refactoring (see #5086)
3933699 Remove comments
b8aa530 Implement Kruskal's algorithm (see #5086)
525f8c2 Implement Breadth-First Search (see #5086)
1b801b6 Minor refactoring
371a960 Minor fix for removing edge and vertex (see #5086)
82a6fc3 Implement minimum spanning tree using Prim's algorithm (see #5086)
dbabb82 Implement Ethernet & LLDP protocols (see #5086)
70988f7 Rollback to do not remove all flow entries when a switch connects (see #5083)
28dbc7c Fix incorrect data offset of PACKET_IN (see #5083)
46b3142 Implement port modification (see #5083)
4556131 Implement PACKET_IN (see #5083)
6700855 Add comments about switch characteristics (see #0)
68fb0e3 Implement flow_removed and packet_out message (see #5083)
a97c74e Implement flow_removed and packet_out message (see #5083)
beedd9f Add interfaces to install a flow rule (see #5083)
beb0090 Implement flow_mod (see #5083)
959f590 Implement flow action (see #5083)
c17b009 Refactoring of openflow.Message (see #5083)
6007efb Implement flow match (see #5083)
33c3ba0 Implement port status message (see #5083)
f2332d8 Add packet length validation for FeaturesReply (see #0)
76ea7fe Refactoring of error message (see #5083)
c027ba4 Implement error message (see #5083)
d05648f Implement port description (see #5083)
7144add minor refactoring (see #0)
954fbbf minor refactoring (see #0)
2172a5a Implement switch descriptions (see #5083)
fcd4c87 Implement switch configuration messages (see #5083)
0660110 Implement device pool (see #5083)
d2f8346 Split common routines OF10 and OF13 transceivers (see #5083)
6a15233 Implement a baseline for OF10 and OF13 protocols (see #5083)
c99c940 Minor refactoring (see #0)
b135795 Minor refactoring (see #0)
4234069 Refactoring for auxiliary connections (see #5083)
0c7c0ff Use map for switch ports information instead of slice (see #0)
32a3684 Minor refactoring (see #0)
d781a30 Refactoring hello phase (see #0)
7a587d4 Implement port modification message (see #5087)
c0fad5e Add atomic increment of transaction ID for concurrent usage (see #0)
6569ffd Implement barrier request and reply (see #5082)
5b5d356 Implement switch configs (see #0)
7699d4c Refactoring of FlowMatch (see #0)
cad74ff Refactoring of handleMessage() (see #0)
7c19e07 Implement all 12-tuples of flow_match (see #0)
28c918f Fix incorrect flow_match due to lack of ether_type (see #0)
3fd30d4 Implement flow_stats request and reply (see #0)
c259128 Fix the connection bug between us and a switch (see #0)
3a850d9 Refactoring of FlowMatch marshaler (see #0)
9b3ce8c Fix incorrect IP representation in a flow match structure (see #0)
14a0b1b Implement packet-out message (see #0)
14664cd Implement packet-in and flow-removed messages (see #0)
a34a56e Implement adding/removing flow rules (see #0)
6e3ccbe Fix incorrect flow action's marshal routines (see #0)
7d5cb40 Implement flow actions (see #0)
d3d43ce Implement port status message (see #0)
8dc17da Implement echo request and reply messages (see #0)
9a2d6b0 Refactoring, and implement feature capabilities and actions (see #0)
52d690c Implement HelloMessage, ErrorMessage, FeaturesRequestMessage and FeaturesReplyMessage (see #0)
f9d2ae9 Initial commit (see #0)
================================================
FILE: Dockerfile
================================================
# Start from a Debian image with the latest version of Go installed
# and a workspace (GOPATH) configured at /go.
FROM golang:1.8
MAINTAINER Kitae Kim
# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/superkkt/cherry/
COPY ./cherry.yaml /usr/local/etc/
# Build cherry inside the container.
RUN go install github.com/superkkt/cherry
# Run the cherry command by default when the container starts.
ENTRYPOINT ["/go/bin/cherry"]
# Document that the service listens on port 6633.
EXPOSE 6633
================================================
FILE: Godeps/Godeps.json
================================================
{
"ImportPath": "github.com/superkkt/cherry",
"GoVersion": "go1.10",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/ant0ine/go-json-rest/rest",
"Comment": "v3.3.1-8-g3a807d6",
"Rev": "3a807d6d8d36e0f9175d6c292030fce09bcbb2db"
},
{
"ImportPath": "github.com/ant0ine/go-json-rest/rest/trie",
"Comment": "v3.3.1-8-g3a807d6",
"Rev": "3a807d6d8d36e0f9175d6c292030fce09bcbb2db"
},
{
"ImportPath": "github.com/fsnotify/fsnotify",
"Comment": "v1.3.0",
"Rev": "30411dbcefb7a1da7e84f75530ad3abe4011b4f8"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.3-59-g1a676ac",
"Rev": "1a676ac6e4dce68e9303a1800773861350374a9e"
},
{
"ImportPath": "github.com/hashicorp/golang-lru",
"Rev": "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
},
{
"ImportPath": "github.com/hashicorp/golang-lru/simplelru",
"Rev": "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
},
{
"ImportPath": "github.com/hashicorp/hcl",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/hcl/ast",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/hcl/parser",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/hcl/scanner",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/hcl/strconv",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/hcl/token",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/json/parser",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/json/scanner",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/hashicorp/hcl/json/token",
"Rev": "d7400db7143f8e869812e50a53acd6c8d92af3b8"
},
{
"ImportPath": "github.com/magiconair/properties",
"Comment": "v1.7.0",
"Rev": "c265cfa48dda6474e208715ca93e987829f572f8"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "d2dd0262208475919e1a362f675cfc0e7c10e905"
},
{
"ImportPath": "github.com/pelletier/go-buffruneio",
"Rev": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d"
},
{
"ImportPath": "github.com/pelletier/go-toml",
"Comment": "v0.3.5-19-g7cb9880",
"Rev": "7cb988051d5045890cb91402a0b5fddc76c627bc"
},
{
"ImportPath": "github.com/pkg/errors",
"Comment": "v0.7.1",
"Rev": "17b591df37844cde689f4d5813e5cea0927d8dd2"
},
{
"ImportPath": "github.com/spf13/afero",
"Rev": "06b7e5f50606ecd49148a01a6008942d9b669217"
},
{
"ImportPath": "github.com/spf13/afero/mem",
"Rev": "06b7e5f50606ecd49148a01a6008942d9b669217"
},
{
"ImportPath": "github.com/spf13/cast",
"Rev": "27b586b42e29bec072fe7379259cc719e1289da6"
},
{
"ImportPath": "github.com/spf13/jwalterweatherman",
"Rev": "33c24e77fb80341fe7130ee7c594256ff08ccc46"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "cb88ea77998c3f024757528e3305022ab50b43be"
},
{
"ImportPath": "github.com/superkkt/go-logging",
"Comment": "v1-8-gd6a8aba",
"Rev": "d6a8aba149c56e15219853fff6500375e1cbb1f2"
},
{
"ImportPath": "github.com/superkkt/viper",
"Rev": "7a4f83d485c248540ad95ae0501252323b8b8682"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "076b546753157f758b316e59bcb51e6807c04057"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "4440cd4f4c2ea31e1872e00de675a86d0c19006c"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "4440cd4f4c2ea31e1872e00de675a86d0c19006c"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "a83829b6f1293c91addabc89d0571c246397bbf4"
}
]
}
================================================
FILE: Godeps/Readme
================================================
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
================================================
FILE: Gopkg.toml
================================================
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/davecgh/go-spew"
version = "1.1.1"
[[constraint]]
name = "github.com/fsnotify/fsnotify"
version = "1.3.0"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.7.1"
[[constraint]]
branch = "master"
name = "github.com/superkkt/go-logging"
[[constraint]]
branch = "master"
name = "github.com/superkkt/viper"
[prune]
go-tests = true
unused-packages = true
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) 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
this service 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 make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. 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.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE 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.
END OF TERMS AND CONDITIONS
================================================
FILE: README.md
================================================
# Cherry
Cherry is an OpenFlow controller written in Go that supports OpenFlow 1.0 and 1.3 protocols. This project is not designed for general purpose, and it instead focuses on SDN (Software-Defined Networking) for an IT service provider.
## Features
* Supports OpenFlow 1.0 and 1.3 protocols
* Focuses on compatibility with commercial OpenFlow-enabled switches
* Supports network topology that has loops in it
* Provides several northbound applications: ProxyARP, L2Switch, Floating-IP, etc.
* Provides simple plugin system for northbound applications
* RESTful API to manage the controller itself
## Supported OpenFlow Switches (Fully Tested)
* Dell Force10 S4810
* Dell Force10 S3048-ON
* Dell Force10 S3148
* Open vSwitch
## Tested OpenFlow Switches
* HP 2920G
* Pica8 P-3295
* Quanta T1048-LB9
## Requirements
* MySQL (or MariaDB) database server
## Quick Start
You can install Cherry on Docker or natively from source based on your preference.
### Installing on Docker
* Install Docker if you don't have it on your system by following instruction: https://docs.docker.com/installation/
* Clone Cherry:
```$ git clone https://github.com/superkkt/cherry.git```
* Copy the template configuration:
```$ sudo cp cherry/cherry.yaml /usr/local/etc```
* Edit the configuration file */usr/local/etc/cherry.yaml* as you want.
* Build Docker image as root:
```# cd cherry; docker build -t cherry .```
* Run as root:
```# docker run -d -p 6633:6633 -v /dev/log:/dev/log -v /your/config/cherry.yaml:/usr/local/etc/cherry.yaml cherry```
The bind mount of /dev/log is to collect syslog messages from the container and then write to the host's syslog daemon.
* That's it! Cherry will be started in L2 switch mode.
### Installing from source
* Install Go language if you don't have it on your system by following instruction: http://golang.org/doc/install
* Clone and compile Cherry:
```$ go get github.com/superkkt/cherry```
* Copy the compiled binary and template configuration:
```$ sudo cp $GOPATH/bin/cherry /usr/local/bin```
```$ sudo cp $GOPATH/src/github.com/superkkt/cherry/cherry.yaml /usr/local/etc```
* Edit the configuration file */usr/local/etc/cherry.yaml* as you want.
* Run:
```$ /usr/local/bin/cherry &```
* That's it! Cherry will be started in L2 switch mode.
## Copyright and License
```
Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
Kitae Kim
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 2 of the License, or
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.
```
================================================
FILE: api/core/api.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package core
import (
"encoding/json"
"fmt"
"net"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("core")
)
type API struct {
api.Server
}
func (r *API) Serve() error {
return r.Server.Serve(
rest.Post("/api/v1/status", api.ResponseHandler(r.status)),
rest.Post("/api/v1/remove", api.ResponseHandler(r.remove)),
rest.Post("/api/v1/announce", api.ResponseHandler(r.announce)),
)
}
func (r *API) status(w api.ResponseWriter, req *rest.Request) {
logger.Debugf("status request from %v", req.RemoteAddr)
w.Write(api.Response{
Status: api.StatusOkay,
Data: struct {
Master bool `json:"master"`
}{
Master: r.Observer.IsMaster(),
},
})
}
func (r *API) remove(w api.ResponseWriter, req *rest.Request) {
p := new(removeParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("remove request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if p.MAC == nil {
if err := r.Controller.RemoveFlows(); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove flows: %v", err.Error())})
return
}
} else {
if err := r.Controller.RemoveFlowsByMAC(p.MAC); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove flows by MAC: %v", err.Error())})
return
}
}
w.Write(api.Response{Status: api.StatusOkay})
}
type removeParam struct {
MAC net.HardwareAddr
}
func (r *removeParam) UnmarshalJSON(data []byte) error {
v := struct {
MAC string `json:"mac"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
// If MAC is empty, remove all flows.
if len(v.MAC) == 0 {
return nil
}
addr, err := net.ParseMAC(v.MAC)
if err != nil {
return err
}
r.MAC = addr
return nil
}
func (r *API) announce(w api.ResponseWriter, req *rest.Request) {
p := new(announceParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("announce request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if err := r.Controller.Announce(p.IP, p.MAC); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to announce a new ARP entry: %v", err.Error())})
return
}
w.Write(api.Response{Status: api.StatusOkay})
}
type announceParam struct {
IP net.IP
MAC net.HardwareAddr
}
func (r *announceParam) UnmarshalJSON(data []byte) error {
v := struct {
IP string `json:"ip"`
MAC string `json:"mac"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
ip := net.ParseIP(v.IP)
if ip == nil {
return fmt.Errorf("invalid IP address: %v", v.IP)
}
mac, err := net.ParseMAC(v.MAC)
if err != nil {
return err
}
r.IP = ip
r.MAC = mac
return nil
}
================================================
FILE: api/response.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package api
import (
"net/http"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
/*
* Status Codes:
*
* 200 = Okay.
* 4xx = Client-side errors.
* 5xx = Server-side errors.
*/
type Status int
const (
StatusOkay = 200
StatusInvalidParameter = 400
StatusIncorrectCredential = 401
StatusUnknownSession = 402
StatusPermissionDenied = 403
StatusDuplicated = 404
StatusNotFound = 405
StatusBlockedAccount = 406
StatusBlockedHost = 407
StatusInternalServerError = 500
StatusServiceUnavailable = 501
)
type Response struct {
Status Status `json:"status"`
Message string `json:"message,omitempty"` // Human readable message related with the status code.
Data interface{} `json:"data,omitempty"`
}
func ResponseHandler(f func(ResponseWriter, *rest.Request)) func(rest.ResponseWriter, *rest.Request) {
return func(w rest.ResponseWriter, req *rest.Request) {
lw := &logWriter{w: w}
f(lw, req)
}
}
type ResponseWriter interface {
// Identical to the http.ResponseWriter interface
Header() http.Header
Write(Response)
// Similar to the http.ResponseWriter interface, with additional JSON related
// headers set.
WriteHeader(int)
}
type logWriter struct {
w rest.ResponseWriter
}
func (r *logWriter) Header() http.Header {
return r.w.Header()
}
func (r *logWriter) Write(resp Response) {
switch {
case resp.Status >= StatusInternalServerError:
logger.Errorf("server-side error response: status=%v, message=%v", resp.Status, resp.Message)
case resp.Status >= StatusInvalidParameter:
logger.Infof("client-side error response: status=%v, message=%v", resp.Status, resp.Message)
default:
logger.Debugf("success response: %v", spew.Sdump(resp))
}
if err := r.w.WriteJson(resp); err != nil {
logger.Errorf("failed to write a JSON response: %v", err)
}
}
func (r *logWriter) WriteHeader(status int) {
r.w.WriteHeader(status)
}
================================================
FILE: api/server.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package api
import (
"errors"
"fmt"
"net"
"net/http"
"github.com/ant0ine/go-json-rest/rest"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("api")
)
type Server struct {
Port uint16
TLS struct {
Cert string // Path for a TLS certification file.
Key string // Path for a TLS private key file.
}
Observer Observer
Controller Controller
}
type Observer interface {
IsMaster() bool
}
type Controller interface {
Announce(net.IP, net.HardwareAddr) error
RemoveFlows() error
RemoveFlowsByMAC(net.HardwareAddr) error
}
func (r *Server) validate() error {
if r.Observer == nil {
return errors.New("nil observer")
}
if r.Controller == nil {
return errors.New("nil controller")
}
return nil
}
func (r *Server) Serve(routes ...*rest.Route) error {
if err := r.validate(); err != nil {
return err
}
api := rest.NewApi()
// Middleware to set the CORS header.
api.Use(rest.MiddlewareSimple(func(handler rest.HandlerFunc) rest.HandlerFunc {
return func(writer rest.ResponseWriter, request *rest.Request) {
writer.Header().Set("Access-Control-Allow-Origin", "*")
handler(writer, request)
}
}))
// Middleware to deny the client requests if we are not the master controller.
api.Use(rest.MiddlewareSimple(func(handler rest.HandlerFunc) rest.HandlerFunc {
return func(writer rest.ResponseWriter, request *rest.Request) {
if r.Observer.IsMaster() == false {
writer.WriteJson(Response{Status: StatusServiceUnavailable, Message: "use the master controller server"})
return
}
handler(writer, request)
}
}))
router, err := rest.MakeRouter(routes...)
if err != nil {
return err
}
api.SetApp(router)
// Listen on all interfaces.
addr := fmt.Sprintf(":%v", r.Port)
if r.TLS.Cert != "" && r.TLS.Key != "" {
err = http.ListenAndServeTLS(addr, r.TLS.Cert, r.TLS.Key, api.MakeHandler())
} else {
err = http.ListenAndServe(addr, api.MakeHandler())
}
return err
}
================================================
FILE: api/ui/api.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("ui")
)
type API struct {
api.Server
DB Database
LDAP LDAP
session *session
}
type Database interface {
// Exec executes all queries of f in a single transaction. f should return the error raised from the Transaction
// without any change or wrapping it for deadlock protection.
Exec(f func(Transaction) error) error
}
type LDAP interface {
Auth(username, password string) (ok bool, err error)
}
type Transaction interface {
UserTransaction
GroupTransaction
SwitchTransaction
NetworkTransaction
IPTransaction
HostTransaction
VIPTransaction
LogTransaction
CategoryTransaction
ComponentTransaction
}
type Search struct {
Key Column `json:"key"`
Value string `json:"value"`
}
func (r *Search) Validate() error {
switch r.Key {
case ColumnIP:
return validateIP(r.Value)
case ColumnMAC:
return validateMAC(r.Value)
case ColumnLogType:
return LogType(r.Value).Validate()
case ColumnLogMethod:
return LogMethod(r.Value).Validate()
case ColumnPort, ColumnGroup, ColumnDescription, ColumnUser:
if len(r.Value) == 0 {
return errors.New("empty search value")
}
return nil
default:
return fmt.Errorf("invalid search key: %v", r.Key)
}
}
// IP format is '1.*.*.*', '1.2.*.*', '1.2.3.*', '1.2.3.4'.
func validateIP(ip string) error {
invalid := fmt.Errorf("invalid IP address: %v", ip)
token := strings.Split(ip, ".")
if len(token) != 4 {
return invalid
}
var wildcard [4]bool
for i, v := range token {
if v == "*" {
wildcard[i] = true
continue
}
d, err := strconv.Atoi(v)
if err != nil || (d < 0 || d > 255) {
return invalid
}
}
if wildcard[0] == true {
return invalid
}
if wildcard[1] == true && (wildcard[2] == false || wildcard[3] == false) {
return invalid
}
if wildcard[2] == true && wildcard[3] == false {
return invalid
}
return nil
}
// MAC format is 'A1:*:*:*:*:*', 'A1:A2:*:*:*:*', 'A1:A2:A3:*:*:*', 'A1:A2:A3:A4:*:*', 'A1:A2:A3:A4:A5:*', 'A1:A2:A3:A4:A5:A6'.
func validateMAC(mac string) error {
invalid := fmt.Errorf("invalid MAC address: %v", mac)
token := strings.Split(mac, ":")
if len(token) != 6 {
return invalid
}
var wildcard [6]bool
for i, v := range token {
if v == "*" {
wildcard[i] = true
continue
}
d, err := strconv.ParseUint(v, 16, 8)
if len(v) != 2 || err != nil || (d < 0 || d > 255) {
return invalid
}
}
if wildcard[0] == true {
return invalid
}
if wildcard[1] == true && (wildcard[2] == false || wildcard[3] == false || wildcard[4] == false || wildcard[5] == false) {
return invalid
}
if wildcard[2] == true && (wildcard[3] == false || wildcard[4] == false || wildcard[5] == false) {
return invalid
}
if wildcard[3] == true && (wildcard[4] == false || wildcard[5] == false) {
return invalid
}
if wildcard[4] == true && wildcard[5] == false {
return invalid
}
return nil
}
type Sort struct {
Key Column `json:"key"`
Order Order `json:"order"`
}
func (r *Sort) Validate() error {
if r.Order <= OrderInvalid || r.Order > OrderDescending {
return errors.New("invalid sort order")
}
if r.Key <= ColumnInvalid || r.Key > ColumnGroup {
return fmt.Errorf("invalid sort key: %v", r.Key)
}
return nil
}
type Column int
const (
ColumnInvalid Column = iota
ColumnTime
ColumnIP
ColumnMAC
ColumnPort
ColumnGroup
ColumnDescription
ColumnUser
ColumnLogType
ColumnLogMethod
)
type Order int
const (
OrderInvalid Order = iota
OrderAscending
OrderDescending
)
type Pagination struct {
Offset uint32 `json:"offset"`
Limit uint8 `json:"limit"`
}
func (r *API) Serve() error {
if r.DB == nil {
return errors.New("nil DB")
}
r.session = newSession(256, 2*time.Hour)
return r.Server.Serve(
rest.Post("/api/v1/user/login", api.ResponseHandler(r.login)),
rest.Post("/api/v1/user/logout", api.ResponseHandler(r.logout)),
rest.Post("/api/v1/user/list", api.ResponseHandler(r.listUser)),
rest.Post("/api/v1/user/add", api.ResponseHandler(r.addUser)),
rest.Post("/api/v1/user/update", api.ResponseHandler(r.updateUser)),
rest.Post("/api/v1/user/reset", api.ResponseHandler(r.resetOTP)),
rest.Post("/api/v1/group/list", api.ResponseHandler(r.listGroup)),
rest.Post("/api/v1/group/add", api.ResponseHandler(r.addGroup)),
rest.Post("/api/v1/group/update", api.ResponseHandler(r.updateGroup)),
rest.Post("/api/v1/group/remove", api.ResponseHandler(r.removeGroup)),
rest.Post("/api/v1/switch/list", api.ResponseHandler(r.listSwitch)),
rest.Post("/api/v1/switch/add", api.ResponseHandler(r.addSwitch)),
rest.Post("/api/v1/switch/remove", api.ResponseHandler(r.removeSwitch)),
rest.Post("/api/v1/network/list", api.ResponseHandler(r.listNetwork)),
rest.Post("/api/v1/network/add", api.ResponseHandler(r.addNetwork)),
rest.Post("/api/v1/network/remove", api.ResponseHandler(r.removeNetwork)),
rest.Post("/api/v1/network/ip", api.ResponseHandler(r.listIP)),
rest.Post("/api/v1/host/list", api.ResponseHandler(r.listHost)),
rest.Post("/api/v1/host/get", api.ResponseHandler(r.getHost)),
rest.Post("/api/v1/host/add", api.ResponseHandler(r.addHost)),
rest.Post("/api/v1/host/update", api.ResponseHandler(r.updateHost)),
rest.Post("/api/v1/host/activate", api.ResponseHandler(r.activateHost)),
rest.Post("/api/v1/host/deactivate", api.ResponseHandler(r.deactivateHost)),
rest.Post("/api/v1/host/remove", api.ResponseHandler(r.removeHost)),
rest.Post("/api/v1/vip/list", api.ResponseHandler(r.listVIP)),
rest.Post("/api/v1/vip/add", api.ResponseHandler(r.addVIP)),
rest.Post("/api/v1/vip/remove", api.ResponseHandler(r.removeVIP)),
rest.Post("/api/v1/vip/toggle", api.ResponseHandler(r.toggleVIP)),
rest.Post("/api/v1/log/list", api.ResponseHandler(r.listLog)),
rest.Post("/api/v1/category/list", api.ResponseHandler(r.listCategory)),
rest.Post("/api/v1/category/add", api.ResponseHandler(r.addCategory)),
rest.Post("/api/v1/category/update", api.ResponseHandler(r.updateCategory)),
rest.Post("/api/v1/category/remove", api.ResponseHandler(r.removeCategory)),
rest.Post("/api/v1/component/list", api.ResponseHandler(r.listComponent)),
rest.Post("/api/v1/component/add", api.ResponseHandler(r.addComponent)),
rest.Post("/api/v1/component/update", api.ResponseHandler(r.updateComponent)),
rest.Post("/api/v1/component/remove", api.ResponseHandler(r.removeComponent)),
)
}
func (r *API) announce(cidr, mac string) error {
i, _, err := net.ParseCIDR(cidr)
if err != nil {
return err
}
m, err := net.ParseMAC(mac)
if err != nil {
return err
}
logger.Debugf("sending ARP announcement to all hosts to update their ARP caches: ip=%v, mac=%v", i, m)
if err := r.Controller.Announce(i, m); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
} else {
logger.Debugf("updated all hosts ARP caches: ip=%v, mac=%v", i, m)
}
return nil
}
================================================
FILE: api/ui/category.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"time"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type CategoryTransaction interface {
// Categories returns a list of registered categories. Pagination limit can be 0 that means no pagination.
Categories(Pagination) ([]*Category, error)
AddCategory(requesterID uint64, name string) (category *Category, duplicated bool, err error)
// UpdateCategory updates name of a category specified by id and then returns information of the category. It returns nil if the category does not exist.
UpdateCategory(requesterID, categoryID uint64, name string) (category *Category, duplicated bool, err error)
// RemoveCategory removes a category specified by id and then returns information of the category before removing. It returns nil if the category does not exist.
RemoveCategory(requesterID, categoryID uint64) (*Category, error)
}
type Category struct {
ID uint64
Name string
Timestamp time.Time
}
func (r *Category) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
Name: r.Name,
Timestamp: r.Timestamp.Unix(),
})
}
func (r *API) listCategory(w api.ResponseWriter, req *rest.Request) {
p := new(listCategoryParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("listCategory request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var category []*Category
f := func(tx Transaction) (err error) {
category, err = tx.Categories(p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the category list: %v", err.Error())})
return
}
logger.Debugf("queried category list: %v", spew.Sdump(category))
w.Write(api.Response{Status: api.StatusOkay, Data: category})
}
type listCategoryParam struct {
SessionID string
Pagination Pagination
}
func (r *listCategoryParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listCategoryParam(v)
return r.validate()
}
func (r *listCategoryParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
return nil
}
func (r *API) addCategory(w api.ResponseWriter, req *rest.Request) {
p := new(addCategoryParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("addCategory request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var category *Category
var duplicated bool
f := func(tx Transaction) (err error) {
category, duplicated, err = tx.AddCategory(session.(*User).ID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new category: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated category: %v", p.Name)})
return
}
logger.Debugf("added category info: %v", spew.Sdump(category))
w.Write(api.Response{Status: api.StatusOkay, Data: category})
}
type addCategoryParam struct {
SessionID string
Name string
}
func (r *addCategoryParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = addCategoryParam(v)
return r.validate()
}
func (r *addCategoryParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 255 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) updateCategory(w api.ResponseWriter, req *rest.Request) {
p := new(updateCategoryParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("updateCategory request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var category *Category
var duplicated bool
f := func(tx Transaction) (err error) {
category, duplicated, err = tx.UpdateCategory(session.(*User).ID, p.ID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to update category info: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated category: %v", p.Name)})
return
}
if category == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found category to update: %v", p.ID)})
return
}
logger.Debugf("updated category info: %v", spew.Sdump(category))
w.Write(api.Response{Status: api.StatusOkay, Data: category})
}
type updateCategoryParam struct {
SessionID string
ID uint64
Name string
}
func (r *updateCategoryParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = updateCategoryParam(v)
return r.validate()
}
func (r *updateCategoryParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid category id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 255 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) removeCategory(w api.ResponseWriter, req *rest.Request) {
p := new(removeCategoryParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("removeCategory request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var category *Category
f := func(tx Transaction) (err error) {
category, err = tx.RemoveCategory(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove category info: %v", err.Error())})
return
}
if category == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found category to remove: %v", p.ID)})
return
}
logger.Debugf("removed category info: %v", spew.Sdump(category))
w.Write(api.Response{Status: api.StatusOkay})
}
type removeCategoryParam struct {
SessionID string
ID uint64
}
func (r *removeCategoryParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeCategoryParam(v)
return r.validate()
}
func (r *removeCategoryParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid category id")
}
return nil
}
================================================
FILE: api/ui/component.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"time"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type ComponentTransaction interface {
// Components returns a list of registered components. Pagination limit can be 0 that means no pagination.
Components(categoryID uint64, pagination Pagination) ([]*Component, error)
AddComponent(requesterID, categoryID uint64, name string) (component *Component, duplicated bool, err error)
// UpdateComponent updates name of a component specified by id and then returns information of the component. It returns nil if the component does not exist.
UpdateComponent(requesterID, componentID uint64, name string) (component *Component, duplicated bool, err error)
// RemoveComponent removes a component specified by id and then returns information of the component before removing. It returns nil if the component does not exist.
RemoveComponent(requesterID, componentID uint64) (*Component, error)
}
type Component struct {
ID uint64
Category Category
Name string
Timestamp time.Time
}
func (r *Component) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
Category Category `json:"category"`
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
Category: r.Category,
Name: r.Name,
Timestamp: r.Timestamp.Unix(),
})
}
func (r *API) listComponent(w api.ResponseWriter, req *rest.Request) {
p := new(listComponentParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("listComponent request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var component []*Component
f := func(tx Transaction) (err error) {
component, err = tx.Components(p.CategoryID, p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the component list: %v", err.Error())})
return
}
logger.Debugf("queried component list: %v", spew.Sdump(component))
w.Write(api.Response{Status: api.StatusOkay, Data: component})
}
type listComponentParam struct {
SessionID string
CategoryID uint64
Pagination Pagination
}
func (r *listComponentParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
CategoryID uint64 `json:"category_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listComponentParam(v)
return r.validate()
}
func (r *listComponentParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.CategoryID == 0 {
return errors.New("invalid category id")
}
return nil
}
func (r *API) addComponent(w api.ResponseWriter, req *rest.Request) {
p := new(addComponentParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("addComponent request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var component *Component
var duplicated bool
f := func(tx Transaction) (err error) {
component, duplicated, err = tx.AddComponent(session.(*User).ID, p.CategoryID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new component: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated component: category_id=%v, name=%v", p.CategoryID, p.Name)})
return
}
logger.Debugf("added component info: %v", spew.Sdump(component))
w.Write(api.Response{Status: api.StatusOkay, Data: component})
}
type addComponentParam struct {
SessionID string
CategoryID uint64
Name string
}
func (r *addComponentParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
CategoryID uint64 `json:"category_id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = addComponentParam(v)
return r.validate()
}
func (r *addComponentParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.CategoryID == 0 {
return errors.New("invalid category id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 255 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) updateComponent(w api.ResponseWriter, req *rest.Request) {
p := new(updateComponentParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("updateComponent request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var component *Component
var duplicated bool
f := func(tx Transaction) (err error) {
component, duplicated, err = tx.UpdateComponent(session.(*User).ID, p.ID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to update component info: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated component: %v", p.Name)})
return
}
if component == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found component to update: %v", p.ID)})
return
}
logger.Debugf("updated component info: %v", spew.Sdump(component))
w.Write(api.Response{Status: api.StatusOkay, Data: component})
}
type updateComponentParam struct {
SessionID string
ID uint64
Name string
}
func (r *updateComponentParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = updateComponentParam(v)
return r.validate()
}
func (r *updateComponentParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid component id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 255 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) removeComponent(w api.ResponseWriter, req *rest.Request) {
p := new(removeComponentParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("removeComponent request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var component *Component
f := func(tx Transaction) (err error) {
component, err = tx.RemoveComponent(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove component info: %v", err.Error())})
return
}
if component == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found component to remove: %v", p.ID)})
return
}
logger.Debugf("removed component info: %v", spew.Sdump(component))
w.Write(api.Response{Status: api.StatusOkay})
}
type removeComponentParam struct {
SessionID string
ID uint64
}
func (r *removeComponentParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeComponentParam(v)
return r.validate()
}
func (r *removeComponentParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid component id")
}
return nil
}
================================================
FILE: api/ui/group.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"time"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type GroupTransaction interface {
// Groups returns a list of registered groups. Pagination limit can be 0 that means no pagination.
Groups(Pagination) ([]*Group, error)
AddGroup(requesterID uint64, name string) (group *Group, duplicated bool, err error)
// UpdateGroup updates name of a group specified by id and then returns information of the group. It returns nil if the group does not exist.
UpdateGroup(requesterID, groupID uint64, name string) (group *Group, duplicated bool, err error)
// RemoveGroup removes a group specified by id and then returns information of the group before removing. It returns nil if the group does not exist.
RemoveGroup(requesterID, groupID uint64) (*Group, error)
}
type Group struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Timestamp time.Time `json:"timestamp"`
}
func (r *Group) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
Name: r.Name,
Timestamp: r.Timestamp.Unix(),
})
}
func (r *API) listGroup(w api.ResponseWriter, req *rest.Request) {
p := new(listGroupParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listGroup request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var group []*Group
f := func(tx Transaction) (err error) {
group, err = tx.Groups(p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the groups: %v", err.Error())})
return
}
logger.Debugf("queried group list: %v", spew.Sdump(group))
w.Write(api.Response{Status: api.StatusOkay, Data: group})
}
type listGroupParam struct {
SessionID string
Pagination Pagination
}
func (r *listGroupParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listGroupParam(v)
return r.validate()
}
func (r *listGroupParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
return nil
}
func (r *API) addGroup(w api.ResponseWriter, req *rest.Request) {
p := new(addGroupParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("addGroup request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var group *Group
var duplicated bool
f := func(tx Transaction) (err error) {
group, duplicated, err = tx.AddGroup(session.(*User).ID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new group: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated group: %v", p.Name)})
return
}
logger.Debugf("added group info: %v", spew.Sdump(group))
w.Write(api.Response{Status: api.StatusOkay, Data: group})
}
type addGroupParam struct {
SessionID string
Name string
}
func (r *addGroupParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = addGroupParam(v)
return r.validate()
}
func (r *addGroupParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 25 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) updateGroup(w api.ResponseWriter, req *rest.Request) {
p := new(updateGroupParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("updateGroup request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var group *Group
var duplicated bool
f := func(tx Transaction) (err error) {
group, duplicated, err = tx.UpdateGroup(session.(*User).ID, p.ID, p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to update a group: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated group: %v", p.Name)})
return
}
if group == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found group to update: %v", p.ID)})
return
}
logger.Debugf("updated the group: %v", spew.Sdump(group))
w.Write(api.Response{Status: api.StatusOkay, Data: group})
}
type updateGroupParam struct {
SessionID string
ID uint64
Name string
}
func (r *updateGroupParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = updateGroupParam(v)
return r.validate()
}
func (r *updateGroupParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid group id")
}
if utf8.RuneCountInString(r.Name) < 2 || utf8.RuneCountInString(r.Name) > 25 {
return fmt.Errorf("invalid name: %v", r.Name)
}
return nil
}
func (r *API) removeGroup(w api.ResponseWriter, req *rest.Request) {
p := new(removeGroupParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("removeGroup request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var group *Group
f := func(tx Transaction) (err error) {
group, err = tx.RemoveGroup(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove a group: %v", err.Error())})
return
}
if group == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found group to remove: %v", p.ID)})
return
}
logger.Debugf("removed the group: %v", spew.Sdump(group))
w.Write(api.Response{Status: api.StatusOkay})
}
type removeGroupParam struct {
SessionID string
ID uint64
}
func (r *removeGroupParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeGroupParam(v)
return r.validate()
}
func (r *removeGroupParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid group id")
}
return nil
}
================================================
FILE: api/ui/host.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"net"
"time"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/superkkt/cherry/network"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
var (
errDuplicated = errors.New("duplicated error")
errNotFound = errors.New("not found error")
errBlocked = errors.New("blocked error")
)
type HostTransaction interface {
Host(id uint64) (*Host, error)
// Hosts returns a list of registered hosts. Search can be nil that means no search. Pagination limit can be 0 that means no pagination.
Hosts(*Search, Sort, Pagination) ([]*Host, error)
AddHost(requesterID, ipID uint64, groupID *uint64, mac net.HardwareAddr, desc string, spec []SpecParam) (host *Host, duplicated bool, err error)
// UpdateHost updates a host specified by id and then returns information of the host. The parameters used for update can be nil that means no update about the parameters.
UpdateHost(requesterID, hostID uint64, ipID, groupID *uint64, mac net.HardwareAddr, description *string, spec []SpecParam) (host *Host, duplicated bool, err error)
// ActivateHost enables a host specified by id and then returns information of the host. It returns nil if the host does not exist.
ActivateHost(requesterID, hostID uint64) (*Host, error)
// DeactivateHost disables a host specified by id and then returns information of the host. It returns nil if the host does not exist.
DeactivateHost(requesterID, hostID uint64) (*Host, error)
CountVIPByHostID(id uint64) (count uint64, err error)
// RemoveHost removes a host specified by id and then returns information of the host before removing. It returns nil if the host does not exist.
RemoveHost(requesterID, hostID uint64) (*Host, error)
}
type Host struct {
ID uint64
IP string // FIXME: Use a native type.
Port string
Group string
MAC string // FIXME: Use a native type.
Description string
Enabled bool
Stale bool
Spec []*Spec
Timestamp time.Time
}
func (r *Host) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
IP string `json:"ip"`
Port string `json:"port"`
Group string `json:"group"`
MAC string `json:"mac"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Stale bool `json:"stale"`
Spec []*Spec `json:"spec"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
IP: r.IP,
Port: r.Port,
Group: r.Group,
MAC: r.MAC,
Description: r.Description,
Enabled: r.Enabled,
Stale: r.Stale,
Spec: r.Spec,
Timestamp: r.Timestamp.Unix(),
})
}
type Spec struct {
ID uint64 `json:"id"`
Component Component `json:"component"`
Count uint16 `json:"count"`
}
func (r *API) listHost(w api.ResponseWriter, req *rest.Request) {
p := new(listHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host []*Host
f := func(tx Transaction) (err error) {
host, err = tx.Hosts(p.Search, p.Sort, p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the hosts: %v", err.Error())})
return
}
logger.Debugf("queried host list: %v", spew.Sdump(host))
w.Write(api.Response{Status: api.StatusOkay, Data: host})
}
type listHostParam struct {
SessionID string
Search *Search
Sort Sort
Pagination Pagination
}
func (r *listHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Search *Search `json:"search"`
Sort Sort `json:"sort"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listHostParam(v)
return r.validate()
}
func (r *listHostParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
// If search is nil, fetch hosts without using search.
if r.Search != nil {
if r.Search.Key <= ColumnTime || r.Search.Key > ColumnDescription {
return errors.New("invalid search key")
}
if err := r.Search.Validate(); err != nil {
return err
}
}
if err := r.Sort.Validate(); err != nil {
return err
}
return nil
}
func (r *API) getHost(w api.ResponseWriter, req *rest.Request) {
p := new(getHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("getHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host *Host
f := func(tx Transaction) (err error) {
host, err = tx.Host(p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the host info: %v", err.Error())})
return
}
if host == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found host: %v", p.ID)})
return
}
logger.Debugf("queried host info: %v", spew.Sdump(host))
w.Write(api.Response{Status: api.StatusOkay, Data: host})
}
type getHostParam struct {
SessionID string
ID uint64
}
func (r *getHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = getHostParam(v)
return r.validate()
}
func (r *getHostParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid host id")
}
return nil
}
func (r *API) addHost(w api.ResponseWriter, req *rest.Request) {
p := new(addHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("addHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host []*Host
f := func(tx Transaction) (err error) {
for _, v := range p.IPID {
h, duplicated, err := tx.AddHost(session.(*User).ID, v, p.GroupID, p.MAC, p.Description, p.Spec)
if err != nil {
return err
}
if duplicated {
return errDuplicated
}
host = append(host, h)
}
return nil
}
if err := r.DB.Exec(f); err != nil {
if err == errDuplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated host: ip_id=%v", p.IPID)})
} else {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new host: %v", err.Error())})
}
return
}
logger.Debugf("added host info: %v", spew.Sdump(host))
for _, v := range host {
if err := r.announce(v.IP, v.MAC); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
}
w.Write(api.Response{Status: api.StatusOkay, Data: host})
}
type addHostParam struct {
SessionID string
IPID []uint64
GroupID *uint64
MAC net.HardwareAddr
Description string
Spec []SpecParam
}
func (r *addHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
IPID []uint64 `json:"ip_id"`
GroupID *uint64 `json:"group_id"`
MAC string `json:"mac"`
Description string `json:"description"`
Spec []SpecParam `json:"spec"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
if len(v.SessionID) != 64 {
return errors.New("invalid session id")
}
if len(v.IPID) == 0 {
return errors.New("empty ip id")
}
if len(v.IPID) > 10 {
return errors.New("too many ip ids")
}
for _, i := range v.IPID {
if i == 0 {
return errors.New("invalid ip id")
}
}
if utf8.RuneCountInString(v.Description) > 255 {
return errors.New("too long description")
}
mac, err := net.ParseMAC(v.MAC)
if err != nil {
return err
}
for _, v := range r.Spec {
if err := v.Validate(); err != nil {
return err
}
}
r.SessionID = v.SessionID
r.IPID = v.IPID
r.GroupID = v.GroupID
r.MAC = mac
r.Description = v.Description
r.Spec = v.Spec
return nil
}
type SpecParam struct {
ComponentID uint64 `json:"component_id"`
Count uint16 `json:"count"`
}
func (r *SpecParam) Validate() error {
if r.ComponentID == 0 {
return errors.New("invalid component id")
}
if r.Count == 0 {
return errors.New("invalid count")
}
return nil
}
func (r *API) updateHost(w api.ResponseWriter, req *rest.Request) {
p := new(updateHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("updateHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var old, new *Host
f := func(tx Transaction) (err error) {
count, err := tx.CountVIPByHostID(p.ID)
if err != nil {
return err
}
if count > 0 {
return errors.New("VIP member host cannot be updated")
}
old, err = tx.Host(p.ID)
if err != nil {
return err
}
if old == nil {
return errNotFound
}
if old.Enabled == false {
return errBlocked
}
h, duplicated, err := tx.UpdateHost(session.(*User).ID, p.ID, p.IPID, p.GroupID, p.MAC, p.Description, p.Spec)
if err != nil {
return err
}
if duplicated {
return errDuplicated
}
new = h
return nil
}
if err := r.DB.Exec(f); err != nil {
switch err {
case errNotFound:
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found host to update: %v", p.ID)})
case errBlocked:
w.Write(api.Response{Status: api.StatusBlockedHost, Message: fmt.Sprintf("unable to update blocked host: %v", p.ID)})
case errDuplicated:
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated host: ip_id=%v", p.IPID)})
default:
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to update a host: %v", err.Error())})
}
return
}
logger.Debugf("updated host info: %v", spew.Sdump(new))
if err := r.announce(old.IP, network.NullMAC.String()); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
if err := r.announce(new.IP, new.MAC); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay, Data: new})
}
type updateHostParam struct {
SessionID string
ID uint64
IPID *uint64
GroupID *uint64
MAC net.HardwareAddr
Description *string
Spec []SpecParam
}
func (r *updateHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
IPID *uint64 `json:"ip_id"`
GroupID *uint64 `json:"group_id"`
MAC *string `json:"mac"`
Description *string `json:"description"`
Spec []SpecParam `json:"spec"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
if len(v.SessionID) != 64 {
return errors.New("invalid session id")
}
if v.ID == 0 {
return errors.New("invalid host id")
}
if v.IPID == nil && v.GroupID == nil && v.MAC == nil && v.Description == nil && v.Spec == nil {
return errors.New("not exist value to update")
}
if v.IPID != nil && *v.IPID == 0 {
return errors.New("invalid ip id")
}
if v.GroupID != nil && *v.GroupID == 0 {
return errors.New("invalid group id")
}
if v.Description != nil && utf8.RuneCountInString(*v.Description) > 255 {
return errors.New("too long description")
}
for _, v := range r.Spec {
if err := v.Validate(); err != nil {
return err
}
}
if v.MAC != nil {
v, err := net.ParseMAC(*v.MAC)
if err != nil {
return err
}
r.MAC = v
}
r.SessionID = v.SessionID
r.ID = v.ID
r.IPID = v.IPID
r.GroupID = v.GroupID
r.Description = v.Description
r.Spec = v.Spec
return nil
}
func (r *API) activateHost(w api.ResponseWriter, req *rest.Request) {
p := new(activateHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("activateHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host *Host
f := func(tx Transaction) (err error) {
host, err = tx.ActivateHost(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to activate a host: %v", err.Error())})
return
}
if host == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found host to activate: %v", p.ID)})
return
}
logger.Debugf("activated host info: %v", spew.Sdump(host))
if err := r.announce(host.IP, host.MAC); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay})
}
type activateHostParam struct {
SessionID string
ID uint64
}
func (r *activateHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = activateHostParam(v)
return r.validate()
}
func (r *activateHostParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid switch id")
}
return nil
}
func (r *API) deactivateHost(w api.ResponseWriter, req *rest.Request) {
p := new(deactivateHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("deactivateHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host *Host
f := func(tx Transaction) (err error) {
count, err := tx.CountVIPByHostID(p.ID)
if err != nil {
return err
}
if count > 0 {
return errors.New("VIP member host cannot be disabled")
}
host, err = tx.DeactivateHost(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to deactivate a host: %v", err.Error())})
return
}
if host == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found host to deactivate: %v", p.ID)})
return
}
logger.Debugf("deactivated host info: %v", spew.Sdump(host))
if err := r.announce(host.IP, network.NullMAC.String()); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay})
}
type deactivateHostParam struct {
SessionID string
ID uint64
}
func (r *deactivateHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = deactivateHostParam(v)
return r.validate()
}
func (r *deactivateHostParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid switch id")
}
return nil
}
func (r *API) removeHost(w api.ResponseWriter, req *rest.Request) {
p := new(removeHostParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("removeHost request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var host *Host
f := func(tx Transaction) (err error) {
host, err = tx.RemoveHost(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove a host: %v", err.Error())})
return
}
if host == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found host to remove: %v", p.ID)})
return
}
logger.Debugf("removed host info: %v", spew.Sdump(host))
if err := r.announce(host.IP, network.NullMAC.String()); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay})
}
type removeHostParam struct {
SessionID string
ID uint64
}
func (r *removeHostParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeHostParam(v)
return r.validate()
}
func (r *removeHostParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid switch id")
}
return nil
}
================================================
FILE: api/ui/ip.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type IPTransaction interface {
IPAddrs(networkID uint64) ([]*IP, error)
}
type IP struct {
ID uint64 `json:"id"`
Address string `json:"address"` // FIXME: Use a native type.
Used bool `json:"used"`
Port string `json:"port"`
Host struct {
Description string `json:"description"`
Enabled bool `json:"enabled"`
Stale bool `json:"stale"`
} `json:"host"`
}
func (r *API) listIP(w api.ResponseWriter, req *rest.Request) {
p := new(listIPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listIP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var ip []*IP
f := func(tx Transaction) (err error) {
ip, err = tx.IPAddrs(p.NetworkID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the network ip list: %v", err.Error())})
return
}
logger.Debugf("queried network ip list: %v", spew.Sdump(ip))
w.Write(api.Response{Status: api.StatusOkay, Data: ip})
}
type listIPParam struct {
SessionID string
NetworkID uint64
}
func (r *listIPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
NetworkID uint64 `json:"network_id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listIPParam(v)
return r.validate()
}
func (r *listIPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.NetworkID == 0 {
return errors.New("invalid network id")
}
return nil
}
================================================
FILE: api/ui/log.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type LogType string
const (
LogTypeUser LogType = "USER"
LogTypeGroup LogType = "GROUP"
LogTypeSwitch LogType = "SWITCH"
LogTypeNetwork LogType = "NETWORK"
LogTypeHost LogType = "HOST"
LogTypeVIP LogType = "VIP"
LogTypeCategory LogType = "CATEGORY"
LogTypeComponent LogType = "COMPONENT"
)
func (r LogType) Validate() error {
if r != LogTypeUser && r != LogTypeGroup && r != LogTypeSwitch && r != LogTypeNetwork &&
r != LogTypeHost && r != LogTypeVIP && r != LogTypeCategory && r != LogTypeComponent {
return fmt.Errorf("invalid log type: %v", r)
}
return nil
}
type LogMethod string
const (
LogMethodAdd LogMethod = "ADD"
LogMethodUpdate LogMethod = "UPDATE"
LogMethodRemove LogMethod = "REMOVE"
)
func (r LogMethod) Validate() error {
if r != LogMethodAdd && r != LogMethodUpdate && r != LogMethodRemove {
return fmt.Errorf("invalid log method: %v", r)
}
return nil
}
type LogTransaction interface {
// Logs returns a list of registered logs. Search can be nil that means no search.
QueryLog(*Search, Pagination) ([]*Log, error)
}
type Log struct {
ID uint64
User string
Type LogType
Method LogMethod
Data string
Timestamp time.Time
}
func (r *Log) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
User string `json:"user"`
Type string `json:"type"`
Method string `json:"method"`
Data string `json:"data"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
User: r.User,
Type: string(r.Type),
Method: string(r.Method),
Data: r.Data,
Timestamp: r.Timestamp.Unix(),
})
}
func (r *API) listLog(w api.ResponseWriter, req *rest.Request) {
p := new(listLogParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listLog request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var log []*Log
f := func(tx Transaction) (err error) {
log, err = tx.QueryLog(p.Search, p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the log list: %v", err.Error())})
return
}
logger.Debugf("queried log list: %v", spew.Sdump(log))
w.Write(api.Response{Status: api.StatusOkay, Data: log})
}
type listLogParam struct {
SessionID string
Search *Search
Pagination Pagination
}
func (r *listLogParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Search *Search `json:"search"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listLogParam(v)
return r.validate()
}
func (r *listLogParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
// If search is nil, fetch logs without using search.
if r.Search != nil {
if r.Search.Key <= ColumnDescription || r.Search.Key > ColumnLogMethod {
return errors.New("invalid search key")
}
if err := r.Search.Validate(); err != nil {
return err
}
}
if r.Pagination.Limit == 0 {
return errors.New("invalid pagination limit")
}
return nil
}
================================================
FILE: api/ui/network.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/superkkt/cherry/api"
"github.com/superkkt/cherry/network"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type NetworkTransaction interface {
// Networks returns a list of registered networks. Address can be nil that means no address search. Pagination limit can be 0 that means no pagination.
Networks(address *string, pagination Pagination) ([]*Network, error)
AddNetwork(requesterID uint64, addr net.IP, mask net.IPMask, gateway net.IP) (network *Network, duplicated bool, err error)
// RemoveNetwork removes a network specified by id and then returns information of the network before removing. It returns nil if the network does not exist.
RemoveNetwork(requesterID, netID uint64) (*Network, error)
}
type Network struct {
ID uint64 `json:"id"`
Address string `json:"address"` // FIXME: Use a native type.
Mask uint8 `json:"mask"` // FIXME: Use a native type.
Gateway string `json:"gateway"` // FIXME: Use a native type.
}
func (r *API) listNetwork(w api.ResponseWriter, req *rest.Request) {
p := new(listNetworkParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listNetwork request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var network []*Network
f := func(tx Transaction) (err error) {
network, err = tx.Networks(p.Address, p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the network list: %v", err.Error())})
return
}
logger.Debugf("queried network list: %v", spew.Sdump(network))
w.Write(api.Response{Status: api.StatusOkay, Data: network})
}
type listNetworkParam struct {
SessionID string
Address *string
Pagination Pagination
}
func (r *listNetworkParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Address *string `json:"address"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listNetworkParam(v)
return r.validate()
}
func (r *listNetworkParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.Address != nil {
if err := validateIP(*r.Address); err != nil {
return err
}
}
return nil
}
func (r *API) addNetwork(w api.ResponseWriter, req *rest.Request) {
p := new(addNetworkParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("addNetwork request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var network *Network
var duplicated bool
f := func(tx Transaction) (err error) {
network, duplicated, err = tx.AddNetwork(session.(*User).ID, p.Address, p.Mask, p.Gateway)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new network: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated network: address=%v, mask=%v", p.Address, p.Mask)})
return
}
logger.Debugf("added network info: %v", spew.Sdump(network))
w.Write(api.Response{Status: api.StatusOkay, Data: network})
}
type addNetworkParam struct {
SessionID string
Address net.IP
Mask net.IPMask
Gateway net.IP
}
func (r *addNetworkParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Address string `json:"address"`
Mask uint8 `json:"mask"`
Gateway string `json:"gateway"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
if len(v.SessionID) != 64 {
return errors.New("invalid session id")
}
if v.Mask < 24 || v.Mask > 30 {
return fmt.Errorf("invalid network mask: %v", v.Mask)
}
_, network, err := net.ParseCIDR(fmt.Sprintf("%v/%v", v.Address, v.Mask))
if err != nil || network == nil {
return fmt.Errorf("invalid network address: %v", v.Address)
}
gateway := net.ParseIP(v.Gateway)
if gateway == nil {
return fmt.Errorf("invalid network gateway: %v", v.Gateway)
}
if err := validateGateway(*network, gateway); err != nil {
return err
}
r.SessionID = v.SessionID
r.Mask = network.Mask
r.Address = network.IP
r.Gateway = gateway
return nil
}
func validateGateway(n net.IPNet, g net.IP) error {
invalid := fmt.Errorf("invalid network gateway: %v", g)
broadcast := net.IP(make([]byte, 4))
for i := range n.IP.To4() {
broadcast[i] = n.IP.To4()[i] | ^n.Mask[i]
}
reserved, err := network.ReservedIP(n)
if err != nil {
return err
}
if n.Contains(g) == false {
return invalid
}
if g.Equal(n.IP) || g.Equal(broadcast) || g.Equal(reserved) {
return invalid
}
return nil
}
func (r *API) removeNetwork(w api.ResponseWriter, req *rest.Request) {
p := new(removeNetworkParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("removeNetwork request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var network *Network
f := func(tx Transaction) (err error) {
network, err = tx.RemoveNetwork(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove a network: %v", err.Error())})
return
}
if network == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found network to remove: %v", p.ID)})
return
}
logger.Debugf("removed a network: %v", spew.Sdump(network))
logger.Debug("removing all flows from the entire switches")
if err := r.Controller.RemoveFlows(); err != nil {
// Ignore this error.
logger.Errorf("failed to remove flows: %v", err)
} else {
logger.Debug("removed all flows from the entire switches")
}
w.Write(api.Response{Status: api.StatusOkay})
}
type removeNetworkParam struct {
SessionID string
ID uint64
}
func (r *removeNetworkParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeNetworkParam(v)
return r.validate()
}
func (r *removeNetworkParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("empty network id")
}
return nil
}
================================================
FILE: api/ui/session.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"strings"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/golang-lru"
)
type session struct {
storage *lru.Cache
timeout time.Duration
}
func newSession(size int, timeout time.Duration) *session {
c, err := lru.New(size)
if err != nil {
panic(err)
}
return &session{
storage: c,
timeout: timeout,
}
}
func (r *session) Add(v interface{}) (id string) {
src := fmt.Sprintf("%v.%v.%v", spew.Sdump(v), time.Now().UnixNano(), rand.Float64())
id = strings.ToUpper(hash(src))
r.storage.Add(id, &sessionEntry{Data: v, Timestamp: time.Now()})
return id
}
type sessionEntry struct {
Data interface{}
Timestamp time.Time
}
func hash(value string) string {
h := sha256.New()
h.Write([]byte(value))
return hex.EncodeToString(h.Sum(nil))
}
func (r *session) Get(id string) (interface{}, bool) {
id = strings.ToUpper(id)
v, ok := r.storage.Get(id)
if ok == false {
return nil, false
}
e := v.(*sessionEntry)
if time.Since(e.Timestamp) > r.timeout {
r.storage.Remove(id)
return nil, false
}
e.Timestamp = time.Now()
return e.Data, true
}
func (r *session) Remove(id string) bool {
id = strings.ToUpper(id)
if r.storage.Contains(id) == false {
return false
}
r.storage.Remove(id)
return true
}
================================================
FILE: api/ui/switch.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type SwitchTransaction interface {
Switches(Pagination) ([]*Switch, error)
AddSwitch(requesterID, dpid uint64, nPorts, firstPort, firstPrintedPort uint16, desc string) (sw *Switch, duplicated bool, err error)
// RemoveSwitch removes a switch specified by id and then returns information of the switch before removing. It returns nil if the switch does not exist.
RemoveSwitch(requesterID, swID uint64) (*Switch, error)
}
type Switch struct {
ID uint64 `json:"id"`
DPID uint64 `json:"dpid"`
NumPorts uint16 `json:"n_ports"`
FirstPort uint16 `json:"first_port"`
FirstPrintedPort uint16 `json:"first_printed_port"`
Description string `json:"description"`
}
func (r *Switch) MarshalJSON() ([]byte, error) {
s := new(struct {
ID uint64 `json:"id"`
DPID struct {
Int uint64 `json:"int"`
Hex string `json:"hex"`
} `json:"dpid"`
NumPorts uint16 `json:"n_ports"`
FirstPort uint16 `json:"first_port"`
FirstPrintedPort uint16 `json:"first_printed_port"`
Description string `json:"description"`
})
s.ID = r.ID
s.DPID.Int = r.DPID
s.DPID.Hex = hexDPID(r.DPID)
s.NumPorts = r.NumPorts
s.FirstPort = r.FirstPort
s.FirstPrintedPort = r.FirstPrintedPort
s.Description = r.Description
return json.Marshal(&s)
}
func hexDPID(dpid uint64) string {
hex := fmt.Sprintf("%016x", dpid)
re := regexp.MustCompile("..")
return strings.TrimRight(re.ReplaceAllString(hex, "$0:"), ":")
}
func (r *API) listSwitch(w api.ResponseWriter, req *rest.Request) {
p := new(listSwitchParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listSwitch request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var sw []*Switch
f := func(tx Transaction) (err error) {
sw, err = tx.Switches(p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the switch list: %v", err.Error())})
return
}
logger.Debugf("queried switch list: %v", spew.Sdump(sw))
w.Write(api.Response{Status: api.StatusOkay, Data: sw})
}
type listSwitchParam struct {
SessionID string
Pagination Pagination
}
func (r *listSwitchParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listSwitchParam(v)
return r.validate()
}
func (r *listSwitchParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.Pagination.Limit == 0 {
return errors.New("invalid pagination limit")
}
return nil
}
func (r *API) addSwitch(w api.ResponseWriter, req *rest.Request) {
p := new(addSwitchParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("addSwitch request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var sw *Switch
var duplicated bool
f := func(tx Transaction) (err error) {
sw, duplicated, err = tx.AddSwitch(session.(*User).ID, p.DPID, p.NumPorts, p.FirstPort, p.FirstPrintedPort, p.Description)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new switch: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated switch: dpid=%v", p.DPID)})
return
}
logger.Debugf("added switch info: %v", spew.Sdump(sw))
w.Write(api.Response{Status: api.StatusOkay, Data: sw})
}
type addSwitchParam struct {
SessionID string
DPID uint64
NumPorts uint16
FirstPort uint16
FirstPrintedPort uint16
Description string
}
func (r *addSwitchParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
DPID string `json:"dpid"`
NumPorts uint16 `json:"n_ports"`
FirstPort uint16 `json:"first_port"`
FirstPrintedPort uint16 `json:"first_printed_port"`
Description string `json:"description"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
if len(v.SessionID) != 64 {
return errors.New("invalid session id")
}
if v.NumPorts == 0 {
return errors.New("invalid number of ports")
}
if v.NumPorts > 512 {
return errors.New("too many ports")
}
if utf8.RuneCountInString(v.Description) > 255 {
return errors.New("too long description")
}
if uint32(v.FirstPort)+uint32(v.NumPorts) > 0xFFFF {
return errors.New("too high first port number")
}
ok, err := regexp.MatchString("^([0-9a-fA-F]{2}:){7}([0-9a-fA-F]{2})$", v.DPID)
if err != nil {
return err
}
// Is the DP id in hex format?
if ok {
v.DPID = strings.Replace(v.DPID, ":", "", -1)
if r.DPID, err = strconv.ParseUint(v.DPID, 16, 64); err != nil {
return err
}
} else {
if r.DPID, err = strconv.ParseUint(v.DPID, 10, 64); err != nil {
return err
}
}
r.SessionID = v.SessionID
r.NumPorts = v.NumPorts
r.FirstPort = v.FirstPort
r.FirstPrintedPort = v.FirstPrintedPort
r.Description = v.Description
return nil
}
func (r *API) removeSwitch(w api.ResponseWriter, req *rest.Request) {
p := new(removeSwitchParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("removeSwitch request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var sw *Switch
f := func(tx Transaction) (err error) {
sw, err = tx.RemoveSwitch(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove a switch: %v", err.Error())})
return
}
if sw == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found switch to remove: %v", p.ID)})
return
}
logger.Debugf("removed a switch: %v", spew.Sdump(sw))
logger.Debug("removing all flows from the entire switches")
if err := r.Controller.RemoveFlows(); err != nil {
// Ignore this error.
logger.Errorf("failed to remove flows: %v", err)
} else {
logger.Debug("removed all flows from the entire switches")
}
w.Write(api.Response{Status: api.StatusOkay})
}
type removeSwitchParam struct {
SessionID string
ID uint64
}
func (r *removeSwitchParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeSwitchParam(v)
return r.validate()
}
func (r *removeSwitchParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid switch id")
}
return nil
}
================================================
FILE: api/ui/user.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/superkkt/cherry/api"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
type UserTransaction interface {
User(name string) (*User, error)
Users(Pagination) ([]*User, error)
AddUser(requesterID uint64, name, key string) (user *User, duplicated bool, err error)
// UpdateUser updates enabled and admin authorization of a user specified by id and then returns information of the user. It returns nil if the user does not exist.
UpdateUser(requesterID, userID uint64, enabled, admin *bool) (*User, error)
ResetOTPKey(name, key string) (ok bool, err error)
}
type User struct {
ID uint64
Name string
Key string // Key used in OTP Authentication.
Enabled bool
Admin bool
Timestamp time.Time
}
func (r *User) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Admin bool `json:"admin"`
Timestamp int64 `json:"timestamp"`
}{
ID: r.ID,
Name: r.Name,
Enabled: r.Enabled,
Admin: r.Admin,
Timestamp: r.Timestamp.Unix(),
})
}
func (r *API) login(w api.ResponseWriter, req *rest.Request) {
p := new(loginParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("login request from %v: %v", req.RemoteAddr, spew.Sdump(&struct {
Name string `json:"name"`
Code string `json:"code"`
}{
Name: p.Name,
Code: p.Code,
}))
var user *User
f := func(tx Transaction) (err error) {
user, err = tx.User(p.Name)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query an user account: %v", err.Error())})
return
}
if user == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found user to login: %v", p.Name)})
return
}
if user.Enabled == false {
w.Write(api.Response{Status: api.StatusBlockedAccount, Message: fmt.Sprintf("login attempt with a blocked account: %v", p.Name)})
return
}
ok, err := r.LDAP.Auth(p.Name, p.Password)
if err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to authenticate an user account: %v", err.Error())})
return
}
if ok == false {
w.Write(api.Response{Status: api.StatusIncorrectCredential, Message: fmt.Sprintf("incorrect username or password: %v", p.Name)})
return
}
if ok := totp.Validate(p.Code, user.Key); ok == false {
w.Write(api.Response{Status: api.StatusIncorrectCredential, Message: fmt.Sprintf("incorrect OTP code: %v", p.Code)})
return
}
id := r.session.Add(user)
logger.Debugf("login success: user=%v, sessionID=%v", spew.Sdump(user), id)
w.Write(api.Response{
Status: api.StatusOkay,
Data: struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
Admin bool `json:"admin"`
}{
SessionID: id,
ID: user.ID,
Admin: user.Admin,
},
})
}
type loginParam struct {
Name string
Password string
Code string // OTP Authentication Code.
}
func (r *loginParam) UnmarshalJSON(data []byte) error {
v := struct {
Name string `json:"name"`
Password string `json:"password"`
Code string `json:"code"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = loginParam(v)
return r.validate()
}
func (r *loginParam) validate() error {
if len(r.Name) == 0 {
return errors.New("empty name")
}
if len(r.Password) == 0 {
return errors.New("empty password")
}
if len(r.Code) != 6 {
return fmt.Errorf("invalid OTP code: %v", r.Code)
}
return nil
}
func (r *API) logout(w api.ResponseWriter, req *rest.Request) {
p := new(logoutParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("logout request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if r.session.Remove(p.SessionID) == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("logout attempt with an unknown session ID: %v", p.SessionID)})
return
}
logger.Debugf("session removed: sessionID=%v", p.SessionID)
w.Write(api.Response{Status: api.StatusOkay})
}
type logoutParam struct {
SessionID string
}
func (r *logoutParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = logoutParam(v)
return r.validate()
}
func (r *logoutParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
return nil
}
func (r *API) listUser(w api.ResponseWriter, req *rest.Request) {
p := new(listUserParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listUser request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
if session.(*User).Admin == false {
w.Write(api.Response{Status: api.StatusPermissionDenied, Message: fmt.Sprintf("not allowed session id: %v", p.SessionID)})
return
}
var user []*User
f := func(tx Transaction) (err error) {
user, err = tx.Users(p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the user accounts: %v", err.Error())})
return
}
logger.Debugf("queried user accounts: %v", spew.Sdump(user))
w.Write(api.Response{Status: api.StatusOkay, Data: user})
}
type listUserParam struct {
SessionID string
Pagination Pagination
}
func (r *listUserParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listUserParam(v)
return r.validate()
}
func (r *listUserParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.Pagination.Limit == 0 {
return errors.New("invalid pagination limit")
}
return nil
}
func (r *API) addUser(w api.ResponseWriter, req *rest.Request) {
p := new(addUserParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("addUser request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
if session.(*User).Admin == false {
w.Write(api.Response{Status: api.StatusPermissionDenied, Message: fmt.Sprintf("not allowed session id: %v", p.SessionID)})
return
}
key, err := generateOTPKey(p.Name)
if err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to generate OTP key: %v", err)})
return
}
var user *User
var duplicated bool
f := func(tx Transaction) (err error) {
user, duplicated, err = tx.AddUser(session.(*User).ID, p.Name, key.Secret())
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new user account: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated user account: %v", p.Name)})
return
}
logger.Debugf("added the user account: %v", spew.Sdump(user))
w.Write(api.Response{
Status: api.StatusOkay,
Data: &struct {
User *User `json:"user"`
OTP string `json:"otp"`
}{
User: user,
OTP: key.String(),
},
})
}
type addUserParam struct {
SessionID string
Name string
}
func (r *addUserParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = addUserParam(v)
return r.validate()
}
func (r *addUserParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if len(r.Name) == 0 {
return errors.New("empty name")
}
return nil
}
func generateOTPKey(account string) (*otp.Key, error) {
return totp.Generate(totp.GenerateOpts{
Issuer: "Cherry",
AccountName: account,
})
}
func (r *API) updateUser(w api.ResponseWriter, req *rest.Request) {
p := new(updateUserParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("updateUser request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
if session.(*User).Admin == false {
w.Write(api.Response{Status: api.StatusPermissionDenied, Message: fmt.Sprintf("not allowed session id: %v", p.SessionID)})
return
}
var user *User
f := func(tx Transaction) (err error) {
user, err = tx.UpdateUser(session.(*User).ID, p.ID, p.Enabled, p.Admin)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to update a user account: %v", err.Error())})
return
}
if user == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found user to update: %v", p.ID)})
return
}
logger.Debugf("updated the user account: %v", spew.Sdump(user))
w.Write(api.Response{Status: api.StatusOkay, Data: user})
}
type updateUserParam struct {
SessionID string
ID uint64
Enabled *bool
Admin *bool
}
func (r *updateUserParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
Enabled *bool `json:"enabled"`
Admin *bool `json:"admin"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = updateUserParam(v)
return r.validate()
}
func (r *updateUserParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid user ID")
}
if r.Enabled == nil && r.Admin == nil {
return errors.New("empty parameter")
}
return nil
}
func (r *API) resetOTP(w api.ResponseWriter, req *rest.Request) {
p := new(resetOTPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode params: %v", err.Error())})
return
}
logger.Debugf("resetOTP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
if session.(*User).Admin == false {
w.Write(api.Response{Status: api.StatusPermissionDenied, Message: fmt.Sprintf("not allowed session id: %v", p.SessionID)})
return
}
key, err := generateOTPKey(p.Name)
if err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to generate OTP key: %v", err)})
return
}
f := func(tx Transaction) (err error) {
ok, err = tx.ResetOTPKey(p.Name, key.Secret())
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to reset OTP of a user account: %v", err.Error())})
return
}
if ok == false {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found user to reset OTP: %v", p.Name)})
return
}
logger.Debugf("reset OTP of the user account: %v", p.Name)
w.Write(api.Response{Status: api.StatusOkay, Data: key.String()})
}
type resetOTPParam struct {
SessionID string
Name string
}
func (r *resetOTPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Name string `json:"name"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = resetOTPParam(v)
return r.validate()
}
func (r *resetOTPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if len(r.Name) == 0 {
return errors.New("empty name")
}
return nil
}
================================================
FILE: api/ui/vip.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ui
import (
"encoding/json"
"errors"
"fmt"
"unicode/utf8"
"github.com/superkkt/cherry/api"
"github.com/superkkt/cherry/network"
"github.com/ant0ine/go-json-rest/rest"
"github.com/davecgh/go-spew/spew"
)
type VIPTransaction interface {
VIPs(Pagination) ([]*VIP, error)
AddVIP(requesterID, ipID, activeID, standbyID uint64, desc string) (vip *VIP, duplicated bool, err error)
// RemoveVIP removes a VIP specified by id and then returns information of the VIP before removing. It returns nil if the VIP does not exist.
RemoveVIP(requesterID, vipID uint64) (*VIP, error)
// ToggleVIP swaps active host and standby host of a VIP specified by id and then returns information of the VIP. It returns nil if the VIP does not exist.
ToggleVIP(requesterID, vipID uint64) (*VIP, error)
}
type VIP struct {
ID uint64 `json:"id"`
IP string `json:"ip"` // FIXME: Use a native type.
ActiveHost Host `json:"active_host"`
StandbyHost Host `json:"standby_host"`
Description string `json:"description"`
}
func (r *API) listVIP(w api.ResponseWriter, req *rest.Request) {
p := new(listVIPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("listVIP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
if _, ok := r.session.Get(p.SessionID); ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var vip []*VIP
f := func(tx Transaction) (err error) {
vip, err = tx.VIPs(p.Pagination)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to query the VIP list: %v", err.Error())})
return
}
logger.Debugf("queried VIP list: %v", spew.Sdump(vip))
w.Write(api.Response{Status: api.StatusOkay, Data: vip})
}
type listVIPParam struct {
SessionID string
Pagination Pagination
}
func (r *listVIPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
Pagination Pagination `json:"pagination"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = listVIPParam(v)
return r.validate()
}
func (r *listVIPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.Pagination.Limit == 0 {
return errors.New("invalid pagination limit")
}
return nil
}
func (r *API) addVIP(w api.ResponseWriter, req *rest.Request) {
p := new(addVIPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("addVIP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var vip *VIP
var duplicated bool
f := func(tx Transaction) (err error) {
vip, duplicated, err = tx.AddVIP(session.(*User).ID, p.IPID, p.ActiveHostID, p.StandbyHostID, p.Description)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to add a new VIP: %v", err.Error())})
return
}
if duplicated {
w.Write(api.Response{Status: api.StatusDuplicated, Message: fmt.Sprintf("duplicated VIP: ip_id=%v", p.IPID)})
return
}
logger.Debugf("added a new VIP: %v", spew.Sdump(vip))
if err := r.announce(vip.IP, vip.ActiveHost.MAC); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay, Data: vip})
}
type addVIPParam struct {
SessionID string
IPID uint64
ActiveHostID uint64
StandbyHostID uint64
Description string
}
func (r *addVIPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
IPID uint64 `json:"ip_id"`
ActiveHostID uint64 `json:"active_host_id"`
StandbyHostID uint64 `json:"standby_host_id"`
Description string `json:"description"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = addVIPParam(v)
return r.validate()
}
func (r *addVIPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ActiveHostID == 0 {
return errors.New("invalid active host id")
}
if r.StandbyHostID == 0 {
return errors.New("invalid standby host id")
}
if r.ActiveHostID == r.StandbyHostID {
return errors.New("same host for the active and standby")
}
if utf8.RuneCountInString(r.Description) > 255 {
return errors.New("too long description")
}
return nil
}
func (r *API) removeVIP(w api.ResponseWriter, req *rest.Request) {
p := new(removeVIPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("removeVIP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var vip *VIP
f := func(tx Transaction) (err error) {
vip, err = tx.RemoveVIP(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to remove a VIP: %v", err.Error())})
return
}
if vip == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found VIP to remove: %v", p.ID)})
return
}
logger.Debugf("removed the VIP: %v", spew.Sdump(vip))
if err := r.announce(vip.IP, network.NullMAC.String()); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay})
}
type removeVIPParam struct {
SessionID string
ID uint64
}
func (r *removeVIPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = removeVIPParam(v)
return r.validate()
}
func (r *removeVIPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid VIP id")
}
return nil
}
func (r *API) toggleVIP(w api.ResponseWriter, req *rest.Request) {
p := new(toggleVIPParam)
if err := req.DecodeJsonPayload(p); err != nil {
w.Write(api.Response{Status: api.StatusInvalidParameter, Message: fmt.Sprintf("failed to decode param: %v", err.Error())})
return
}
logger.Debugf("toggleVIP request from %v: %v", req.RemoteAddr, spew.Sdump(p))
session, ok := r.session.Get(p.SessionID)
if ok == false {
w.Write(api.Response{Status: api.StatusUnknownSession, Message: fmt.Sprintf("unknown session id: %v", p.SessionID)})
return
}
var vip *VIP
f := func(tx Transaction) (err error) {
vip, err = tx.ToggleVIP(session.(*User).ID, p.ID)
return err
}
if err := r.DB.Exec(f); err != nil {
w.Write(api.Response{Status: api.StatusInternalServerError, Message: fmt.Sprintf("failed to toggle a VIP: %v", err.Error())})
return
}
if vip == nil {
w.Write(api.Response{Status: api.StatusNotFound, Message: fmt.Sprintf("not found VIP to toggle: %v", p.ID)})
return
}
logger.Debugf("toggled the VIP: %v", spew.Sdump(vip))
if err := r.announce(vip.IP, vip.ActiveHost.MAC); err != nil {
// Ignore this error.
logger.Errorf("failed to send ARP announcement: %v", err)
}
w.Write(api.Response{Status: api.StatusOkay})
}
type toggleVIPParam struct {
SessionID string
ID uint64
}
func (r *toggleVIPParam) UnmarshalJSON(data []byte) error {
v := struct {
SessionID string `json:"session_id"`
ID uint64 `json:"id"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*r = toggleVIPParam(v)
return r.validate()
}
func (r *toggleVIPParam) validate() error {
if len(r.SessionID) != 64 {
return errors.New("invalid session id")
}
if r.ID == 0 {
return errors.New("invalid VIP id")
}
return nil
}
================================================
FILE: cmd/cherry/cherry.yaml
================================================
default:
port: 6633
# The logger will only write log messages whose level is equal to or higher than log_level.
# Lower log level is more verbose. (DEBUG < INFO < WARNING < ERROR < CRITICAL)
# This log_level value can be dynamically changed without restarting the daemon.
log_level: "INFO"
# North-bound applications separated by comma. They will receive a packet in order they appear.
applications: "DHCP, VirtualIP, Discovery, Monitor, ProxyARP, L2Switch, Announcer"
# Email address that will be notified when an abnormal events occur.
admin_email: "name@domain.com"
# Default VLAN ID. All switches should have this VLAN ID on all OF ports.
vlan_id: 1000
mysql:
# host:port[,host:port,host:port,...]
addr: "localhost:3306"
username: "username"
password: "password"
name: "dbname"
rest:
port: 7070
tls: true
cert_file: "/your_tls_cert_file"
key_file: "/your_tls_key_file"
================================================
FILE: cmd/cherry/main.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package main
import (
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/superkkt/cherry"
"github.com/superkkt/cherry/api"
"github.com/superkkt/cherry/api/core"
"github.com/superkkt/cherry/database"
"github.com/superkkt/cherry/election"
"github.com/superkkt/cherry/log"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound"
"github.com/fsnotify/fsnotify"
"github.com/pkg/errors"
"github.com/superkkt/go-logging"
"github.com/superkkt/viper"
)
const (
programName = "cherry"
programVersion = cherry.Version
defaultLogLevel = logging.INFO
)
var (
logger = logging.MustGetLogger("main")
loggerLeveled logging.LeveledBackend
showVersion = flag.Bool("version", false, "Show program version and exit")
defaultConfigFile = flag.String("config", fmt.Sprintf("/usr/local/etc/%v.yaml", programName), "absolute path of the configuration file")
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Parse()
if *showVersion {
fmt.Printf("Version: %v\n", programVersion)
os.Exit(0)
}
initConfig()
if err := initLog(getLogLevel(viper.GetString("default.log_level"))); err != nil {
logger.Fatalf("failed to init log: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
db, err := database.NewMySQL()
if err != nil {
logger.Fatalf("failed to init MySQL database: %v", err)
}
observer := initElectionObserver(ctx, db)
controller := network.NewController(db)
initAPIServer(observer, controller)
manager, err := createAppManager(db)
if err != nil {
logger.Fatalf("failed to create application manager: %v", err)
}
manager.AddEventSender(controller)
initSignalHandler(controller, manager, cancel)
listen(ctx, viper.GetInt("default.port"), controller, observer)
}
func initConfig() {
viper.SetConfigFile(*defaultConfigFile)
// Read the config file.
if err := viper.ReadInConfig(); err != nil {
logger.Fatalf("failed to read the config file: %v", err)
}
// Watching and re-reading config file whenever it changes.
viper.OnConfigChange(func(e fsnotify.Event) {
// Ignore the WRITE operation to avoid reading empty config.
if e.Op != fsnotify.Write {
return
}
if loggerLeveled != nil {
// Set log level for all modules
loggerLeveled.SetLevel(getLogLevel(viper.GetString("default.log_level")), "")
}
})
viper.WatchConfig()
if err := validateConfig(); err != nil {
logger.Fatalf("failed to validate the configuration: %v", err)
}
}
func validateConfig() error {
if port := viper.GetInt("default.port"); port <= 0 || port > 0xFFFF {
return errors.New("invalid default.port")
}
if len(viper.GetString("default.log_level")) == 0 {
return errors.New("invalid default.log_level")
}
if len(viper.GetString("default.applications")) == 0 {
return errors.New("invalid default.applications")
}
if len(viper.GetString("default.admin_email")) == 0 {
return errors.New("invalid default.admin_email")
}
vlanID := viper.GetInt("default.vlan_id")
if vlanID < 0 || vlanID > 4095 {
return errors.New("invalid default.vlan_id in the config file")
}
return nil
}
func initElectionObserver(ctx context.Context, db *database.MySQL) *election.Observer {
observer := election.New(db)
go func() {
if err := observer.Run(ctx); err != nil {
logger.Fatalf("failed to run the election observer: %v", err)
}
logger.Debugf("election observer terminated")
}()
return observer
}
func initAPIServer(observer *election.Observer, controller *network.Controller) {
go func() {
s := api.Server{}
s.Port = uint16(viper.GetInt("rest.port"))
if viper.GetBool("rest.tls") == true {
s.TLS.Cert = viper.GetString("rest.cert_file")
s.TLS.Key = viper.GetString("rest.key_file")
}
s.Observer = observer
s.Controller = controller
srv := &core.API{Server: s}
if err := srv.Serve(); err != nil {
logger.Fatalf("failed to run the API server: %v", err)
}
}()
}
func initSignalHandler(controller *network.Controller, manager *northbound.Manager, cancel context.CancelFunc) {
go func() {
c := make(chan os.Signal, 5)
// All incoming signals will be transferred to the channel
signal.Notify(c)
// Infinte loop.
for {
s := <-c
if s == syscall.SIGTERM || s == syscall.SIGINT {
// Graceful shutdown
logger.Warning("Shutting down...")
cancel()
// Timeout for cancelation
time.Sleep(5 * time.Second)
os.Exit(0)
} else if s == syscall.SIGHUP {
fmt.Println("* Controller status:")
fmt.Println(controller.String())
fmt.Printf("\n* Manager status:\n")
fmt.Println(manager.String())
}
}
}()
}
func initLog(level logging.Level) error {
backend, err := log.NewSyslog(programName)
if err != nil {
return err
}
backend = logging.NewBackendFormatter(backend, logging.MustStringFormatter(`%{level}: %{shortpkg}.%{shortfunc}: %{message}`))
loggerLeveled = logging.AddModuleLevel(backend)
// Set log level for all modules
loggerLeveled.SetLevel(level, "")
logging.SetBackend(loggerLeveled)
return nil
}
func getLogLevel(level string) logging.Level {
level = strings.ToUpper(level)
ret, err := logging.LogLevel(level)
if err != nil {
logger.Infof("invalid log level=%v, defaulting to %v..", level, defaultLogLevel)
return defaultLogLevel
}
return ret
}
func listen(ctx context.Context, port int, controller *network.Controller, observer *election.Observer) {
type KeepAliver interface {
SetKeepAlive(keepalive bool) error
SetKeepAlivePeriod(d time.Duration) error
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
logger.Errorf("failed to listen on %v port: %v", port, err)
return
}
defer listener.Close()
// Connection dispatcher.
f := func(c chan<- net.Conn) {
for {
conn, err := listener.Accept()
if err != nil {
logger.Errorf("failed to accept a new connection: %v", err)
continue
}
logger.Infof("new device is connected from %v", conn.RemoteAddr())
// Only the master controller can serve the connections!
if observer.IsMaster() == false {
logger.Warningf("disconnecting the newly connected device (%v) because we are not the master controller!", conn.RemoteAddr())
conn.Close()
continue
}
// Pass the new connection into the backlog queue.
c <- conn
}
}
backlog := make(chan net.Conn, 32)
go f(backlog)
// Infinite loop
for {
select {
case <-ctx.Done():
logger.Debug("terminating the main listener loop...")
return
case conn := <-backlog:
logger.Debug("fetching a new connection from the backlog..")
if v, ok := conn.(KeepAliver); ok {
logger.Debug("trying to enable socket keepalive..")
if err := v.SetKeepAlive(true); err == nil {
logger.Debug("setting socket keepalive period...")
// Makes a broken connection will be disconnected within 45 seconds.
// http://felixge.de/2014/08/26/tcp-keepalive-with-golang.html
v.SetKeepAlivePeriod(time.Duration(5) * time.Second)
} else {
logger.Errorf("failed to enable socket keepalive: %v", err)
}
}
controller.AddConnection(ctx, conn)
}
}
}
func createAppManager(db *database.MySQL) (*northbound.Manager, error) {
manager, err := northbound.NewManager(db)
if err != nil {
return nil, err
}
apps, err := parseApplications()
if err != nil {
return nil, errors.Wrap(err, "failed to parse applications")
}
for _, v := range apps {
if err := manager.Enable(v); err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("enabling %v", v))
}
}
return manager, nil
}
func parseApplications() ([]string, error) {
// Remove spaces, and then split it using comma
tokens := strings.Split(strings.Replace(viper.GetString("default.applications"), " ", "", -1), ",")
if len(tokens) == 0 {
return nil, errors.New("empty application")
}
return tokens, nil
}
================================================
FILE: cmd/walnut/main.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/superkkt/cherry"
"github.com/superkkt/cherry/api"
"github.com/superkkt/cherry/api/ui"
"github.com/superkkt/cherry/database"
"github.com/superkkt/cherry/ldap"
"github.com/superkkt/cherry/log"
"github.com/fsnotify/fsnotify"
"github.com/superkkt/go-logging"
"github.com/superkkt/viper"
)
const (
programName = "walnut"
programVersion = cherry.Version
defaultLogLevel = logging.INFO
)
var (
logger = logging.MustGetLogger("main")
loggerLeveled logging.LeveledBackend
showHelp = flag.Bool("help", false, "show this help and exit")
showVersion = flag.Bool("version", false, "show program version and exit")
defaultConfigFile = flag.String("config", fmt.Sprintf("/usr/local/etc/%v.yaml", programName), "absolute path of the configuration file")
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
rand.Seed(time.Now().Unix())
}
func main() {
parseCmdLines()
initConfig()
initLog()
initAPIServer()
waitSignal()
logger.Infof("%v (version %v) shutdown complete!", programName, programVersion)
}
// Handle the command-line arguments.
func parseCmdLines() {
flag.Parse()
if *showHelp {
flag.Usage()
os.Exit(0)
}
if *showVersion {
fmt.Printf("%v v%v\n", programName, programVersion)
os.Exit(0)
}
}
func initConfig() {
viper.SetConfigFile(*defaultConfigFile)
// Read the config file.
if err := viper.ReadInConfig(); err != nil {
logger.Fatalf("failed to read the config file: %v", err)
}
// Watching and re-reading config file whenever it changes.
viper.OnConfigChange(func(e fsnotify.Event) {
// Ignore all the fsnotify operations except WRITE to avoid reading empty config.
if e.Op != fsnotify.Write {
return
}
logger.Infof("config file changed: %v", e.Name)
// Set log level for all modules
loggerLeveled.SetLevel(getLogLevel(), "")
})
viper.WatchConfig()
if err := validateConfig(); err != nil {
logger.Fatalf("invalid configuration: %v", err)
}
}
// validateConfig validates essential configurations.
func validateConfig() error {
// TODO: Add validation you want.
return nil
}
func initLog() {
logDriver := viper.GetString("log.driver")
var err error
var backend logging.Backend
switch strings.ToLower(logDriver) {
case "stderr":
backend = logging.NewLogBackend(os.Stderr, "", 0)
backend = logging.NewBackendFormatter(backend, logging.MustStringFormatter(`%{time} [%{pid}] %{level}: %{shortpkg}.%{longfunc}: %{message}`))
case "syslog":
backend, err = log.NewSyslog(programName)
if err != nil {
logger.Fatalf("failed to init log: %v", err)
}
backend = logging.NewBackendFormatter(backend, logging.MustStringFormatter(`%{level}: %{shortpkg}.%{longfunc}: %{message}`))
default:
logger.Fatalf("unsupported log driver: %v", logDriver)
}
loggerLeveled = logging.AddModuleLevel(backend)
// Set log level for all modules
loggerLeveled.SetLevel(getLogLevel(), "")
logging.SetBackend(loggerLeveled)
}
func getLogLevel() logging.Level {
level := strings.ToUpper(viper.GetString("log.level"))
ret, err := logging.LogLevel(level)
if err != nil {
logger.Errorf("invalid log.level=%v, defaulting to %v..", level, defaultLogLevel)
return defaultLogLevel
}
return ret
}
func initDatabase() *database.MySQL {
db, err := database.NewMySQL()
if err != nil {
logger.Fatalf("failed to init MySQL database: %v", err)
}
return db
}
func initCoreSDK() *coreSDK {
client, err := newCoreSDK(viper.GetString("core_api_url"))
if err != nil {
logger.Fatalf("failed to init the core API's SDK: %v", err)
}
return client
}
func initLDAPClient() *ldap.Client {
return ldap.New(viper.Sub("ldap"), 5)
}
func initAPIServer() {
go func() {
s := api.Server{}
s.Port = uint16(viper.GetInt("rest.port"))
if viper.GetBool("rest.tls") == true {
s.TLS.Cert = viper.GetString("rest.cert_file")
s.TLS.Key = viper.GetString("rest.key_file")
}
sdk := initCoreSDK()
s.Observer = sdk
s.Controller = sdk
srv := &ui.API{Server: s, DB: initDatabase(), LDAP: initLDAPClient()}
if err := srv.Serve(); err != nil {
logger.Fatalf("failed to run the API server: %v", err)
}
}()
}
// waitSignal waits until we receive SIGTERM or SIGINT signals.
func waitSignal() {
c := make(chan os.Signal, 1)
// Following signals will be transferred to the channel c.
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPIPE)
// Infinite loop.
for {
s := <-c
switch s {
case syscall.SIGTERM, syscall.SIGINT:
logger.Infof("caught %v signal: shutting down...", s)
return
default:
logger.Infof("caught %v signal: ignored!", s)
}
}
}
================================================
FILE: cmd/walnut/sdk.go
================================================
/*
* Copyright 2019 Samjung Data Service, Inc. All Rights Reserved.
*
* Authors:
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*/
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
)
type coreSDK struct {
baseURL string
client *http.Client
}
func newCoreSDK(baseURL string) (*coreSDK, error) {
if _, err := url.Parse(baseURL); err != nil {
return nil, err
}
return &coreSDK{
baseURL: baseURL,
client: &http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: 30 * time.Second,
},
}, nil
}
func (r *coreSDK) Announce(ip net.IP, mac net.HardwareAddr) error {
arg := &struct {
IP string `json:"ip"`
MAC string `json:"mac"`
}{
IP: ip.String(),
MAC: mac.String(),
}
return r.call("POST", "/api/v1/announce", arg, nil)
}
func (r *coreSDK) RemoveFlows() error {
return r.call("POST", "/api/v1/remove", nil, nil)
}
func (r *coreSDK) RemoveFlowsByMAC(mac net.HardwareAddr) error {
arg := &struct {
MAC string `json:"mac"`
}{mac.String()}
return r.call("POST", "/api/v1/remove", arg, nil)
}
func (r *coreSDK) IsMaster() bool {
res := new(struct {
Master bool `json:"master"`
})
if err := r.call("POST", "/api/v1/status", nil, res); err != nil {
return false
}
return res.Master
}
func (r *coreSDK) call(method, command string, param interface{}, result interface{}) error {
payload, err := json.Marshal(param)
if err != nil {
return err
}
logger.Debugf("REST request: %v", string(payload))
req, err := http.NewRequest(method, r.baseURL+command, bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := r.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("unexpected HTTP status code: %v", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
logger.Debugf("REST response: %v", string(body))
res := new(struct {
Status int `json:"status"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
})
if err := json.Unmarshal(body, res); err != nil {
return err
}
if res.Status != 200 {
return fmt.Errorf("unexpected response status: status=%v, message=%v", res.Status, res.Message)
}
if result == nil {
// Ignore the query result.
return nil
}
return json.Unmarshal(res.Data, &result)
}
================================================
FILE: cmd/walnut/walnut.yaml
================================================
# NOTE:
#
# Parameters marked as a DYNAMIC can be dynamically updated without restarting the daemon.
log:
# (DYNAMIC)
# The logger will only write log messages whose level is equal to or higher
# than this level. It should be one of DEBUG, INFO, WARNING, ERROR or CRITICAL.
# Lower log level is more verbose. (DEBUG < INFO < WARNING < ERROR < CRITICAL)
level: "INFO"
# log_driver can be one of stderr or syslog.
driver: "stderr"
mysql:
# host:port[,host:port,host:port,...]
addr: "localhost:3306"
username: "username"
password: "password"
name: "database"
rest:
port: 4500
tls: true
cert_file: "/your_tls_cert_file"
key_file: "/your_tls_key_file"
core_api_url: "http://localhost:7070"
ldap:
addr: "localhost:636"
base_dn: "DC=direct,DC=co,DC=kr"
admin:
name: "name"
password: "password"
attr:
login: "sAMAccountName"
================================================
FILE: database/mysql.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package database
import (
"database/sql"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"net"
"runtime"
"strings"
"time"
"github.com/superkkt/cherry/api/ui"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app/announcer"
"github.com/superkkt/cherry/northbound/app/dhcp"
"github.com/superkkt/cherry/northbound/app/discovery"
"github.com/superkkt/cherry/northbound/app/virtualip"
"github.com/go-sql-driver/mysql"
"github.com/superkkt/go-logging"
"github.com/superkkt/viper"
)
const (
maxDeadlockRetry = 5
deadlockErrCode uint16 = 1213
duplicatedErrCode uint16 = 1062
foreignkeyErrCode uint16 = 1451
clusterDialerNetwork = "cluster"
)
var (
logger = logging.MustGetLogger("database")
maxIdleConn = runtime.NumCPU()
maxOpenConn = maxIdleConn * 2
)
type MySQL struct {
db *sql.DB
random *rand.Rand
}
func NewMySQL() (*MySQL, error) {
addr := viper.GetString("mysql.addr")
if err := validateClusterAddr(addr); err != nil {
return nil, err
}
// Register the custom dialer.
mysql.RegisterDial(clusterDialerNetwork, clusterDialer)
param := "readTimeout=1m&writeTimeout=1m&parseTime=true&loc=Local&maxAllowedPacket=0"
dsn := fmt.Sprintf("%v:%v@%v(%v)/%v?%v", viper.GetString("mysql.username"), viper.GetString("mysql.password"), clusterDialerNetwork, addr, viper.GetString("mysql.name"), param)
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(maxOpenConn)
db.SetMaxIdleConns(maxIdleConn)
// Make sure that all the connections are established to a same node, instead of distributing them into multiple nodes.
db.SetConnMaxLifetime(5 * time.Minute)
if err := db.Ping(); err != nil {
return nil, err
}
return &MySQL{
db: db,
random: rand.New(&randomSource{src: rand.NewSource(time.Now().Unix())}),
}, nil
}
func validateClusterAddr(addr string) error {
if len(addr) == 0 {
return errors.New("empty cluster address")
}
token := strings.Split(strings.Replace(addr, " ", "", -1), ",")
if len(token) == 0 {
return fmt.Errorf("invalid cluster address: %v", addr)
}
for _, v := range token {
if _, err := net.ResolveTCPAddr("tcp", v); err != nil {
return fmt.Errorf("invalid cluster address: %v: %v", v, err)
}
}
return nil
}
// clusterDialer tries to sequentially connect to each hosts from the address in the
// order of their appearance and then returns the first successfully connected one.
func clusterDialer(addr string) (net.Conn, error) {
token := strings.Split(strings.Replace(addr, " ", "", -1), ",")
for _, v := range token {
logger.Debugf("dialing to %v", v)
conn, err := net.DialTimeout("tcp", v, 5*time.Second)
if err == nil {
// Connected!
logger.Debugf("successfully connected to %v", v)
return conn, nil
}
logger.Errorf("failed to dial: %v", err)
}
return nil, errors.New("failed to dial: no available cluster node")
}
func isDeadlock(err error) bool {
e, ok := err.(*mysql.MySQLError)
if !ok {
return false
}
return e.Number == deadlockErrCode
}
func isDuplicated(err error) bool {
e, ok := err.(*mysql.MySQLError)
if !ok {
return false
}
return e.Number == duplicatedErrCode
}
func isForeignkeyErr(err error) bool {
e, ok := err.(*mysql.MySQLError)
if !ok {
return false
}
return e.Number == foreignkeyErrCode
}
func (r *MySQL) query(f func(*sql.Tx) error) error {
deadlockRetry := 0
for {
tx, err := r.db.Begin()
if err != nil {
return err
}
err = f(tx)
// Success?
if err == nil {
// Yes! but Commit also may raise an error.
err = tx.Commit()
// Success?
if err == nil {
// Transaction committed successfully!
return nil
}
// Fallthrough!
}
// No! query failed.
tx.Rollback()
// Need to retry due to a deadlock?
if !isDeadlock(err) || deadlockRetry >= maxDeadlockRetry {
// No, do not retry and just return the error.
return err
}
// Yes, a deadlock occurrs. Re-execute the queries again after some sleep!
logger.Infof("query failed due to a deadlock: caller=%v", caller())
time.Sleep(time.Duration(rand.Int31n(500)) * time.Millisecond)
deadlockRetry++
}
}
func caller() string {
pc, file, line, ok := runtime.Caller(2)
if !ok {
return "unknown"
}
f := runtime.FuncForPC(pc)
if f == nil {
return fmt.Sprintf("%v:%v", file, line)
}
return fmt.Sprintf("%v (%v:%v)", f.Name(), file, line)
}
func (r *MySQL) MAC(ip net.IP) (mac net.HardwareAddr, ok bool, err error) {
if ip == nil {
panic("IP address is nil")
}
f := func(tx *sql.Tx) error {
// Union query from both vip and host tables
qry := `(SELECT B.mac, B.enabled
FROM vip
A JOIN host B ON A.active_host_id = B.id
JOIN ip C ON A.ip_id = C.id
WHERE C.address = INET_ATON(?)
)
UNION ALL
(SELECT A.mac, A.enabled
FROM host A
JOIN ip B ON A.ip_id = B.id
WHERE B.address = INET_ATON(?)
)
LIMIT 1`
row, err := tx.Query(qry, ip.String(), ip.String())
if err != nil {
return err
}
defer row.Close()
// Unknown IP address?
if !row.Next() {
return nil
}
if err := row.Err(); err != nil {
return err
}
var v []byte
var enabled bool
if err := row.Scan(&v, &enabled); err != nil {
return err
}
if v == nil || len(v) != 6 {
return errors.New("invalid MAC address")
}
if enabled == false {
return nil
}
mac = net.HardwareAddr(v)
ok = true
return nil
}
err = r.query(f)
return mac, ok, err
}
func (r *MySQL) Location(mac net.HardwareAddr) (dpid string, port uint32, status network.LocationStatus, err error) {
if mac == nil {
panic("MAC address is nil")
}
f := func(tx *sql.Tx) error {
// Initial value.
status = network.LocationUnregistered
var portID sql.NullInt64
qry := "SELECT `port_id` FROM `host` WHERE `mac` = ? ORDER BY `port_id` DESC LOCK IN SHARE MODE"
if err := tx.QueryRow(qry, []byte(mac)).Scan(&portID); err != nil {
// Unregistered host?
if err == sql.ErrNoRows {
return nil
} else {
return err
}
}
// NULL port ID?
if portID.Valid == false {
// The node is registered, but we don't know its physical location yet.
status = network.LocationUndiscovered
return nil
}
qry = "SELECT B.`dpid`, A.`number` FROM `port` A JOIN `switch` B ON A.`switch_id` = B.`id` WHERE A.`id` = ?"
if err := tx.QueryRow(qry, portID.Int64).Scan(&dpid, &port); err != nil {
if err == sql.ErrNoRows { // FIXME: Is this possible?
return nil
} else {
return err
}
}
status = network.LocationDiscovered
return nil
}
if err := r.query(f); err != nil {
return "", 0, network.LocationUnregistered, err
}
return dpid, port, status, nil
}
func (r *MySQL) TogglePortVIP(swDPID uint64, portNum uint16) (result []virtualip.Address, err error) {
f := func(tx *sql.Tx) error {
portID, err := portID(tx, swDPID, portNum)
if err != nil {
return err
}
vips, err := getPortVIPs(tx, portID)
if err != nil {
return err
}
for _, v := range vips {
if err := swapVIPHosts(tx, v); err != nil {
return err
}
// Get standby's MAC address as the standby host will be active soon!
mac, err := hostMAC(tx, v.standby)
if err != nil {
return err
}
result = append(result, virtualip.Address{IP: v.address, MAC: mac})
}
return nil
}
if err = r.query(f); err != nil {
return nil, err
}
return result, nil
}
func (r *MySQL) ToggleDeviceVIP(swDPID uint64) (result []virtualip.Address, err error) {
f := func(tx *sql.Tx) error {
vips, err := getDeviceVIPs(tx, swDPID)
if err != nil {
return err
}
for _, v := range vips {
if err := swapVIPHosts(tx, v); err != nil {
return err
}
// Get standby's MAC address as the standby host will be active soon!
mac, err := hostMAC(tx, v.standby)
if err != nil {
return err
}
result = append(result, virtualip.Address{IP: v.address, MAC: mac})
}
return nil
}
if err = r.query(f); err != nil {
return nil, err
}
return result, nil
}
func portID(tx *sql.Tx, swDPID uint64, portNum uint16) (uint64, error) {
qry := `SELECT A.id
FROM port A
JOIN switch B ON A.switch_id = B.id
WHERE A.number = ? AND B.dpid = ?
LOCK IN SHARE MODE`
row, err := tx.Query(qry, portNum, swDPID)
if err != nil {
return 0, err
}
defer row.Close()
// Empty row?
if !row.Next() {
return 0, fmt.Errorf("unknown switch port (DPID=%v, Number=%v)", swDPID, portNum)
}
var id uint64
if err := row.Scan(&id); err != nil {
return 0, err
}
return id, nil
}
type vip struct {
id uint64
address net.IP
active uint64
standby uint64
}
func getPortVIPs(tx *sql.Tx, portID uint64) (result []vip, err error) {
qry := `SELECT A.id, INET_NTOA(C.address), A.active_host_id, A.standby_host_id
FROM vip A
JOIN host B ON A.active_host_id = B.id
JOIN ip C ON A.ip_id = C.id
WHERE B.port_id = ?
FOR UPDATE`
rows, err := tx.Query(qry, portID)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var id, active, standby uint64
var address string
if err := rows.Scan(&id, &address, &active, &standby); err != nil {
return nil, err
}
ip := net.ParseIP(address)
if ip == nil {
return nil, fmt.Errorf("invalid IPv4 address: %v", address)
}
result = append(result, vip{id, ip, active, standby})
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func getDeviceVIPs(tx *sql.Tx, swDPID uint64) (result []vip, err error) {
qry := `SELECT A.id, INET_NTOA(E.address), A.active_host_id, A.standby_host_id
FROM vip A
JOIN host B ON A.active_host_id = B.id
JOIN port C ON B.port_id = C.id
JOIN switch D ON D.id = C.switch_id
JOIN ip E ON E.id = A.ip_id
WHERE D.dpid = ?
FOR UPDATE`
rows, err := tx.Query(qry, swDPID)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var id, active, standby uint64
var address string
if err := rows.Scan(&id, &address, &active, &standby); err != nil {
return nil, err
}
ip := net.ParseIP(address)
if ip == nil {
return nil, fmt.Errorf("invalid IPv4 address: %v", address)
}
result = append(result, vip{id, ip, active, standby})
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func swapVIPHosts(tx *sql.Tx, v vip) error {
qry := "UPDATE vip SET active_host_id = ?, standby_host_id = ? WHERE id = ?"
// Swap active and standby hosts
_, err := tx.Exec(qry, v.standby, v.active, v.id)
if err != nil {
return err
}
if err := updateARPTableEntryByVIP(tx, v.id, false); err != nil {
return err
}
return nil
}
func hostMAC(tx *sql.Tx, hostID uint64) (net.HardwareAddr, error) {
row, err := tx.Query("SELECT HEX(mac) FROM host WHERE id = ?", hostID)
if err != nil {
return nil, err
}
defer row.Close()
// Empty row?
if !row.Next() {
return nil, fmt.Errorf("unknown host (ID=%v)", hostID)
}
var v string
if err := row.Scan(&v); err != nil {
return nil, err
}
mac, err := decodeMAC(v)
if err != nil {
return nil, err
}
return mac, nil
}
// GetUndiscoveredHosts returns IP addresses whose physical location is still
// undiscovered or staled more than expiration. result can be nil on empty result.
func (r *MySQL) GetUndiscoveredHosts(expiration time.Duration) (result []net.IPNet, err error) {
f := func(tx *sql.Tx) error {
// NOTE: Do not include VIP addresses!
qry := "SELECT IFNULL(INET_NTOA(B.`address`), '0.0.0.0'), IFNULL(C.`mask`, 0) "
qry += "FROM `host` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "JOIN `network` C ON B.`network_id` = C.`id` "
qry += "WHERE A.`port_id` IS NULL OR A.`last_updated_timestamp` < NOW() - INTERVAL ? SECOND"
rows, err := tx.Query(qry, uint64(expiration.Seconds()))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var addr string
var mask int
if err := rows.Scan(&addr, &mask); err != nil {
return err
}
ip := net.ParseIP(addr)
if ip == nil {
return fmt.Errorf("invalid IP address: %v", addr)
}
if ip.IsUnspecified() {
continue
}
netmask := net.CIDRMask(mask, 32)
if netmask == nil {
return fmt.Errorf("invalid network mask: IP=%v, Mask=%v", addr, mask)
}
result = append(result, net.IPNet{IP: ip, Mask: netmask})
}
return rows.Err()
}
if err = r.query(f); err != nil {
return nil, err
}
return result, nil
}
// UpdateHostLocation updates the physical location of a host, whose MAC and IP
// addresses are matched with mac and ip, to the port identified by swDPID and
// portNum. updated will be true if its location has been actually updated.
func (r *MySQL) UpdateHostLocation(mac net.HardwareAddr, ip net.IP, swDPID uint64, portNum uint16) (updated bool, err error) {
f := func(tx *sql.Tx) error {
hostID, ok, err := getHostID(tx, mac, ip)
if err != nil {
return err
}
// Unknown host?
if !ok {
updated = false
return nil
}
portID, err := portID(tx, swDPID, portNum)
if err != nil {
return err
}
updated, err = updateLocation(tx, hostID, portID)
if err != nil {
return err
}
return nil
}
if err = r.query(f); err != nil {
return false, err
}
return updated, nil
}
func getHostID(tx *sql.Tx, mac net.HardwareAddr, ip net.IP) (hostID uint64, ok bool, err error) {
qry := "SELECT A.`id` "
qry += "FROM `host` A "
qry += "JOIN `ip` B "
qry += "ON A.`ip_id` = B.`id` "
qry += "WHERE A.`mac` = ? AND B.`address` = INET_ATON(?) "
qry += "LOCK IN SHARE MODE"
row, err := tx.Query(qry, []byte(mac), ip.String())
if err != nil {
return 0, false, err
}
defer row.Close()
// Empty row?
if !row.Next() {
return 0, false, nil
}
if err := row.Scan(&hostID); err != nil {
return 0, false, err
}
return hostID, true, nil
}
func updateLocation(tx *sql.Tx, hostID, portID uint64) (updated bool, err error) {
var id uint64
qry := "SELECT `id` FROM `host` WHERE `id` = ? AND `port_id` = ?"
err = tx.QueryRow(qry, hostID, portID).Scan(&id)
// Real error?
if err != nil && err != sql.ErrNoRows {
return false, err
}
// Need to update?
if err == sql.ErrNoRows {
updated = true
}
qry = "UPDATE `host` SET `port_id` = ?, `last_updated_timestamp` = NOW() WHERE `id` = ?"
_, err = tx.Exec(qry, portID, hostID)
if err != nil {
return false, err
}
return updated, nil
}
// ResetHostLocationsByPort sets NULL to the host locations that belong to the
// port specified by swDPID and portNum.
func (r *MySQL) ResetHostLocationsByPort(swDPID uint64, portNum uint16) error {
f := func(tx *sql.Tx) error {
portID, err := portID(tx, swDPID, portNum)
if err != nil {
return err
}
qry := "UPDATE `host` SET `port_id` = NULL WHERE `port_id` = ?"
if _, err := tx.Exec(qry, portID); err != nil {
return err
}
return nil
}
return r.query(f)
}
// ResetHostLocationsByDevice sets NULL to the host locations that belong to the
// device specified by swDPID.
func (r *MySQL) ResetHostLocationsByDevice(swDPID uint64) error {
f := func(tx *sql.Tx) error {
qry := "UPDATE `host` A "
qry += "JOIN `port` B ON A.`port_id` = B.`id` "
qry += "JOIN `switch` C ON B.`switch_id` = C.`id` "
qry += "SET A.`port_id` = NULL "
qry += "WHERE C.`dpid` = ?"
_, err := tx.Exec(qry, swDPID)
if err != nil {
return err
}
return nil
}
return r.query(f)
}
// Elect selects a new master as uid if there is a no existing master that has
// been updated within expiration. elected will be true if this uid has been
// elected as the new master or was already elected.
func (r *MySQL) Elect(uid string, expiration time.Duration) (elected bool, err error) {
qry := "INSERT INTO `master_election` (`id`, `name`, `timestamp`) VALUES (1, ?, NOW()) "
qry += "ON DUPLICATE KEY UPDATE "
qry += fmt.Sprintf("`name` = IF (`timestamp` < NOW() - INTERVAL %v SECOND, VALUES(`name`), `name`), ", int(expiration.Seconds()))
qry += "`timestamp` = IF (`name` = VALUES(`name`), VALUES(`timestamp`), `timestamp`)"
if _, err := r.db.Exec(qry, uid); err != nil {
return false, err
}
var count int
qry = "SELECT COUNT(*) FROM `master_election` WHERE `id` = 1 AND `name` = ?"
if err := r.db.QueryRow(qry, uid).Scan(&count); err != nil {
return false, err
}
return count > 0, nil
}
// MACAddrs returns all the registered MAC addresses.
func (r *MySQL) MACAddrs() (result []net.HardwareAddr, err error) {
f := func(tx *sql.Tx) error {
rows, err := tx.Query("SELECT HEX(`mac`) FROM `host`")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var mac string
if err := rows.Scan(&mac); err != nil {
return err
}
// Parse the MAC address.
v, err := decodeMAC(mac)
if err != nil {
return err
}
result = append(result, v)
}
return rows.Err()
}
if err = r.query(f); err != nil {
return nil, err
}
return result, nil
}
func (r *MySQL) RenewARPTable() error {
f := func(tx *sql.Tx) error {
hosts, err := getHostARPEntries(tx)
if err != nil {
return err
}
vips, err := getVIPARPEntries(tx)
if err != nil {
return err
}
for _, v := range append(hosts, vips...) {
if err := updateARPTableEntry(tx, v.IP, v.MAC); err != nil {
return err
}
}
return nil
}
return r.query(f)
}
type arpEntry struct {
IP string
MAC string
}
func getHostARPEntries(tx *sql.Tx) (result []arpEntry, err error) {
qry := "SELECT INET_NTOA(B.`address`), HEX(A.`mac`), A.`enabled` FROM `host` A JOIN `ip` B ON A.`ip_id` = B.`id`"
rows, err := tx.Query(qry)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var ip, mac string
var enabled bool
if err := rows.Scan(&ip, &mac, &enabled); err != nil {
return nil, err
}
if enabled == false {
continue
}
result = append(result, arpEntry{IP: ip, MAC: mac})
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func getVIPARPEntries(tx *sql.Tx) (result []arpEntry, err error) {
qry := "SELECT INET_NTOA(B.`address`), HEX(C.`mac`), C.`enabled` FROM `vip` A JOIN `ip` B ON A.`ip_id` = B.`id` JOIN `host` C ON A.`active_host_id` = C.`id`"
rows, err := tx.Query(qry)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var ip, mac string
var enabled bool
if err := rows.Scan(&ip, &mac, &enabled); err != nil {
return nil, err
}
if enabled == false {
continue
}
result = append(result, arpEntry{IP: ip, MAC: mac})
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (r *MySQL) GetARPTable() (result []announcer.ARPTableEntry, err error) {
f := func(tx *sql.Tx) error {
rows, err := tx.Query("SELECT INET_NTOA(`ip`), HEX(`mac`) FROM `arp`")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var ip, mac string
if err := rows.Scan(&ip, &mac); err != nil {
return err
}
addr := net.ParseIP(ip)
if addr == nil {
return fmt.Errorf("invalid IP address: %v", ip)
}
hwAddr, err := decodeMAC(mac)
if err != nil {
return err
}
result = append(result, announcer.ARPTableEntry{IP: addr, MAC: hwAddr})
}
return rows.Err()
}
if err = r.query(f); err != nil {
return nil, err
}
return result, nil
}
func updateARPTableEntryByHost(tx *sql.Tx, id uint64, invalidate bool) error {
var ip, mac string
qry := "SELECT INET_NTOA(B.`address`), HEX(A.`mac`) "
qry += "FROM `host` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "WHERE A.`id` = ?"
if err := tx.QueryRow(qry, id).Scan(&ip, &mac); err != nil {
return err
}
if invalidate == true {
mac = encodeMAC(network.NullMAC)
}
return updateARPTableEntry(tx, ip, mac)
}
func updateARPTableEntryByVIP(tx *sql.Tx, id uint64, invalidate bool) error {
var ip, mac string
qry := "SELECT INET_NTOA(B.`address`), HEX(C.`mac`) "
qry += "FROM `vip` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "JOIN `host` C ON A.`active_host_id` = C.`id` "
qry += "WHERE A.`id` = ?"
if err := tx.QueryRow(qry, id).Scan(&ip, &mac); err != nil {
return err
}
if invalidate == true {
mac = encodeMAC(network.NullMAC)
}
return updateARPTableEntry(tx, ip, mac)
}
func updateARPTableEntry(tx *sql.Tx, ip, mac string) error {
qry := "INSERT INTO `arp` (`ip`, `mac`) VALUES (INET_ATON(?), UNHEX(?)) ON DUPLICATE KEY UPDATE `mac` = UNHEX(?)"
if _, err := tx.Exec(qry, ip, mac, mac); err != nil {
return err
}
logger.Debugf("updated ARP table entry: IP=%v, MAC=%v", ip, mac)
return nil
}
func (r *MySQL) DHCP(mac net.HardwareAddr) (conf *dhcp.NetConfig, err error) {
f := func(tx *sql.Tx) error {
var ip, gateway string
var mask int
qry := "SELECT INET_NTOA(B.`address`), C.`mask`, INET_NTOA(C.`gateway`) "
qry += "FROM `host` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "JOIN `network` C ON B.`network_id` = C.`id` "
qry += "WHERE A.`mac` = ?"
if err := tx.QueryRow(qry, []byte(mac)).Scan(&ip, &mask, &gateway); err != nil {
if err == sql.ErrNoRows {
return nil
}
return err
}
v := &dhcp.NetConfig{
IP: net.ParseIP(ip),
Mask: net.CIDRMask(mask, net.IPv4len*8),
Gateway: net.ParseIP(gateway),
}
if v.IP == nil || v.Mask == nil || v.Gateway == nil {
return fmt.Errorf("invalid DHCP network configuration: MAC=%v, IP=%v, mask=%v, gateway=%v", mac, ip, mask, gateway)
}
if v.IP.Mask(v.Mask).Equal(v.Gateway.Mask(v.Mask)) == false {
return fmt.Errorf("invalid DHCP network configuration: invalid gateway address: MAC=%v, IP=%v, mask=%v, gateway=%v", mac, ip, mask, gateway)
}
conf = v
return nil
}
if err := r.query(f); err != nil {
return nil, err
}
return conf, nil
}
// Exec executes all queries of f in a single transaction. f should return the error raised from the ui.Transaction
// without any change or wrapping it for deadlock protection.
func (r *MySQL) Exec(f func(ui.Transaction) error) error {
deadlockRetry := 0
for {
tx, err := r.db.Begin()
if err != nil {
return err
}
err = f(&uiTx{handle: tx})
// Success?
if err == nil {
// Yes! but Commit also may raise an error.
err = tx.Commit()
// Success?
if err == nil {
// Transaction committed successfully!
return nil
}
// Fallthrough!
}
// No! query failed.
tx.Rollback()
// Need to retry due to a deadlock?
if !isDeadlock(err) || deadlockRetry >= maxDeadlockRetry {
// No, do not retry and just return the error.
return err
}
// Yes, a deadlock occurrs. Re-execute the queries again after some sleep!
logger.Infof("query failed due to a deadlock: caller=%v", caller())
time.Sleep(time.Duration(rand.Int31n(500)) * time.Millisecond)
deadlockRetry++
}
}
type uiTx struct {
handle *sql.Tx
}
func (r *uiTx) Groups(pagination ui.Pagination) (group []*ui.Group, err error) {
qry := "SELECT `id`, `name`, `timestamp` "
qry += "FROM `group` "
qry += "ORDER BY `id` DESC "
if pagination.Limit > 0 {
qry += fmt.Sprintf("LIMIT %v, %v", pagination.Offset, pagination.Limit)
}
rows, err := r.handle.Query(qry)
if err != nil {
return nil, err
}
defer rows.Close()
group = []*ui.Group{}
for rows.Next() {
v := new(ui.Group)
if err := rows.Scan(&v.ID, &v.Name, &v.Timestamp); err != nil {
return nil, err
}
group = append(group, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return group, nil
}
func (r *uiTx) AddGroup(requesterID uint64, name string) (group *ui.Group, duplicated bool, err error) {
qry := "INSERT INTO `group` (`name`, `timestamp`) VALUES (?, NOW())"
result, err := r.handle.Exec(qry, name)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, false, err
}
group, err = getGroup(r.handle, uint64(id))
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeGroup, logMethodAdd, group); err != nil {
return nil, false, err
}
return group, false, nil
}
func getGroup(tx *sql.Tx, id uint64) (*ui.Group, error) {
qry := "SELECT `id`, `name`, `timestamp` "
qry += "FROM `group` "
qry += "WHERE `id` = ?"
v := new(ui.Group)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.Name, &v.Timestamp); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) UpdateGroup(requesterID, groupID uint64, name string) (group *ui.Group, duplicated bool, err error) {
qry := "UPDATE `group` SET `name` = ? WHERE `id` = ?"
result, err := r.handle.Exec(qry, name, groupID)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, false, err
}
// Not found group to update.
if nRows == 0 {
return nil, false, nil
}
group, err = getGroup(r.handle, groupID)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeGroup, logMethodUpdate, group); err != nil {
return nil, false, err
}
return group, false, nil
}
func (r *uiTx) RemoveGroup(requesterID, groupID uint64) (group *ui.Group, err error) {
group, err = getGroup(r.handle, groupID)
if err != nil {
// Not found group to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if _, err := r.handle.Exec("DELETE FROM `group` WHERE `id` = ?", groupID); err != nil {
if isForeignkeyErr(err) {
return nil, errors.New("failed to remove a group: it has child hosts that are being used by group")
}
return nil, err
}
if err := r.log(requesterID, logTypeGroup, logMethodRemove, group); err != nil {
return nil, err
}
return group, nil
}
func (r *uiTx) Hosts(search *ui.Search, sort ui.Sort, pagination ui.Pagination) (host []*ui.Host, err error) {
qry, args := buildHostsQuery(search, sort, pagination)
rows, err := r.handle.Query(qry, args...)
if err != nil {
return nil, err
}
defer rows.Close()
host = []*ui.Host{}
for rows.Next() {
v := new(ui.Host)
var timestamp time.Time
if err := rows.Scan(&v.ID, &v.IP, &v.Port, &v.Group, &v.MAC, &v.Description, &v.Enabled, ×tamp, &v.Timestamp); err != nil {
return nil, err
}
// Parse the MAC address.
mac, err := decodeMAC(v.MAC)
if err != nil {
return nil, err
}
v.MAC = mac.String()
// Check its freshness.
if time.Now().Sub(timestamp) > discovery.ProbeInterval*2 {
v.Stale = true
}
host = append(host, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
for _, h := range host {
spec, err := getSpec(r.handle, h.ID)
if err != nil {
return nil, err
}
h.Spec = spec
}
return host, nil
}
func buildHostsQuery(search *ui.Search, sort ui.Sort, pagination ui.Pagination) (qry string, args []interface{}) {
qry = "SELECT `host`.`id`, " // ID
qry += " CONCAT(INET_NTOA(`ip`.`address`), '/', `network`.`mask`), " // IP
qry += " IFNULL(CONCAT(`switch`.`description`, '/', `port`.`number` - `switch`.`first_port` + `switch`.`first_printed_port`), ''), " // Port
qry += " IFNULL(`group`.`name`, ''), " // Group
qry += " HEX(`host`.`mac`), " // MAC
qry += " `host`.`description`, " // Description
qry += " `host`.`enabled`, " // Enabled
qry += " `host`.`last_updated_timestamp`, " // Stale
qry += " `host`.`created_timestamp` " // Timestamp
qry += "FROM `host` "
qry += "JOIN `ip` ON `host`.`ip_id` = `ip`.`id` "
qry += "LEFT JOIN `port` ON `host`.`port_id` = `port`.`id` "
qry += "LEFT JOIN `switch` ON `port`.`switch_id` = `switch`.`id` "
qry += "JOIN `network` ON `ip`.`network_id` = `network`.`id` "
qry += "LEFT JOIN `group` ON `host`.`group_id` = `group`.`id` "
if search != nil {
switch search.Key {
// Query by description supports full text search.
case ui.ColumnDescription:
qry += fmt.Sprintf("WHERE MATCH (`host`.`description`) AGAINST (CONCAT(?, '*') IN BOOLEAN MODE) ")
args = append(args, search.Value)
case ui.ColumnPort:
qry += fmt.Sprintf("WHERE `switch`.`description` LIKE CONCAT(?, '%%') ")
args = append(args, search.Value)
case ui.ColumnGroup:
qry += fmt.Sprintf("WHERE `group`.`name` LIKE CONCAT(?, '%%') ")
args = append(args, search.Value)
case ui.ColumnIP:
start, end := rangeIP(search.Value)
qry += fmt.Sprintf("WHERE `ip`.`address` BETWEEN %v AND %v ", start, end)
case ui.ColumnMAC:
start, end := rangeMAC(search.Value)
qry += fmt.Sprintf("WHERE `host`.`mac` BETWEEN UNHEX('%v') AND UNHEX('%v') ", start, end)
default:
panic(fmt.Sprintf("invalid search key: %v", search.Key))
}
}
v := []string{}
switch sort.Key {
case ui.ColumnTime:
// Do nothing. It's the default order.
case ui.ColumnIP:
v = append(v, "`ip`.`address`")
case ui.ColumnMAC:
v = append(v, "`host`.`mac`")
case ui.ColumnGroup:
v = append(v, "`group`.`name`")
case ui.ColumnPort:
v = append(v, "`port`.`switch_id`")
v = append(v, "`port`.`number`")
default:
panic(fmt.Sprintf("invalid sort key: %v", sort.Key))
}
// If duplicated value is existed, sort by id.
v = append(v, "`host`.`id`")
switch sort.Order {
case ui.OrderAscending:
qry += fmt.Sprintf("ORDER BY %v ASC ", strings.Join(v, " ASC, "))
case ui.OrderDescending:
qry += fmt.Sprintf("ORDER BY %v DESC ", strings.Join(v, " DESC, "))
default:
panic(fmt.Sprintf("invalid sort order: %v", sort.Order))
}
if pagination.Limit > 0 {
qry += fmt.Sprintf("LIMIT %v, %v", pagination.Offset, pagination.Limit)
}
return qry, args
}
// IP format is '1.*.*.*', '1.2.*.*', '1.2.3.*', '1.2.3.4'.
func rangeIP(ip string) (start, end uint32) {
s := net.ParseIP(strings.Replace(ip, "*", "0", -1))
if s == nil {
panic("this ip should be parsed")
}
e := net.ParseIP(strings.Replace(ip, "*", "255", -1))
if e == nil {
panic("this ip should be parsed")
}
return convertIPToInt(s), convertIPToInt(e)
}
func convertIPToInt(ip net.IP) uint32 {
v := ip.To4()
if v == nil {
panic("unexpected IP address format")
}
return binary.BigEndian.Uint32(v)
}
// MAC format is 'A1:*:*:*:*:*', 'A1:A2:*:*:*:*', 'A1:A2:A3:*:*:*', 'A1:A2:A3:A4:*:*', 'A1:A2:A3:A4:A5:*', 'A1:A2:A3:A4:A5:A6'.
func rangeMAC(mac string) (start, end string) {
s, err := net.ParseMAC(strings.Replace(mac, "*", "00", -1))
if err != nil {
panic("this mac should be parsed")
}
e, err := net.ParseMAC(strings.Replace(mac, "*", "FF", -1))
if err != nil {
panic("this mac should be parsed")
}
return encodeMAC(s), encodeMAC(e)
}
func (r *uiTx) Host(id uint64) (host *ui.Host, err error) {
h, err := getHost(r.handle, id)
if err != nil {
// Ignore the no rows error.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return h.convert(), nil
}
func getHost(tx *sql.Tx, id uint64) (*host, error) {
qry := "SELECT `host`.`id`, " // ID
qry += " `host`.`ip_id`, " // IP ID
qry += " CONCAT(INET_NTOA(`ip`.`address`), '/', `network`.`mask`), " // IP Address
qry += " IFNULL(CONCAT(`switch`.`description`, '/', `port`.`number` - `switch`.`first_port` + `switch`.`first_printed_port`), ''), " // Port
qry += " `host`.`group_id`, " // Group ID
qry += " IFNULL(`group`.`name`, ''), " // Group Name
qry += " HEX(`host`.`mac`), " // MAC
qry += " `host`.`description`, " // Description
qry += " `host`.`enabled`, " // Enabled
qry += " `host`.`last_updated_timestamp`, " // Stale
qry += " `host`.`created_timestamp` " // Timestamp
qry += "FROM `host` "
qry += "JOIN `ip` ON `host`.`ip_id` = `ip`.`id` "
qry += "LEFT JOIN `port` ON `host`.`port_id` = `port`.`id` "
qry += "LEFT JOIN `switch` ON `port`.`switch_id` = `switch`.`id` "
qry += "JOIN `network` ON `ip`.`network_id` = `network`.`id` "
qry += "LEFT JOIN `group` ON `host`.`group_id` = `group`.`id` "
qry += "WHERE `host`.`id` = ?"
v := new(host)
var timestamp time.Time
var mac string
if err := tx.QueryRow(qry, id).Scan(&v.id, &v.ip.id, &v.ip.address, &v.port, &v.group.id, &v.group.name, &mac, &v.description, &v.enabled, ×tamp, &v.timestamp); err != nil {
return nil, err
}
// Parse the MAC address.
var err error
v.mac, err = decodeMAC(mac)
if err != nil {
return nil, err
}
// Check its freshness.
if time.Now().Sub(timestamp) > discovery.ProbeInterval*2 {
v.stale = true
}
spec, err := getSpec(tx, id)
if err != nil {
return nil, err
}
v.spec = spec
return v, nil
}
type host struct {
id uint64
ip struct {
id uint64
address string
}
port string
group struct {
id *uint64
name string
}
mac net.HardwareAddr
description string
enabled bool
stale bool
spec []*ui.Spec
timestamp time.Time
}
func (r *host) convert() *ui.Host {
return &ui.Host{
ID: r.id,
IP: r.ip.address,
Port: r.port,
Group: r.group.name,
MAC: r.mac.String(),
Description: r.description,
Enabled: r.enabled,
Stale: r.stale,
Spec: r.spec,
Timestamp: r.timestamp,
}
}
func (r *uiTx) AddHost(requesterID, ipID uint64, groupID *uint64, mac net.HardwareAddr, desc string, spec []ui.SpecParam) (host *ui.Host, duplicated bool, err error) {
h, duplicated, err := addNewHost(r.handle, ipID, groupID, mac, desc, spec)
if err != nil {
return nil, false, err
}
if duplicated {
return nil, true, nil
}
if err := r.log(requesterID, logTypeHost, logMethodAdd, h.convert()); err != nil {
return nil, false, err
}
return h.convert(), false, nil
}
func addNewHost(tx *sql.Tx, ipID uint64, groupID *uint64, mac net.HardwareAddr, desc string, spec []ui.SpecParam) (host *host, duplicated bool, err error) {
ok, err := isAvailableIP(tx, ipID)
if err != nil {
return nil, false, err
}
if ok == false {
return nil, true, nil
}
qry := "INSERT INTO `host` (`ip_id`, `group_id`, `mac`, `description`, `last_updated_timestamp`, `enabled`, `created_timestamp`) VALUES (?, ?, UNHEX(?), ?, NOW(), TRUE, NOW())"
result, err := tx.Exec(qry, ipID, groupID, encodeMAC(mac), desc)
if err != nil {
return nil, false, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, false, err
}
if spec != nil {
for _, v := range spec {
if err := addSpec(tx, uint64(id), v.ComponentID, v.Count); err != nil {
return nil, false, err
}
}
}
if err := updateARPTableEntryByHost(tx, uint64(id), false); err != nil {
return nil, false, err
}
host, err = getHost(tx, uint64(id))
if err != nil {
return nil, false, err
}
return host, false, nil
}
func isAvailableIP(tx *sql.Tx, id uint64) (bool, error) {
row, err := tx.Query("SELECT used FROM ip WHERE id = ? FOR UPDATE", id)
if err != nil {
return false, err
}
defer row.Close()
// Empty row?
if !row.Next() {
return false, errors.New("unknown IP address ID")
}
var used bool
if err := row.Scan(&used); err != nil {
return false, err
}
return !used, nil
}
func encodeMAC(mac net.HardwareAddr) string {
// Remove spaces and colons
return strings.Replace(strings.Replace(mac.String(), ":", "", -1), " ", "", -1)
}
func decodeMAC(s string) (net.HardwareAddr, error) {
v, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
if len(v) != 6 {
return nil, fmt.Errorf("invalid MAC address: %v", v)
}
return net.HardwareAddr(v), nil
}
func (r *uiTx) UpdateHost(requesterID, hostID uint64, ipID, groupID *uint64, mac net.HardwareAddr, desc *string, spec []ui.SpecParam) (host *ui.Host, duplicated bool, err error) {
old, err := getHost(r.handle, hostID)
if err != nil {
// Not found host to update.
if err == sql.ErrNoRows {
return nil, false, nil
}
return nil, false, err
}
new, duplicated, err := updateHost(r.handle, old, ipID, groupID, mac, desc, spec)
if err != nil {
return nil, false, err
}
if duplicated {
return nil, true, nil
}
err = r.log(requesterID, logTypeHost, logMethodUpdate, &struct {
Old *ui.Host `json:"old"`
New *ui.Host `json:"new"`
}{
Old: old.convert(),
New: new.convert(),
})
if err != nil {
return nil, false, err
}
return new.convert(), false, nil
}
func updateHost(tx *sql.Tx, old *host, ipID, groupID *uint64, mac net.HardwareAddr, desc *string, spec []ui.SpecParam) (new *host, duplicated bool, err error) {
if err := removeHost(tx, old.id); err != nil {
return nil, false, err
}
if ipID == nil {
ipID = &old.ip.id
}
if groupID == nil {
groupID = old.group.id
}
if mac == nil {
mac = old.mac
}
if desc == nil {
desc = &old.description
}
if spec == nil {
for _, v := range old.spec {
spec = append(spec, ui.SpecParam{
ComponentID: v.Component.ID,
Count: v.Count,
})
}
}
return addNewHost(tx, *ipID, groupID, mac, *desc, spec)
}
func (r *uiTx) ActivateHost(requesterID, hostID uint64) (host *ui.Host, err error) {
qry := "UPDATE `host` SET `enabled` = TRUE WHERE `id` = ?"
result, err := r.handle.Exec(qry, hostID)
if err != nil {
return nil, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, err
}
// Not found host to activate.
if nRows == 0 {
return nil, nil
}
if err := updateARPTableEntryByHost(r.handle, hostID, false); err != nil {
return nil, err
}
h, err := getHost(r.handle, hostID)
if err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeHost, logMethodUpdate, h.convert()); err != nil {
return nil, err
}
return h.convert(), nil
}
func (r *uiTx) DeactivateHost(requesterID, hostID uint64) (host *ui.Host, err error) {
qry := "UPDATE `host` SET `enabled` = FALSE WHERE `id` = ?"
result, err := r.handle.Exec(qry, hostID)
if err != nil {
return nil, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, err
}
// Not found host to deactivate.
if nRows == 0 {
return nil, nil
}
if err := updateARPTableEntryByHost(r.handle, hostID, true); err != nil {
return nil, err
}
h, err := getHost(r.handle, hostID)
if err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeHost, logMethodUpdate, h.convert()); err != nil {
return nil, err
}
return h.convert(), nil
}
func (r *uiTx) CountVIPByHostID(id uint64) (count uint64, err error) {
qry := "SELECT COUNT(*) FROM `vip` WHERE `active_host_id` = ? OR `standby_host_id` = ?"
if err := r.handle.QueryRow(qry, id, id).Scan(&count); err != nil {
return 0, err
}
return count, nil
}
func (r *uiTx) RemoveHost(requesterID, hostID uint64) (host *ui.Host, err error) {
h, err := getHost(r.handle, hostID)
if err != nil {
// Not found host to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if err := removeHost(r.handle, hostID); err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeHost, logMethodRemove, h.convert()); err != nil {
return nil, err
}
return h.convert(), nil
}
func removeHost(tx *sql.Tx, id uint64) error {
if err := updateARPTableEntryByHost(tx, id, true); err != nil {
return err
}
_, err := tx.Exec("DELETE FROM host WHERE id = ?", id)
if err != nil && isForeignkeyErr(err) {
return errors.New("failed to remove a host: it has child VIP addresses")
}
return err
}
func (r *uiTx) IPAddrs(networkID uint64) (address []*ui.IP, err error) {
qry := "SELECT A.`id`, " // ID
qry += " INET_NTOA(A.`address`), " // Address
qry += " A.`used`, " // Used
qry += " C.`description`, " // Host Description
qry += " C.`enabled`, " // Host Enabled
qry += " C.`last_updated_timestamp`, " // Host Stale
qry += " IFNULL(CONCAT(E.`description`, '/', D.`number` - E.`first_port` + E.`first_printed_port`), '') " // Port
qry += "FROM `ip` A "
qry += "JOIN `network` B ON A.`network_id` = B.`id` "
qry += "LEFT JOIN `host` C ON C.`ip_id` = A.`id` "
qry += "LEFT JOIN `port` D ON D.`id` = C.`port_id` "
qry += "LEFT JOIN `switch` E ON E.`id` = D.`switch_id` "
qry += "WHERE A.`network_id` = ? "
qry += "ORDER BY A.`address` ASC"
rows, err := r.handle.Query(qry, networkID)
if err != nil {
return nil, err
}
defer rows.Close()
address = []*ui.IP{}
for rows.Next() {
v := new(ui.IP)
var desc, port sql.NullString
var enabled sql.NullBool
var timestamp *time.Time
if err := rows.Scan(&v.ID, &v.Address, &v.Used, &desc, &enabled, ×tamp, &port); err != nil {
return nil, err
}
v.Host.Description = desc.String
v.Host.Enabled = enabled.Bool
v.Port = port.String
// Check its freshness.
if timestamp != nil && (time.Now().Sub(*timestamp) > discovery.ProbeInterval*2) {
v.Host.Stale = true
}
address = append(address, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return address, nil
}
func (r *uiTx) Networks(address *string, pagination ui.Pagination) (network []*ui.Network, err error) {
qry := "SELECT `id`, INET_NTOA(`address`), `mask`, INET_NTOA(`gateway`) "
qry += "FROM `network` "
if address != nil {
start, end := rangeIP(*address)
qry += fmt.Sprintf("WHERE `address` BETWEEN %v AND %v ", start, end)
}
qry += "ORDER BY `address` ASC, `mask` ASC "
if pagination.Limit > 0 {
qry += fmt.Sprintf("LIMIT %v, %v", pagination.Offset, pagination.Limit)
}
rows, err := r.handle.Query(qry)
if err != nil {
return nil, err
}
defer rows.Close()
network = []*ui.Network{}
for rows.Next() {
v := new(ui.Network)
if err := rows.Scan(&v.ID, &v.Address, &v.Mask, &v.Gateway); err != nil {
return nil, err
}
network = append(network, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return network, nil
}
func (r *uiTx) AddNetwork(requesterID uint64, addr net.IP, mask net.IPMask, gateway net.IP) (network *ui.Network, duplicated bool, err error) {
id, err := addNetwork(r.handle, addr, mask, gateway)
if err != nil {
// No error.
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
if err := addIPAddrs(r.handle, id, addr, mask); err != nil {
return nil, false, err
}
network, err = getNetwork(r.handle, id)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeNetwork, logMethodAdd, network); err != nil {
return nil, false, err
}
return network, false, nil
}
func addNetwork(tx *sql.Tx, addr net.IP, mask net.IPMask, gateway net.IP) (netID uint64, err error) {
qry := "INSERT INTO network (address, mask, gateway) VALUES (INET_ATON(?), ?, INET_ATON(?))"
ones, _ := mask.Size()
result, err := tx.Exec(qry, addr.String(), ones, gateway.String())
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return uint64(id), nil
}
func addIPAddrs(tx *sql.Tx, netID uint64, addr net.IP, mask net.IPMask) error {
stmt, err := tx.Prepare("INSERT INTO ip (network_id, address) VALUES (?, INET_ATON(?) + ?)")
if err != nil {
return err
}
defer stmt.Close()
reserved, err := network.ReservedIP(net.IPNet{IP: addr, Mask: mask})
if err != nil {
return err
}
ones, bits := mask.Size()
n_addrs := int(math.Pow(2, float64(bits-ones))) - 2 // Minus two due to network and broadcast addresses
for i := 0; i < n_addrs; i++ {
// Skip the reserved IP address.
if reserved.Equal(calculateIP(addr, uint32(i+1))) == true {
continue
}
if _, err := stmt.Exec(netID, addr.String(), i+1); err != nil {
return err
}
}
return nil
}
func calculateIP(network net.IP, n uint32) net.IP {
ip := network.To4()
if ip == nil {
panic(fmt.Sprintf("invalid IPv4 address: %v", network))
}
res := net.IPv4zero.To4()
binary.BigEndian.PutUint32(res, binary.BigEndian.Uint32(ip)+n)
return res
}
func getNetwork(tx *sql.Tx, id uint64) (*ui.Network, error) {
qry := "SELECT `id`, INET_NTOA(`address`), `mask`, INET_NTOA(`gateway`) "
qry += "FROM `network` "
qry += "WHERE `id` = ?"
v := new(ui.Network)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.Address, &v.Mask, &v.Gateway); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) RemoveNetwork(requesterID, netID uint64) (network *ui.Network, err error) {
network, err = getNetwork(r.handle, netID)
if err != nil {
// Not found network to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if _, err := r.handle.Exec("DELETE FROM `network` WHERE `id` = ?", netID); err != nil {
if isForeignkeyErr(err) {
return nil, errors.New("failed to remove a network: it has child IP addresses that are being used by hosts")
}
return nil, err
}
if err := r.log(requesterID, logTypeNetwork, logMethodRemove, network); err != nil {
return nil, err
}
return network, nil
}
func (r *uiTx) Switches(pagination ui.Pagination) (sw []*ui.Switch, err error) {
qry := "SELECT `id`, `dpid`, `n_ports`, `first_port`, `first_printed_port`, `description` "
qry += "FROM `switch` "
qry += "ORDER BY `id` DESC "
qry += "LIMIT ?, ?"
rows, err := r.handle.Query(qry, pagination.Offset, pagination.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
sw = []*ui.Switch{}
for rows.Next() {
v := new(ui.Switch)
if err := rows.Scan(&v.ID, &v.DPID, &v.NumPorts, &v.FirstPort, &v.FirstPrintedPort, &v.Description); err != nil {
return nil, err
}
sw = append(sw, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return sw, nil
}
func (r *uiTx) AddSwitch(requesterID, dpid uint64, nPorts, firstPort, firstPrintedPort uint16, desc string) (sw *ui.Switch, duplicated bool, err error) {
id, err := addSwitch(r.handle, dpid, nPorts, firstPort, firstPrintedPort, desc)
if err != nil {
// No error.
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
if err := addPorts(r.handle, id, firstPort, nPorts); err != nil {
return nil, false, err
}
sw, err = getSwitch(r.handle, id)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeSwitch, logMethodAdd, sw); err != nil {
return nil, false, err
}
return sw, false, nil
}
func addSwitch(tx *sql.Tx, dpid uint64, nPorts, firstPort, firstPrintedPort uint16, desc string) (swID uint64, err error) {
qry := "INSERT INTO switch (dpid, n_ports, first_port, first_printed_port, description) VALUES (?, ?, ?, ?, ?)"
result, err := tx.Exec(qry, dpid, nPorts, firstPort, firstPrintedPort, desc)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return uint64(id), nil
}
func addPorts(tx *sql.Tx, swID uint64, firstPort, n_ports uint16) error {
stmt, err := tx.Prepare("INSERT INTO port (switch_id, number) VALUES (?, ?)")
if err != nil {
return err
}
defer stmt.Close()
for i := uint16(0); i < n_ports; i++ {
if _, err := stmt.Exec(swID, firstPort+i); err != nil {
return err
}
}
return nil
}
func getSwitch(tx *sql.Tx, id uint64) (*ui.Switch, error) {
qry := "SELECT `id`, `dpid`, `n_ports`, `first_port`, `first_printed_port`, `description` "
qry += "FROM `switch` "
qry += "WHERE `id` = ?"
v := new(ui.Switch)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.DPID, &v.NumPorts, &v.FirstPort, &v.FirstPrintedPort, &v.Description); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) RemoveSwitch(requesterID, swID uint64) (sw *ui.Switch, err error) {
sw, err = getSwitch(r.handle, swID)
if err != nil {
// Not found switch to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if _, err := r.handle.Exec("DELETE FROM `switch` WHERE `id` = ?", swID); err != nil {
if isForeignkeyErr(err) {
return nil, errors.New("failed to remove a switch: it has child hosts connected to this switch")
}
return nil, err
}
if err := r.log(requesterID, logTypeSwitch, logMethodRemove, sw); err != nil {
return nil, err
}
return sw, nil
}
func (r *uiTx) User(name string) (user *ui.User, err error) {
qry := "SELECT `id`, `name`, `key`, `enabled`, `admin`, `timestamp` "
qry += "FROM `user` "
qry += "WHERE `name` = ?"
v := new(ui.User)
if err := r.handle.QueryRow(qry, name).Scan(&v.ID, &v.Name, &v.Key, &v.Enabled, &v.Admin, &v.Timestamp); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return v, nil
}
func (r *uiTx) Users(pagination ui.Pagination) (user []*ui.User, err error) {
qry := "SELECT `id`, `name`, `key`, `enabled`, `admin`, `timestamp` "
qry += "FROM `user` "
qry += "ORDER BY `id` DESC "
qry += "LIMIT ?, ?"
rows, err := r.handle.Query(qry, pagination.Offset, pagination.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
user = []*ui.User{}
for rows.Next() {
v := new(ui.User)
if err := rows.Scan(&v.ID, &v.Name, &v.Key, &v.Enabled, &v.Admin, &v.Timestamp); err != nil {
return nil, err
}
user = append(user, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return user, nil
}
func (r *uiTx) AddUser(requesterID uint64, name, key string) (user *ui.User, duplicated bool, err error) {
qry := "INSERT INTO `user` (`name`, `key`, `enabled`, `admin`, `timestamp`) "
qry += "VALUES (?, ?, TRUE, FALSE, NOW())"
result, err := r.handle.Exec(qry, name, key)
if err != nil {
// No error.
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, false, err
}
user, err = getUser(r.handle, uint64(id))
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeUser, logMethodAdd, user); err != nil {
return nil, false, err
}
return user, false, nil
}
func getUser(tx *sql.Tx, id uint64) (*ui.User, error) {
qry := "SELECT `id`, `name`, `key`, `enabled`, `admin`, `timestamp` "
qry += "FROM `user` "
qry += "WHERE `id` = ?"
v := new(ui.User)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.Name, &v.Key, &v.Enabled, &v.Admin, &v.Timestamp); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) UpdateUser(requesterID, userID uint64, enabled, admin *bool) (user *ui.User, err error) {
set := []string{}
args := []interface{}{}
if enabled != nil {
set = append(set, "`enabled` = ?")
args = append(args, *enabled)
}
if admin != nil {
set = append(set, "`admin` = ?")
args = append(args, *admin)
}
if len(set) == 0 {
return nil, nil
}
qry := fmt.Sprintf("UPDATE `user` SET %v WHERE `id` = %v", strings.Join(set, ","), userID)
result, err := r.handle.Exec(qry, args...)
if err != nil {
return nil, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, err
}
// Not found user to update.
if nRows == 0 {
return nil, nil
}
user, err = getUser(r.handle, userID)
if err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeUser, logMethodUpdate, user); err != nil {
return nil, err
}
return user, nil
}
func (r *uiTx) ResetOTPKey(name, key string) (ok bool, err error) {
result, err := r.handle.Exec("UPDATE `user` SET `key` = ? WHERE `name` = ?", key, name)
if err != nil {
return false, err
}
nRows, err := result.RowsAffected()
if err != nil {
return false, err
}
// Not found user to reset OTP.
if nRows == 0 {
return false, nil
}
return true, nil
}
func (r *uiTx) VIPs(pagination ui.Pagination) (vip []*ui.VIP, err error) {
reg, err := r.getVIPs(pagination)
if err != nil {
return nil, err
}
vip = []*ui.VIP{}
for _, v := range reg {
active, err := r.Host(v.active)
if err != nil {
return nil, err
}
if active == nil {
return nil, fmt.Errorf("unknown active host: id=%v", v.active)
}
standby, err := r.Host(v.standby)
if err != nil {
return nil, err
}
if standby == nil {
return nil, fmt.Errorf("unknown standby host: id=%v", v.standby)
}
vip = append(vip, &ui.VIP{
ID: v.id,
IP: v.address,
ActiveHost: *active,
StandbyHost: *standby,
Description: v.description,
})
}
return vip, nil
}
type registeredVIP struct {
id uint64
address string
active uint64
standby uint64
description string
}
func (r *uiTx) getVIPs(pagination ui.Pagination) (vip []registeredVIP, err error) {
qry := "SELECT A.`id`, CONCAT(INET_NTOA(B.`address`), '/', C.`mask`), A.`active_host_id`, A.`standby_host_id`, A.`description` "
qry += "FROM `vip` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "JOIN `network` C ON C.`id` = B.`network_id` "
qry += "ORDER BY A.`id` DESC "
qry += "LIMIT ?, ?"
rows, err := r.handle.Query(qry, pagination.Offset, pagination.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
vip = []registeredVIP{}
for rows.Next() {
v := registeredVIP{}
if err := rows.Scan(&v.id, &v.address, &v.active, &v.standby, &v.description); err != nil {
return nil, err
}
vip = append(vip, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return vip, nil
}
func (r *uiTx) AddVIP(requesterID, ipID, activeID, standbyID uint64, desc string) (vip *ui.VIP, duplicated bool, err error) {
ok, err := isAvailableIP(r.handle, ipID)
if err != nil {
return nil, false, err
}
// No error.
if ok == false {
return nil, true, nil
}
id, err := addNewVIP(r.handle, ipID, activeID, standbyID, desc)
if err != nil {
return nil, false, err
}
vip, err = getVIP(r.handle, id)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeVIP, logMethodAdd, vip); err != nil {
return nil, false, err
}
return vip, false, nil
}
func addNewVIP(tx *sql.Tx, ipID, activeID, standbyID uint64, desc string) (uint64, error) {
enabled, err := isEnabledHost(tx, activeID)
if err != nil {
return 0, err
}
if enabled == false {
return 0, errors.New("disabled host cannot be used for VIP")
}
enabled, err = isEnabledHost(tx, standbyID)
if err != nil {
return 0, err
}
if enabled == false {
return 0, errors.New("disabled host cannot be used for VIP")
}
qry := "INSERT INTO vip (ip_id, active_host_id, standby_host_id, description) VALUES (?, ?, ?, ?)"
result, err := tx.Exec(qry, ipID, activeID, standbyID, desc)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
if err := updateARPTableEntryByVIP(tx, uint64(id), false); err != nil {
return 0, err
}
return uint64(id), nil
}
func isEnabledHost(tx *sql.Tx, id uint64) (enabled bool, err error) {
qry := "SELECT `enabled` FROM `host` WHERE `id` = ?"
if err := tx.QueryRow(qry, id).Scan(&enabled); err != nil {
return false, err
}
return enabled, nil
}
func getVIP(tx *sql.Tx, id uint64) (vip *ui.VIP, err error) {
qry := "SELECT A.`id`, CONCAT(INET_NTOA(B.`address`), '/', C.`mask`), A.`active_host_id`, A.`standby_host_id`, A.`description` "
qry += "FROM `vip` A "
qry += "JOIN `ip` B ON A.`ip_id` = B.`id` "
qry += "JOIN `network` C ON C.`id` = B.`network_id` "
qry += "WHERE A.`id` = ?"
v := new(registeredVIP)
if err := tx.QueryRow(qry, id).Scan(&v.id, &v.address, &v.active, &v.standby, &v.description); err != nil {
return nil, err
}
active, err := getHost(tx, v.active)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("unknown active host: id=%v", v.active)
}
return nil, err
}
standby, err := getHost(tx, v.standby)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("unknown standby host: id=%v", v.standby)
}
return nil, err
}
return &ui.VIP{
ID: v.id,
IP: v.address,
ActiveHost: *active.convert(),
StandbyHost: *standby.convert(),
Description: v.description,
}, nil
}
func (r *uiTx) RemoveVIP(requesterID, vipID uint64) (vip *ui.VIP, err error) {
vip, err = getVIP(r.handle, vipID)
if err != nil {
// Not found VIP to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if err := updateARPTableEntryByVIP(r.handle, vipID, true); err != nil {
return nil, err
}
if _, err := r.handle.Exec("DELETE FROM `vip` WHERE `id` = ?", vipID); err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeVIP, logMethodRemove, vip); err != nil {
return nil, err
}
return vip, nil
}
func (r *uiTx) ToggleVIP(requesterID, vipID uint64) (res *ui.VIP, err error) {
v, err := getVIP(r.handle, vipID)
if err != nil {
// Not found VIP to toggle.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
ip, _, err := net.ParseCIDR(v.IP)
if err != nil {
return nil, err
}
err = swapVIPHosts(r.handle, vip{
id: v.ID,
address: ip,
active: v.ActiveHost.ID,
standby: v.StandbyHost.ID,
})
if err != nil {
return nil, err
}
res, err = getVIP(r.handle, vipID)
if err != nil {
return nil, err
}
if err := r.log(requesterID, logTypeVIP, logMethodUpdate, res); err != nil {
return nil, err
}
return res, nil
}
func (r *uiTx) QueryLog(search *ui.Search, pagination ui.Pagination) (log []*ui.Log, err error) {
qry, args := buildLogsQuery(search, pagination)
rows, err := r.handle.Query(qry, args...)
if err != nil {
return nil, err
}
defer rows.Close()
log = []*ui.Log{}
for rows.Next() {
v := new(ui.Log)
if err := rows.Scan(&v.ID, &v.User, &v.Type, &v.Method, &v.Data, &v.Timestamp); err != nil {
return nil, err
}
log = append(log, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return log, nil
}
func buildLogsQuery(search *ui.Search, pagination ui.Pagination) (qry string, args []interface{}) {
qry = "SELECT `log`.`id`, " // ID
qry += " `user`.`name`, " // User
qry += " `log`.`type`, " // Type
qry += " `log`.`method`, " // Method
qry += " `log`.`data`, " // Data
qry += " `log`.`timestamp` " // Timestamp
qry += "FROM `log` "
qry += "JOIN `user` ON `log`.`user_id` = `user`.`id` "
if search != nil {
switch search.Key {
case ui.ColumnUser:
qry += fmt.Sprintf("WHERE `user`.`name` LIKE CONCAT(?, '%%') ")
case ui.ColumnLogType:
qry += fmt.Sprintf("WHERE `log`.`type` = ? ")
case ui.ColumnLogMethod:
qry += fmt.Sprintf("WHERE `log`.`method` = ? ")
default:
panic(fmt.Sprintf("invalid search key: %v", search.Key))
}
args = append(args, search.Value)
}
qry += "ORDER BY `log`.`id` DESC "
qry += "LIMIT ?, ?"
args = append(args, pagination.Offset)
args = append(args, pagination.Limit)
return qry, args
}
type logType int
const (
logTypeInvalid logType = iota
logTypeUser
logTypeGroup
logTypeSwitch
logTypeNetwork
logTypeHost
logTypeVIP
logTypeCategory
logTypeComponent
)
func (r logType) validate() error {
if r <= logTypeInvalid || r > logTypeComponent {
return fmt.Errorf("invalid log type: %v", r)
}
return nil
}
type logMethod int
const (
logMethodInvalid logMethod = iota
logMethodAdd
logMethodUpdate
logMethodRemove
)
func (r logMethod) validate() error {
if r <= logMethodInvalid || r > logMethodRemove {
return fmt.Errorf("invalid log method: %v", r)
}
return nil
}
func (r *uiTx) log(userID uint64, t logType, m logMethod, data interface{}) error {
if err := t.validate(); err != nil {
return err
}
if err := m.validate(); err != nil {
return err
}
b, err := json.Marshal(data)
if err != nil {
return err
}
qry := "INSERT INTO `log` (`user_id`, `type`, `method`, `data`, `timestamp`) VALUES (?, ?, ?, ?, NOW())"
_, err = r.handle.Exec(qry, userID, t, m, b)
return err
}
func (r *uiTx) Categories(pagination ui.Pagination) (category []*ui.Category, err error) {
qry := "SELECT `id`, `name`, `timestamp` "
qry += "FROM `category` "
qry += "ORDER BY `id` DESC "
if pagination.Limit > 0 {
qry += fmt.Sprintf("LIMIT %v, %v", pagination.Offset, pagination.Limit)
}
rows, err := r.handle.Query(qry)
if err != nil {
return nil, err
}
defer rows.Close()
category = []*ui.Category{}
for rows.Next() {
v := new(ui.Category)
if err := rows.Scan(&v.ID, &v.Name, &v.Timestamp); err != nil {
return nil, err
}
category = append(category, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return category, nil
}
func (r *uiTx) AddCategory(requesterID uint64, name string) (category *ui.Category, duplicated bool, err error) {
qry := "INSERT INTO `category` (`name`, `timestamp`) VALUES (?, NOW())"
result, err := r.handle.Exec(qry, name)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, false, err
}
category, err = getCategory(r.handle, uint64(id))
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeCategory, logMethodAdd, category); err != nil {
return nil, false, err
}
return category, false, nil
}
func getCategory(tx *sql.Tx, id uint64) (category *ui.Category, err error) {
qry := "SELECT `id`, `name`, `timestamp` "
qry += "FROM `category` "
qry += "WHERE `id` = ?"
v := new(ui.Category)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.Name, &v.Timestamp); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) UpdateCategory(requesterID, categoryID uint64, name string) (category *ui.Category, duplicated bool, err error) {
qry := "UPDATE `category` SET `name` = ? WHERE `id` = ?"
result, err := r.handle.Exec(qry, name, categoryID)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, false, err
}
// Not found category to update.
if nRows == 0 {
return nil, false, nil
}
category, err = getCategory(r.handle, categoryID)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeCategory, logMethodUpdate, category); err != nil {
return nil, false, err
}
return category, false, nil
}
func (r *uiTx) RemoveCategory(requesterID, categoryID uint64) (category *ui.Category, err error) {
category, err = getCategory(r.handle, categoryID)
if err != nil {
// Not found category to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if _, err = r.handle.Exec("DELETE FROM `category` WHERE `id` = ?", categoryID); err != nil {
if isForeignkeyErr(err) {
return nil, errors.New("failed to remove a category: it has child components that are being used by category")
}
return nil, err
}
if err := r.log(requesterID, logTypeCategory, logMethodRemove, category); err != nil {
return nil, err
}
return category, nil
}
func (r *uiTx) Components(categoryID uint64, pagination ui.Pagination) (component []*ui.Component, err error) {
qry := "SELECT A.`id`, A.`name`, A.`timestamp`, B.`id`, B.`name`, B.`timestamp` "
qry += "FROM `component` A "
qry += "JOIN `category` B ON A.`category_id` = B.`id` "
qry += "WHERE B.`id`= ? "
qry += "ORDER BY A.`id` DESC "
if pagination.Limit > 0 {
qry += fmt.Sprintf("LIMIT %v, %v", pagination.Offset, pagination.Limit)
}
rows, err := r.handle.Query(qry, categoryID)
if err != nil {
return nil, err
}
defer rows.Close()
component = []*ui.Component{}
for rows.Next() {
v := new(ui.Component)
if err := rows.Scan(&v.ID, &v.Name, &v.Timestamp, &v.Category.ID, &v.Category.Name, &v.Category.Timestamp); err != nil {
return nil, err
}
component = append(component, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return component, nil
}
func (r *uiTx) AddComponent(requesterID, categoryID uint64, name string) (component *ui.Component, duplicated bool, err error) {
qry := "INSERT INTO `component` (`category_id`, `name`, `timestamp`) VALUES (?, ?, NOW())"
result, err := r.handle.Exec(qry, categoryID, name)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, false, err
}
component, err = getComponent(r.handle, uint64(id))
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeComponent, logMethodAdd, component); err != nil {
return nil, false, err
}
return component, false, nil
}
func getComponent(tx *sql.Tx, id uint64) (component *ui.Component, err error) {
qry := "SELECT A.`id`, A.`name`, A.`timestamp`, B.`id`, B.`name`, B.`timestamp` "
qry += "FROM `component` A "
qry += "JOIN `category` B ON A.`category_id` = B.`id` "
qry += "WHERE A.`id` = ?"
v := new(ui.Component)
if err := tx.QueryRow(qry, id).Scan(&v.ID, &v.Name, &v.Timestamp, &v.Category.ID, &v.Category.Name, &v.Category.Timestamp); err != nil {
return nil, err
}
return v, nil
}
func (r *uiTx) UpdateComponent(requesterID, componentID uint64, name string) (component *ui.Component, duplicated bool, err error) {
qry := "UPDATE `component` SET `name` = ? WHERE `id` = ?"
result, err := r.handle.Exec(qry, name, componentID)
if err != nil {
if isDuplicated(err) {
return nil, true, nil
}
return nil, false, err
}
nRows, err := result.RowsAffected()
if err != nil {
return nil, false, err
}
// Not found component to update.
if nRows == 0 {
return nil, false, nil
}
component, err = getComponent(r.handle, componentID)
if err != nil {
return nil, false, err
}
if err := r.log(requesterID, logTypeComponent, logMethodUpdate, component); err != nil {
return nil, false, err
}
return component, false, nil
}
func (r *uiTx) RemoveComponent(requesterID, componentID uint64) (component *ui.Component, err error) {
component, err = getComponent(r.handle, componentID)
if err != nil {
// Not found component to remove.
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
if _, err := r.handle.Exec("DELETE FROM `component` WHERE `id` = ?", componentID); err != nil {
if isForeignkeyErr(err) {
return nil, errors.New("failed to remove a component: it has child specs that are being used by component")
}
return nil, err
}
if err := r.log(requesterID, logTypeComponent, logMethodRemove, component); err != nil {
return nil, err
}
return component, nil
}
func getSpec(tx *sql.Tx, hostID uint64) (spec []*ui.Spec, err error) {
qry := "SELECT A.`id`, A.`count`, B.`id`, B.`name`, B.`timestamp`, C.`id`, C.`name`, C.`timestamp` "
qry += "FROM `spec` A "
qry += "JOIN `component` B ON A.`component_id` = B.`id` "
qry += "JOIN `category` C ON B.`category_id` = C.`id` "
qry += "WHERE A.`host_id` = ? "
qry += "ORDER BY A.`id` DESC"
rows, err := tx.Query(qry, hostID)
if err != nil {
return nil, err
}
defer rows.Close()
spec = []*ui.Spec{}
for rows.Next() {
v := new(ui.Spec)
c := ui.Component{}
if err := rows.Scan(&v.ID, &v.Count, &c.ID, &c.Name, &c.Timestamp, &c.Category.ID, &c.Category.Name, &c.Category.Timestamp); err != nil {
return nil, err
}
v.Component = c
spec = append(spec, v)
}
if err := rows.Err(); err != nil {
return nil, err
}
return spec, nil
}
func addSpec(tx *sql.Tx, hostID, componentID uint64, count uint16) error {
qry := "INSERT INTO `spec` (`host_id`, `component_id`, `count`) VALUES (?, ?, ?)"
_, err := tx.Exec(qry, hostID, componentID, count)
return err
}
================================================
FILE: database/mysql_schema.sql
================================================
-- MySQL dump 10.14 Distrib 5.5.44-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: cherry
-- ------------------------------------------------------
-- Server version 5.5.44-MariaDB-1ubuntu0.14.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `user`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`key` char(32) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`admin` tinyint(1) NOT NULL DEFAULT '0',
`timestamp` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `group`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `group` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `log`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`type` ENUM('USER', 'GROUP', 'SWITCH', 'NETWORK', 'HOST', 'VIP', 'CATEGORY', 'COMPONENT') NOT NULL,
`method` ENUM('ADD', 'UPDATE', 'REMOVE') NOT NULL,
`data` LONGTEXT NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
KEY `type` (`type`),
KEY `method` (`method`),
CONSTRAINT `log_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `category`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `category` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `component`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `component` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`category_id` bigint(20) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `component` (`category_id`, `name`),
CONSTRAINT `component_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `spec`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `spec` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`host_id` bigint(20) unsigned NOT NULL,
`component_id` bigint(20) unsigned NOT NULL,
`count` smallint(5) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `spec` (`host_id`, `component_id`),
CONSTRAINT `spec_ibfk_1` FOREIGN KEY (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `spec_ibfk_2` FOREIGN KEY (`component_id`) REFERENCES `component` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `arp`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `arp` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`ip` int(10) unsigned NOT NULL,
`mac` binary(6) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `master_election`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `master_election` (
`id` tinyint(3) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `acl`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `acl` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`network` int(10) unsigned NOT NULL,
`mask` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `acl` (`network`,`mask`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `network`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `network` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`address` int(10) unsigned NOT NULL,
`mask` int(10) unsigned NOT NULL,
`gateway` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `address` (`address`,`mask`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `switch`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `switch` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`dpid` bigint(20) unsigned NOT NULL,
`n_ports` smallint(5) unsigned NOT NULL,
`first_port` smallint(5) unsigned NOT NULL DEFAULT '1',
`first_printed_port` smallint(5) unsigned NOT NULL DEFAULT '0',
`description` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `dpid` (`dpid`),
KEY `description` (`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `port`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `port` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`switch_id` bigint(20) unsigned NOT NULL,
`number` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `number` (`switch_id`,`number`),
CONSTRAINT `port_ibfk_1` FOREIGN KEY (`switch_id`) REFERENCES `switch` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `ip`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `ip` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`network_id` bigint(20) unsigned NOT NULL,
`address` int(10) unsigned NOT NULL,
`used` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `address` (`address`),
KEY `used` (`used`),
KEY `network_id` (`network_id`),
CONSTRAINT `ip_ibfk_1` FOREIGN KEY (`network_id`) REFERENCES `network` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `host`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `host` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`ip_id` bigint(20) unsigned NOT NULL,
`port_id` bigint(20) unsigned default NULL,
`group_id` bigint(20) unsigned default NULL,
`mac` binary(6) NOT NULL,
`description` varchar(255) NOT NULL,
`last_updated_timestamp` datetime NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`created_timestamp` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ip` (`ip_id`),
KEY `port_id` (`port_id`),
KEY `mac` (`mac`),
KEY `last_updated_timestamp` (`last_updated_timestamp`),
FULLTEXT `description` (`description`),
CONSTRAINT `host_ibfk_1` FOREIGN KEY (`ip_id`) REFERENCES `ip` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `host_ibfk_2` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `host_ibfk_3` FOREIGN KEY (`group_id`) REFERENCES `group` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER disable_ip AFTER INSERT ON host FOR EACH ROW UPDATE ip SET used = 1 WHERE id = NEW.ip_id */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER update_ip AFTER UPDATE ON host
FOR EACH ROW BEGIN
UPDATE ip SET used = 0 WHERE id = OLD.ip_id;
UPDATE ip SET used = 1 WHERE id = NEW.ip_id;
END */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER enable_ip AFTER DELETE ON host FOR EACH ROW UPDATE ip SET used = 0 WHERE id = OLD.ip_id */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
--
-- Table structure for table `vip`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE IF NOT EXISTS `vip` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`ip_id` bigint(20) unsigned NOT NULL,
`active_host_id` bigint(20) unsigned NOT NULL,
`standby_host_id` bigint(20) unsigned NOT NULL,
`description` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `vip` (`ip_id`),
CONSTRAINT `vip_ibfk_1` FOREIGN KEY (`ip_id`) REFERENCES `ip` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `vip_ibfk_2` FOREIGN KEY (`active_host_id`) REFERENCES `host` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `vip_ibfk_3` FOREIGN KEY (`standby_host_id`) REFERENCES `host` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER disable_vip AFTER INSERT ON vip FOR EACH ROW UPDATE ip SET used = 1 WHERE id = NEW.ip_id */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER update_vip AFTER UPDATE ON vip
FOR EACH ROW BEGIN
UPDATE ip SET used = 0 WHERE id = OLD.ip_id;
UPDATE ip SET used = 1 WHERE id = NEW.ip_id;
END */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8 */ ;
/*!50003 SET character_set_results = utf8 */ ;
/*!50003 SET collation_connection = utf8_general_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = '' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER enable_vip AFTER DELETE ON vip FOR EACH ROW UPDATE ip SET used = 0 WHERE id = OLD.ip_id */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
--
-- Dumping routines for database 'cherry'
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2015-09-09 15:27:02
================================================
FILE: database/random.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package database
import (
"math/rand"
"sync"
)
// randomSourece is safe for concurrent use by multiple goroutines.
type randomSource struct {
sync.Mutex
src rand.Source
}
func (r *randomSource) Int63() (n int64) {
r.Lock()
defer r.Unlock()
return r.src.Int63()
}
func (r *randomSource) Seed(seed int64) {
r.Lock()
defer r.Unlock()
r.src.Seed(seed)
}
================================================
FILE: election/election.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package election
import (
"context"
"crypto/sha256"
"fmt"
"math/rand"
"os"
"sync"
"time"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("election")
)
const (
interval = 1 * time.Second
)
type Observer struct {
uid string
db Database
mutex sync.Mutex
master bool
}
type Database interface {
// Elect selects a new master as uid if there is a no existing master that has
// been updated within expiration. elected will be true if this uid has been
// elected as the new master or was already elected.
Elect(uid string, expiration time.Duration) (elected bool, err error)
}
func New(db Database) *Observer {
return &Observer{
uid: generateRandomUID(),
db: db,
}
}
func generateRandomUID() string {
src := fmt.Sprintf("%v.%v.%v", time.Now().UnixNano(), os.Getpid(), rand.Int63())
sum := sha256.Sum256([]byte(src))
return fmt.Sprintf("%x", sum)
}
func (r *Observer) Run(ctx context.Context) error {
logger.Debugf("starting an election observer: uid=%v", r.uid)
ticker := time.Tick(interval)
// Infinite loop.
for {
prev := r.getMaster()
elected, err := r.db.Elect(r.uid, interval*15)
if err != nil {
return err
}
r.setMaster(elected)
logger.Debugf("master election result: prev=%v, elected=%v", prev, elected)
if prev != elected {
if prev == true {
// Previous master.
logger.Fatal("master controller has been changed: demoted from the master: self shutting down to avoid split-brain")
} else {
// New master.
logger.Warning("master controller has been changed: elected as a new master")
}
}
// Wait the context cancels or the ticker rasises.
select {
case <-ctx.Done():
logger.Debug("terminating the election observer...")
return nil
case <-ticker:
// Do nothing.
}
}
}
func (r *Observer) IsMaster() bool {
return r.getMaster()
}
func (r *Observer) setMaster(value bool) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.master = value
}
func (r *Observer) getMaster() bool {
r.mutex.Lock()
defer r.mutex.Unlock()
return r.master
}
================================================
FILE: graph/graph.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package graph
import (
"bytes"
"container/list"
"errors"
"fmt"
"sort"
"sync"
"time"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("graph")
)
// Vertex is a node (e.g., switch) that consists of at least one or more points.
type Vertex interface {
ID() string
}
// Point is a spot (e.g., switch port) on a vertex. We need this to represent multiple links among two vertexies.
type Point interface {
ID() string
Vertex() Vertex
}
// Edge is a bi-directional link, which has a weight, among two points.
type Edge interface {
ID() string
Points() [2]Point
Weight() float64
}
type edge struct {
value Edge
enabled bool
timestamp time.Time
}
type vertex struct {
value Vertex
edges map[string]*edge
}
type Graph struct {
mutex sync.RWMutex
vertexies map[string]vertex
edges map[string]*edge
points map[string]*edge
}
func New() *Graph {
return &Graph{
vertexies: make(map[string]vertex),
edges: make(map[string]*edge),
points: make(map[string]*edge),
}
}
func (r *Graph) String() string {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
var buf bytes.Buffer
for _, v := range r.edges {
e := v.value
buf.WriteString(fmt.Sprintf("Edge ID=%v, Enabled=%v, Timestamp=%v\n", e.ID(), v.enabled, v.timestamp))
}
return buf.String()
}
func (r *Graph) AddVertex(v Vertex) {
r.mutex.Lock()
defer r.mutex.Unlock()
if v == nil {
panic("adding nil vertex")
}
// Check duplication
_, ok := r.vertexies[v.ID()]
if ok {
return
}
r.vertexies[v.ID()] = vertex{
value: v,
edges: make(map[string]*edge),
}
r.calculateMST()
}
func (r *Graph) removeEdge(e Edge) {
v := e.Points()
v1, ok := r.vertexies[v[0].Vertex().ID()]
if ok {
delete(v1.edges, e.ID())
}
v2, ok := r.vertexies[v[1].Vertex().ID()]
if ok {
delete(v2.edges, e.ID())
}
delete(r.edges, e.ID())
delete(r.points, v[0].ID())
delete(r.points, v[1].ID())
}
func (r *Graph) RemoveVertex(v Vertex) {
r.mutex.Lock()
defer r.mutex.Unlock()
if v == nil {
panic("removing nil vertex")
}
vertex, ok := r.vertexies[v.ID()]
if !ok {
return
}
for _, e := range vertex.edges {
r.removeEdge(e.value)
}
delete(r.vertexies, v.ID())
r.calculateMST()
}
func (r *Graph) AddEdge(e Edge) (added bool, err error) {
r.mutex.Lock()
defer r.mutex.Unlock()
if e == nil {
panic("adding nil edge")
}
// Check duplication
elem, ok := r.edges[e.ID()]
if ok {
// Update the timestamp if we already have same one.
elem.timestamp = time.Now()
logger.Debugf("updated the edge timestamp: id=%v", e.ID())
return false, nil
}
points := e.Points()
if points[0].Vertex() == nil || points[1].Vertex() == nil {
panic("adding an edge pointing to nil vertex")
}
first, ok1 := r.vertexies[points[0].Vertex().ID()]
second, ok2 := r.vertexies[points[1].Vertex().ID()]
if !ok1 || !ok2 {
return false, errors.New("AddEdge: adding an edge to unknown vertex")
}
edge := &edge{value: e, timestamp: time.Now()}
r.edges[e.ID()] = edge
first.edges[e.ID()] = edge
second.edges[e.ID()] = edge
r.points[points[0].ID()] = edge
r.points[points[1].ID()] = edge
r.calculateMST()
logger.Debugf("added a new edge: id=%v", e.ID())
return true, nil
}
func (r *Graph) RemoveEdge(p Point) {
r.mutex.Lock()
defer r.mutex.Unlock()
e, ok := r.points[p.ID()]
if !ok {
return
}
r.removeEdge(e.value)
r.calculateMST()
logger.Debugf("removed an edge: id=%v", e.value.ID())
}
// IsEdge returns whether p is on an edge between two vertexeis.
func (r *Graph) IsEdge(p Point) bool {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
if p == nil {
panic("nil point")
}
_, ok := r.points[p.ID()]
return ok
}
// IsEnabledPoint returns whether p is an active point that is not disabled by the minimum spanning tree.
func (r *Graph) IsEnabledPoint(p Point) bool {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
if p == nil {
panic("nil point")
}
v, ok := r.points[p.ID()]
if !ok {
return false
}
return v.enabled
}
type sortedEdge []*edge
func (r sortedEdge) Len() int {
return len(r)
}
func (r sortedEdge) Less(i, j int) bool {
if r[i].value.Weight() < r[j].value.Weight() {
return true
}
return false
}
func (r sortedEdge) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r *Graph) pickRootVertex() Vertex {
// Pick arbitrary vertex node that has at least one edge.
for _, v := range r.vertexies {
if len(v.edges) == 0 {
continue
}
return v.value
}
return nil
}
func (r *Graph) resetEdges() *list.List {
edges := make(sortedEdge, 0)
for _, v := range r.edges {
// Disable all edges
v.enabled = false
edges = append(edges, v)
}
sort.Sort(edges)
result := list.New()
for _, v := range edges {
result.PushBack(v)
}
return result
}
func (r *Graph) pickValidVertexies() []Vertex {
result := make([]Vertex, 0)
for _, v := range r.vertexies {
if len(v.edges) == 0 {
continue
}
result = append(result, v.value)
}
return result
}
func (r *Graph) makeClusters() map[string]*list.List {
result := make(map[string]*list.List)
for _, v := range r.vertexies {
l := list.New()
l.PushBack(v)
result[v.value.ID()] = l
}
return result
}
func mergeCluster(clusters map[string]*list.List, l1, l2 *list.List) {
v := list.New()
v.PushBackList(l1)
v.PushBackList(l2)
for elem := v.Front(); elem != nil; elem = elem.Next() {
vertex := elem.Value.(vertex)
clusters[vertex.value.ID()] = v
}
}
// calculateMST finds a minimum spanning tree of this graph using Kruskal's algorithm.
// A caller should lock the mutex before calling this function.
func (r *Graph) calculateMST() {
if len(r.edges) == 0 || len(r.vertexies) == 0 {
return
}
// FIXME: Use priority queue instead of sorting!
edges := r.resetEdges()
clusters := r.makeClusters()
count := 0
for count < len(r.vertexies)-1 {
if edges.Len() == 0 {
break
}
// Pop the minimum weighted edge
elem := edges.Front()
e := elem.Value.(*edge)
edges.Remove(elem)
points := e.value.Points()
v1, ok := clusters[points[0].Vertex().ID()]
if !ok {
panic("invalid edge pointing an unknown vertex")
}
v2, ok := clusters[points[1].Vertex().ID()]
if !ok {
panic("invalid edge pointing an unknown vertex")
}
// Prevent a loop
if v1 == v2 {
continue
}
// Found new edge to be included in MST
mergeCluster(clusters, v1, v2)
e.enabled = true
count++
}
}
type queue struct {
list *list.List
}
func newQueue() *queue {
return &queue{list.New()}
}
func (r *queue) enqueue(v interface{}) {
r.list.PushBack(v)
}
func (r *queue) dequeue() interface{} {
v := r.list.Front()
if v != nil {
r.list.Remove(v)
}
return v.Value
}
func (r *queue) length() int {
return r.list.Len()
}
type Path struct {
V Vertex
E Edge
}
func (r *Graph) FindPath(src, dst Vertex) []Path {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
if len(r.vertexies) == 0 || len(r.edges) == 0 {
return []Path{}
}
visited := make(map[string]bool)
prev := make(map[string]Path)
queue := newQueue()
queue.enqueue(src)
visited[src.ID()] = true
// Implementation of BFS algorithm
for queue.length() > 0 {
v := queue.dequeue()
if v == nil {
panic("nil element is fetched from the queue")
}
vertex, ok := r.vertexies[v.(Vertex).ID()]
if !ok {
return []Path{}
}
for _, w := range vertex.edges {
// We only use edges that belong to MST.
if w.enabled == false {
continue
}
points := w.value.Points()
next := points[0]
if points[0].Vertex().ID() == vertex.value.ID() {
next = points[1]
}
if _, ok := visited[next.Vertex().ID()]; ok {
continue
}
visited[next.Vertex().ID()] = true
prev[next.Vertex().ID()] = Path{V: vertex.value, E: w.value}
queue.enqueue(next.Vertex())
}
}
u := dst
result := make([]Path, 0)
for {
path, ok := prev[u.ID()]
if !ok {
break
}
result = append(result, path)
u = path.V
}
return reverse(result)
}
func reverse(data []Path) []Path {
length := len(data)
if length == 0 {
return data
}
result := make([]Path, length)
for i, j := 0, length-1; i < length; i, j = i+1, j-1 {
result[i] = data[j]
}
return result
}
func (r *Graph) RemoveStaleEdges(expiration time.Duration) (removed bool) {
r.mutex.Lock()
defer r.mutex.Unlock()
for _, edge := range r.edges {
if time.Now().Sub(edge.timestamp) < expiration {
continue
}
logger.Infof("removing a stale edge from the topology: id=%v", edge.value.ID())
r.removeEdge(edge.value)
removed = true
}
if removed {
r.calculateMST()
}
return removed
}
================================================
FILE: graph/graph_test.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package graph
import (
"fmt"
"testing"
)
type node struct {
dpid string
}
func (r node) ID() string {
return fmt.Sprintf("%v", r.dpid)
}
type point struct {
dpid string
port uint32
}
func (r point) ID() string {
return fmt.Sprintf("%v:%v", r.dpid, r.port)
}
func (r point) Vertex() Vertex {
return node{r.dpid}
}
type link struct {
points [2]point
weight float64
}
func (r link) ID() string {
return fmt.Sprintf("%v:%v/%v:%v", r.points[0].dpid, r.points[0].port, r.points[1].dpid, r.points[1].port)
}
func (r link) Points() [2]Point {
return [2]Point{r.points[0], r.points[1]}
}
func (r link) Weight() float64 {
return r.weight
}
func printEnabledEdges(g *Graph) (int, float64) {
count := 0
weight := 0.0
for _, v := range g.edges {
if v.enabled == false {
continue
}
fmt.Printf("Edge: %+v\n", v.value)
count++
weight += v.value.Weight()
}
return count, weight
}
func TestInvalidMST(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.calculateMST()
c, _ := printEnabledEdges(graph)
if c != 0 {
t.Fatalf("Unexpected MST: expected len=0, got=%v", c)
}
e := link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
}
if _, err := graph.AddEdge(e); err == nil {
t.Fatal("Expected error, but not occurred!")
}
}
func TestRemoveVertex(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
e := link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
graph.RemoveVertex(node{"a"})
if len(graph.vertexies) != 1 {
t.Fatalf("Expected node length is 1, got=%v\n", len(graph.vertexies))
}
if len(graph.edges) != 0 {
t.Fatalf("Expected edge length is 0, got=%v\n", len(graph.edges))
}
if len(graph.points) != 0 {
t.Fatalf("Expected points length is 0, got=%v\n", len(graph.points))
}
v := graph.vertexies["b"]
if len(v.edges) != 0 {
t.Fatalf("Expected edge length is 0, got=%v\n", len(v.edges))
}
}
func TestRemoveEdges(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
e := link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
graph.RemoveEdge(point{"a", 1})
if len(graph.edges) != 0 {
t.Fatalf("Expected edge length is 0, got=%v\n", len(graph.edges))
}
if len(graph.points) != 0 {
t.Fatalf("Expected points length is 0, got=%v\n", len(graph.points))
}
a := graph.vertexies["a"]
b := graph.vertexies["b"]
if len(a.edges) != 0 || len(b.edges) != 0 {
t.Fatalf("Expected # of edges is 0/0, got=%v/%v\n", len(a.edges), len(b.edges))
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
if len(a.edges) != 1 || len(b.edges) != 1 {
t.Fatalf("Expected # of edges is 1/1, got=%v/%v\n", len(a.edges), len(b.edges))
}
graph.RemoveEdge(point{"a", 1})
if len(graph.edges) != 0 {
t.Fatalf("Expected edge length is 0, got=%v\n", len(graph.edges))
}
if len(graph.points) != 0 {
t.Fatalf("Expected points length is 0, got=%v\n", len(graph.points))
}
if len(a.edges) != 0 || len(b.edges) != 0 {
t.Fatalf("Expected # of edges is 0/0, got=%v/%v\n", len(a.edges), len(b.edges))
}
}
func TestDuplicatedEdges(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
e := link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
if _, err := graph.AddEdge(e); err != nil {
t.Fatal(err)
}
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 1 || w != 2 {
t.Fatalf("Unexpected MST: expected=1/2, got=%v/%v", c, w)
}
}
func TestMST0(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
graph.AddVertex(node{"c"})
graph.AddVertex(node{"d"})
edges := make([]link, 0)
edges = append(edges, link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"c", 1}, point{"d", 3}},
weight: 3,
})
for _, v := range edges {
if _, err := graph.AddEdge(v); err != nil {
t.Fatal(err)
}
}
for i := 0; i < 100; i++ {
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 2 || w != 5 {
t.Fatalf("Unexpected MST: expected=2/5, got=%v/%v", c, w)
}
path := graph.FindPath(node{"a"}, node{"b"})
fmt.Printf("Path: %+v\n", path)
total := 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 1 || total != 2 {
t.Fatalf("Unexpected Path: expected=1/2, got=%v/%v", len(path), total)
}
path = graph.FindPath(node{"d"}, node{"c"})
fmt.Printf("Path: %+v\n", path)
total = 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 1 || total != 3 {
t.Fatalf("Unexpected Path: expected=1/3, got=%v/%v", len(path), total)
}
path = graph.FindPath(node{"b"}, node{"c"})
fmt.Printf("Path: %+v\n", path)
total = 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 0 || total != 0 {
t.Fatalf("Unexpected Path: expected=0/0, got=%v/%v", len(path), total)
}
}
}
func TestMST1(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
graph.AddVertex(node{"c"})
graph.AddVertex(node{"d"})
edges := make([]link, 0)
edges = append(edges, link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"a", 2}, point{"d", 1}},
weight: 1,
})
edges = append(edges, link{
points: [2]point{point{"b", 2}, point{"d", 2}},
weight: 3,
})
edges = append(edges, link{
points: [2]point{point{"c", 1}, point{"d", 3}},
weight: 3,
})
for _, v := range edges {
if _, err := graph.AddEdge(v); err != nil {
t.Fatal(err)
}
}
for i := 0; i < 100; i++ {
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 3 || w != 6 {
t.Fatalf("Unexpected MST: expected=3/6, got=%v/%v", c, w)
}
path := graph.FindPath(node{"b"}, node{"c"})
fmt.Printf("Path: %+v\n", path)
total := 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 3 || total != 6 {
t.Fatalf("Unexpected Path: expected=3/6, got=%v/%v", len(path), total)
}
}
}
func TestMST2(t *testing.T) {
graph := New()
graph.AddVertex(node{"a"})
graph.AddVertex(node{"b"})
graph.AddVertex(node{"c"})
graph.AddVertex(node{"d"})
graph.AddVertex(node{"e"})
graph.AddVertex(node{"f"})
graph.AddVertex(node{"g"})
edges := make([]link, 0)
edges = append(edges, link{
points: [2]point{point{"a", 1}, point{"b", 1}},
weight: 4,
})
edges = append(edges, link{
points: [2]point{point{"a", 2}, point{"c", 1}},
weight: 8,
})
edges = append(edges, link{
points: [2]point{point{"b", 2}, point{"c", 2}},
weight: 9,
})
edges = append(edges, link{
points: [2]point{point{"b", 1}, point{"d", 3}},
weight: 8,
})
edges = append(edges, link{
points: [2]point{point{"b", 1}, point{"e", 3}},
weight: 10,
})
edges = append(edges, link{
points: [2]point{point{"c", 1}, point{"d", 3}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"c", 1}, point{"f", 3}},
weight: 1,
})
edges = append(edges, link{
points: [2]point{point{"d", 1}, point{"e", 3}},
weight: 7,
})
edges = append(edges, link{
points: [2]point{point{"d", 1}, point{"f", 3}},
weight: 9,
})
edges = append(edges, link{
points: [2]point{point{"e", 1}, point{"f", 3}},
weight: 5,
})
edges = append(edges, link{
points: [2]point{point{"e", 1}, point{"g", 3}},
weight: 6,
})
edges = append(edges, link{
points: [2]point{point{"f", 1}, point{"g", 3}},
weight: 2,
})
for _, v := range edges {
if _, err := graph.AddEdge(v); err != nil {
t.Fatal(err)
}
}
for i := 0; i < 100; i++ {
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 6 || w != 22 {
t.Fatalf("Unexpected MST: expected=6/22, got=%v/%v", c, w)
}
path := graph.FindPath(node{"d"}, node{"e"})
fmt.Printf("Path: %+v\n", path)
total := 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 3 || total != 8 {
t.Fatalf("Unexpected Path: expected=3/8, got=%v/%v", len(path), total)
}
}
}
func TestMST3(t *testing.T) {
graph := New()
graph.AddVertex(node{"0"})
graph.AddVertex(node{"1"})
graph.AddVertex(node{"2"})
graph.AddVertex(node{"3"})
graph.AddVertex(node{"4"})
graph.AddVertex(node{"5"})
graph.AddVertex(node{"6"})
graph.AddVertex(node{"7"})
graph.AddVertex(node{"8"})
edges := make([]link, 0)
edges = append(edges, link{
points: [2]point{point{"0", 1}, point{"1", 1}},
weight: 4,
})
edges = append(edges, link{
points: [2]point{point{"0", 2}, point{"7", 1}},
weight: 8,
})
edges = append(edges, link{
points: [2]point{point{"1", 2}, point{"2", 2}},
weight: 9,
})
edges = append(edges, link{
points: [2]point{point{"1", 1}, point{"7", 3}},
weight: 11,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"3", 3}},
weight: 7,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"5", 3}},
weight: 4,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"8", 3}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"3", 1}, point{"4", 3}},
weight: 9,
})
edges = append(edges, link{
points: [2]point{point{"3", 1}, point{"5", 3}},
weight: 14,
})
edges = append(edges, link{
points: [2]point{point{"4", 1}, point{"5", 3}},
weight: 10,
})
edges = append(edges, link{
points: [2]point{point{"5", 1}, point{"6", 3}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"6", 1}, point{"7", 3}},
weight: 1,
})
edges = append(edges, link{
points: [2]point{point{"6", 1}, point{"8", 3}},
weight: 6,
})
edges = append(edges, link{
points: [2]point{point{"7", 1}, point{"8", 3}},
weight: 7,
})
for _, v := range edges {
if _, err := graph.AddEdge(v); err != nil {
t.Fatal(err)
}
}
for i := 0; i < 100; i++ {
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 8 || w != 37 {
t.Fatalf("Unexpected MST: expected=8/37, got=%v/%v", c, w)
}
path := graph.FindPath(node{"0"}, node{"8"})
fmt.Printf("Path: %+v\n", path)
total := 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 5 || total != 17 {
t.Fatalf("Unexpected Path: expected=5/17, got=%v/%v", len(path), total)
}
}
}
func TestMST4(t *testing.T) {
graph := New()
graph.AddVertex(node{"0"})
graph.AddVertex(node{"1"})
graph.AddVertex(node{"2"})
graph.AddVertex(node{"3"})
graph.AddVertex(node{"4"})
graph.AddVertex(node{"5"})
edges := make([]link, 0)
edges = append(edges, link{
points: [2]point{point{"0", 1}, point{"1", 1}},
weight: 3,
})
edges = append(edges, link{
points: [2]point{point{"0", 2}, point{"2", 1}},
weight: 1,
})
edges = append(edges, link{
points: [2]point{point{"0", 2}, point{"3", 2}},
weight: 6,
})
edges = append(edges, link{
points: [2]point{point{"1", 1}, point{"2", 3}},
weight: 5,
})
edges = append(edges, link{
points: [2]point{point{"1", 1}, point{"4", 3}},
weight: 3,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"3", 3}},
weight: 5,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"4", 3}},
weight: 6,
})
edges = append(edges, link{
points: [2]point{point{"2", 1}, point{"5", 3}},
weight: 4,
})
edges = append(edges, link{
points: [2]point{point{"3", 1}, point{"5", 3}},
weight: 2,
})
edges = append(edges, link{
points: [2]point{point{"4", 1}, point{"5", 3}},
weight: 6,
})
for _, v := range edges {
if _, err := graph.AddEdge(v); err != nil {
t.Fatal(err)
}
}
for i := 0; i < 100; i++ {
graph.calculateMST()
c, w := printEnabledEdges(graph)
if c != 5 || w != 13 {
t.Fatalf("Unexpected MST: expected=5/13, got=%v/%v", c, w)
}
path := graph.FindPath(node{"1"}, node{"3"})
fmt.Printf("Path: %+v\n", path)
total := 0.0
for _, v := range path {
total += v.E.Weight()
}
if len(path) != 4 || total != 10 {
t.Fatalf("Unexpected Path: expected=4/10, got=%v/%v", len(path), total)
}
}
}
================================================
FILE: ldap/client.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ldap
import (
"crypto/tls"
"fmt"
"sync"
"github.com/superkkt/viper"
"gopkg.in/ldap.v3"
)
type Client struct {
mutex *sync.Mutex
cond *sync.Cond
config *viper.Viper
tls *tls.Config
numConn int
}
func New(config *viper.Viper, maxConn int) *Client {
mutex := new(sync.Mutex)
return &Client{
mutex: mutex,
cond: sync.NewCond(mutex),
config: config,
tls: &tls.Config{InsecureSkipVerify: true},
numConn: maxConn,
}
}
func (r *Client) Auth(username, password string) (ok bool, err error) {
conn, err := r.acquireConn()
if err != nil {
return false, err
}
defer r.releaseConn(conn)
if err := r.bindAdmin(conn); err != nil {
return false, err
}
dn, err := r.getDN(conn, username)
if err != nil {
return false, err
}
// Incorrect username.
if len(dn) == 0 {
return false, nil
}
if err = conn.Bind(dn, password); err != nil {
if e, ok := err.(*ldap.Error); ok {
// Incorrect password.
if e.ResultCode == ldap.LDAPResultInvalidCredentials {
return false, nil
}
}
return false, err
}
return true, nil
}
func (r *Client) bindAdmin(conn *ldap.Conn) error {
return conn.Bind(r.config.GetString("admin.name"), r.config.GetString("admin.password"))
}
// It returns empty string, if no user matches username.
func (r *Client) getDN(conn *ldap.Conn, username string) (dn string, err error) {
result, err := conn.Search(ldap.NewSearchRequest(
r.config.GetString("base_dn"),
ldap.ScopeWholeSubtree, ldap.DerefAlways, 1, 0, false,
fmt.Sprintf("(&(%v=%v)(objectclass=user))", r.config.GetString("attr.login"), username),
[]string{"DN"},
nil,
))
if err != nil {
return "", err
}
if len(result.Entries) == 0 {
return "", nil
}
return result.Entries[0].DN, nil
}
================================================
FILE: ldap/conn.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ldap
import (
"time"
"gopkg.in/ldap.v3"
)
func (r *Client) acquireConn() (conn *ldap.Conn, err error) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.numConn == 0 {
r.cond.Wait()
}
return r.createConn()
}
func (r *Client) createConn() (*ldap.Conn, error) {
conn, err := ldap.DialTLS("tcp", r.config.GetString("addr"), r.tls)
if err != nil {
return nil, err
}
conn.SetTimeout(30 * time.Second)
r.numConn--
if r.numConn < 0 {
panic("negative numConn value")
}
return conn, nil
}
func (r *Client) releaseConn(conn *ldap.Conn) {
r.mutex.Lock()
defer r.mutex.Unlock()
conn.Close()
r.numConn++
r.cond.Signal()
}
================================================
FILE: log/syslog.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package log
import (
"fmt"
slog "log/syslog"
"runtime"
"strings"
"github.com/superkkt/go-logging"
)
type syslog struct {
writer *slog.Writer
}
func NewSyslog(prefix string) (logging.Backend, error) {
w, err := slog.New(slog.LOG_CRIT, prefix)
if err != nil {
return nil, err
}
return &syslog{writer: w}, nil
}
func (r *syslog) Log(level logging.Level, calldepth int, record *logging.Record) error {
line := fmt.Sprintf("%v (TID=%v)", record.Formatted(calldepth+1), getGoRoutineID())
switch level {
case logging.CRITICAL:
return r.writer.Crit(line)
case logging.ERROR:
return r.writer.Err(line)
case logging.WARNING:
return r.writer.Warning(line)
case logging.NOTICE:
return r.writer.Notice(line)
case logging.INFO:
return r.writer.Info(line)
case logging.DEBUG:
return r.writer.Debug(line)
default:
panic("unexpected log level")
}
}
func getGoRoutineID() string {
var buf [64]byte
n := runtime.Stack(buf[:], false)
return strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
}
================================================
FILE: network/controller.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"context"
"net"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/protocol"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("network")
)
type database interface {
Location(mac net.HardwareAddr) (dpid string, port uint32, status LocationStatus, err error)
}
type LocationStatus int
const (
// Unregistered MAC address.
LocationUnregistered LocationStatus = iota
// Registered MAC address, but we don't know its physical location yet.
LocationUndiscovered
// Registered MAC address, and we know its physical location.
LocationDiscovered
)
type EventListener interface {
ControllerEventListener
TopologyEventListener
}
type ControllerEventListener interface {
OnPacketIn(Finder, *Port, *protocol.Ethernet) error
OnPortUp(Finder, *Port) error
OnPortDown(Finder, *Port) error
OnDeviceUp(Finder, *Device) error
OnDeviceDown(Finder, *Device) error
OnFlowRemoved(Finder, openflow.FlowRemoved) error
}
type TopologyEventListener interface {
OnTopologyChange(Finder) error
}
type Controller struct {
topo *topology
listener EventListener
}
func NewController(db database) *Controller {
return &Controller{
topo: newTopology(db),
}
}
func (r *Controller) AddConnection(ctx context.Context, c net.Conn) {
conf := sessionConfig{
conn: c,
watcher: r.topo,
finder: r.topo,
listener: r.listener,
}
session := newSession(conf)
go session.Run(ctx)
}
func (r *Controller) SetEventListener(l EventListener) {
r.listener = l
r.topo.setEventListener(l)
}
func (r *Controller) String() string {
return r.topo.String()
}
func (r *Controller) Announce(ip net.IP, mac net.HardwareAddr) error {
for _, device := range r.topo.Devices() {
logger.Debugf("sending ARP announcement for a host (IP: %v, MAC: %v) via %v", ip, mac, device.ID())
if err := device.SendARPAnnouncement(ip, mac); err != nil {
logger.Errorf("failed to send ARP announcement via %v: %v", device.ID(), err)
continue
}
}
return nil
}
func (r *Controller) RemoveFlows() error {
for _, device := range r.topo.Devices() {
logger.Infof("removing all flows from %v", device.ID())
if err := device.RemoveFlows(); err != nil {
logger.Warningf("failed to remove all flows on %v device: %v", device.ID(), err)
continue
}
}
return nil
}
func (r *Controller) RemoveFlowsByMAC(mac net.HardwareAddr) error {
for _, device := range r.topo.Devices() {
if err := device.RemoveFlowByMAC(mac); err != nil {
logger.Errorf("failed to remove flows for %v from %v: %v", mac, device.ID(), err)
continue
}
logger.Debugf("removed flows whose destination MAC address is %v on %v", mac, device.ID())
}
return nil
}
================================================
FILE: network/device.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"encoding"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/openflow/transceiver"
"github.com/superkkt/cherry/protocol"
"github.com/superkkt/viper"
)
type Descriptions struct {
Manufacturer string
Hardware string
Software string
Serial string
Description string
}
type Features struct {
DPID uint64
NumBuffers uint32
NumTables uint8
}
type Device struct {
mutex sync.RWMutex
id string
session *session
descriptions Descriptions
features Features
ports map[uint32]*Port
flowTableID uint8 // Table IDs that we install flows
factory openflow.Factory
closed bool
flowCache *flowCache
vlanID uint16
}
var (
ErrClosedDevice = errors.New("already closed device")
)
func newDevice(s *session) *Device {
if s == nil {
panic("Session is nil")
}
vlanID := viper.GetInt("default.vlan_id")
if vlanID < 0 || vlanID > 4095 {
// vlanID should be already checked in the main code.
panic("invalid default.vlan_id in the config file")
}
return &Device{
session: s,
ports: make(map[uint32]*Port),
flowCache: newFlowCache(5 * time.Second),
vlanID: uint16(vlanID),
}
}
func (r *Device) String() string {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
v := fmt.Sprintf("Device ID=%v, Descriptions=%+v, Features=%+v, # of ports=%v, FlowTableID=%v, Connected=%v\n", r.id, r.descriptions, r.features, len(r.ports), r.flowTableID, !r.closed)
for _, p := range r.ports {
v += fmt.Sprintf("\t%v\n", p.String())
}
return v
}
func (r *Device) ID() string {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.id
}
func (r *Device) setID(id string) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.id = id
}
func (r *Device) isReady() bool {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return len(r.id) > 0
}
func (r *Device) Factory() openflow.Factory {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.factory
}
func (r *Device) setFactory(f openflow.Factory) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if f == nil {
panic("Factory is nil")
}
r.factory = f
}
func (r *Device) Writer() transceiver.Writer {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.session
}
func (r *Device) Descriptions() Descriptions {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.descriptions
}
func (r *Device) setDescriptions(d Descriptions) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.descriptions = d
}
func (r *Device) Features() Features {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.features
}
func (r *Device) setFeatures(f Features) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.features = f
}
// Port may return nil if there is no port whose number is num
func (r *Device) Port(num uint32) *Port {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.ports[num]
}
func (r *Device) Ports() []*Port {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
p := make([]*Port, 0)
for _, v := range r.ports {
p = append(p, v)
}
return p
}
func (r *Device) setPort(num uint32, p openflow.Port) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if p == nil {
panic("Port is nil")
}
logger.Debugf("Device=%v, PortNum=%v, AdminUp=%v, LinkUp=%v", r.id, p.Number(), !p.IsPortDown(), !p.IsLinkDown())
port, ok := r.ports[num]
if ok {
port.SetValue(p)
} else {
v := NewPort(r, num)
v.SetValue(p)
r.ports[num] = v
}
}
func (r *Device) FlowTableID() uint8 {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.flowTableID
}
func (r *Device) setFlowTableID(id uint8) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.flowTableID = id
}
func (r *Device) SendMessage(msg encoding.BinaryMarshaler) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if msg == nil {
panic("Message is nil")
}
if r.closed {
return ErrClosedDevice
}
return r.session.Write(msg)
}
func (r *Device) IsClosed() bool {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.closed
}
// SetFlow installs a normal flow entry for packet switching and routing into the switch device.
func (r *Device) SetFlow(match openflow.Match, port openflow.OutPort) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
// Set the default VLAN ID. It is necessary to use the L2 MAC flow table of Dell SXXX switches.
match.SetVLANID(r.vlanID)
action, err := r.factory.NewAction()
if err != nil {
return err
}
action.SetOutPort(port)
inst, err := r.factory.NewInstruction()
if err != nil {
return err
}
inst.ApplyAction(action)
// For valid (non-overlapping) ADD requests, or those with no overlap checking,
// the switch must insert the flow entry at the lowest numbered table for which
// the switch supports all wildcards set in the flow_match struct, and for which
// the priority would be observed during the matching process. If a flow entry
// with identical header fields and priority already resides in any table, then
// that entry, including its counters, must be removed, and the new flow entry added.
flow, err := r.factory.NewFlowMod(openflow.FlowAdd)
if err != nil {
return err
}
flow.SetTableID(r.flowTableID)
// This idle timeout is actually useless because we update the installed flows
// more frequently than this timeout.
flow.SetIdleTimeout(90)
flow.SetPriority(10)
flow.SetFlowMatch(match)
flow.SetFlowInstruction(inst)
ok, err := r.flowCache.InProgress(match, port)
if err != nil {
return err
}
if ok {
logger.Debugf("skip to install a new flow: already installed one: deviceID=%v", r.id)
return nil
}
// Install the new flow.
if err := r.session.Write(flow); err != nil {
return err
}
if err := r.flowCache.Add(match, port); err != nil {
return err
}
barrier, err := r.factory.NewBarrierRequest()
if err != nil {
return err
}
return r.session.Write(barrier)
}
// RemoveFlows removes all the normal flows except special ones for table miss and ARP packets.
func (r *Device) RemoveFlows() error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
match, err := r.factory.NewMatch()
if err != nil {
return err
}
// Default VLAN ID specified for the normal flows.
match.SetVLANID(r.vlanID)
// Set output port to OFPP_NONE
port := openflow.NewOutPort()
port.SetNone()
flowmod, err := r.factory.NewFlowMod(openflow.FlowDelete)
if err != nil {
return err
}
// Remove all the normal flows, except the special table miss and ARP flows whose MSB is 1.
flowmod.SetCookieMask(0x1 << 63)
flowmod.SetTableID(0xFF) // ALL
flowmod.SetFlowMatch(match)
flowmod.SetOutPort(port)
if err := r.session.Write(flowmod); err != nil {
return err
}
r.flowCache.RemoveAll()
return nil
}
// TODO:
// Remove the flow caches that match the removed flows. This is not a critical
// issue, but same flows cannot be installed until the caches are expired.
func (r *Device) RemoveFlow(match openflow.Match, port openflow.OutPort) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
// Default VLAN ID specified for the normal flows.
match.SetVLANID(r.vlanID)
flowmod, err := r.factory.NewFlowMod(openflow.FlowDelete)
if err != nil {
return err
}
// Remove all the normal flows, except the table miss and ARP flows whose MSB is 1.
flowmod.SetCookieMask(0x1 << 63)
flowmod.SetTableID(0xFF) // ALL
flowmod.SetFlowMatch(match)
flowmod.SetOutPort(port)
return r.session.Write(flowmod)
}
// TODO:
// Remove the flow caches that match the removed flows. This is not a critical
// issue, but same flows cannot be installed until the caches are expired.
func (r *Device) RemoveFlowByMAC(mac net.HardwareAddr) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
match, err := r.factory.NewMatch()
if err != nil {
return err
}
// Default VLAN ID specified for the normal flows.
match.SetVLANID(r.vlanID)
match.SetDstMAC(mac)
port := openflow.NewOutPort()
port.SetNone()
flowmod, err := r.factory.NewFlowMod(openflow.FlowDelete)
if err != nil {
return err
}
// Remove all the normal flows, except the table miss and ARP flows whose MSB is 1.
flowmod.SetCookieMask(0x1 << 63)
flowmod.SetTableID(0xFF) // ALL
flowmod.SetFlowMatch(match)
flowmod.SetOutPort(port)
return r.session.Write(flowmod)
}
// NullMAC is a random local MAC address, which does not belong to any host, to disconnect a host from the network.
var NullMAC = net.HardwareAddr([]byte{0x06, 0xff, 0x01, 0x21, 0x09, 0x03})
func (r *Device) SendARPAnnouncement(ip net.IP, mac net.HardwareAddr) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
announcement, err := newARPRequestFrame(mac, ip, ip)
if err != nil {
return err
}
return r.flood(nil, announcement)
}
func (r *Device) SendARPDiscovery(sha net.HardwareAddr, spa, tpa net.IP) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
probe, err := newARPRequestFrame(sha, spa, tpa)
if err != nil {
return err
}
return r.flood(nil, probe)
}
func newARPRequestFrame(sha net.HardwareAddr, spa, tpa net.IP) ([]byte, error) {
arp := protocol.NewARPRequest(sha, net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0}), spa, tpa)
req, err := arp.MarshalBinary()
if err != nil {
return nil, err
}
eth := protocol.Ethernet{
SrcMAC: sha,
DstMAC: net.HardwareAddr([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}),
Type: 0x0806,
Payload: req,
}
return eth.MarshalBinary()
}
// Flood broadcasts the packet to all ports of this device, except the ingress port if ingress is not nil.
func (r *Device) Flood(ingress *Port, packet []byte) error {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if r.closed {
return ErrClosedDevice
}
return r.flood(ingress, packet)
}
// flood broadcasts the packet to all ports of this device, except the ingress port if ingress is not nil.
func (r *Device) flood(ingress *Port, packet []byte) error {
inPort := openflow.NewInPort()
if ingress != nil {
inPort.SetValue(ingress.Number())
} else {
inPort.SetController()
}
outPort := openflow.NewOutPort()
// FLOOD means all ports except the ingress one.
outPort.SetFlood()
action, err := r.factory.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
out, err := r.factory.NewPacketOut()
if err != nil {
return err
}
out.SetInPort(inPort)
out.SetAction(action)
out.SetData(packet)
return r.session.Write(out)
}
func (r *Device) Close() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.closed = true
}
================================================
FILE: network/error.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
type networkErr struct {
temporary bool
err error
}
func (r *networkErr) Error() string {
return r.err.Error()
}
func (r *networkErr) Temporary() bool {
return r.temporary
}
================================================
FILE: network/flow_cache.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"fmt"
"time"
"github.com/superkkt/cherry/openflow"
lru "github.com/hashicorp/golang-lru"
)
type flowCache struct {
cache *lru.Cache
expiration time.Duration
}
func newFlowCache(expiration time.Duration) *flowCache {
c, err := lru.New(8192)
if err != nil {
panic(fmt.Sprintf("failed to init a LRU flow cache: %v", err))
}
return &flowCache{
cache: c,
expiration: expiration,
}
}
func (r *flowCache) Add(match openflow.Match, port openflow.OutPort) error {
key, err := r.key(match, port)
if err != nil {
return err
}
t := time.Now()
// Update if the key already exists.
r.cache.Add(key, t)
logger.Debugf("added a new flow cache: key=%v, timestamp=%v", key, t)
return nil
}
func (r *flowCache) key(match openflow.Match, port openflow.OutPort) (string, error) {
m, err := match.MarshalBinary()
if err != nil {
return "", err
}
return fmt.Sprintf("%v/%v", m, port), nil
}
func (r *flowCache) InProgress(match openflow.Match, port openflow.OutPort) (ok bool, err error) {
key, err := r.key(match, port)
if err != nil {
return false, err
}
v, ok := r.cache.Get(key)
if !ok {
return false, nil
}
timestamp := v.(time.Time)
// Timeout?
if time.Since(timestamp) > r.expiration {
r.cache.Remove(key)
logger.Debugf("removed the timed-out flow cache: key=%v", key)
return false, nil
}
return true, nil
}
func (r *flowCache) RemoveAll() {
r.cache.Purge()
logger.Debug("removed all the flow caches")
}
================================================
FILE: network/link.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"fmt"
"sort"
"github.com/superkkt/cherry/graph"
)
type link struct {
ports [2]*Port
}
func newLink(ports [2]*Port) *link {
return &link{
ports: ports,
}
}
func (r *link) ID() string {
s := []string{r.ports[0].ID(), r.ports[1].ID()}
sort.Strings(s)
return fmt.Sprintf("%v/%v", s[0], s[1])
}
func (r *link) Points() [2]graph.Point {
return [2]graph.Point{r.ports[0], r.ports[1]}
}
func (r *link) Weight() float64 {
// TODO: Calculate weight dynamically based on the link speed among these two ports
return 0
}
================================================
FILE: network/node.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"fmt"
"net"
)
type Node struct {
port *Port
mac net.HardwareAddr
}
func NewNode(p *Port, mac net.HardwareAddr) *Node {
return &Node{
port: p,
mac: mac,
}
}
func (r *Node) String() string {
return fmt.Sprintf("Node Port=(%v), MAC=%v", r.port, r.mac)
}
func (r *Node) Port() *Port {
return r.port
}
func (r *Node) MAC() net.HardwareAddr {
return r.mac
}
================================================
FILE: network/of10_session.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/openflow/of10"
"github.com/superkkt/cherry/openflow/transceiver"
"github.com/pkg/errors"
)
type of10Session struct {
device *Device
// True after we get the first barrier reply that means all the previously
// installed flows on the device have been removed, and then the ACL flow for
// ARP packes has been installed.
checkpoint bool
}
func newOF10Session(d *Device) *of10Session {
return &of10Session{
device: d,
}
}
func (r *of10Session) OnHello(f openflow.Factory, w transceiver.Writer, v openflow.Hello) error {
if err := sendHello(f, w); err != nil {
return errors.Wrap(err, "failed to send HELLO")
}
if err := sendSetConfig(f, w); err != nil {
return errors.Wrap(err, "failed to send SET_CONFIG")
}
if err := sendRemoveAllFlows(f, w); err != nil {
return errors.Wrap(err, "failed to send FLOW_MOD to remove all flows")
}
if err := setTemporaryDrop(f, w); err != nil {
return errors.Wrap(err, "failed to set the temporary drop rule")
}
if err := setARPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the ARP sender")
}
if err := setLLDPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the LLDP sender")
}
if err := setDHCPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the DHCP sender")
}
if err := sendBarrierRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send BARRIER_REQUEST")
}
return nil
}
func (r *of10Session) OnBarrierReply(f openflow.Factory, w transceiver.Writer, v openflow.BarrierReply) error {
if r.checkpoint {
logger.Debugf("ignore the barrier reply: DPID=%v", r.device.ID())
// Do nothing if this session has been already negotiated.
return nil
}
if err := sendDescriptionRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send DESCRIPTION_REQUEST")
}
if err := sendFeaturesRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send FEATURE_REQUEST")
}
r.checkpoint = true
return nil
}
func (r *of10Session) OnError(f openflow.Factory, w transceiver.Writer, v openflow.Error) error {
return nil
}
func (r *of10Session) OnFeaturesReply(f openflow.Factory, w transceiver.Writer, v openflow.FeaturesReply) error {
ports := v.Ports()
for _, p := range ports {
logger.Debugf("PortNum=%v, AdminUp=%v, LinkUp=%v", p.Number(), !p.IsPortDown(), !p.IsLinkDown())
if p.Number() > of10.OFPP_MAX {
logger.Debugf("invalid port number: %v", p.Number())
continue
}
r.device.setPort(p.Number(), p)
if !p.IsPortDown() && !p.IsLinkDown() {
// Send LLDP to update network topology
if err := sendLLDP(r.device, p); err != nil {
logger.Errorf("failed to send LLDP: %v", err)
continue
}
logger.Debugf("sent a LLDP packet to %v:%v", r.device.ID(), p.Number())
}
}
return nil
}
func (r *of10Session) OnGetConfigReply(f openflow.Factory, w transceiver.Writer, v openflow.GetConfigReply) error {
return nil
}
func (r *of10Session) OnDescReply(f openflow.Factory, w transceiver.Writer, v openflow.DescReply) error {
return nil
}
func (r *of10Session) OnPortDescReply(f openflow.Factory, w transceiver.Writer, v openflow.PortDescReply) error {
// Do nothing because OpenFlow 1.0 uses FeaturesReply instead of PortDescReply.
return nil
}
func (r *of10Session) OnPortStatus(f openflow.Factory, w transceiver.Writer, v openflow.PortStatus) error {
return nil
}
func (r *of10Session) OnFlowRemoved(f openflow.Factory, w transceiver.Writer, v openflow.FlowRemoved) error {
return nil
}
func (r *of10Session) OnPacketIn(f openflow.Factory, w transceiver.Writer, v openflow.PacketIn) error {
return nil
}
================================================
FILE: network/of13_session.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"strings"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/openflow/of13"
"github.com/superkkt/cherry/openflow/transceiver"
"github.com/pkg/errors"
)
type of13Session struct {
device *Device
// True after we get the first barrier reply that means all the previously
// installed flows on the device have been removed, and then the ACL flow for
// ARP packes has been installed.
checkpoint bool
}
func newOF13Session(d *Device) *of13Session {
return &of13Session{
device: d,
}
}
func (r *of13Session) OnHello(f openflow.Factory, w transceiver.Writer, v openflow.Hello) error {
if err := sendHello(f, w); err != nil {
return errors.Wrap(err, "failed to send HELLO")
}
if err := sendSetConfig(f, w); err != nil {
return errors.Wrap(err, "failed to send SET_CONFIG")
}
if err := sendRemoveAllFlows(f, w); err != nil {
return errors.Wrap(err, "failed to send FLOW_MOD to remove all flows")
}
if err := setTemporaryDrop(f, w); err != nil {
return errors.Wrap(err, "failed to set the temporary drop rule")
}
if err := setARPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the ARP sender")
}
if err := setLLDPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the LLDP sender")
}
if err := setDHCPSender(f, w); err != nil {
return errors.Wrap(err, "failed to set the DHCP sender")
}
if err := sendBarrierRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send BARRIER_REQUEST")
}
return nil
}
func (r *of13Session) OnBarrierReply(f openflow.Factory, w transceiver.Writer, v openflow.BarrierReply) error {
if r.checkpoint {
logger.Debugf("ignore the barrier reply: DPID=%v", r.device.ID())
// Do nothing if this session has been already negotiated.
return nil
}
if err := sendFeaturesRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send FEATURE_REQUEST")
}
r.checkpoint = true
return nil
}
func (r *of13Session) OnError(f openflow.Factory, w transceiver.Writer, v openflow.Error) error {
return nil
}
func (r *of13Session) OnFeaturesReply(f openflow.Factory, w transceiver.Writer, v openflow.FeaturesReply) error {
if err := sendDescriptionRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send DESCRIPTION_REQUEST")
}
return nil
}
func (r *of13Session) OnGetConfigReply(f openflow.Factory, w transceiver.Writer, v openflow.GetConfigReply) error {
return nil
}
func isHP2920_24G(msg openflow.DescReply) bool {
return strings.HasPrefix(msg.Manufacturer(), "HP") && strings.HasPrefix(msg.Hardware(), "2920-24G")
}
func isAS460054_T(msg openflow.DescReply) bool {
return strings.Contains(msg.Hardware(), "AS4600-54T")
}
func (r *of13Session) setTableMiss(f openflow.Factory, w transceiver.Writer, tableID uint8, inst openflow.Instruction) error {
match, err := f.NewMatch() // Wildcard
if err != nil {
return err
}
msg, err := f.NewFlowMod(openflow.FlowAdd)
if err != nil {
return err
}
// We use MSB to represent whether the flow is table miss or not
msg.SetCookie(0x1 << 63)
msg.SetTableID(tableID)
// Permanent flow entry
msg.SetIdleTimeout(0)
msg.SetHardTimeout(0)
// Table-miss entry should have zero priority
msg.SetPriority(0)
msg.SetFlowMatch(match)
msg.SetFlowInstruction(inst)
return w.Write(msg)
}
func (r *of13Session) setHP2920TableMiss(f openflow.Factory, w transceiver.Writer) error {
// Table-100 is a hardware table, and Table-200 is a software table
// that has very low performance.
inst, err := f.NewInstruction()
if err != nil {
return err
}
// 0 -> 100
inst.GotoTable(100)
if err := r.setTableMiss(f, w, 0, inst); err != nil {
return errors.Wrap(err, "failed to set table_miss flow entry")
}
// 100 -> 200
inst.GotoTable(200)
if err := r.setTableMiss(f, w, 100, inst); err != nil {
return errors.Wrap(err, "failed to set table_miss flow entry")
}
// 200 -> Controller
outPort := openflow.NewOutPort()
outPort.SetController()
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
inst.ApplyAction(action)
if err := r.setTableMiss(f, w, 200, inst); err != nil {
return errors.Wrap(err, "failed to set table_miss flow entry")
}
r.device.setFlowTableID(200)
return nil
}
func (r *of13Session) setAS4600TableMiss(f openflow.Factory, w transceiver.Writer) error {
// FIXME:
// AS460054-T gives an error (type=5, code=1) that means TABLE_FULL
// when we install a table-miss flow on Table-0 after we delete all
// flows already installed from the switch. Is this a bug of this switch??
return nil
}
func (r *of13Session) setDefaultTableMiss(f openflow.Factory, w transceiver.Writer) error {
inst, err := f.NewInstruction()
if err != nil {
return err
}
// 0 -> Controller
outPort := openflow.NewOutPort()
outPort.SetController()
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
inst.ApplyAction(action)
if err := r.setTableMiss(f, w, 0, inst); err != nil {
return errors.Wrap(err, "failed to set table_miss flow entry")
}
r.device.setFlowTableID(0)
return nil
}
func (r *of13Session) OnDescReply(f openflow.Factory, w transceiver.Writer, v openflow.DescReply) error {
var err error
// FIXME:
// Implement general routines for various table structures of OF1.3 switches
// based on table features reply
switch {
case isHP2920_24G(v):
err = r.setHP2920TableMiss(f, w)
case isAS460054_T(v):
err = r.setAS4600TableMiss(f, w)
default:
err = r.setDefaultTableMiss(f, w)
}
if err != nil {
return err
}
if err := sendPortDescriptionRequest(f, w); err != nil {
return errors.Wrap(err, "failed to send DESCRIPTION_REQUEST")
}
return nil
}
func (r *of13Session) OnPortDescReply(f openflow.Factory, w transceiver.Writer, v openflow.PortDescReply) error {
ports := v.Ports()
for _, p := range ports {
logger.Debugf("PortNum=%v, AdminUp=%v, LinkUp=%v", p.Number(), !p.IsPortDown(), !p.IsLinkDown())
if p.Number() > of13.OFPP_MAX {
logger.Debugf("invalid port number: %v", p.Number())
continue
}
r.device.setPort(p.Number(), p)
if !p.IsPortDown() && !p.IsLinkDown() {
// Send LLDP to update network topology
if err := sendLLDP(r.device, p); err != nil {
logger.Errorf("failed to send LLDP: %v", err)
continue
}
logger.Debugf("sent a LLDP packet to %v:%v", r.device.ID(), p.Number())
}
}
return nil
}
func (r *of13Session) OnPortStatus(f openflow.Factory, w transceiver.Writer, v openflow.PortStatus) error {
return nil
}
func (r *of13Session) OnFlowRemoved(f openflow.Factory, w transceiver.Writer, v openflow.FlowRemoved) error {
return nil
}
func (r *of13Session) OnPacketIn(f openflow.Factory, w transceiver.Writer, v openflow.PacketIn) error {
return nil
}
================================================
FILE: network/port.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"fmt"
"sync"
"github.com/superkkt/cherry/graph"
"github.com/superkkt/cherry/openflow"
)
// Port represents a switch port and also implements the graph.Point interface.
type Port struct {
mutex sync.RWMutex
device *Device
number uint32
value openflow.Port
}
func NewPort(d *Device, num uint32) *Port {
return &Port{
device: d,
number: num,
}
}
func (r *Port) String() string {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return fmt.Sprintf("Port Number=%v, Device_id=%v, AdminUp=%v, LinkUp=%v", r.number, r.device.ID(), !r.value.IsPortDown(), !r.value.IsLinkDown())
}
func (r *Port) ID() string {
return fmt.Sprintf("%v:%v", r.device.ID(), r.number)
}
func (r *Port) Vertex() graph.Vertex {
return r.device
}
func (r *Port) Device() *Device {
return r.device
}
func (r *Port) Number() uint32 {
return r.number
}
func (r *Port) Value() openflow.Port {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.value
}
func (r *Port) SetValue(p openflow.Port) {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.value = p
}
================================================
FILE: network/reserve.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"encoding/binary"
"fmt"
"net"
)
// ReservedIP returns the IP address reserved for the network.
func ReservedIP(n net.IPNet) (net.IP, error) {
if n.IP == nil || n.Mask == nil {
return nil, fmt.Errorf("invalid IP network: %v", n)
}
ip := n.IP.Mask(n.Mask).To4()
if ip == nil {
return nil, fmt.Errorf("invalid IPv4 address: %v", n)
}
ones, bits := n.Mask.Size()
if (ones == 0 && bits == 0) || bits <= ones || bits != 32 || ones > 30 {
return nil, fmt.Errorf("invalid IP mask: %v", n)
}
if ones == 24 {
// This is a workaround because x.x.x.254 IP addresses are already in use.
return net.IPv4(ip[0], ip[1], ip[2], 188), nil
}
// Use the final IP address of the network.
v := binary.BigEndian.Uint32(ip)
h := uint32(1<
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"net"
"testing"
)
func TestReservedIP(t *testing.T) {
src := []struct {
Network net.IPNet
Reserved net.IP
ErrorExpected bool
}{
{
Network: net.IPNet{IP: net.IPv4(223, 130, 121, 0), Mask: net.CIDRMask(24, 32)},
Reserved: net.IPv4(223, 130, 121, 188),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: net.IPv4(192, 168, 126, 100), Mask: net.CIDRMask(24, 32)},
Reserved: net.IPv4(192, 168, 126, 188),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: net.IPv4(192, 168, 126, 100), Mask: net.CIDRMask(25, 32)},
Reserved: net.IPv4(192, 168, 126, 126),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: net.IPv4(192, 168, 126, 250), Mask: net.CIDRMask(25, 32)},
Reserved: net.IPv4(192, 168, 126, 254),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: net.IPv4(10, 0, 0, 1), Mask: net.CIDRMask(8, 32)},
Reserved: net.IPv4(10, 255, 255, 254),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: net.IPv4(10, 0, 0, 1), Mask: net.CIDRMask(30, 32)},
Reserved: net.IPv4(10, 0, 0, 2),
ErrorExpected: false,
},
{
Network: net.IPNet{IP: nil, Mask: net.CIDRMask(24, 32)},
ErrorExpected: true,
},
{
Network: net.IPNet{IP: net.IPv4(10, 0, 0, 1), Mask: nil},
ErrorExpected: true,
},
{
Network: net.IPNet{IP: nil, Mask: nil},
ErrorExpected: true,
},
{
Network: net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(24, 64)},
ErrorExpected: true,
},
{
Network: net.IPNet{IP: net.IPv4(10, 0, 0, 1), Mask: net.CIDRMask(31, 32)},
ErrorExpected: true,
},
}
for _, v := range src {
ip, err := ReservedIP(v.Network)
if v.ErrorExpected == false && err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v.ErrorExpected == true && err == nil {
t.Fatal("expected error, but no error returns")
}
if ip.Equal(v.Reserved) == false {
t.Fatalf("unexpected reserved IP address: expected=%v, actual=%v", v.Reserved, ip)
}
}
}
================================================
FILE: network/session.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"bytes"
"context"
"encoding"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/openflow/of10"
"github.com/superkkt/cherry/openflow/of13"
"github.com/superkkt/cherry/openflow/transceiver"
"github.com/superkkt/cherry/protocol"
)
var (
errNotNegotiated = errors.New("invalid command on non-negotiated session")
)
const (
deviceExplorerInterval = 1 * time.Minute
)
type session struct {
negotiated bool
device *Device
transceiver *transceiver.Transceiver
handler transceiver.Handler
watcher watcher
finder Finder
listener ControllerEventListener
}
type sessionConfig struct {
conn net.Conn
watcher watcher
finder Finder
listener ControllerEventListener
}
func checkParam(c sessionConfig) {
if c.conn == nil {
panic("Conn is nil")
}
if c.watcher == nil {
panic("Watcher is nil")
}
if c.finder == nil {
panic("Finder is nil")
}
if c.listener == nil {
panic("Listener is nil")
}
}
func newSession(c sessionConfig) *session {
checkParam(c)
stream := transceiver.NewStream(c.conn, 0xFFFF)
v := new(session)
v.watcher = c.watcher
v.finder = c.finder
v.listener = c.listener
v.device = newDevice(v)
v.transceiver = transceiver.NewTransceiver(stream, v)
return v
}
func (r *session) OnHello(f openflow.Factory, w transceiver.Writer, v openflow.Hello) error {
logger.Debugf("HELLO (ver=%v) is received", v.Version())
// Ignore duplicated HELLO messages
if r.negotiated {
return nil
}
switch v.Version() {
case openflow.OF10_VERSION:
r.handler = newOF10Session(r.device)
case openflow.OF13_VERSION:
r.handler = newOF13Session(r.device)
default:
return fmt.Errorf("unsupported OpenFlow version: %v", v.Version())
}
r.device.setFactory(f)
r.negotiated = true
return r.handler.OnHello(f, w, v)
}
func (r *session) OnError(f openflow.Factory, w transceiver.Writer, v openflow.Error) error {
// Is this the CHECK_OVERLAP error?
if v.Class() == 3 && v.Code() == 1 {
// Ignore this CHECK_OVERLAP error
logger.Debug("FLOW_MOD is overlapped")
return nil
}
logger.Errorf("ERROR (DPID=%v, class=%v, code=%v, data=%v)", r.device.ID(), v.Class(), v.Code(), v.Data())
if !r.negotiated {
return errNotNegotiated
}
return r.handler.OnError(f, w, v)
}
func (r *session) OnFeaturesReply(f openflow.Factory, w transceiver.Writer, v openflow.FeaturesReply) error {
logger.Debugf("FEATURES_REPLY (DPID=%v, NumBufs=%v, NumTables=%v)", v.DPID(), v.NumBuffers(), v.NumTables())
if !r.negotiated {
return errNotNegotiated
}
// First FeaturesReply packet?
if r.device.isReady() {
// No, the device already has been initialized that means this is not the first
// FeaturesReply packet. This additional FeaturesReply packet is raised by our
// device explorer. So, we have to skip the following device initialization routine.
logger.Debug("received FEATURES_REPLY that is a response for our device explorer's probe")
return r.handler.OnFeaturesReply(f, w, v)
}
// We got a first FeaturesReply packet! Let's initialize this device.
dpid := strconv.FormatUint(v.DPID(), 10)
// Already connected device?
if r.finder.Device(dpid) != nil {
return errors.New("duplicated device DPID (aux. connection is not supported yet)")
}
r.device.setID(dpid)
logger.Infof("device is ready: DPID=%v, Description=%+v", dpid, r.device.Descriptions())
// We assume a device is up after setting its DPID
if err := r.listener.OnDeviceUp(r.finder, r.device); err != nil {
return err
}
r.watcher.DeviceAdded(r.device)
features := Features{
DPID: v.DPID(),
NumBuffers: v.NumBuffers(),
NumTables: v.NumTables(),
}
r.device.setFeatures(features)
return r.handler.OnFeaturesReply(f, w, v)
}
func (r *session) OnGetConfigReply(f openflow.Factory, w transceiver.Writer, v openflow.GetConfigReply) error {
logger.Debug("GET_CONFIG_REPLY is received")
if !r.negotiated {
return errNotNegotiated
}
return r.handler.OnGetConfigReply(f, w, v)
}
func (r *session) OnDescReply(f openflow.Factory, w transceiver.Writer, v openflow.DescReply) error {
logger.Debug("DESC_REPLY is received")
if !r.negotiated {
return errNotNegotiated
}
logger.Debugf("Manufacturer=%v, Hardware=%v, Software=%v, Serial=%v, Description=%v", v.Manufacturer(), v.Hardware(), v.Software(), v.Serial(), v.Description())
desc := Descriptions{
Manufacturer: v.Manufacturer(),
Hardware: v.Hardware(),
Software: v.Software(),
Serial: v.Serial(),
Description: v.Description(),
}
r.device.setDescriptions(desc)
return r.handler.OnDescReply(f, w, v)
}
func (r *session) OnPortDescReply(f openflow.Factory, w transceiver.Writer, v openflow.PortDescReply) error {
logger.Debugf("PORT_DESC_REPLY is received (# of ports=%v)", len(v.Ports()))
if !r.negotiated {
return errNotNegotiated
}
return r.handler.OnPortDescReply(f, w, v)
}
func newLLDPEtherFrame(deviceID string, port openflow.Port) ([]byte, error) {
lldp := &protocol.LLDP{
ChassisID: protocol.LLDPChassisID{
SubType: 7, // Locally assigned alpha-numeric string
Data: []byte(deviceID),
},
PortID: protocol.LLDPPortID{
SubType: 5, // Interface Name
Data: []byte(fmt.Sprintf("cherry/%v", port.Number())),
},
TTL: 120,
}
payload, err := lldp.MarshalBinary()
if err != nil {
return nil, err
}
ethernet := &protocol.Ethernet{
SrcMAC: port.MAC(),
// LLDP multicast MAC address
DstMAC: []byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E},
// LLDP ethertype
Type: 0x88CC,
Payload: payload,
}
frame, err := ethernet.MarshalBinary()
if err != nil {
return nil, err
}
return frame, nil
}
func sendLLDP(device *Device, p openflow.Port) error {
lldp, err := newLLDPEtherFrame(device.ID(), p)
if err != nil {
return err
}
outPort := openflow.NewOutPort()
outPort.SetValue(p.Number())
// Packet out to the port
action, err := device.Factory().NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
out, err := device.Factory().NewPacketOut()
if err != nil {
return err
}
// From controller
out.SetInPort(openflow.NewInPort())
out.SetAction(action)
out.SetData(lldp)
return device.SendMessage(out)
}
func (r *session) sendPortEvent(portNum uint32, up bool) {
port := r.device.Port(portNum)
if port == nil {
return
}
if up {
if err := r.listener.OnPortUp(r.finder, port); err != nil {
logger.Errorf("OnPortUp: %v", err)
return
}
} else {
if err := r.listener.OnPortDown(r.finder, port); err != nil {
logger.Errorf("OnPortDown: %v", err)
return
}
}
}
func (r *session) updatePort(v openflow.PortStatus) {
port := v.Port()
switch v.Version() {
case openflow.OF10_VERSION:
if port.Number() > of10.OFPP_MAX {
return
}
case openflow.OF13_VERSION:
if port.Number() > of13.OFPP_MAX {
return
}
default:
panic("unsupported OpenFlow version")
}
r.device.setPort(port.Number(), port)
}
func (r *session) OnPortStatus(f openflow.Factory, w transceiver.Writer, v openflow.PortStatus) error {
logger.Debug("PORT_STATUS is received")
if !r.negotiated {
return errNotNegotiated
}
port := v.Port()
logger.Debugf("Device=%v, PortNum=%v, AdminUp=%v, LinkUp=%v", r.device.ID(), port.Number(), !port.IsPortDown(), !port.IsLinkDown())
r.updatePort(v)
// Send port event
up := !port.IsPortDown() && !port.IsLinkDown()
r.sendPortEvent(port.Number(), up)
// Is this an enabled port?
if up && r.device.isReady() {
// Send LLDP to update network topology
if err := sendLLDP(r.device, port); err != nil {
return err
}
} else {
// Send port removed event
p := r.device.Port(port.Number())
if p != nil {
r.watcher.PortRemoved(p)
}
}
return r.handler.OnPortStatus(f, w, v)
}
func (r *session) OnFlowRemoved(f openflow.Factory, w transceiver.Writer, v openflow.FlowRemoved) error {
logger.Debugf("FLOW_REMOVED is received (cookie=%v)", v.Cookie())
if !r.negotiated {
return errNotNegotiated
}
if err := r.listener.OnFlowRemoved(r.finder, v); err != nil {
logger.Errorf("error on OnFlowRemoved listeners: %v", err)
// Ignore this error and keep go on.
}
return r.handler.OnFlowRemoved(f, w, v)
}
func getEthernet(packet []byte) (*protocol.Ethernet, error) {
eth := new(protocol.Ethernet)
if err := eth.UnmarshalBinary(packet); err != nil {
return nil, err
}
return eth, nil
}
func isLLDP(e *protocol.Ethernet) bool {
return e.Type == 0x88CC
}
func getLLDP(packet []byte) (*protocol.LLDP, error) {
lldp := new(protocol.LLDP)
if err := lldp.UnmarshalBinary(packet); err != nil {
return nil, err
}
return lldp, nil
}
func isCherryLLDP(p *protocol.LLDP) bool {
// We sent a LLDP packet that has ChassisID.SubType=7, PortID.SubType=5,
// and port ID starting with "cherry/".
if p.ChassisID.SubType != 7 || p.ChassisID.Data == nil {
// Do nothing if this packet is not the one we sent
return false
}
if p.PortID.SubType != 5 || p.PortID.Data == nil {
return false
}
if len(p.PortID.Data) <= 7 || !bytes.HasPrefix(p.PortID.Data, []byte("cherry/")) {
return false
}
return true
}
func extractDeviceInfo(p *protocol.LLDP) (deviceID string, portNum uint32, err error) {
if !isCherryLLDP(p) {
return "", 0, errors.New("not found cherry LLDP packet")
}
deviceID = string(p.ChassisID.Data)
// PortID.Data string consists of "cherry/" and port number
num, err := strconv.ParseUint(string(p.PortID.Data[7:]), 10, 32)
if err != nil {
return "", 0, err
}
return deviceID, uint32(num), nil
}
func (r *session) findNeighborPort(deviceID string, portNum uint32) (*Port, error) {
device := r.finder.Device(deviceID)
if device == nil {
return nil, fmt.Errorf("failed to find a neighbor device: deviceID=%v", deviceID)
}
port := device.Port(portNum)
if port == nil {
return nil, fmt.Errorf("failed to find a neighbor port: deviceID=%v, portNum=%v", deviceID, portNum)
}
return port, nil
}
func (r *session) handleLLDP(inPort *Port, ethernet *protocol.Ethernet) error {
lldp, err := getLLDP(ethernet.Payload)
if err != nil {
return err
}
deviceID, portNum, err := extractDeviceInfo(lldp)
if err != nil {
// Do nothing if this packet is not the one we sent
logger.Debug("ignoring a LLDP packet issued by an unknown device")
return nil
}
port, err := r.findNeighborPort(deviceID, portNum)
if err != nil {
// Do nothing if we cannot find neighbor device and its port
logger.Debugf("ignoring a LLDP packet: %v", err)
return nil
}
r.watcher.DeviceLinked([2]*Port{inPort, port})
return nil
}
func (r *session) OnPacketIn(f openflow.Factory, w transceiver.Writer, v openflow.PacketIn) error {
if !r.negotiated {
return errNotNegotiated
}
logger.Debugf("PACKET_IN is received (device=%v, inport=%v, reason=%v, tableID=%v, cookie=%v)",
r.device.ID(), v.InPort(), v.Reason(), v.TableID(), v.Cookie())
// Do nothing if the ingress device is not yet ready.
if r.device.isReady() == false {
logger.Debugf("ignoring PACKET_IN: device is not ready: device=%v, inPort=%v", r.device.ID(), v.InPort())
// Drop the incoming packet.
return nil
}
ethernet, err := getEthernet(v.Data())
if err != nil {
return err
}
logger.Debugf("PACKET_IN ethernet: src=%v, dst=%v, type=%v", ethernet.SrcMAC, ethernet.DstMAC, ethernet.Type)
inPort := r.device.Port(v.InPort())
if inPort == nil {
logger.Errorf("failed to find a port: deviceID=%v, portNum=%v, so ignore PACKET_IN..", r.device.ID(), v.InPort())
return nil
}
// Process LLDP, and then add an edge among two switches. This should be executed
// before checking whether the ingress port is one of STP disabled ports!
if isLLDP(ethernet) {
return r.handleLLDP(inPort, ethernet)
}
// Do nothing if the ingress port is an edge between switches and is disabled by STP.
if r.finder.IsEdge(inPort) && !r.finder.IsEnabledBySTP(inPort) {
logger.Debugf("ignoring PACKET_IN from %v:%v by STP", r.device.ID(), v.InPort())
return nil
}
// Call specific version handler
if err := r.handler.OnPacketIn(f, w, v); err != nil {
return err
}
return r.listener.OnPacketIn(r.finder, inPort, ethernet)
}
func (r *session) OnBarrierReply(f openflow.Factory, w transceiver.Writer, v openflow.BarrierReply) error {
if !r.negotiated {
return errNotNegotiated
}
logger.Debugf("BARRIER_REPLY is received (device=%v)", r.device.ID())
return r.handler.OnBarrierReply(f, w, v)
}
func (r *session) Run(ctx context.Context) {
stopExplorer := r.runDeviceExplorer(ctx)
logger.Debugf("started a new device explorer")
if err := r.transceiver.Run(ctx); err != nil {
logger.Errorf("openflow transceiver is unexpectedly closed: %v", err)
}
logger.Infof("disconnected device (DPID=%v)", r.device.ID())
stopExplorer()
r.transceiver.Close()
r.device.Close()
if r.device.isReady() {
if err := r.listener.OnDeviceDown(r.finder, r.device); err != nil {
logger.Errorf("OnDeviceDown: %v", err)
}
r.watcher.DeviceRemoved(r.device)
}
}
func (r *session) runDeviceExplorer(ctx context.Context) context.CancelFunc {
subCtx, canceller := context.WithCancel(ctx)
go func() {
// Note taht ticker will deliver the first tick after specified duration.
ticker := time.Tick(deviceExplorerInterval)
// Infinite loop.
for {
// Wait the context cancels or the ticker rasises.
select {
case <-subCtx.Done():
logger.Debugf("terminating the device explorer: deviceID=%v", r.device.ID())
return
case <-ticker:
if r.device.isReady() == false {
logger.Debug("skip to execute the device explorer due to incomplete device status")
continue
}
logger.Debugf("executing the device explorer: deviceID=%v", r.device.ID())
// Query switch ports information. LLDP will also be delivered to the ports in the query reply handlers.
switch r.device.Factory().ProtocolVersion() {
case openflow.OF10_VERSION:
// OF10 provides ports information in the FeaturesReply packet.
if err := sendFeaturesRequest(r.device.Factory(), r.device.Writer()); err != nil {
logger.Errorf("failed to send a feature request: %v", err)
continue
}
logger.Debugf("sent a FeaturesRequest packet to %v", r.device.ID())
case openflow.OF13_VERSION:
// OF13 provides ports information in the PortDescriptionReply packet.
if err := sendPortDescriptionRequest(r.device.Factory(), r.device.Writer()); err != nil {
logger.Errorf("failed to send a port description request: %v", err)
continue
}
logger.Debugf("sent a PortDescriptionRequest packet to %v", r.device.ID())
default:
panic(fmt.Sprintf("unexpected OpenFlow protocol version: %v", r.device.Factory().ProtocolVersion()))
}
}
}
}()
return canceller
}
func (r *session) Write(msg encoding.BinaryMarshaler) error {
return r.transceiver.Write(msg)
}
func sendHello(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewHello()
if err != nil {
return err
}
return w.Write(msg)
}
func sendSetConfig(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewSetConfig()
if err != nil {
return err
}
msg.SetFlags(openflow.FragNormal)
msg.SetMissSendLength(0xFFFF)
return w.Write(msg)
}
func sendFeaturesRequest(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewFeaturesRequest()
if err != nil {
return err
}
return w.Write(msg)
}
func sendDescriptionRequest(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewDescRequest()
if err != nil {
return err
}
return w.Write(msg)
}
func sendBarrierRequest(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewBarrierRequest()
if err != nil {
return err
}
return w.Write(msg)
}
func sendPortDescriptionRequest(f openflow.Factory, w transceiver.Writer) error {
msg, err := f.NewPortDescRequest()
if err != nil {
return err
}
return w.Write(msg)
}
// setARPSender installs a flow that sends all ARP packets to the controller.
func setARPSender(f openflow.Factory, w transceiver.Writer) error {
match, err := f.NewMatch()
if err != nil {
return err
}
match.SetEtherType(0x0806 /* ARP */)
// Permanent flow.
return setSpecialFlow(f, w, match, 100, 0, 0, false)
}
// setLLDPSender installs a flow that sends all LLDP packets to the controller.
func setLLDPSender(f openflow.Factory, w transceiver.Writer) error {
match, err := f.NewMatch()
if err != nil {
return err
}
match.SetEtherType(0x88CC /* LLDP */)
// Permanent flow.
return setSpecialFlow(f, w, match, 100, 0, 0, false)
}
// setTemporaryDrop installs a temporary flow that drops all the packets.
func setTemporaryDrop(f openflow.Factory, w transceiver.Writer) error {
// Wildcard to match all packets.
match, err := f.NewMatch()
if err != nil {
return err
}
// Temporary flow that will be removed after a few seconds.
return setSpecialFlow(f, w, match, 50, 0, 5, true)
}
func setDHCPSender(f openflow.Factory, w transceiver.Writer) error {
match, err := f.NewMatch()
if err != nil {
return err
}
match.SetEtherType(0x0800) // IPv4.
match.SetIPProtocol(0x11) // UDP.
match.SetSrcPort(68) // BOOTP Client Port.
match.SetDstPort(67) // BOOTP Server Port.
// Permanent flow.
return setSpecialFlow(f, w, match, 100, 0, 0, false)
}
func setSpecialFlow(f openflow.Factory, w transceiver.Writer, match openflow.Match, priority, idleTimeout, hardTimeout uint16, allDrop bool) error {
flow, err := f.NewFlowMod(openflow.FlowAdd)
if err != nil {
return err
}
flow.SetIdleTimeout(idleTimeout)
flow.SetHardTimeout(hardTimeout)
flow.SetPriority(priority)
flow.SetFlowMatch(match)
// Forward all matched packets to the controller if allDrop is false. Note that
// the matched packet is dropped if no forward actions are present in the flow.
if allDrop == false {
outPort := openflow.NewOutPort()
outPort.SetController()
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
inst, err := f.NewInstruction()
if err != nil {
return err
}
inst.ApplyAction(action)
flow.SetFlowInstruction(inst)
}
return w.Write(flow)
}
// sendRemoveAllFlows removes all the previously installed flows including special
// flows for table-miss and ARP packets.
func sendRemoveAllFlows(f openflow.Factory, w transceiver.Writer) error {
match, err := f.NewMatch() // Wildcard
if err != nil {
return err
}
msg, err := f.NewFlowMod(openflow.FlowDelete)
if err != nil {
return err
}
// Wildcard
msg.SetTableID(0xFF)
msg.SetFlowMatch(match)
return w.Write(msg)
}
================================================
FILE: network/topology.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package network
import (
"bytes"
"fmt"
"net"
"sync"
"time"
"github.com/superkkt/cherry/graph"
"github.com/pkg/errors"
)
type watcher interface {
DeviceAdded(*Device)
DeviceLinked([2]*Port)
DeviceRemoved(*Device)
PortRemoved(*Port)
}
type Finder interface {
Device(id string) *Device
Devices() []*Device
// IsEnabledBySTP returns whether p is disabled by spanning tree protocol
IsEnabledBySTP(p *Port) bool
// IsEdge returns whether p is an edge among two switches
IsEdge(p *Port) bool
Node(mac net.HardwareAddr) (*Node, LocationStatus, error)
Path(srcDeviceID, dstDeviceID string) [][2]*Port
}
type topology struct {
mutex sync.RWMutex
// Key is the device ID
devices map[string]*Device
graph *graph.Graph
listener TopologyEventListener
db database
}
func newTopology(db database) *topology {
v := &topology{
devices: make(map[string]*Device),
graph: graph.New(),
db: db,
}
go v.staleEdgeRemover()
return v
}
func (r *topology) String() string {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%v\n", r.graph))
return buf.String()
}
func (r *topology) setEventListener(l TopologyEventListener) {
r.listener = l
}
// Caller should make sure the mutex is unlocked before calling this function.
// Otherwise, event listeners may cause a deadlock by calling other topology functions.
func (r *topology) sendEvent() {
if r.listener == nil {
return
}
if err := r.listener.OnTopologyChange(r); err != nil {
logger.Errorf("OnTopologyChange: %v", err)
return
}
}
func (r *topology) Devices() []*Device {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
v := make([]*Device, 0)
for _, d := range r.devices {
v = append(v, d)
}
return v
}
// Device may return nil if a device whose ID is id does not exist
func (r *topology) Device(id string) *Device {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.devices[id]
}
func (r *topology) DeviceAdded(d *Device) {
// NOTE: This is an anonymous function (NOT a goroutine!) that has a critical section.
func() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.devices[d.ID()] = d
r.graph.AddVertex(d)
}()
// XXX: Make sure the mutex is unlocked before calling sendEvent().
r.sendEvent()
}
// XXX: Caller should lock the mutex
func (r *topology) removeDevice(d *Device) {
// Remove from the device database
delete(r.devices, d.ID())
}
func (r *topology) DeviceRemoved(d *Device) {
// NOTE: This is an anonymous function (NOT a goroutine!) that has a critical section.
func() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
r.removeDevice(d)
r.graph.RemoveVertex(d)
}()
// XXX: Make sure the mutex is unlocked before calling sendEvent().
r.sendEvent()
}
func (r *topology) DeviceLinked(ports [2]*Port) {
var added bool
var err error
// NOTE: This is an anonymous function (NOT a goroutine!) that has a critical section.
func() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
link := newLink(ports)
added, err = r.graph.AddEdge(link)
if err != nil {
logger.Errorf("failed to add a new graph edge: %v", err)
return
}
}()
// Send the event only if the topology has been changed.
if err == nil && added {
// XXX: Make sure the mutex is unlocked before calling sendEvent().
r.sendEvent()
logger.Infof("devices have been linked: %v:%v / %v:%v", ports[0].Device().ID(), ports[0].Number(), ports[1].Device().ID(), ports[1].Number())
}
}
// Node may return nil if the node is unregistered or still undiscovered.
func (r *topology) Node(mac net.HardwareAddr) (*Node, LocationStatus, error) {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
dpid, portNum, status, err := r.db.Location(mac)
if err != nil {
return nil, status, errors.Wrap(&networkErr{temporary: true, err: err}, "querying host location to the database")
}
if status != LocationDiscovered {
return nil, status, nil
}
device, ok := r.devices[dpid]
if !ok {
return nil, LocationUnregistered, nil
}
port := device.Port(portNum)
if port == nil {
return nil, LocationUnregistered, nil
}
return NewNode(port, mac), LocationDiscovered, nil
}
func (r *topology) PortRemoved(p *Port) {
edge := false
// NOTE: This is an anonymous function (NOT a goroutine!) that has a critical section.
func() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
if edge = r.graph.IsEdge(p); edge == true {
// Remove an edge from the graph if this port is an edge connected to another switch
r.graph.RemoveEdge(p)
}
}()
if edge {
// XXX: Make sure the mutex is unlocked before calling sendEvent().
r.sendEvent()
}
}
func (r *topology) Path(srcDeviceID, dstDeviceID string) [][2]*Port {
// Read lock
r.mutex.RLock()
defer r.mutex.RUnlock()
v := make([][2]*Port, 0)
src := r.devices[srcDeviceID]
dst := r.devices[dstDeviceID]
// Unknown source or destination device?
if src == nil || dst == nil {
// Return empty path
return v
}
path := r.graph.FindPath(src, dst)
for _, p := range path {
device := p.V.(*Device)
link := p.E.(*link)
v = append(v, pickPort(device, link))
}
return v
}
func pickPort(d *Device, l *link) [2]*Port {
p := l.Points()
if p[0].Vertex().ID() == d.ID() {
return [2]*Port{p[0].(*Port), p[1].(*Port)}
}
return [2]*Port{p[1].(*Port), p[0].(*Port)}
}
func (r *topology) IsEdge(p *Port) bool {
return r.graph.IsEdge(p)
}
func (r *topology) IsEnabledBySTP(p *Port) bool {
return r.graph.IsEnabledPoint(p)
}
// staleEdgeRemover removes stale edges that have not been updated for a long time.
func (r *topology) staleEdgeRemover() {
ticker := time.Tick(10 * time.Second)
// Infinite loop.
for range ticker {
var removed bool
// NOTE: This is an anonymous function (NOT a goroutine!) that has a critical section.
func() {
// Write lock
r.mutex.Lock()
defer r.mutex.Unlock()
logger.Debug("trying to remove stale edges from the topology...")
removed = r.graph.RemoveStaleEdges(deviceExplorerInterval * 3)
}()
// Send the event only if the topology has been changed.
if removed {
logger.Debug("removed stale edge(s) from the topology")
// XXX: Make sure the mutex is unlocked before calling sendEvent().
r.sendEvent()
}
}
}
================================================
FILE: northbound/app/announcer/announcer.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
*
* Kitae Kim
* Donam Kim
* Jooyoung Kang
* Changjin Choi
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package announcer
import (
"fmt"
"math/rand"
"net"
"sync"
"time"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("announcer")
)
// Announcer periodically broadcasts ARP announcements to update ARP cache tables on all hosts in the network.
type Announcer struct {
app.BaseProcessor
db database
once sync.Once
}
type database interface {
RenewARPTable() error
GetARPTable() ([]ARPTableEntry, error)
}
type ARPTableEntry struct {
IP net.IP
MAC net.HardwareAddr
}
func New(db database) *Announcer {
return &Announcer{
db: db,
}
}
func (r *Announcer) Init() error {
return r.db.RenewARPTable()
}
func (r *Announcer) Name() string {
return "Announcer"
}
func (r *Announcer) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *Announcer) OnDeviceUp(finder network.Finder, device *network.Device) error {
// Make sure that there is only one broadcaster in this application.
r.once.Do(func() {
// Run the background broadcaster for periodic ARP announcement.
go r.broadcaster(finder)
})
return r.BaseProcessor.OnDeviceUp(finder, device)
}
func (r *Announcer) broadcaster(finder network.Finder) {
logger.Debug("executed ARP announcement broadcaster")
backoff := newBackoff(finder)
ticker := time.Tick(30 * time.Second)
// Infinite loop.
for range ticker {
entries, err := r.db.GetARPTable()
if err != nil {
logger.Errorf("failed to get ARP table entries: %v", err)
continue
}
for _, v := range entries {
logger.Debugf("broadcasting an ARP announcement for a host: IP=%v, MAC=%v", v.IP, v.MAC)
if err := backoff.Broadcast(v.IP, v.MAC); err != nil {
logger.Errorf("failed to broadcast an ARP announcement: %v", err)
continue
}
// Sleep to mitigate the peak latency of processing PACKET_INs.
time.Sleep(time.Duration(10+rand.Intn(100)) * time.Millisecond)
}
}
}
================================================
FILE: northbound/app/announcer/backoff.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package announcer
import (
"bytes"
"math"
"net"
"sync"
"time"
"github.com/superkkt/cherry/network"
lru "github.com/hashicorp/golang-lru"
)
type backoff struct {
finder network.Finder
mutex sync.Mutex
cache *lru.Cache
}
func newBackoff(finder network.Finder) *backoff {
if finder == nil {
panic("nil finder parameter")
}
cache, err := lru.New(16384)
if err != nil {
panic(err)
}
return &backoff{
finder: finder,
cache: cache,
}
}
func (r *backoff) Broadcast(ip net.IP, mac net.HardwareAddr) error {
r.mutex.Lock()
defer r.mutex.Unlock()
return r.getAnnouncer(ip, mac).Broadcast()
}
func (r *backoff) getAnnouncer(ip net.IP, mac net.HardwareAddr) *announcer {
var result *announcer
v, ok := r.cache.Get(ip.String())
if !ok || bytes.Equal(v.(*announcer).MAC, mac) == false {
result = &announcer{
Finder: r.finder,
IP: ip,
MAC: mac,
}
r.cache.Add(ip.String(), result)
} else {
result = v.(*announcer)
}
return result
}
type announcer struct {
Finder network.Finder
IP net.IP
MAC net.HardwareAddr
count uint64
timestamp time.Time
}
func (r *announcer) Broadcast() error {
delay := r.calculateDelay()
if time.Now().Sub(r.timestamp) < delay {
// Do nothing. We need more time.
logger.Debugf("skip to broadcast an ARP announcement for %v until %v", r.IP, r.timestamp.Add(delay))
return nil
}
broadcasted := false
for _, device := range r.Finder.Devices() {
if err := device.SendARPAnnouncement(r.IP, r.MAC); err != nil {
return err
}
logger.Debugf("sent an ARP announcement: DPID=%v, IP=%v, MAC=%v", device.ID(), r.IP, r.MAC)
broadcasted = true
}
if broadcasted {
r.count++
r.timestamp = time.Now()
}
return nil
}
const maxDelay = 1 * time.Hour
func (r *announcer) calculateDelay() time.Duration {
// Overflow?
if float64(r.count) > math.Log2(maxDelay.Seconds()) {
return maxDelay
}
// Exponential delay.
return time.Duration(math.Pow(2, float64(r.count))) * time.Second
}
================================================
FILE: northbound/app/dhcp/dhcp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package dhcp
import (
"encoding/hex"
"fmt"
"net"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/protocol"
"github.com/davecgh/go-spew/spew"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("dhcp")
// 192.0.2.2 is one of the reserved addresses (TEST-NET-1). See RFC 5737.
serverIP = net.IPv4(192, 0, 2, 2)
// A locally administered MAC address (https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local).
serverMAC = net.HardwareAddr([]byte{0x06, 0xff, 0x15, 0x88, 0x67, 0x76})
)
type DHCP struct {
app.BaseProcessor
db Database
}
type Database interface {
// DHCP returns a network configuration matched with the hardware address, otherwise nil.
DHCP(net.HardwareAddr) (*NetConfig, error)
}
type NetConfig struct {
IP net.IP // Host IP address.
Mask net.IPMask // Network mask.
Gateway net.IP // Gateway (router) IP address.
}
func New(db Database) *DHCP {
return &DHCP{
db: db,
}
}
func (r *DHCP) Init() error {
return nil
}
func (r *DHCP) Name() string {
return "DHCP"
}
func (r *DHCP) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *DHCP) OnPacketIn(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) error {
if eth.Type != 0x0800 /* IPv4 */ {
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
ip := new(protocol.IPv4)
if err := ip.UnmarshalBinary(eth.Payload); err != nil {
logger.Debugf("drop an invalid IPv4 packet: %v", err)
return nil
}
if ip.Protocol != 0x11 /* UDP */ {
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
udp := new(protocol.UDP)
if err := udp.UnmarshalBinary(ip.Payload); err != nil {
logger.Debugf("drop an invalid UDP packet: %v", err)
return nil
}
// DHCP client and server ports?
if udp.SrcPort != 68 || udp.DstPort != 67 {
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
dhcp := new(protocol.DHCP)
if err := dhcp.UnmarshalBinary(udp.Payload); err != nil {
logger.Debugf("bypass an invalid DHCP packet: %v", err)
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
logger.Debugf("processing a DHCP packet: ingress=%v, data=%v", ingress.ID(), spew.Sdump(dhcp))
if r.processDHCPPacket(ingress, dhcp) == true {
logger.Infof("bypass the DHCP packet: ingress=%v, data=%v", ingress.ID(), spew.Sdump(dhcp))
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
return nil
}
func (r *DHCP) processDHCPPacket(ingress *network.Port, dhcp *protocol.DHCP) (bypass bool) {
t, ok := dhcp.Option(0x35) // Message type.
if ok == false || len(t.Value) < 1 {
logger.Debugf("unknown DHCP message type: %v", spew.Sdump(dhcp))
return true
}
conf, err := r.db.DHCP(dhcp.CHAddr)
if err != nil {
logger.Errorf("failed to query DHCP client IP address: %v", err)
return true
}
if conf == nil {
logger.Debugf("unknown DHCP MAC address: %v", dhcp.CHAddr)
return true
}
if conf.Gateway.To4() == nil {
logger.Errorf("invalid IPv4 gateway address: %v", conf.Gateway)
return true
}
switch t.Value[0] {
case 0x01: // Discover.
if err := r.processDHCPDiscover(ingress, dhcp, conf); err != nil {
logger.Errorf("failed to process DHCP discover packet: %v", err)
return true
}
case 0x03: // Request.
if err := r.processDHCPRequest(ingress, dhcp, conf); err != nil {
logger.Errorf("failed to process DHCP request packet: %v", err)
return true
}
default:
logger.Debugf("unexpected DHCP message type: %v", t.Value[0])
return true
}
// Consume the DHCP packet and do not forward into other apps.
return false
}
var (
dns = []byte{8, 8, 8, 8, 1, 1, 1, 1, 168, 126, 63, 1} // DNS (8.8.8.8, 1.1.1.1, 168.126.63.1).
leaseTimeSec = []byte{0x00, 0x01, 0x51, 0x80} // IP Address Lease Time (86400 seconds).
broadcastMAC = net.HardwareAddr([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
broadcastIP = net.IPv4(255, 255, 255, 255)
)
func (r *DHCP) processDHCPDiscover(ingress *network.Port, disc *protocol.DHCP, conf *NetConfig) error {
dhcp := &protocol.DHCP{
Op: protocol.DHCPOpcodeReply,
Hops: 0,
XID: disc.XID,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: conf.IP,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: disc.CHAddr,
SName: "",
File: "",
Options: []protocol.DHCPOption{
{Code: 53, Value: []byte{2}}, // Message Type (DHCPOFFER).
{Code: 1, Value: conf.Mask}, // Subnet Mask.
{Code: 3, Value: conf.Gateway.To4()}, // Router.
{Code: 6, Value: dns}, // DNS.
{Code: 51, Value: leaseTimeSec}, // IP Address Lease Time.
{Code: 54, Value: serverIP.To4()}, // Server Identifier.
},
}
reply, err := buildReply(broadcastMAC, broadcastIP, dhcp)
if err != nil {
return err
}
logger.Debugf("sending a DHCPOFFER: %v (packet=%v)", spew.Sdump(dhcp), hex.EncodeToString(reply))
return sendReply(ingress, reply)
}
func (r *DHCP) processDHCPRequest(ingress *network.Port, req *protocol.DHCP, conf *NetConfig) error {
dhcp := &protocol.DHCP{
Op: protocol.DHCPOpcodeReply,
Hops: 0,
XID: req.XID,
Elapsed: 0,
Flags: 0,
CIAddr: req.CIAddr,
YIAddr: conf.IP,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: req.CHAddr,
SName: "",
File: "",
Options: []protocol.DHCPOption{
{Code: 53, Value: []byte{5}}, // Message Type (DHCPACK).
{Code: 1, Value: conf.Mask}, // Subnet Mask.
{Code: 3, Value: conf.Gateway.To4()}, // Router.
{Code: 6, Value: dns}, // DNS.
{Code: 51, Value: leaseTimeSec}, // IP Address Lease Time.
{Code: 54, Value: serverIP.To4()}, // Server Identifier.
},
}
dstMAC := broadcastMAC
dstIP := broadcastIP
// Renewal request?
if req.CIAddr.Equal(net.IPv4zero) == false {
dstMAC = req.CHAddr
dstIP = req.CIAddr
}
reply, err := buildReply(dstMAC, dstIP, dhcp)
if err != nil {
return err
}
logger.Debugf("sending a DHCPACK: %v (packet=%v)", spew.Sdump(dhcp), hex.EncodeToString(reply))
return sendReply(ingress, reply)
}
func buildReply(dstMAC net.HardwareAddr, dstIP net.IP, dhcp *protocol.DHCP) ([]byte, error) {
payload, err := dhcp.MarshalBinary()
if err != nil {
return nil, err
}
udp := &protocol.UDP{
SrcPort: 67,
DstPort: 68,
Length: uint16(len(payload)),
Payload: payload,
}
udp.SetPseudoHeader(serverIP, dstIP)
datagram, err := udp.MarshalBinary()
if err != nil {
return nil, err
}
ip := protocol.NewIPv4(serverIP, dstIP, 0x11, datagram)
packet, err := ip.MarshalBinary()
if err != nil {
return nil, err
}
eth := protocol.Ethernet{
SrcMAC: serverMAC,
DstMAC: dstMAC,
Type: 0x0800,
Payload: packet,
}
return eth.MarshalBinary()
}
func sendReply(ingress *network.Port, packet []byte) error {
f := ingress.Device().Factory()
inPort := openflow.NewInPort()
inPort.SetController()
outPort := openflow.NewOutPort()
outPort.SetValue(ingress.Number())
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
out, err := f.NewPacketOut()
if err != nil {
return err
}
out.SetInPort(inPort)
out.SetAction(action)
out.SetData(packet)
return ingress.Device().SendMessage(out)
}
================================================
FILE: northbound/app/discovery/discovery.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package discovery
import (
"bytes"
"context"
"fmt"
"net"
"strconv"
"sync"
"time"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/cherry/protocol"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("discovery")
// A locally administered MAC address (https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local).
myMAC = net.HardwareAddr([]byte{0x06, 0xff, 0x82, 0x87, 0x29, 0x34})
)
const (
ProbeInterval = 30 * time.Second
)
type processor struct {
app.BaseProcessor
db Database
mutex sync.Mutex
canceller map[string]context.CancelFunc // Key = Device ID.
}
type Database interface {
// GetUndiscoveredHosts returns IP addresses whose physical location is still
// undiscovered or staled more than expiration.
GetUndiscoveredHosts(expiration time.Duration) ([]net.IPNet, error)
// UpdateHostLocation updates the physical location of a host, whose MAC and IP
// addresses are matched with mac and ip, to the port identified by swDPID and
// portNum. updated will be true if its location has been actually updated.
UpdateHostLocation(mac net.HardwareAddr, ip net.IP, swDPID uint64, portNum uint16) (updated bool, err error)
// ResetHostLocationsByPort sets NULL to the host locations that belong to the
// port specified by swDPID and portNum.
ResetHostLocationsByPort(swDPID uint64, portNum uint16) error
// ResetHostLocationsByDevice sets NULL to the host locations that belong to the
// device specified by swDPID.
ResetHostLocationsByDevice(swDPID uint64) error
}
func New(db Database) app.Processor {
return &processor{
db: db,
canceller: make(map[string]context.CancelFunc),
}
}
func (r *processor) Name() string {
return "Discovery"
}
func (r *processor) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *processor) OnDeviceUp(finder network.Finder, device *network.Device) error {
// Make sure that there is only one ARP sender for a device.
r.stopARPSender(device.ID())
r.runARPSender(device)
// Propagate this event to the next processors.
return r.BaseProcessor.OnDeviceUp(finder, device)
}
func (r *processor) runARPSender(device *network.Device) {
r.mutex.Lock()
defer r.mutex.Unlock()
ctx, cancel := context.WithCancel(context.Background())
go func() {
// Infinite loop.
for {
select {
case <-ctx.Done():
logger.Debugf("terminating the ARP sender: deviceID=%v", device.ID())
return
default:
}
if err := r.sendARPDiscovery(device); err != nil {
logger.Errorf("failed to send ARP probes: %v", err)
// Ignore this error and keep go on.
}
// This sleep delay should be shorter than ProbeInterval.
time.Sleep(1500 * time.Millisecond)
}
}()
r.canceller[device.ID()] = cancel
}
func (r *processor) sendARPDiscovery(device *network.Device) error {
if device.IsClosed() {
return fmt.Errorf("already closed deivce: id=%v", device.ID())
}
hosts, err := r.db.GetUndiscoveredHosts(ProbeInterval)
if err != nil {
return err
}
for _, addr := range hosts {
reserved, err := network.ReservedIP(addr)
if err != nil {
return err
}
if err := device.SendARPDiscovery(myMAC, reserved, addr.IP); err != nil {
return err
}
logger.Debugf("sent an ARP probe for %v on %v", addr.IP, device.ID())
// Sleep to mitigate the peak latency of processing PACKET_INs.
time.Sleep(10 * time.Millisecond)
}
return nil
}
func (r *processor) stopARPSender(deviceID string) {
r.mutex.Lock()
defer r.mutex.Unlock()
cancel, ok := r.canceller[deviceID]
if !ok {
return
}
cancel()
delete(r.canceller, deviceID)
}
func (r *processor) OnPacketIn(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) error {
// ARP?
if eth.Type != 0x0806 {
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
arp := new(protocol.ARP)
if err := arp.UnmarshalBinary(eth.Payload); err != nil {
return err
}
logger.Debugf("received ARP packet: %v", arp)
switch arp.Operation {
case 1:
return r.processARPRequest(finder, ingress, eth, arp)
case 2:
return r.processARPReply(finder, ingress, eth, arp)
default:
logger.Debugf("dropping the ARP packet that has invalid operaion code: %v", arp)
// Drop this packet. Do not pass it to the next processors.
return nil
}
}
func (r *processor) processARPRequest(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet, arp *protocol.ARP) error {
// Our ARP probe?
if bytes.Equal(arp.SHA, myMAC) {
// Drop this packet! This packet should not be propagated among switches.
logger.Debugf("dropping our ARP probe that was propagated via an edge among switches: deviceID=%v", ingress.Device().ID())
return nil
} else {
// Propagate this ARP request, wich is raised from a host, to the next processors.
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
}
func (r *processor) processARPReply(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet, arp *protocol.ARP) error {
// The target (not source!) hardware address of the ARP reply packet (or the destination MAC address of the ethernet
// frame) should be equal to the myMAC address if it is a counterpart for our ARP probe.
if bytes.Equal(arp.THA, myMAC) == false && bytes.Equal(eth.DstMAC, myMAC) == false {
logger.Debugf("unexpected ARP reply: %v", arp)
// Drop this packet. Do not pass it to the next processors.
return nil
}
if finder.IsEdge(ingress) {
logger.Debugf("dropping ARP reply received from an edge among switches: ingress=%v, arp=%v", ingress.ID(), arp)
// Drop this packet. Do not pass it to the next processors.
return nil
}
// This ARP reply packet has been processed. Do not pass it to the next processors.
return r.macLearning(finder, ingress, arp)
}
func (r *processor) macLearning(finder network.Finder, ingress *network.Port, arp *protocol.ARP) error {
swDPID, err := strconv.ParseUint(ingress.Device().ID(), 10, 64)
if err != nil {
return fmt.Errorf("invalid device ID: %v", ingress.Device().ID())
}
// Update the host location in the database if SHA and SPA are matched.
updated, err := r.db.UpdateHostLocation(arp.SHA, arp.SPA, swDPID, uint16(ingress.Number()))
if err != nil {
return err
}
// Remove installed flows for this host if the location has been changed.
if updated {
logger.Infof("update host location: IP=%v, MAC=%v, deviceID=%v, portNum=%v", arp.SPA, arp.SHA, swDPID, ingress.Number())
// Remove flows from all devices.
for _, device := range finder.Devices() {
if err := device.RemoveFlowByMAC(arp.SHA); err != nil {
logger.Errorf("failed to remove flows from %v: %v", device.ID(), err)
continue
}
logger.Debugf("removed flows whose destination MAC address is %v on %v", arp.SHA, device.ID())
}
} else {
logger.Debugf("skip to update host location: unknown host or no location change: IP=%v, MAC=%v, deviceID=%v, portNum=%v", arp.SPA, arp.SHA, swDPID, ingress.Number())
}
return nil
}
func (r *processor) OnPortDown(finder network.Finder, port *network.Port) error {
swDPID, err := strconv.ParseUint(port.Device().ID(), 10, 64)
if err != nil {
return fmt.Errorf("invalid device ID: %v", port.Device().ID())
}
// Set NULLs to the host locations that associated with this port so that the
// packets heading to these hosts will be broadcasted until we discover it again.
if err := r.db.ResetHostLocationsByPort(swDPID, uint16(port.Number())); err != nil {
return err
}
// Propagate this event to the next processors.
return r.BaseProcessor.OnPortDown(finder, port)
}
func (r *processor) OnDeviceDown(finder network.Finder, device *network.Device) error {
// Stop the ARP request sender.
r.stopARPSender(device.ID())
swDPID, err := strconv.ParseUint(device.ID(), 10, 64)
if err != nil {
return fmt.Errorf("invalid device ID: %v", device.ID())
}
// Set NULLs to the host locations that belong to this device so that the packets
// heading to these hosts will be broadcasted until we discover them again.
if err := r.db.ResetHostLocationsByDevice(swDPID); err != nil {
return err
}
// Propagate this event to the next processors.
return r.BaseProcessor.OnDeviceDown(finder, device)
}
func (r *processor) OnTopologyChange(finder network.Finder) error {
for _, device := range finder.Devices() {
swDPID, err := strconv.ParseUint(device.ID(), 10, 64)
if err != nil {
return fmt.Errorf("invalid device ID: %v", device.ID())
}
for _, port := range device.Ports() {
if finder.IsEdge(port) == false {
continue
}
if err := r.db.ResetHostLocationsByPort(swDPID, uint16(port.Number())); err != nil {
logger.Errorf("failed to reset host locations by port: DPID=%v, PortNum=%v, err=%v", swDPID, port.Number(), err)
continue
}
logger.Debugf("reset host locations that belong to the edge port: DPID=%v, PortNum=%v", swDPID, port.Number())
}
}
// Propagate this event to the next processors.
return r.BaseProcessor.OnTopologyChange(finder)
}
================================================
FILE: northbound/app/l2switch/storm_controller.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package l2switch
import (
"sync"
"time"
"github.com/superkkt/cherry/network"
"github.com/davecgh/go-spew/spew"
)
type stormController struct {
mutex sync.Mutex
max uint
broadcasts []time.Time
bcaster broadcaster
}
type broadcaster interface {
flood(ingress *network.Port, packet []byte) error
}
// max is the number of broadcasts that are allowed per second.
func newStormController(max uint, bcaster broadcaster) *stormController {
if max <= 0 {
panic("max should be greater than zero")
}
if bcaster == nil {
panic("bcaster is nil")
}
return &stormController{
max: max,
broadcasts: make([]time.Time, 0),
bcaster: bcaster,
}
}
func (r *stormController) broadcast(ingress *network.Port, packet []byte) error {
r.mutex.Lock()
defer r.mutex.Unlock()
t := time.Now()
bcasts := append(r.broadcasts, t)
l := uint(len(bcasts))
if l <= r.max {
r.broadcasts = bcasts
return r.bcaster.flood(ingress, packet)
}
// Only allows r.max broadcasts per 1 second
if t.Sub(bcasts[0]) > 1*time.Second {
// Shrink (l > r.max)
r.broadcasts = bcasts[l-r.max : l]
return r.bcaster.flood(ingress, packet)
}
// Deny! r.broadcast should not be updated!
if ingress != nil && packet != nil {
logger.Infof("too many broadcasts: broadcast is denied to avoid the broadcast storm: ingress=%v, packet=%v", ingress.ID(), spew.Sdump(packet))
} else {
logger.Info("too many broadcasts: broadcast is denied to avoid the broadcast storm")
}
return nil
}
================================================
FILE: northbound/app/l2switch/storm_controller_test.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package l2switch
import (
"fmt"
"testing"
"time"
"github.com/superkkt/cherry/network"
)
func TestStorm(t *testing.T) {
max := uint(100)
dummy := new(dummyFlooder)
storm := newStormController(max, dummy)
fmt.Printf("%v\n", time.Now())
for i := uint(0); i < max; i++ {
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(i+1) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", i+1, dummy.getCounter())
}
}
for i := 0; i < 10; i++ {
fmt.Printf("%v\n", time.Now())
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(max) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", max, dummy.getCounter())
}
}
time.Sleep(1 * time.Second)
fmt.Printf("%v\n", time.Now())
for i := uint(0); i < max-1; i++ {
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(max+i+1) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", max+1, dummy.getCounter())
}
}
}
func TestPeriodicBroadcast(t *testing.T) {
max := uint(1)
dummy := new(dummyFlooder)
storm := newStormController(max, dummy)
for i := 0; i < 10; i++ {
fmt.Printf("Count: %v, Timestamp: %v\n", i, time.Now())
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(i+1) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", i+1, dummy.getCounter())
}
time.Sleep(1 * time.Second)
}
}
func TestPeriodicStorm(t *testing.T) {
max := uint(1)
dummy := new(dummyFlooder)
storm := newStormController(max, dummy)
for i := 0; i < 10; i++ {
fmt.Printf("Count: %v, Timestamp: %v\n", i, time.Now())
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(i+1) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", i+1, dummy.getCounter())
}
storm.broadcast(nil, nil)
if dummy.getCounter() != uint64(i+1) {
t.Fatalf("Unexpected flood counter: expected=%v, got=%v", i+1, dummy.getCounter())
}
time.Sleep(1 * time.Second)
}
}
type dummyFlooder struct {
counter uint64
}
func (r *dummyFlooder) flood(ingress *network.Port, packet []byte) error {
r.counter++
return nil
}
func (r *dummyFlooder) getCounter() uint64 {
return r.counter
}
================================================
FILE: northbound/app/l2switch/switch.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package l2switch
import (
"bytes"
"fmt"
"net"
"sync"
"time"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/protocol"
"github.com/davecgh/go-spew/spew"
"github.com/pkg/errors"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("l2switch")
)
type L2Switch struct {
app.BaseProcessor
stormCtrl *stormController
db Database
once sync.Once
}
type Database interface {
// MACAddrs returns all the registered MAC addresses.
MACAddrs() ([]net.HardwareAddr, error)
}
func New(db Database) *L2Switch {
return &L2Switch{
stormCtrl: newStormController(100, new(flooder)),
db: db,
}
}
type flooder struct{}
// flood broadcasts packet to all ports on the ingress device, except the ingress port itself.
func (r *flooder) flood(ingress *network.Port, packet []byte) error {
return ingress.Device().Flood(ingress, packet)
}
func (r *L2Switch) Init() error {
return nil
}
func (r *L2Switch) Name() string {
return "L2Switch"
}
func isBroadcast(eth *protocol.Ethernet) bool {
return bytes.Compare(eth.DstMAC, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) == 0
}
type flowParam struct {
device *network.Device
dstMAC net.HardwareAddr
outPort uint32
}
func (r flowParam) String() string {
return fmt.Sprintf("Device=%v, DstMAC=%v, OutPort=%v", r.device.ID(), r.dstMAC, r.outPort)
}
func (r *L2Switch) setFlow(p flowParam) error {
f := p.device.Factory()
match, err := f.NewMatch()
if err != nil {
return err
}
match.SetDstMAC(p.dstMAC)
outPort := openflow.NewOutPort()
outPort.SetValue(p.outPort)
if err := p.device.SetFlow(match, outPort); err != nil {
return err
}
logger.Debugf("installed a new flow rule: %v", p)
return nil
}
type switchParam struct {
finder network.Finder
ethernet *protocol.Ethernet
ingress *network.Port
egress *network.Port
rawPacket []byte
}
func (r *L2Switch) switching(p switchParam) error {
param := flowParam{
device: p.ingress.Device(),
dstMAC: p.ethernet.DstMAC,
outPort: p.egress.Number(),
}
if err := r.setFlow(param); err != nil {
return err
}
// Send this ethernet packet directly to the destination node
logger.Debugf("sending a packet (Src=%v, Dst=%v) to egress port %v..", p.ethernet.SrcMAC, p.ethernet.DstMAC, p.egress.ID())
return r.PacketOut(p.egress, p.rawPacket)
}
func (r *L2Switch) OnPacketIn(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) error {
drop, err := r.processPacket(finder, ingress, eth)
if drop || err != nil {
return err
}
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
func (r *L2Switch) processPacket(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) (drop bool, err error) {
logger.Debugf("PACKET_IN.. Ingress=%v, SrcMAC=%v, DstMAC=%v", ingress.ID(), eth.SrcMAC, eth.DstMAC)
packet, err := eth.MarshalBinary()
if err != nil {
return false, err
}
// Broadcast?
if isBroadcast(eth) {
logger.Debugf("broadcasting.. Ingress=%v, SrcMAC=%v, DstMAC=%v, Packet=%v", ingress.ID(), eth.SrcMAC, eth.DstMAC, spew.Sdump(packet))
return true, r.stormCtrl.broadcast(ingress, packet)
}
logger.Debugf("finding node for %v...", eth.DstMAC)
dstNode, status, err := finder.Node(eth.DstMAC)
if err != nil {
return true, errors.Wrap(err, fmt.Sprintf("locating a node (MAC=%v)", eth.DstMAC))
}
if status != network.LocationDiscovered {
if status == network.LocationUndiscovered {
// Broadcast!
logger.Debugf("undiscovered node! broadcasting.. SrcMAC=%v, DstMAC=%v", eth.SrcMAC, eth.DstMAC)
return true, ingress.Device().Flood(ingress, packet)
} else if status == network.LocationUnregistered {
// Drop!
logger.Debugf("unknown node! dropping.. SrcMAC=%v, DstMAC=%v", eth.SrcMAC, eth.DstMAC)
return true, nil
} else {
panic(fmt.Sprintf("unexpected location status: %v", status))
}
}
logger.Debugf("found the node for %v: deviceID=%v, portNum=%v", eth.DstMAC, dstNode.Port().Device().ID(), dstNode.Port().Number())
// Disconnected node?
port := dstNode.Port().Value()
if port.IsPortDown() || port.IsLinkDown() {
logger.Debugf("disconnected node! dropping.. SrcMAC=%v, DstMAC=%v", eth.SrcMAC, eth.DstMAC)
return true, nil
}
param := switchParam{}
// Check whether src and dst nodes reside on a same switch device
if ingress.Device().ID() == dstNode.Port().Device().ID() {
param = switchParam{
finder: finder,
ethernet: eth,
ingress: ingress,
egress: dstNode.Port(),
rawPacket: packet,
}
} else {
path := finder.Path(ingress.Device().ID(), dstNode.Port().Device().ID())
if len(path) == 0 {
logger.Debugf("empty path.. dropping SrcMAC=%v, DstMAC=%v", eth.SrcMAC, eth.DstMAC)
return true, nil
}
egress := path[0][0]
// Drop this packet if it goes back to the ingress port to avoid duplicated packet routing
if ingress.Number() == egress.Number() {
logger.Debugf("ignore routing path that goes back to the ingress port (SrcMAC=%v, DstMAC=%v)", eth.SrcMAC, eth.DstMAC)
return true, nil
}
param = switchParam{
finder: finder,
ethernet: eth,
ingress: ingress,
egress: egress,
rawPacket: packet,
}
}
return true, r.switching(param)
}
func (r *L2Switch) OnTopologyChange(finder network.Finder) error {
logger.Debug("OnTopologyChange..")
// We should remove all edges from all switch devices when the network topology is changed.
// Otherwise, installed flow rules in switches may result in incorrect packet routing based on the previous topology.
if err := r.removeAllFlows(finder.Devices()); err != nil {
return err
}
return r.BaseProcessor.OnTopologyChange(finder)
}
func (r *L2Switch) removeAllFlows(devices []*network.Device) error {
logger.Debug("removing all flows from all devices..")
for _, d := range devices {
if d.IsClosed() {
continue
}
if err := d.RemoveFlows(); err != nil {
return err
}
logger.Debugf("removed all flows from DPID %v", d.ID())
}
return nil
}
func (r *L2Switch) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *L2Switch) OnPortDown(finder network.Finder, port *network.Port) error {
logger.Infof("port down! DPID=%v, number=%v", port.Device().ID(), port.Number())
device := port.Device()
factory := device.Factory()
// Wildcard match
match, err := factory.NewMatch()
if err != nil {
return err
}
outPort := openflow.NewOutPort()
outPort.SetValue(port.Number())
if err := device.RemoveFlow(match, outPort); err != nil {
return errors.Wrap(err, fmt.Sprintf("removing flows heading to port %v", port.ID()))
}
logger.Debugf("removed all flows heading to the port %v", port.ID())
return r.BaseProcessor.OnPortDown(finder, port)
}
func (r *L2Switch) OnDeviceUp(finder network.Finder, device *network.Device) error {
// Make sure that there is only one flow manager in this application.
r.once.Do(func() {
// Run the background flow manager.
go r.flowManager(finder)
})
return r.BaseProcessor.OnDeviceUp(finder, device)
}
func (r *L2Switch) flowManager(finder network.Finder) {
logger.Debug("executed flow manager")
// This interval should be shorter than the flow hard-timeout specified by network.Device.SetFlow().
ticker := time.Tick(35 * time.Second)
// Infinite loop.
for range ticker {
mac, err := r.db.MACAddrs()
if err != nil {
logger.Errorf("failed to get MAC addresses: %v", err)
continue
}
logger.Debugf("got %v MAC addresses", len(mac))
for _, addr := range mac {
logger.Debugf("modifying the flow for %v...", addr)
r.modifyFlows(finder, addr)
}
}
}
func (r *L2Switch) modifyFlows(finder network.Finder, mac net.HardwareAddr) {
// Locate the destination node for the address.
node, status, err := finder.Node(mac)
if err != nil {
logger.Errorf("failed to locate the node %v: %v", mac, err)
return
}
if status != network.LocationDiscovered {
logger.Debugf("skip flow management for %v: undiscovered location", mac)
return
}
// Disconnected node?
port := node.Port().Value()
if port.IsPortDown() || port.IsLinkDown() {
logger.Debugf("skip flow management for %v: link down", mac)
return
}
// Update the flows on all devices.
for _, device := range finder.Devices() {
var egress *network.Port
// Reside on this device?
if device.ID() == node.Port().Device().ID() {
logger.Debugf("reside on the same device: DPID=%v, Port=%v", device.ID(), node.Port().Number())
egress = node.Port()
} else {
// Find the shortest path from this device to an another device that is connected to the destination node.
path := finder.Path(device.ID(), node.Port().Device().ID())
// No path to the destination node?
if len(path) == 0 {
logger.Debugf("skip flow management for %v on %v: no path", mac, device.ID())
continue
}
egress = path[0][0]
}
flow := flowParam{
device: device,
dstMAC: mac,
outPort: egress.Number(),
}
if err := r.setFlow(flow); err != nil {
logger.Errorf("failed to modify the flows for %v on %v: %v", mac, device.ID(), err)
continue
}
}
}
================================================
FILE: northbound/app/monitor/monitor.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package monitor
import (
"errors"
"fmt"
"strings"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/go-logging"
"github.com/superkkt/viper"
)
var (
logger = logging.MustGetLogger("monitor")
)
type Monitor struct {
app.BaseProcessor
email string
}
func New() *Monitor {
return &Monitor{}
}
func (r *Monitor) Init() error {
email := viper.GetString("default.admin_email")
if len(email) == 0 || !strings.Contains(email, "@") {
return errors.New("invalid admin_email in the config file")
}
r.email = email
return nil
}
func (r *Monitor) Name() string {
return "Monitor"
}
func (r *Monitor) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *Monitor) OnDeviceUp(finder network.Finder, device *network.Device) error {
go func() {
subject := "Cherry: device is up!"
body := fmt.Sprintf("DPID: %v", device.ID())
if err := r.sendAlarm(subject, body); err != nil {
logger.Errorf("failed to send an alarm email: %v", err)
}
}()
logger.Warningf("switch device up: DPID=%v", device.ID())
return r.BaseProcessor.OnDeviceUp(finder, device)
}
func (r *Monitor) OnDeviceDown(finder network.Finder, device *network.Device) error {
go func() {
subject := "Cherry: device is down!"
body := fmt.Sprintf("DPID: %v", device.ID())
if err := r.sendAlarm(subject, body); err != nil {
logger.Errorf("failed to send an alarm email: %v", err)
}
}()
logger.Warningf("switch device down: DPID=%v", device.ID())
return r.BaseProcessor.OnDeviceDown(finder, device)
}
func (r *Monitor) sendAlarm(subject, body string) error {
from := "noreply@sds.co.kr"
to := []string{r.email}
header := fmt.Sprintf("From: %v\r\nTo: %v\r\nSubject: %v", from, r.email, subject)
msg := []byte(fmt.Sprintf("%v\r\n\r\n%v", header, body))
if err := sendmail(from, to, msg); err != nil {
return err
}
logger.Debugf("sent an alarm email to %v: subject=%v", r.email, subject)
return nil
}
================================================
FILE: northbound/app/monitor/sendmail.go
================================================
package monitor
import (
"fmt"
"net"
"net/smtp"
"strings"
"github.com/pkg/errors"
)
// msg is a RFC 822-style email with headers first, a blank line, and then the message body.
// The lines of msg should be CRLF terminated.
func sendmail(from string, to []string, msg []byte) error {
mx, err := lookupMX(from)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to lookup MX for %v", from))
}
err = nil
for _, v := range mx {
err = smtp.SendMail(fmt.Sprintf("%v:25", v.Host), nil, from, to, msg)
if err != nil {
continue
}
// Sent
return nil
}
return err
}
func lookupMX(email string) ([]*net.MX, error) {
tokens := strings.Split(email, "@")
if len(tokens) != 2 {
return nil, fmt.Errorf("invalid email address: %v", email)
}
return net.LookupMX(tokens[1])
}
================================================
FILE: northbound/app/processor.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package app
import (
"fmt"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/protocol"
)
// Processor should prepare to be executed by multiple goroutines simultaneously.
type Processor interface {
Dependencies() []string
fmt.Stringer
Init() error
// Name returns the application name that is globally unique
Name() string
network.EventListener
Next() (next Processor, ok bool)
SetNext(Processor)
}
type BaseProcessor struct {
next Processor
}
func (r *BaseProcessor) Init() error {
return nil
}
func (r *BaseProcessor) Name() string {
return "BaseProcessor"
}
func (r *BaseProcessor) Dependencies() []string {
return []string{}
}
func (r *BaseProcessor) OnPacketIn(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnPacketIn(finder, ingress, eth)
}
func (r *BaseProcessor) OnDeviceUp(finder network.Finder, device *network.Device) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnDeviceUp(finder, device)
}
func (r *BaseProcessor) OnDeviceDown(finder network.Finder, device *network.Device) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnDeviceDown(finder, device)
}
func (r *BaseProcessor) OnPortUp(finder network.Finder, port *network.Port) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnPortUp(finder, port)
}
func (r *BaseProcessor) OnPortDown(finder network.Finder, port *network.Port) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnPortDown(finder, port)
}
func (r *BaseProcessor) OnTopologyChange(finder network.Finder) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnTopologyChange(finder)
}
func (r *BaseProcessor) OnFlowRemoved(finder network.Finder, flow openflow.FlowRemoved) error {
// Do nothging and execute the next processor if it exists
next, ok := r.Next()
if !ok {
return nil
}
return next.OnFlowRemoved(finder, flow)
}
func (r *BaseProcessor) Next() (next Processor, ok bool) {
if r.next != nil {
return r.next, true
}
return nil, false
}
func (r *BaseProcessor) SetNext(next Processor) {
r.next = next
}
func (r *BaseProcessor) PacketOut(egress *network.Port, packet []byte) error {
f := egress.Device().Factory()
inPort := openflow.NewInPort()
inPort.SetController()
outPort := openflow.NewOutPort()
outPort.SetValue(egress.Number())
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
out, err := f.NewPacketOut()
if err != nil {
return err
}
out.SetInPort(inPort)
out.SetAction(action)
out.SetData(packet)
return egress.Device().SendMessage(out)
}
================================================
FILE: northbound/app/proxyarp/arp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package proxyarp
import (
"bytes"
"fmt"
"net"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/protocol"
"github.com/pkg/errors"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("proxyarp")
)
type ProxyARP struct {
app.BaseProcessor
db database
}
type database interface {
MAC(ip net.IP) (mac net.HardwareAddr, ok bool, err error)
}
type Host struct {
IP net.IP
MAC net.HardwareAddr
}
func New(db database) *ProxyARP {
return &ProxyARP{
db: db,
}
}
func (r *ProxyARP) Init() error {
return nil
}
func (r *ProxyARP) Name() string {
return "ProxyARP"
}
func (r *ProxyARP) OnPacketIn(finder network.Finder, ingress *network.Port, eth *protocol.Ethernet) error {
// ARP?
if eth.Type != 0x0806 {
return r.BaseProcessor.OnPacketIn(finder, ingress, eth)
}
logger.Debugf("received ARP packet.. ingress=%v, srcEthMAC=%v, dstEthMAC=%v", ingress.ID(), eth.SrcMAC, eth.DstMAC)
arp := new(protocol.ARP)
if err := arp.UnmarshalBinary(eth.Payload); err != nil {
return err
}
// Drop ARP announcement
if isARPAnnouncement(arp) {
// We don't allow a host sends ARP announcement to the network. This controller only can send it,
// and we will flood the announcement to all switch devices using PACKET_OUT when we need it.
logger.Debugf("drop ARP announcements.. ingress=%v (%v)", ingress.ID(), arp)
return nil
}
// ARP request?
if arp.Operation != 1 {
// Drop all ARP packets whose type is not a reqeust.
logger.Infof("drop ARP packet whose type is not a request.. ingress=%v (%v)", ingress.ID(), arp)
return nil
}
mac, ok, err := r.db.MAC(arp.TPA)
if err != nil {
return errors.Wrap(&proxyarpErr{temporary: true, err: err}, "failed to query MAC")
}
if !ok {
logger.Debugf("drop the ARP request for unknown host (%v)", arp.TPA)
// Unknown hosts. Drop the packet.
return nil
}
logger.Debugf("ARP request for %v (%v)", arp.TPA, mac)
reply, err := makeARPReply(arp, mac)
if err != nil {
return err
}
logger.Debugf("sending ARP reply to %v..", ingress.ID())
return sendARPReply(ingress, reply)
}
func sendARPReply(ingress *network.Port, packet []byte) error {
f := ingress.Device().Factory()
inPort := openflow.NewInPort()
inPort.SetController()
outPort := openflow.NewOutPort()
outPort.SetValue(ingress.Number())
action, err := f.NewAction()
if err != nil {
return err
}
action.SetOutPort(outPort)
out, err := f.NewPacketOut()
if err != nil {
return err
}
out.SetInPort(inPort)
out.SetAction(action)
out.SetData(packet)
return ingress.Device().SendMessage(out)
}
func isARPAnnouncement(request *protocol.ARP) bool {
sameProtoAddr := request.SPA.Equal(request.TPA)
sameHWAddr := bytes.Compare(request.SHA, request.THA) == 0
zeroTarget := bytes.Compare(request.THA, []byte{0, 0, 0, 0, 0, 0}) == 0
broadcastTarget := bytes.Compare(request.THA, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) == 0
if sameProtoAddr && (zeroTarget || broadcastTarget || sameHWAddr) {
return true
}
return false
}
func makeARPReply(request *protocol.ARP, mac net.HardwareAddr) ([]byte, error) {
v := protocol.NewARPReply(mac, request.SHA, request.TPA, request.SPA)
reply, err := v.MarshalBinary()
if err != nil {
return nil, err
}
eth := protocol.Ethernet{
SrcMAC: mac,
DstMAC: request.SHA,
Type: 0x0806,
Payload: reply,
}
return eth.MarshalBinary()
}
func (r *ProxyARP) String() string {
return fmt.Sprintf("%v", r.Name())
}
================================================
FILE: northbound/app/proxyarp/error.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package proxyarp
type proxyarpErr struct {
temporary bool
err error
}
func (r *proxyarpErr) Error() string {
return r.err.Error()
}
func (r *proxyarpErr) Temporary() bool {
return r.temporary
}
================================================
FILE: northbound/app/virtualip/virtualip.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package virtualip
import (
"fmt"
"net"
"strconv"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("virtualip")
)
// NOTE: This VirtualIP module should be executed before the Discovery module.
type VirtualIP struct {
app.BaseProcessor
db database
}
type database interface {
ToggleDeviceVIP(swDPID uint64) ([]Address, error)
TogglePortVIP(swDPID uint64, portNum uint16) ([]Address, error)
}
type Address struct {
IP net.IP
MAC net.HardwareAddr
}
func New(db database) *VirtualIP {
return &VirtualIP{
db: db,
}
}
func (r *VirtualIP) Init() error {
return nil
}
func (r *VirtualIP) Name() string {
return "VirtualIP"
}
func (r *VirtualIP) String() string {
return fmt.Sprintf("%v", r.Name())
}
func (r *VirtualIP) OnPortDown(finder network.Finder, port *network.Port) error {
logger.Debugf("port down! checking VIPs that belong to the port... (DPID=%v, Port=%v)", port.Device().ID(), port.Number())
dpid, err := strconv.ParseUint(port.Device().ID(), 10, 64)
if err != nil {
logger.Errorf("invalid switch DPID: %v", port.Device().ID())
return r.BaseProcessor.OnPortDown(finder, port)
}
vips, err := r.db.TogglePortVIP(dpid, uint16(port.Number()))
if err != nil {
logger.Errorf("failed to toggle VIP hosts: %v", err)
return r.BaseProcessor.OnPortDown(finder, port)
}
broadcastARPAnnouncement(finder, vips)
return r.BaseProcessor.OnPortDown(finder, port)
}
func (r *VirtualIP) OnDeviceDown(finder network.Finder, device *network.Device) error {
logger.Debugf("device down! checking VIPs that belong to the device... (DPID=%v)", device.ID())
dpid, err := strconv.ParseUint(device.ID(), 10, 64)
if err != nil {
logger.Errorf("invalid switch DPID: %v", device.ID())
return r.BaseProcessor.OnDeviceDown(finder, device)
}
vips, err := r.db.ToggleDeviceVIP(dpid)
if err != nil {
logger.Errorf("failed to toggle VIP hosts: %v", err)
return r.BaseProcessor.OnDeviceDown(finder, device)
}
broadcastARPAnnouncement(finder, vips)
return r.BaseProcessor.OnDeviceDown(finder, device)
}
func broadcastARPAnnouncement(finder network.Finder, vips []Address) {
for _, v := range vips {
for _, d := range finder.Devices() {
if err := d.SendARPAnnouncement(v.IP, v.MAC); err != nil {
logger.Errorf("failed to broadcast ARP announcement: %v", err)
continue
}
}
logger.Warningf("VIP toggled: IP=%v, MAC=%v", v.IP, v.MAC)
}
}
================================================
FILE: northbound/manager.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package northbound
import (
"bytes"
"fmt"
"strings"
"sync"
"github.com/superkkt/cherry/database"
"github.com/superkkt/cherry/network"
"github.com/superkkt/cherry/northbound/app"
"github.com/superkkt/cherry/northbound/app/announcer"
"github.com/superkkt/cherry/northbound/app/dhcp"
"github.com/superkkt/cherry/northbound/app/discovery"
"github.com/superkkt/cherry/northbound/app/l2switch"
"github.com/superkkt/cherry/northbound/app/monitor"
"github.com/superkkt/cherry/northbound/app/proxyarp"
"github.com/superkkt/cherry/northbound/app/virtualip"
"github.com/pkg/errors"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("northbound")
)
type EventSender interface {
SetEventListener(network.EventListener)
}
type application struct {
instance app.Processor
enabled bool
}
type Manager struct {
mutex sync.Mutex
apps map[string]*application // Registered applications
head, tail app.Processor
db *database.MySQL
}
func NewManager(db *database.MySQL) (*Manager, error) {
v := &Manager{
apps: make(map[string]*application),
db: db,
}
// Registering north-bound applications
v.register(discovery.New(db))
v.register(l2switch.New(db))
v.register(proxyarp.New(db))
v.register(monitor.New())
v.register(virtualip.New(db))
v.register(announcer.New(db))
v.register(dhcp.New(db))
return v, nil
}
func (r *Manager) register(app app.Processor) {
r.apps[strings.ToUpper(app.Name())] = &application{
instance: app,
enabled: false,
}
}
// XXX: Caller should lock the mutex before they call this function
func (r *Manager) checkDependencies(appNames []string) error {
if appNames == nil || len(appNames) == 0 {
// No dependency
return nil
}
for _, name := range appNames {
app, ok := r.apps[strings.ToUpper(name)]
logger.Debugf("app: %+v, ok: %v", app, ok)
if !ok || !app.enabled {
return fmt.Errorf("%v application is not loaded", name)
}
}
return nil
}
func (r *Manager) Enable(appName string) error {
r.mutex.Lock()
defer r.mutex.Unlock()
logger.Debugf("enabling %v application..", appName)
v, ok := r.apps[strings.ToUpper(appName)]
if !ok {
return fmt.Errorf("unknown application: %v", appName)
}
if v.enabled == true {
logger.Debugf("%v: already enabled", appName)
return nil
}
app := v.instance
if err := app.Init(); err != nil {
return errors.Wrap(err, "initializing application")
}
if err := r.checkDependencies(app.Dependencies()); err != nil {
return errors.Wrap(err, "checking dependencies")
}
v.enabled = true
logger.Debugf("enabled %v application", appName)
if r.head == nil {
r.head = app
r.tail = app
} else {
r.tail.SetNext(app)
r.tail = app
}
return nil
}
func (r *Manager) AddEventSender(sender EventSender) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.head == nil {
return
}
sender.SetEventListener(r.head)
}
func (r *Manager) String() string {
r.mutex.Lock()
defer r.mutex.Unlock()
var buf bytes.Buffer
app := r.head
for app != nil {
buf.WriteString(fmt.Sprintf("%v\n", app))
next, ok := app.Next()
if !ok {
break
}
app = next
}
return buf.String()
}
================================================
FILE: openflow/action.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
"net"
"github.com/pkg/errors"
)
type Action interface {
DstMAC() (ok bool, mac net.HardwareAddr)
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Queue() (ok bool, queue uint32)
// Error() returns last error message
Error() error
OutPort() OutPort
SetDstMAC(mac net.HardwareAddr)
SetQueue(queue uint32)
SetOutPort(port OutPort)
SetSrcMAC(mac net.HardwareAddr)
SetVLANID(vid uint16)
SrcMAC() (ok bool, mac net.HardwareAddr)
VLANID() (ok bool, vid uint16)
}
type BaseAction struct {
err error
output OutPort
srcMAC *net.HardwareAddr
dstMAC *net.HardwareAddr
queue int64
vlanID int32
}
func NewBaseAction() *BaseAction {
return &BaseAction{
queue: -1,
vlanID: -1,
}
}
func (r *BaseAction) VLANID() (ok bool, vid uint16) {
if r.vlanID == -1 {
return false, 0
}
return true, uint16(r.vlanID)
}
func (r *BaseAction) SetVLANID(vid uint16) {
r.vlanID = int32(vid)
}
func (r *BaseAction) Queue() (ok bool, queue uint32) {
if r.queue == -1 {
return false, 0
}
return true, uint32(r.queue)
}
func (r *BaseAction) SetQueue(queue uint32) {
r.queue = int64(queue)
}
func (r *BaseAction) SetOutPort(port OutPort) {
r.output = port
}
func (r *BaseAction) OutPort() OutPort {
return r.output
}
func (r *BaseAction) SetSrcMAC(mac net.HardwareAddr) {
if mac == nil || len(mac) < 6 {
r.err = errors.Wrap(ErrInvalidMACAddress, "SetSrcMAC")
return
}
r.srcMAC = &mac
}
func (r *BaseAction) SrcMAC() (ok bool, mac net.HardwareAddr) {
if r.srcMAC == nil {
return false, net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
}
return true, *r.srcMAC
}
func (r *BaseAction) SetDstMAC(mac net.HardwareAddr) {
if mac == nil || len(mac) < 6 {
r.err = errors.Wrap(ErrInvalidMACAddress, "SetDstMAC")
return
}
r.dstMAC = &mac
}
func (r *BaseAction) DstMAC() (ok bool, mac net.HardwareAddr) {
if r.dstMAC == nil {
return false, net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
}
return true, *r.dstMAC
}
func (r *BaseAction) Error() error {
return r.err
}
================================================
FILE: openflow/barrier.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type BarrierRequest interface {
Header
encoding.BinaryMarshaler
}
type BarrierReply interface {
Header
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/config.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type ConfigFlag uint16
const (
FragNormal ConfigFlag = iota
FragDrop
FragReasm
FragMask
)
type Config interface {
// Error() returns last error message
Error() error
Flags() ConfigFlag
MissSendLength() uint16
SetFlags(flags ConfigFlag)
SetMissSendLength(length uint16)
}
type SetConfig interface {
Header
Config
encoding.BinaryMarshaler
}
type GetConfigRequest interface {
Header
encoding.BinaryMarshaler
}
type GetConfigReply interface {
Header
Config
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/const.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
const (
OF10_VERSION = 0x01
OF13_VERSION = 0x04
)
================================================
FILE: openflow/description.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
// Description reqeust
type DescRequest interface {
Header
encoding.BinaryMarshaler
}
// Description reply
type DescReply interface {
Header
Manufacturer() string
Hardware() string
Software() string
Serial() string
Description() string
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/echo.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type Echo interface {
Data() []byte
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
// Error() returns last error message
Error() error
Header
SetData(data []byte)
}
type EchoRequest interface {
Echo
}
type EchoReply interface {
Echo
}
type BaseEcho struct {
err error
Message
data []byte
}
func (r *BaseEcho) Data() []byte {
return r.data
}
func (r *BaseEcho) SetData(data []byte) {
if data == nil {
panic("data is nil")
}
r.data = data
}
func (r *BaseEcho) Error() error {
return r.err
}
func (r *BaseEcho) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
r.SetPayload(r.data)
return r.Message.MarshalBinary()
}
func (r *BaseEcho) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
r.data = r.Payload()
return nil
}
================================================
FILE: openflow/error.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
"encoding/binary"
)
type Error interface {
Header
Class() uint16 // Error type
Code() uint16
Data() []byte
encoding.BinaryUnmarshaler
}
type BaseError struct {
Message
class uint16
code uint16
data []byte
}
func (r *BaseError) Class() uint16 {
return r.class
}
func (r *BaseError) Code() uint16 {
return r.code
}
func (r *BaseError) Data() []byte {
return r.data
}
func (r *BaseError) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 4 {
return ErrInvalidPacketLength
}
r.class = binary.BigEndian.Uint16(payload[0:2])
r.code = binary.BigEndian.Uint16(payload[2:4])
if len(payload) > 4 {
r.data = payload[4:]
}
return nil
}
================================================
FILE: openflow/factory.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"errors"
)
var (
ErrInvalidPacketLength = errors.New("invalid packet length")
ErrUnsupportedVersion = errors.New("unsupported protocol version")
ErrUnsupportedMessage = errors.New("unsupported message type")
ErrInvalidMACAddress = errors.New("invalid MAC address")
ErrInvalidIPAddress = errors.New("invalid IP address")
ErrUnsupportedIPProtocol = errors.New("unsupported IP protocol")
ErrUnsupportedEtherType = errors.New("unsupported Ethernet type")
ErrMissingIPProtocol = errors.New("missing IP protocol")
ErrMissingEtherType = errors.New("missing Ethernet type")
ErrUnsupportedMatchType = errors.New("unsupported flow match type")
ErrInvalidPropertyMethod = errors.New("invalid property method")
)
// Abstract factory
type Factory interface {
ProtocolVersion() uint8
NewAction() (Action, error)
NewBarrierRequest() (BarrierRequest, error)
NewBarrierReply() (BarrierReply, error)
NewDescRequest() (DescRequest, error)
NewDescReply() (DescReply, error)
NewEchoRequest() (EchoRequest, error)
NewEchoReply() (EchoReply, error)
NewError() (Error, error)
NewFeaturesRequest() (FeaturesRequest, error)
NewFeaturesReply() (FeaturesReply, error)
NewFlowMod(cmd FlowModCmd) (FlowMod, error)
NewFlowRemoved() (FlowRemoved, error)
NewFlowStatsRequest() (FlowStatsRequest, error)
// TODO: NewFlowStatsReply() (FlowStatsReply, error)
NewGetConfigRequest() (GetConfigRequest, error)
NewGetConfigReply() (GetConfigReply, error)
NewHello() (Hello, error)
NewInstruction() (Instruction, error)
NewMatch() (Match, error)
NewPacketIn() (PacketIn, error)
NewPacketOut() (PacketOut, error)
NewPortDescRequest() (PortDescRequest, error)
NewPortDescReply() (PortDescReply, error)
NewPortStatus() (PortStatus, error)
NewQueueGetConfigRequest() (QueueGetConfigRequest, error)
NewSetConfig() (SetConfig, error)
NewTableFeaturesRequest() (TableFeaturesRequest, error)
// TODO: NewTableFeaturesReply() (TableFeaturesReply, error)
}
================================================
FILE: openflow/features.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type FeaturesRequest interface {
Header
encoding.BinaryMarshaler
}
type FeaturesReply interface {
Header
DPID() uint64
NumBuffers() uint32
NumTables() uint8
Capabilities() uint32
Actions() uint32
Ports() []Port
AuxID() uint8
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/flow_mod.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type FlowModCmd uint8
const (
FlowAdd FlowModCmd = iota
FlowModify
FlowDelete
)
type FlowMod interface {
Cookie() uint64
CookieMask() uint64
encoding.BinaryMarshaler
Error() error
FlowInstruction() Instruction
FlowMatch() Match
HardTimeout() uint16
Header
IdleTimeout() uint16
OutPort() OutPort
Priority() uint16
SetCookie(cookie uint64)
SetCookieMask(mask uint64)
SetFlowInstruction(action Instruction)
SetFlowMatch(match Match)
SetHardTimeout(timeout uint16)
SetIdleTimeout(timeout uint16)
SetOutPort(port OutPort)
SetPriority(priority uint16)
SetTableID(id uint8)
TableID() uint8
}
================================================
FILE: openflow/flow_removed.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type FlowRemoved interface {
Header
Cookie() uint64
Priority() uint16
Reason() uint8
TableID() uint8
DurationSec() uint32
DurationNanoSec() uint32
IdleTimeout() uint16
HardTimeout() uint16
PacketCount() uint64
ByteCount() uint64
Match() Match
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/flow_stats.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type FlowStatsRequest interface {
Cookie() uint64
CookieMask() uint64
encoding.BinaryMarshaler
Error() error
Header
Match() Match
SetCookie(cookie uint64)
SetCookieMask(mask uint64)
SetMatch(match Match)
// 0xFF means all table
SetTableID(id uint8)
TableID() uint8
}
// TODO: Implement FlowStatsReply
================================================
FILE: openflow/hello.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type Hello interface {
Header
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}
type BaseHello struct {
Message
}
func (r *BaseHello) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
func (r *BaseHello) UnmarshalBinary(data []byte) error {
return r.Message.UnmarshalBinary(data)
}
================================================
FILE: openflow/instruction.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type Instruction interface {
ApplyAction(act Action)
encoding.BinaryMarshaler
Error() error
GotoTable(tableID uint8)
WriteAction(act Action)
}
================================================
FILE: openflow/match.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
"net"
)
type Match interface {
DstIP() *net.IPNet
DstMAC() (wildcard bool, mac net.HardwareAddr)
// DstPort returns protocol (TCP or UDP) destination port number
DstPort() (wildcard bool, port uint16)
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Error() error
EtherType() (wildcard bool, etherType uint16)
// InPort returns switch port number
InPort() (wildcard bool, inport InPort)
IPProtocol() (wildcard bool, protocol uint8)
SetDstIP(ip *net.IPNet)
SetDstMAC(mac net.HardwareAddr)
// SetDstPort sets protocol (TCP or UDP) destination port number
SetDstPort(p uint16)
SetEtherType(t uint16)
// SetInPort sets switch port number
SetInPort(port InPort)
SetIPProtocol(p uint8)
SetSrcIP(ip *net.IPNet)
SetSrcMAC(mac net.HardwareAddr)
// SetSrcPort sets protocol (TCP or UDP) source port number
SetSrcPort(p uint16)
SetVLANID(id uint16)
SetVLANPriority(p uint8)
SetWildcardEtherType()
SetWildcardDstMAC()
// SetWildcardDstPort sets protocol (TCP or UDP) destination port number as a wildcard
SetWildcardDstPort()
SetWildcardSrcMAC()
// SetWildcardSrcPort sets protocol (TCP or UDP) source port number as a wildcard
SetWildcardSrcPort()
// SetWildcardInPort sets switch port number as a wildcard
SetWildcardInPort()
SetWildcardIPProtocol()
SetWildcardVLANID()
SetWildcardVLANPriority()
SrcIP() *net.IPNet
SrcMAC() (wildcard bool, mac net.HardwareAddr)
// SrcPort returns protocol (TCP or UDP) source port number
SrcPort() (wildcard bool, port uint16)
VLANID() (wildcard bool, vlanID uint16)
VLANPriority() (wildcard bool, priority uint8)
}
================================================
FILE: openflow/message.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding/binary"
)
type Header interface {
Version() uint8
Type() uint8
TransactionID() uint32
SetTransactionID(xid uint32)
}
type Message struct {
version uint8
msgType uint8
xid uint32
length uint16
payload []byte
}
func NewMessage(version uint8, msgType uint8, xid uint32) Message {
return Message{
version: version,
msgType: msgType,
xid: xid,
length: 8,
}
}
func (r *Message) Version() uint8 {
return r.version
}
func (r *Message) Type() uint8 {
return r.msgType
}
func (r *Message) TransactionID() uint32 {
return r.xid
}
func (r *Message) SetTransactionID(xid uint32) {
r.xid = xid
}
func (r *Message) Payload() []byte {
return r.payload
}
func (r *Message) SetPayload(payload []byte) {
r.payload = payload
if payload == nil {
r.length = 8
} else {
r.length = uint16(8 + len(payload))
}
}
func (r *Message) MarshalBinary() ([]byte, error) {
var length uint16 = 8
if r.payload != nil {
length += uint16(len(r.payload))
}
v := make([]byte, length)
v[0] = r.version
v[1] = r.msgType
binary.BigEndian.PutUint16(v[2:4], length)
binary.BigEndian.PutUint32(v[4:8], r.xid)
if length > 8 {
copy(v[8:], r.payload)
}
return v, nil
}
func (r *Message) UnmarshalBinary(data []byte) error {
if data == nil || len(data) < 8 {
return ErrInvalidPacketLength
}
r.version = data[0]
r.msgType = data[1]
r.length = binary.BigEndian.Uint16(data[2:4])
if r.length < 8 || len(data) < int(r.length) {
return ErrInvalidPacketLength
}
r.xid = binary.BigEndian.Uint32(data[4:8])
r.payload = data[8:r.length]
return nil
}
================================================
FILE: openflow/of10/action.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"net"
"github.com/superkkt/cherry/openflow"
)
type Action struct {
*openflow.BaseAction
}
func NewAction() openflow.Action {
return &Action{
openflow.NewBaseAction(),
}
}
func marshalOutPort(p openflow.OutPort) ([]byte, error) {
v := make([]byte, 8)
binary.BigEndian.PutUint16(v[0:2], uint16(OFPAT_OUTPUT))
binary.BigEndian.PutUint16(v[2:4], 8)
var port uint16
switch {
case p.IsTable():
port = OFPP_TABLE
case p.IsFlood():
port = OFPP_FLOOD
case p.IsAll():
port = OFPP_ALL
case p.IsController():
port = OFPP_CONTROLLER
case p.IsInPort():
port = OFPP_IN_PORT
case p.IsNone():
port = OFPP_NONE
default:
port = uint16(p.Value())
}
binary.BigEndian.PutUint16(v[4:6], port)
// We don't support buffer ID and partial PACKET_IN
binary.BigEndian.PutUint16(v[6:8], 0xFFFF)
return v, nil
}
func marshalQueue(p openflow.OutPort, queue uint32) ([]byte, error) {
v := make([]byte, 16)
binary.BigEndian.PutUint16(v[0:2], uint16(OFPAT_ENQUEUE))
binary.BigEndian.PutUint16(v[2:4], 16)
var port uint16
switch {
case p.IsTable():
port = OFPP_TABLE
case p.IsFlood():
port = OFPP_FLOOD
case p.IsAll():
port = OFPP_ALL
case p.IsController():
port = OFPP_CONTROLLER
case p.IsNone():
port = OFPP_NONE
default:
port = uint16(p.Value())
}
binary.BigEndian.PutUint16(v[4:6], port)
// v[6:12] is padding
binary.BigEndian.PutUint32(v[12:16], queue)
return v, nil
}
func marshalMAC(t uint16, mac net.HardwareAddr) ([]byte, error) {
if mac == nil || len(mac) < 6 {
return nil, openflow.ErrInvalidMACAddress
}
v := make([]byte, 16)
binary.BigEndian.PutUint16(v[0:2], t)
binary.BigEndian.PutUint16(v[2:4], 16)
copy(v[4:10], mac)
return v, nil
}
func marshalVLANID(vid uint16) ([]byte, error) {
v := make([]byte, 8)
binary.BigEndian.PutUint16(v[0:2], uint16(OFPAT_SET_VLAN_VID))
binary.BigEndian.PutUint16(v[2:4], 8)
binary.BigEndian.PutUint16(v[4:6], vid)
// v[6:8] is padding
return v, nil
}
func (r *Action) MarshalBinary() ([]byte, error) {
if err := r.Error(); err != nil {
return nil, err
}
result := make([]byte, 0)
if ok, srcMAC := r.SrcMAC(); ok {
v, err := marshalMAC(OFPAT_SET_DL_SRC, srcMAC)
if err != nil {
return nil, err
}
result = append(result, v...)
}
if ok, dstMAC := r.DstMAC(); ok {
v, err := marshalMAC(OFPAT_SET_DL_DST, dstMAC)
if err != nil {
return nil, err
}
result = append(result, v...)
}
ok, vlanID := r.VLANID()
if ok {
v, err := marshalVLANID(vlanID)
if err != nil {
return nil, err
}
result = append(result, v...)
}
// XXX: Output action should be specified as a last element of this action command.
var buf []byte
var err error
// Need QoS?
ok, queueID := r.Queue()
if ok {
buf, err = marshalQueue(r.OutPort(), queueID)
} else {
buf, err = marshalOutPort(r.OutPort())
}
if err != nil {
return nil, err
}
result = append(result, buf...)
return result, nil
}
func (r *Action) UnmarshalBinary(data []byte) error {
buf := data
for len(buf) >= 4 {
t := binary.BigEndian.Uint16(buf[0:2])
length := binary.BigEndian.Uint16(buf[2:4])
if len(buf) < int(length) {
return openflow.ErrInvalidPacketLength
}
switch t {
case OFPAT_OUTPUT:
if len(buf) < 8 {
return openflow.ErrInvalidPacketLength
}
outPort := openflow.NewOutPort()
outPort.SetValue(uint32(binary.BigEndian.Uint16(buf[4:6])))
r.SetOutPort(outPort)
if err := r.Error(); err != nil {
return err
}
case OFPAT_SET_DL_SRC:
if len(buf) < 16 {
return openflow.ErrInvalidPacketLength
}
r.SetSrcMAC(buf[4:10])
if err := r.Error(); err != nil {
return err
}
case OFPAT_SET_DL_DST:
if len(buf) < 16 {
return openflow.ErrInvalidPacketLength
}
r.SetDstMAC(buf[4:10])
if err := r.Error(); err != nil {
return err
}
case OFPAT_ENQUEUE:
if len(buf) < 16 {
return openflow.ErrInvalidPacketLength
}
outPort := openflow.NewOutPort()
outPort.SetValue(uint32(binary.BigEndian.Uint16(buf[4:6])))
r.SetOutPort(outPort)
r.SetQueue(binary.BigEndian.Uint32(buf[12:16]))
if err := r.Error(); err != nil {
return err
}
case OFPAT_SET_VLAN_VID:
if len(buf) < 8 {
return openflow.ErrInvalidPacketLength
}
r.SetVLANID(binary.BigEndian.Uint16(buf[4:6]))
if err := r.Error(); err != nil {
return err
}
default:
// Do nothing
}
buf = buf[length:]
}
return nil
}
================================================
FILE: openflow/of10/barrier.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"github.com/superkkt/cherry/openflow"
)
type BarrierRequest struct {
openflow.Message
}
func NewBarrierRequest(xid uint32) openflow.BarrierRequest {
return &BarrierRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_BARRIER_REQUEST, xid),
}
}
func (r *BarrierRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type BarrierReply struct {
openflow.Message
}
func (r *BarrierReply) UnmarshalBinary(data []byte) error {
return r.Message.UnmarshalBinary(data)
}
================================================
FILE: openflow/of10/config.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"fmt"
"github.com/superkkt/cherry/openflow"
)
type Config struct {
err error
flags uint16
missSendLength uint16
}
func (r *Config) Flags() openflow.ConfigFlag {
switch r.flags {
case OFPC_FRAG_NORMAL:
return openflow.FragNormal
case OFPC_FRAG_DROP:
return openflow.FragDrop
case OFPC_FRAG_REASM:
return openflow.FragReasm
case OFPC_FRAG_MASK:
return openflow.FragMask
default:
panic(fmt.Sprintf("unexpected config flag: %v", r.flags))
}
}
func (r *Config) SetFlags(flags openflow.ConfigFlag) {
switch flags {
case openflow.FragNormal:
r.flags = OFPC_FRAG_NORMAL
case openflow.FragDrop:
r.flags = OFPC_FRAG_DROP
case openflow.FragReasm:
r.flags = OFPC_FRAG_REASM
case openflow.FragMask:
r.flags = OFPC_FRAG_MASK
default:
r.err = fmt.Errorf("SetFlags: unexpected config flag: %v", flags)
}
}
func (r *Config) MissSendLength() uint16 {
return r.missSendLength
}
func (r *Config) SetMissSendLength(length uint16) {
r.missSendLength = length
}
func (r *Config) Error() error {
return r.err
}
type SetConfig struct {
openflow.Message
Config
}
func NewSetConfig(xid uint32) openflow.SetConfig {
return &SetConfig{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_SET_CONFIG, xid),
Config: Config{
flags: OFPC_FRAG_NORMAL,
missSendLength: 0xFFFF,
},
}
}
func (r *SetConfig) MarshalBinary() ([]byte, error) {
if err := r.Error(); err != nil {
return nil, err
}
v := make([]byte, 4)
binary.BigEndian.PutUint16(v[0:2], r.flags)
binary.BigEndian.PutUint16(v[2:4], r.missSendLength)
r.SetPayload(v)
return r.Message.MarshalBinary()
}
type GetConfigRequest struct {
openflow.Message
}
func NewGetConfigRequest(xid uint32) openflow.GetConfigRequest {
return &GetConfigRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_GET_CONFIG_REQUEST, xid),
}
}
func (r *GetConfigRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type GetConfigReply struct {
openflow.Message
Config
}
func (r *GetConfigReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 4 {
return openflow.ErrInvalidPacketLength
}
r.flags = binary.BigEndian.Uint16(payload[0:2])
r.missSendLength = binary.BigEndian.Uint16(payload[2:4])
return nil
}
================================================
FILE: openflow/of10/const.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
const (
OFPT_HELLO = iota
OFPT_ERROR
OFPT_ECHO_REQUEST
OFPT_ECHO_REPLY
OFPT_VENDOR
OFPT_FEATURES_REQUEST
OFPT_FEATURES_REPLY
OFPT_GET_CONFIG_REQUEST
OFPT_GET_CONFIG_REPLY
OFPT_SET_CONFIG
OFPT_PACKET_IN
OFPT_FLOW_REMOVED
OFPT_PORT_STATUS
OFPT_PACKET_OUT
OFPT_FLOW_MOD
OFPT_PORT_MOD
OFPT_STATS_REQUEST
OFPT_STATS_REPLY
OFPT_BARRIER_REQUEST
OFPT_BARRIER_REPLY
OFPT_QUEUE_GET_CONFIG_REQUEST
OFPT_QUEUE_GET_CONFIG_REPLY
)
const (
OFPAT_OUTPUT = iota /* Output to switch port. */
OFPAT_SET_VLAN_VID /* Set the 802.1q VLAN id. */
OFPAT_SET_VLAN_PCP /* Set the 802.1q priority. */
OFPAT_STRIP_VLAN /* Strip the 802.1q header. */
OFPAT_SET_DL_SRC /* Ethernet source address. */
OFPAT_SET_DL_DST /* Ethernet destination address. */
OFPAT_SET_NW_SRC /* IP source address. */
OFPAT_SET_NW_DST /* IP destination address. */
OFPAT_SET_NW_TOS /* IP ToS (DSCP field, 6 bits). */
OFPAT_SET_TP_SRC /* TCP/UDP source port. */
OFPAT_SET_TP_DST /* TCP/UDP destination port. */
OFPAT_ENQUEUE /* Output to queue. */
OFPAT_VENDOR = 0xffff
)
const (
OFPP_MAX = 0xff00
OFPP_IN_PORT = 0xfff8
OFPP_TABLE = 0xfff9
OFPP_FLOOD = 0xfffb
OFPP_ALL = 0xfffc
OFPP_CONTROLLER = 0xfffd
OFPP_NONE = 0xffff
)
const (
OFPFW_IN_PORT = 1 << 0 /* Switch input port. */
OFPFW_DL_VLAN = 1 << 1 /* VLAN id. */
OFPFW_DL_SRC = 1 << 2 /* Ethernet source address. */
OFPFW_DL_DST = 1 << 3 /* Ethernet destination address. */
OFPFW_DL_TYPE = 1 << 4 /* Ethernet frame type. */
OFPFW_NW_PROTO = 1 << 5 /* IP protocol. */
OFPFW_TP_SRC = 1 << 6 /* TCP/UDP source port. */
OFPFW_TP_DST = 1 << 7 /* TCP/UDP destination port. */
OFPFW_DL_VLAN_PCP = 1 << 20 /* VLAN priority. */
OFPFW_NW_TOS = 1 << 21 /* IP ToS (DSCP field, 6 bits). */
)
const (
OFPPF_10MB_HD = 1 << 0 /* 10 Mb half-duplex rate support. */
OFPPF_10MB_FD = 1 << 1 /* 10 Mb full-duplex rate support. */
OFPPF_100MB_HD = 1 << 2 /* 100 Mb half-duplex rate support. */
OFPPF_100MB_FD = 1 << 3 /* 100 Mb full-duplex rate support. */
OFPPF_1GB_HD = 1 << 4 /* 1 Gb half-duplex rate support. */
OFPPF_1GB_FD = 1 << 5 /* 1 Gb full-duplex rate support. */
OFPPF_10GB_FD = 1 << 6 /* 10 Gb full-duplex rate support. */
OFPPF_COPPER = 1 << 7 /* Copper medium. */
OFPPF_FIBER = 1 << 8 /* Fiber medium. */
OFPPF_AUTONEG = 1 << 9 /* Auto-negotiation. */
OFPPF_PAUSE = 1 << 10 /* Pause. */
OFPPF_PAUSE_ASYM = 1 << 11 /* Asymmetric pause. */
)
const (
OFPPC_PORT_DOWN = 1 << 0
OFPPC_NO_STP = 1 << 1
OFPPC_NO_RECV = 1 << 2
OFPPC_NO_RECV_STP = 1 << 3
OFPPC_NO_FLOOD = 1 << 4
OFPPC_NO_FWD = 1 << 5
OFPPC_NO_PACKET_IN = 1 << 6
)
const (
OFPPS_LINK_DOWN = 1 << 0
OFPPS_STP_LISTEN = 0 << 8 /* Not learning or relaying frames. */
OFPPS_STP_LEARN = 1 << 8 /* Learning but not relaying frames. */
OFPPS_STP_FORWARD = 2 << 8 /* Learning and relaying frames. */
OFPPS_STP_BLOCK = 3 << 8 /* Not part of spanning tree. */
OFPPS_STP_MASK = 3 << 8 /* Bit mask for OFPPS_STP_* values. */
)
const (
OFPFF_SEND_FLOW_REM = 1 << 0 /* Send flow removed message when flow expires or is deleted. */
OFPFF_CHECK_OVERLAP = 1 << 1 /* Check for overlapping entries first. */
OFPFF_EMERG = 1 << 2 /* Remark this is for emergency. */
)
const (
OFP_NO_BUFFER = 0xffffffff
)
const (
OFPFC_ADD = 0 /* New flow. */
OFPFC_MODIFY = 1 /* Modify all matching flows. */
OFPFC_MODIFY_STRICT = 2 /* Modify entry strictly matching wildcards and priority. */
OFPFC_DELETE = 3 /* Delete all matching flows. */
OFPFC_DELETE_STRICT = 4 /* Delete entry strictly matching wildcards and priority. */
)
const (
/* Description of this OpenFlow switch.
* The request body is empty.
* The reply body is struct ofp_desc_stats. */
OFPST_DESC = iota
/* Individual flow statistics.
* The request body is struct ofp_flow_stats_request.
* The reply body is an array of struct ofp_flow_stats. */
OFPST_FLOW
/* Aggregate flow statistics.
* The request body is struct ofp_aggregate_stats_request.
* The reply body is struct ofp_aggregate_stats_reply. */
OFPST_AGGREGATE
/* Flow table statistics.
* The request body is empty.
* The reply body is an array of struct ofp_table_stats. */
OFPST_TABLE
/* Physical port statistics.
* The request body is struct ofp_port_stats_request.
* The reply body is an array of struct ofp_port_stats. */
OFPST_PORT
/* Queue statistics for a port
* The request body defines the port
* The reply body is an array of struct ofp_queue_stats */
OFPST_QUEUE
/* Vendor extension.
* The request and reply bodies begin with a 32-bit vendor ID, which takes
* the same form as in "struct ofp_vendor_header". The request and reply
* bodies are otherwise vendor-defined. */
OFPST_VENDOR = 0xffff
)
const (
OFPC_FRAG_NORMAL = iota /* No special handling for fragments. */
OFPC_FRAG_DROP /* Drop fragments. */
OFPC_FRAG_REASM /* Reassemble (only if OFPC_IP_REASM set). */
OFPC_FRAG_MASK
)
const (
OFPPR_ADD = 0
OFPPR_DELETE = 1
OFPPR_MODIFY = 2
)
================================================
FILE: openflow/of10/description.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"strings"
"github.com/superkkt/cherry/openflow"
)
type DescRequest struct {
openflow.Message
}
func NewDescRequest(xid uint32) openflow.DescRequest {
return &DescRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_STATS_REQUEST, xid),
}
}
func (r *DescRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 4)
binary.BigEndian.PutUint16(v[0:2], OFPST_DESC)
// v[2:4] is flags, but not yet defined
r.SetPayload(v)
return r.Message.MarshalBinary()
}
type DescReply struct {
openflow.Message
manufacturer string
hardware string
software string
serial string
description string
}
func (r DescReply) Manufacturer() string {
return r.manufacturer
}
func (r DescReply) Hardware() string {
return r.hardware
}
func (r DescReply) Software() string {
return r.software
}
func (r DescReply) Serial() string {
return r.serial
}
func (r DescReply) Description() string {
return r.description
}
func (r *DescReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 1060 {
return openflow.ErrInvalidPacketLength
}
// payload[0:4] is type and flag of ofp_stats_reply
r.manufacturer = strings.TrimRight(string(payload[4:260]), "\x00")
r.hardware = strings.TrimRight(string(payload[260:516]), "\x00")
r.software = strings.TrimRight(string(payload[516:772]), "\x00")
r.serial = strings.TrimRight(string(payload[772:804]), "\x00")
r.description = strings.TrimRight(string(payload[804:1060]), "\x00")
return nil
}
================================================
FILE: openflow/of10/echo.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"github.com/superkkt/cherry/openflow"
)
func NewEchoRequest(xid uint32) openflow.EchoRequest {
return &openflow.BaseEcho{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_ECHO_REQUEST, xid),
}
}
func NewEchoReply(xid uint32) openflow.EchoReply {
return &openflow.BaseEcho{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_ECHO_REPLY, xid),
}
}
================================================
FILE: openflow/of10/factory.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"errors"
"fmt"
"sync/atomic"
"github.com/superkkt/cherry/openflow"
)
// Concrete factory
type Factory struct {
xid uint32
}
func NewFactory() openflow.Factory {
return &Factory{}
}
func (r *Factory) ProtocolVersion() uint8 {
return openflow.OF10_VERSION
}
func (r *Factory) getTransactionID() uint32 {
// Transaction ID will be started from 1, not 0.
return atomic.AddUint32(&r.xid, 1)
}
func (r *Factory) NewHello() (openflow.Hello, error) {
return NewHello(r.getTransactionID()), nil
}
func (r *Factory) NewEchoRequest() (openflow.EchoRequest, error) {
return NewEchoRequest(r.getTransactionID()), nil
}
func (r *Factory) NewEchoReply() (openflow.EchoReply, error) {
return NewEchoReply(r.getTransactionID()), nil
}
func (r *Factory) NewAction() (openflow.Action, error) {
return NewAction(), nil
}
func (r *Factory) NewMatch() (openflow.Match, error) {
return NewMatch(), nil
}
func (r *Factory) NewBarrierRequest() (openflow.BarrierRequest, error) {
return NewBarrierRequest(r.getTransactionID()), nil
}
func (r *Factory) NewBarrierReply() (openflow.BarrierReply, error) {
return new(BarrierReply), nil
}
func (r *Factory) NewSetConfig() (openflow.SetConfig, error) {
return NewSetConfig(r.getTransactionID()), nil
}
func (r *Factory) NewGetConfigRequest() (openflow.GetConfigRequest, error) {
return NewGetConfigRequest(r.getTransactionID()), nil
}
func (r *Factory) NewGetConfigReply() (openflow.GetConfigReply, error) {
return new(GetConfigReply), nil
}
func (r *Factory) NewFeaturesRequest() (openflow.FeaturesRequest, error) {
return NewFeaturesRequest(r.getTransactionID()), nil
}
func (r *Factory) NewFeaturesReply() (openflow.FeaturesReply, error) {
return new(FeaturesReply), nil
}
func getFlowModCmd(cmd openflow.FlowModCmd) uint16 {
var c uint16
switch cmd {
case openflow.FlowAdd:
c = OFPFC_ADD
case openflow.FlowModify:
c = OFPFC_MODIFY
case openflow.FlowDelete:
c = OFPFC_DELETE
default:
panic(fmt.Sprintf("unexpected FlowModCmd: %v", cmd))
}
return c
}
func (r *Factory) NewFlowMod(cmd openflow.FlowModCmd) (openflow.FlowMod, error) {
return NewFlowMod(r.getTransactionID(), getFlowModCmd(cmd)), nil
}
func (r *Factory) NewFlowRemoved() (openflow.FlowRemoved, error) {
return new(FlowRemoved), nil
}
func (r *Factory) NewPacketIn() (openflow.PacketIn, error) {
return new(PacketIn), nil
}
func (r *Factory) NewPacketOut() (openflow.PacketOut, error) {
return NewPacketOut(r.getTransactionID()), nil
}
func (r *Factory) NewPortStatus() (openflow.PortStatus, error) {
return new(PortStatus), nil
}
func (r *Factory) NewDescRequest() (openflow.DescRequest, error) {
return NewDescRequest(r.getTransactionID()), nil
}
func (r *Factory) NewDescReply() (openflow.DescReply, error) {
return new(DescReply), nil
}
func (r *Factory) NewFlowStatsRequest() (openflow.FlowStatsRequest, error) {
return NewFlowStatsRequest(r.getTransactionID()), nil
}
// TODO: NewFlowStatsReply() (openflow.FlowStatsReply, error) {
func (r *Factory) NewPortDescRequest() (openflow.PortDescRequest, error) {
return nil, errors.New("of10 does not support PortDescRequest")
}
func (r *Factory) NewPortDescReply() (openflow.PortDescReply, error) {
return nil, errors.New("of10 does not support PortDescReply")
}
func (r *Factory) NewTableFeaturesRequest() (openflow.TableFeaturesRequest, error) {
return nil, errors.New("of10 does not support TableFeaturesRequest")
}
func (r *Factory) NewError() (openflow.Error, error) {
return new(openflow.BaseError), nil
}
// TODO: NewTableFeaturesReply() (TableFeaturesReply, error)
func (r *Factory) NewInstruction() (openflow.Instruction, error) {
return new(Instruction), nil
}
func (r *Factory) NewQueueGetConfigRequest() (openflow.QueueGetConfigRequest, error) {
return NewQueueGetConfigRequest(r.getTransactionID()), nil
}
================================================
FILE: openflow/of10/features.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type FeaturesRequest struct {
openflow.Message
}
func NewFeaturesRequest(xid uint32) openflow.FeaturesRequest {
return &FeaturesRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_FEATURES_REQUEST, xid),
}
}
func (r *FeaturesRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type FeaturesReply struct {
openflow.Message
dpid uint64
numBuffers uint32
numTables uint8
capabilities uint32
actions uint32
ports []openflow.Port
}
func (r FeaturesReply) DPID() uint64 {
return r.dpid
}
func (r FeaturesReply) NumBuffers() uint32 {
return r.numBuffers
}
func (r FeaturesReply) NumTables() uint8 {
return r.numTables
}
func (r FeaturesReply) Capabilities() uint32 {
return r.capabilities
}
func (r FeaturesReply) Actions() uint32 {
return r.actions
}
func (r FeaturesReply) Ports() []openflow.Port {
return r.ports
}
func (r FeaturesReply) AuxID() uint8 {
// OpenFlow 1.0 does not have auxilary ID
return 0
}
func (r *FeaturesReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 24 {
return openflow.ErrInvalidPacketLength
}
r.dpid = binary.BigEndian.Uint64(payload[0:8])
r.numBuffers = binary.BigEndian.Uint32(payload[8:12])
r.numTables = payload[12]
r.capabilities = binary.BigEndian.Uint32(payload[16:20])
r.actions = binary.BigEndian.Uint32(payload[20:24])
nPorts := (len(payload) - 24) / 48
if nPorts == 0 {
return nil
}
r.ports = make([]openflow.Port, nPorts)
for i := 0; i < nPorts; i++ {
buf := payload[24+i*48:]
r.ports[i] = new(Port)
if err := r.ports[i].UnmarshalBinary(buf[0:48]); err != nil {
return err
}
}
return nil
}
================================================
FILE: openflow/of10/flow_mod.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"errors"
"github.com/superkkt/cherry/openflow"
)
type FlowMod struct {
err error
openflow.Message
command uint16
cookie uint64
idleTimeout uint16
hardTimeout uint16
priority uint16
match openflow.Match
instruction openflow.Instruction
outPort openflow.OutPort
}
func NewFlowMod(xid uint32, cmd uint16) openflow.FlowMod {
// Default out_port value is OFPP_NONE (OFPP_ANY)
outPort := openflow.NewOutPort()
outPort.SetNone()
return &FlowMod{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_FLOW_MOD, xid),
command: cmd,
outPort: outPort,
}
}
func (r *FlowMod) Error() error {
return r.err
}
func (r *FlowMod) Cookie() uint64 {
return r.cookie
}
func (r *FlowMod) SetCookie(cookie uint64) {
r.cookie = cookie
}
func (r *FlowMod) CookieMask() uint64 {
// OpenFlow 1.0 does not have the cookie mask
return 0
}
func (r *FlowMod) SetCookieMask(mask uint64) {
// OpenFlow 1.0 does not have the cookie mask
}
func (r *FlowMod) TableID() uint8 {
// OpenFlow 1.0 does not have table ID
return 0
}
func (r *FlowMod) SetTableID(id uint8) {
// OpenFlow 1.0 does not have table ID
}
func (r *FlowMod) IdleTimeout() uint16 {
return r.idleTimeout
}
func (r *FlowMod) SetIdleTimeout(timeout uint16) {
r.idleTimeout = timeout
}
func (r *FlowMod) HardTimeout() uint16 {
return r.hardTimeout
}
func (r *FlowMod) SetHardTimeout(timeout uint16) {
r.hardTimeout = timeout
}
func (r *FlowMod) Priority() uint16 {
return r.priority
}
func (r *FlowMod) SetPriority(priority uint16) {
r.priority = priority
}
func (r *FlowMod) FlowMatch() openflow.Match {
return r.match
}
func (r *FlowMod) SetFlowMatch(match openflow.Match) {
if match == nil {
panic("flow match is nil")
}
r.match = match
}
func (r *FlowMod) FlowInstruction() openflow.Instruction {
return r.instruction
}
func (r *FlowMod) SetFlowInstruction(inst openflow.Instruction) {
if inst == nil {
panic("flow instruction is nil")
}
r.instruction = inst
}
func (r *FlowMod) OutPort() openflow.OutPort {
return r.outPort
}
func (r *FlowMod) SetOutPort(p openflow.OutPort) {
r.outPort = p
}
func (r *FlowMod) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
v := make([]byte, 24)
binary.BigEndian.PutUint64(v[0:8], r.cookie)
binary.BigEndian.PutUint16(v[8:10], r.command)
binary.BigEndian.PutUint16(v[10:12], r.idleTimeout)
binary.BigEndian.PutUint16(v[12:14], r.hardTimeout)
binary.BigEndian.PutUint16(v[14:16], r.priority)
binary.BigEndian.PutUint32(v[16:20], OFP_NO_BUFFER)
if r.outPort.IsNone() {
binary.BigEndian.PutUint16(v[20:22], OFPP_NONE)
} else {
binary.BigEndian.PutUint16(v[20:22], uint16(r.outPort.Value()))
}
binary.BigEndian.PutUint16(v[22:24], OFPFF_SEND_FLOW_REM)
if r.match == nil {
return nil, errors.New("empty flow match")
}
result, err := r.match.MarshalBinary()
if err != nil {
return nil, err
}
result = append(result, v...)
if r.instruction != nil {
instruction, err := r.instruction.MarshalBinary()
if err != nil {
return nil, err
}
result = append(result, instruction...)
}
r.SetPayload(result)
return r.Message.MarshalBinary()
}
================================================
FILE: openflow/of10/flow_removed.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type FlowRemoved struct {
openflow.Message
match openflow.Match
cookie uint64
priority uint16
reason uint8
durationSec uint32
durationNanoSec uint32
idleTimeout uint16
packetCount uint64
byteCount uint64
}
func (r FlowRemoved) Cookie() uint64 {
return r.cookie
}
func (r FlowRemoved) Priority() uint16 {
return r.priority
}
func (r FlowRemoved) Reason() uint8 {
return r.reason
}
func (r FlowRemoved) TableID() uint8 {
// OpenFlow 1.0 does not have table ID
return 0
}
func (r FlowRemoved) DurationSec() uint32 {
return r.durationSec
}
func (r FlowRemoved) DurationNanoSec() uint32 {
return r.durationNanoSec
}
func (r FlowRemoved) IdleTimeout() uint16 {
return r.idleTimeout
}
func (r FlowRemoved) HardTimeout() uint16 {
// OpenFlow 1.0 does not have hard timeout value in the flow removed message
return 0
}
func (r FlowRemoved) PacketCount() uint64 {
return r.packetCount
}
func (r FlowRemoved) ByteCount() uint64 {
return r.byteCount
}
func (r FlowRemoved) Match() openflow.Match {
return r.match
}
func (r *FlowRemoved) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 80 {
return openflow.ErrInvalidPacketLength
}
r.match = NewMatch()
if err := r.match.UnmarshalBinary(payload[0:40]); err != nil {
return err
}
r.cookie = binary.BigEndian.Uint64(payload[40:48])
r.priority = binary.BigEndian.Uint16(payload[48:50])
r.reason = payload[50]
// payload[51] is padding
r.durationSec = binary.BigEndian.Uint32(payload[52:56])
r.durationNanoSec = binary.BigEndian.Uint32(payload[56:60])
r.idleTimeout = binary.BigEndian.Uint16(payload[60:62])
// payload[62:64] is padding
r.packetCount = binary.BigEndian.Uint64(payload[64:72])
r.byteCount = binary.BigEndian.Uint64(payload[72:80])
return nil
}
================================================
FILE: openflow/of10/flow_stats.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"errors"
"github.com/superkkt/cherry/openflow"
)
type FlowStatsRequest struct {
err error
openflow.Message
match openflow.Match
tableID uint8
}
func NewFlowStatsRequest(xid uint32) openflow.FlowStatsRequest {
return &FlowStatsRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_STATS_REQUEST, xid),
}
}
func (r *FlowStatsRequest) Error() error {
return r.err
}
func (r *FlowStatsRequest) Cookie() uint64 {
// OpenFlow 1.0 does not have cookie
return 0
}
func (r *FlowStatsRequest) SetCookie(cookie uint64) {
// OpenFlow 1.0 does not have cookie
}
func (r *FlowStatsRequest) CookieMask() uint64 {
// OpenFlow 1.0 does not have cookie
return 0
}
func (r *FlowStatsRequest) SetCookieMask(mask uint64) {
// OpenFlow 1.0 does not have cookie
}
func (r *FlowStatsRequest) Match() openflow.Match {
return r.match
}
func (r *FlowStatsRequest) SetMatch(match openflow.Match) {
if match == nil {
panic("match is nil")
}
r.match = match
}
func (r *FlowStatsRequest) TableID() uint8 {
return r.tableID
}
// 0xFF means all table
func (r *FlowStatsRequest) SetTableID(id uint8) {
r.tableID = id
}
// TODO: Need testing
func (r *FlowStatsRequest) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
v := make([]byte, 48)
binary.BigEndian.PutUint16(v[0:2], OFPST_FLOW)
// v[2:4] is flags, but not yet defined
if r.match == nil {
return nil, errors.New("empty flow match")
}
match, err := r.match.MarshalBinary()
if err != nil {
return nil, err
}
copy(v[4:44], match)
v[44] = r.tableID
// v[45] is padding
binary.BigEndian.PutUint16(v[46:48], OFPP_NONE)
r.SetPayload(v)
return r.Message.MarshalBinary()
}
// TODO: Implement FlowStatsReply
================================================
FILE: openflow/of10/hello.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"github.com/superkkt/cherry/openflow"
)
func NewHello(xid uint32) openflow.Hello {
return &openflow.BaseHello{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_HELLO, xid),
}
}
================================================
FILE: openflow/of10/instruction.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"errors"
"github.com/superkkt/cherry/openflow"
)
type Instruction struct {
err error
action openflow.Action
}
func (r *Instruction) Error() error {
return r.err
}
func (r *Instruction) GotoTable(tableID uint8) {
// OpenFlow 1.0 does not support GotoTable
}
func (r *Instruction) WriteAction(act openflow.Action) {
if act == nil {
panic("act is nil")
}
r.action = act
}
func (r *Instruction) ApplyAction(act openflow.Action) {
if act == nil {
panic("act is nil")
}
r.action = act
}
func (r *Instruction) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
if r.action == nil {
return nil, errors.New("empty action of an instruction")
}
return r.action.MarshalBinary()
}
================================================
FILE: openflow/of10/match.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"net"
"github.com/superkkt/cherry/openflow"
"github.com/pkg/errors"
)
type Wildcard struct {
InPort bool /* Switch input port. */
VLANID bool /* VLAN id. */
SrcMAC bool /* Ethernet source address. */
DstMAC bool /* Ethernet destination address. */
EtherType bool /* Ethernet frame type. */
Protocol bool /* IP protocol. */
SrcPort bool /* TCP/UDP source port. */
DstPort bool /* TCP/UDP destination port. */
// IP source address wildcard bit count. 0 is exact match,
// 1 ignores the LSB, 2 ignores the 2 least-significant bits, ...,
// 32 and higher wildcard the entire field.
SrcIP uint8
DstIP uint8
VLANPriority bool /* VLAN priority. */
}
func newWildcardAll() *Wildcard {
return &Wildcard{
InPort: true,
VLANID: true,
SrcMAC: true,
DstMAC: true,
EtherType: true,
Protocol: true,
SrcPort: true,
DstPort: true,
SrcIP: 32,
DstIP: 32,
VLANPriority: true,
}
}
func (r *Wildcard) MarshalBinary() ([]byte, error) {
// We only support IPv4 yet
if r.SrcIP > 32 || r.DstIP > 32 {
return nil, errors.New("invalid IP address wildcard bit count")
}
var v uint32 = 0
if r.InPort {
v = v | OFPFW_IN_PORT
}
if r.VLANID {
v = v | OFPFW_DL_VLAN
}
if r.SrcMAC {
v = v | OFPFW_DL_SRC
}
if r.DstMAC {
v = v | OFPFW_DL_DST
}
if r.EtherType {
v = v | OFPFW_DL_TYPE
}
if r.Protocol {
v = v | OFPFW_NW_PROTO
}
if r.SrcPort {
v = v | OFPFW_TP_SRC
}
if r.DstPort {
v = v | OFPFW_TP_DST
}
if r.SrcIP > 0 {
v = v | (uint32(r.SrcIP) << 8)
}
if r.DstIP > 0 {
v = v | (uint32(r.DstIP) << 14)
}
if r.VLANPriority {
v = v | OFPFW_DL_VLAN_PCP
}
// ToS is always wildcarded
v = v | OFPFW_NW_TOS
data := make([]byte, 4)
binary.BigEndian.PutUint32(data[0:4], v)
return data, nil
}
func (r *Wildcard) UnmarshalBinary(data []byte) error {
if data == nil || len(data) < 4 {
return openflow.ErrInvalidPacketLength
}
w := binary.BigEndian.Uint32(data[0:4])
if w&OFPFW_IN_PORT != 0 {
r.InPort = true
}
if w&OFPFW_DL_VLAN != 0 {
r.VLANID = true
}
if w&OFPFW_DL_SRC != 0 {
r.SrcMAC = true
}
if w&OFPFW_DL_DST != 0 {
r.DstMAC = true
}
if w&OFPFW_DL_TYPE != 0 {
r.EtherType = true
}
if w&OFPFW_NW_PROTO != 0 {
r.Protocol = true
}
if w&OFPFW_TP_SRC != 0 {
r.SrcPort = true
}
if w&OFPFW_TP_DST != 0 {
r.DstPort = true
}
r.SrcIP = uint8((w & (uint32(0x3F) << 8)) >> 8)
r.DstIP = uint8((w & (uint32(0x3F) << 14)) >> 14)
if w&OFPFW_DL_VLAN_PCP != 0 {
r.VLANPriority = true
}
return nil
}
type Match struct {
err error
wildcards *Wildcard
inPort uint16
srcMAC net.HardwareAddr
dstMAC net.HardwareAddr
vlanID uint16
vlanPriority uint8
etherType uint16
protocol uint8
srcIP net.IP
dstIP net.IP
srcPort uint16
dstPort uint16
}
// NewMatch returns a Match whose fields are all wildcarded
func NewMatch() openflow.Match {
return &Match{
wildcards: newWildcardAll(),
srcMAC: net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0}),
dstMAC: net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0}),
srcIP: net.IPv4zero,
dstIP: net.IPv4zero,
}
}
func (r *Match) Error() error {
return r.err
}
func (r *Match) SetWildcardSrcPort() {
r.srcPort = 0
r.wildcards.SrcPort = true
}
func (r *Match) SetSrcPort(p uint16) {
// IPv4?
if r.etherType != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetSrcPort")
return
}
// TCP or UDP?
if r.protocol != 0x06 && r.protocol != 0x11 {
r.err = errors.Wrap(openflow.ErrUnsupportedIPProtocol, "SetSrcPort")
return
}
r.srcPort = p
r.wildcards.SrcPort = false
}
func (r *Match) SrcPort() (wildcard bool, port uint16) {
return r.wildcards.SrcPort, r.srcPort
}
func (r *Match) SetWildcardDstPort() {
r.dstPort = 0
r.wildcards.DstPort = true
}
func (r *Match) SetDstPort(p uint16) {
// IPv4?
if r.etherType != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetDstPort")
return
}
// TCP or UDP?
if r.protocol != 0x06 && r.protocol != 0x11 {
r.err = errors.Wrap(openflow.ErrUnsupportedIPProtocol, "SetDstPort")
return
}
r.dstPort = p
r.wildcards.DstPort = false
}
func (r *Match) DstPort() (wildcard bool, port uint16) {
return r.wildcards.DstPort, r.dstPort
}
func (r *Match) SetWildcardVLANID() {
r.vlanID = 0
r.wildcards.VLANID = true
}
func (r *Match) SetVLANID(id uint16) {
r.vlanID = id
r.wildcards.VLANID = false
}
func (r *Match) VLANID() (wildcard bool, vlanID uint16) {
return r.wildcards.VLANID, r.vlanID
}
func (r *Match) SetWildcardVLANPriority() {
r.vlanPriority = 0
r.wildcards.VLANPriority = true
}
func (r *Match) SetVLANPriority(p uint8) {
r.vlanPriority = p
r.wildcards.VLANPriority = false
}
func (r *Match) VLANPriority() (wildcard bool, priority uint8) {
return r.wildcards.VLANPriority, r.vlanPriority
}
func (r *Match) SetWildcardIPProtocol() {
r.protocol = 0
r.wildcards.Protocol = true
}
func (r *Match) SetIPProtocol(p uint8) {
// IPv4?
if r.etherType != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetIPProtocol")
return
}
r.protocol = p
r.wildcards.Protocol = false
}
func (r *Match) IPProtocol() (wildcard bool, protocol uint8) {
return r.wildcards.Protocol, r.protocol
}
func (r *Match) SetWildcardInPort() {
r.inPort = 0
r.wildcards.InPort = true
}
func (r *Match) SetInPort(port openflow.InPort) {
r.inPort = uint16(port.Value())
r.wildcards.InPort = false
}
func (r *Match) InPort() (wildcard bool, inport openflow.InPort) {
v := openflow.NewInPort()
v.SetValue(uint32(r.inPort))
return r.wildcards.InPort, v
}
func (r *Match) SetWildcardSrcMAC() {
r.srcMAC = net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
r.wildcards.SrcMAC = true
}
func (r *Match) SetSrcMAC(mac net.HardwareAddr) {
if mac == nil || len(mac) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidMACAddress, "SetSrcMAC")
return
}
r.srcMAC = make([]byte, len(mac))
copy(r.srcMAC, mac)
r.wildcards.SrcMAC = false
}
func (r *Match) SrcMAC() (wildcard bool, mac net.HardwareAddr) {
return r.wildcards.SrcMAC, r.srcMAC
}
func (r *Match) SetWildcardDstMAC() {
r.dstMAC = net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
r.wildcards.DstMAC = true
}
func (r *Match) SetDstMAC(mac net.HardwareAddr) {
if mac == nil || len(mac) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidMACAddress, "SetDstMAC")
return
}
r.dstMAC = make([]byte, len(mac))
copy(r.dstMAC, mac)
r.wildcards.DstMAC = false
}
func (r *Match) DstMAC() (wildcard bool, mac net.HardwareAddr) {
return r.wildcards.DstMAC, r.dstMAC
}
func (r *Match) SetSrcIP(ip *net.IPNet) {
if ip == nil {
panic("ip is nil")
}
if ip.IP == nil || len(ip.IP) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidIPAddress, "SetSrcIP")
return
}
// IPv4?
if r.etherType != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetSrcIP")
return
}
r.srcIP = make([]byte, len(ip.IP))
copy(r.srcIP, ip.IP)
netmaskBits, _ := ip.Mask.Size()
if netmaskBits >= 32 {
r.wildcards.SrcIP = 0
} else {
r.wildcards.SrcIP = uint8(32 - netmaskBits)
}
}
func (r *Match) SrcIP() *net.IPNet {
ip := make([]byte, len(r.srcIP))
copy(ip, r.srcIP)
return &net.IPNet{
IP: ip,
Mask: net.CIDRMask(32-int(r.wildcards.SrcIP), 32),
}
}
func (r *Match) SetDstIP(ip *net.IPNet) {
if ip == nil {
panic("ip is nil")
}
if ip.IP == nil || len(ip.IP) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidIPAddress, "SetDstIP")
return
}
// IPv4?
if r.etherType != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetDstIP")
return
}
r.dstIP = make([]byte, len(ip.IP))
copy(r.dstIP, ip.IP)
netmaskBits, _ := ip.Mask.Size()
if netmaskBits >= 32 {
r.wildcards.DstIP = 0
} else {
r.wildcards.DstIP = uint8(32 - netmaskBits)
}
}
func (r *Match) DstIP() *net.IPNet {
ip := make([]byte, len(r.dstIP))
copy(ip, r.dstIP)
return &net.IPNet{
IP: ip,
Mask: net.CIDRMask(32-int(r.wildcards.DstIP), 32),
}
}
func (r *Match) SetWildcardEtherType() {
r.etherType = 0
r.wildcards.EtherType = true
}
func (r *Match) SetEtherType(t uint16) {
r.etherType = t
r.wildcards.EtherType = false
}
func (r *Match) EtherType() (wildcard bool, etherType uint16) {
return r.wildcards.EtherType, r.etherType
}
func (r *Match) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
if r.srcMAC == nil {
return nil, errors.New("empty source MAC address")
}
if r.dstMAC == nil {
return nil, errors.New("empty destination MAC address")
}
if r.srcIP == nil {
return nil, errors.New("empty source IP address")
}
if r.dstIP == nil {
return nil, errors.New("empty destination IP address")
}
wildcard, err := r.wildcards.MarshalBinary()
if err != nil {
return nil, err
}
data := make([]byte, 40)
copy(data[0:4], wildcard)
binary.BigEndian.PutUint16(data[4:6], r.inPort)
copy(data[6:12], r.srcMAC)
copy(data[12:18], r.dstMAC)
binary.BigEndian.PutUint16(data[18:20], r.vlanID)
data[20] = r.vlanPriority
// data[21] = padding
binary.BigEndian.PutUint16(data[22:24], r.etherType)
data[25] = r.protocol
// data[26:28] = padding
srcIP := r.srcIP.To4()
if srcIP == nil {
return nil, errors.New("source IP address is not an IPv4 address")
}
copy(data[28:32], srcIP)
dstIP := r.dstIP.To4()
if dstIP == nil {
return nil, errors.New("destination IP address is not an IPv4 address")
}
copy(data[32:36], dstIP)
binary.BigEndian.PutUint16(data[36:38], r.srcPort)
binary.BigEndian.PutUint16(data[38:40], r.dstPort)
return data, nil
}
func (r *Match) UnmarshalBinary(data []byte) error {
if len(data) < 40 {
return openflow.ErrInvalidPacketLength
}
r.wildcards = new(Wildcard)
if err := r.wildcards.UnmarshalBinary(data[0:4]); err != nil {
return err
}
r.inPort = binary.BigEndian.Uint16(data[4:6])
r.srcMAC = make(net.HardwareAddr, 6)
copy(r.srcMAC, data[6:12])
r.dstMAC = make(net.HardwareAddr, 6)
copy(r.dstMAC, data[12:18])
r.vlanID = binary.BigEndian.Uint16(data[18:20])
r.vlanPriority = data[20]
// data[21] = padding
r.etherType = binary.BigEndian.Uint16(data[22:24])
r.protocol = data[25]
// data[26:28] = padding
r.srcIP = net.IPv4(data[28], data[29], data[30], data[31])
r.dstIP = net.IPv4(data[32], data[33], data[34], data[35])
r.srcPort = binary.BigEndian.Uint16(data[36:38])
r.dstPort = binary.BigEndian.Uint16(data[38:40])
return nil
}
================================================
FILE: openflow/of10/packet_in.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type PacketIn struct {
openflow.Message
bufferID uint32
length uint16
inPort uint16
reason uint8
data []byte
}
func (r PacketIn) BufferID() uint32 {
return r.bufferID
}
func (r PacketIn) InPort() uint32 {
return uint32(r.inPort)
}
func (r PacketIn) Data() []byte {
return r.data
}
func (r PacketIn) Length() uint16 {
return r.length
}
func (r PacketIn) TableID() uint8 {
// OpenFlow 1.0 does not have table ID
return 0
}
func (r PacketIn) Reason() uint8 {
return r.reason
}
func (r PacketIn) Cookie() uint64 {
// OpenFlow 1.0 does not have cookie
return 0
}
func (r *PacketIn) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 10 {
return openflow.ErrInvalidPacketLength
}
r.bufferID = binary.BigEndian.Uint32(payload[0:4])
r.length = binary.BigEndian.Uint16(payload[4:6])
r.inPort = binary.BigEndian.Uint16(payload[6:8])
r.reason = payload[8]
// payload[9] is padding
if len(payload) >= 10 {
// TODO: Check data size by comparing with r.Length
r.data = payload[10:]
}
return nil
}
================================================
FILE: openflow/of10/packet_out.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type PacketOut struct {
err error
openflow.Message
inPort openflow.InPort
action openflow.Action
data []byte
}
func NewPacketOut(xid uint32) openflow.PacketOut {
return &PacketOut{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_PACKET_OUT, xid),
}
}
func (r *PacketOut) Error() error {
return r.err
}
func (r *PacketOut) InPort() openflow.InPort {
return r.inPort
}
func (r *PacketOut) SetInPort(port openflow.InPort) {
r.inPort = port
}
func (r *PacketOut) Action() openflow.Action {
return r.action
}
func (r *PacketOut) SetAction(action openflow.Action) {
if action == nil {
panic("action is nil")
}
r.action = action
}
func (r *PacketOut) Data() []byte {
return r.data
}
func (r *PacketOut) SetData(data []byte) {
if data == nil {
panic("data is nil")
}
r.data = data
}
func (r *PacketOut) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
// XXX:
// Dell S4810 switch does not support OFPAT_SET_DL_SRC and
// OFPAT_SET_DL_DST actions on a packet out message
action := make([]byte, 0)
if r.action != nil {
a, err := r.action.MarshalBinary()
if err != nil {
return nil, err
}
action = append(action, a...)
}
v := make([]byte, 8)
binary.BigEndian.PutUint32(v[0:4], OFP_NO_BUFFER)
port := uint16(r.inPort.Value())
if r.inPort.IsController() {
port = OFPP_CONTROLLER
}
binary.BigEndian.PutUint16(v[4:6], port)
binary.BigEndian.PutUint16(v[6:8], uint16(len(action)))
v = append(v, action...)
if r.data != nil && len(r.data) > 0 {
v = append(v, r.data...)
}
r.SetPayload(v)
return r.Message.MarshalBinary()
}
================================================
FILE: openflow/of10/port.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"net"
"strings"
"github.com/superkkt/cherry/openflow"
)
type Port struct {
number uint16
mac net.HardwareAddr
name string
// Bitmap of OFPPC_* flags
config uint32
// Bitmap of OFPPS_* flags
state uint32
//
// Bitmaps of OFPPF_* that describe features. All bits zeroed if unsupported or unavailable.
//
current, advertised, supported, peer uint32
}
func (r Port) Number() uint32 {
return uint32(r.number)
}
func (r Port) MAC() net.HardwareAddr {
return r.mac
}
func (r Port) Name() string {
return r.name
}
func (r Port) IsPortDown() bool {
if r.config&OFPPC_PORT_DOWN != 0 {
return true
}
return false
}
func (r Port) IsLinkDown() bool {
if r.state&OFPPS_LINK_DOWN != 0 {
return true
}
return false
}
func (r Port) IsCopper() bool {
return r.current&OFPPF_COPPER != 0
}
func (r Port) IsFiber() bool {
return r.current&OFPPF_FIBER != 0
}
func (r Port) IsAutoNego() bool {
return r.current&OFPPF_AUTONEG != 0
}
func (r Port) Speed() uint64 {
switch {
case r.current&OFPPF_10MB_HD != 0:
return 5
case r.current&OFPPF_10MB_FD != 0:
return 10
case r.current&OFPPF_100MB_HD != 0:
return 50
case r.current&OFPPF_100MB_FD != 0:
return 100
case r.current&OFPPF_1GB_HD != 0:
return 500
case r.current&OFPPF_1GB_FD != 0:
return 1000
case r.current&OFPPF_10GB_FD != 0:
return 10000
default:
return 0
}
}
func (r *Port) UnmarshalBinary(data []byte) error {
if len(data) < 48 {
return openflow.ErrInvalidPacketLength
}
r.number = binary.BigEndian.Uint16(data[0:2])
r.mac = make(net.HardwareAddr, 6)
copy(r.mac, data[2:8])
r.name = strings.TrimRight(string(data[8:24]), "\x00")
r.config = binary.BigEndian.Uint32(data[24:28])
r.state = binary.BigEndian.Uint32(data[28:32])
r.current = binary.BigEndian.Uint32(data[32:36])
r.advertised = binary.BigEndian.Uint32(data[36:40])
r.supported = binary.BigEndian.Uint32(data[40:44])
r.peer = binary.BigEndian.Uint32(data[44:48])
return nil
}
================================================
FILE: openflow/of10/port_status.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"github.com/superkkt/cherry/openflow"
)
type PortStatus struct {
openflow.Message
reason uint8
port openflow.Port
}
func (r PortStatus) Reason() openflow.PortReason {
switch r.reason {
case OFPPR_ADD:
return openflow.PortAdded
case OFPPR_DELETE:
return openflow.PortDeleted
case OFPPR_MODIFY:
return openflow.PortModified
default:
return openflow.PortReason(r.reason)
}
}
func (r PortStatus) Port() openflow.Port {
return r.port
}
func (r *PortStatus) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 56 {
return openflow.ErrInvalidPacketLength
}
r.reason = payload[0]
// payload[1:8] is padding
r.port = new(Port)
if err := r.port.UnmarshalBinary(payload[8:]); err != nil {
return err
}
return nil
}
================================================
FILE: openflow/of10/queue.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of10
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type QueueGetConfigRequest struct {
openflow.Message
port openflow.OutPort
}
func NewQueueGetConfigRequest(xid uint32) openflow.QueueGetConfigRequest {
return &QueueGetConfigRequest{
Message: openflow.NewMessage(openflow.OF10_VERSION, OFPT_QUEUE_GET_CONFIG_REQUEST, xid),
}
}
func (r *QueueGetConfigRequest) Port() openflow.OutPort {
return r.port
}
func (r *QueueGetConfigRequest) SetPort(p openflow.OutPort) {
r.port = p
}
func (r *QueueGetConfigRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 4)
binary.BigEndian.PutUint16(v[0:2], uint16(r.port.Value()))
// v[2:4] is padding
r.SetPayload(v)
return r.Message.MarshalBinary()
}
// QueueGetConfigReply implementations by ksang:
type QueueProperty struct {
typ openflow.PropertyType
length uint16
rate uint16
}
func NewQueueProperty() openflow.QueueProperty {
return &QueueProperty{}
}
func (r *QueueProperty) Type() openflow.PropertyType {
return r.typ
}
func (r *QueueProperty) Length() uint16 {
return r.length
}
func (r *QueueProperty) Rate() (uint16, error) {
// openflow 1.0 only supports min rate
if r.typ != openflow.OFPQT_MIN_RATE {
return 0x0, openflow.ErrInvalidPropertyMethod
}
return r.rate, nil
}
func (r *QueueProperty) Experimenter() (uint32, error) {
// openflow 1.0 doesn't support Experimenter
return 0x0, openflow.ErrInvalidPropertyMethod
}
func (r *QueueProperty) Data() []byte {
return nil
}
func (r *QueueProperty) UnmarshalBinary(data []byte) error {
if len(data) < 8 {
return openflow.ErrInvalidPacketLength
}
r.typ = openflow.PropertyType(binary.BigEndian.Uint16(data[0:2]))
r.length = binary.BigEndian.Uint16(data[2:4])
// data[4:8] is pad
if r.typ == openflow.OFPQT_NONE {
return nil
}
if len(data) < int(r.length) || int(r.length) < 16 {
return openflow.ErrInvalidPacketLength
}
r.rate = binary.BigEndian.Uint16(data[8:10])
// data[10:16] is pad
return nil
}
type Queue struct {
id uint32
length uint16
property []openflow.QueueProperty
}
func (r *Queue) ID() uint32 {
return r.id
}
func (r *Queue) Port() uint32 {
// openflow 1.0 dones't have port info in queue structure
return 0x0
}
func (r *Queue) Length() uint16 {
return r.length
}
func (r *Queue) Property() []openflow.QueueProperty {
return r.property
}
func (r *Queue) UnmarshalBinary(data []byte) error {
if len(data) < 8 {
return openflow.ErrInvalidPacketLength
}
r.id = binary.BigEndian.Uint32(data[0:4])
r.length = binary.BigEndian.Uint16(data[4:6])
// data[6:8] is pad
if len(data) < int(r.length) || int(r.length) < 8 {
return openflow.ErrInvalidPacketLength
}
for i := 8; i < int(r.length); {
p := NewQueueProperty()
if err := p.UnmarshalBinary(data[i:]); err != nil {
return err
}
r.property = append(r.property, p)
i += int(p.Length())
}
return nil
}
func NewQueue() openflow.Queue {
return &Queue{}
}
type QueueGetConfigReply struct {
openflow.Message
port uint16
queue []openflow.Queue
}
func (r *QueueGetConfigReply) Port() uint32 {
return uint32(r.port)
}
func (r *QueueGetConfigReply) Queue() []openflow.Queue {
return r.queue
}
func (r *QueueGetConfigReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 8 {
return openflow.ErrInvalidPacketLength
}
r.port = binary.BigEndian.Uint16(payload[0:2])
// Unmarshal Queues
for i := 8; i < len(payload); {
q := NewQueue()
if err := q.UnmarshalBinary(payload[i:]); err != nil {
return err
}
r.queue = append(r.queue, q)
i += int(q.Length())
}
return nil
}
================================================
FILE: openflow/of13/action.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"bytes"
"encoding/binary"
"errors"
"net"
"github.com/superkkt/cherry/openflow"
)
type Action struct {
*openflow.BaseAction
}
func NewAction() openflow.Action {
return &Action{
openflow.NewBaseAction(),
}
}
func marshalOutput(p openflow.OutPort) ([]byte, error) {
v := make([]byte, 16)
binary.BigEndian.PutUint16(v[0:2], uint16(OFPAT_OUTPUT))
binary.BigEndian.PutUint16(v[2:4], 16)
var port uint32
switch {
case p.IsTable():
port = OFPP_TABLE
case p.IsFlood():
port = OFPP_FLOOD
case p.IsAll():
port = OFPP_ALL
case p.IsController():
port = OFPP_CONTROLLER
case p.IsInPort():
port = OFPP_IN_PORT
case p.IsNone():
port = OFPP_ANY
default:
port = p.Value()
}
binary.BigEndian.PutUint32(v[4:8], port)
// We don't support buffer ID and partial PACKET_IN
binary.BigEndian.PutUint16(v[8:10], 0xFFFF)
return v, nil
}
func marshalMAC(t uint8, mac net.HardwareAddr) ([]byte, error) {
if mac == nil || len(mac) < 6 {
return nil, openflow.ErrInvalidMACAddress
}
tlv, err := marshalHardwareAddrTLV(t, mac)
if err != nil {
return nil, err
}
v := make([]byte, 4+len(tlv))
binary.BigEndian.PutUint16(v[0:2], OFPAT_SET_FIELD)
// Add padding to align as a multiple of 8
rem := (len(v)) % 8
if rem > 0 {
v = append(v, bytes.Repeat([]byte{0}, 8-rem)...)
}
binary.BigEndian.PutUint16(v[2:4], uint16(len(v)))
copy(v[4:], tlv)
return v, nil
}
// TODO: Marshal Enqueue
// TODO: Marshal SetVLANVID
func (r *Action) MarshalBinary() ([]byte, error) {
if err := r.Error(); err != nil {
return nil, err
}
result := make([]byte, 0)
if ok, srcMAC := r.SrcMAC(); ok {
v, err := marshalMAC(OFPXMT_OFB_ETH_SRC, srcMAC)
if err != nil {
return nil, err
}
result = append(result, v...)
}
if ok, dstMAC := r.DstMAC(); ok {
v, err := marshalMAC(OFPXMT_OFB_ETH_DST, dstMAC)
if err != nil {
return nil, err
}
result = append(result, v...)
}
v, err := marshalOutput(r.OutPort())
if err != nil {
return nil, err
}
result = append(result, v...)
return result, nil
}
// TODO: Unmarshal Enqueue
// TODO: Unmarshal SetVLANVID
func (r *Action) UnmarshalBinary(data []byte) error {
buf := data
for len(buf) >= 4 {
t := binary.BigEndian.Uint16(buf[0:2])
length := binary.BigEndian.Uint16(buf[2:4])
if len(buf) < int(length) {
return openflow.ErrInvalidPacketLength
}
switch t {
case OFPAT_OUTPUT:
if len(buf) < 8 {
return openflow.ErrInvalidPacketLength
}
outPort := openflow.NewOutPort()
outPort.SetValue(binary.BigEndian.Uint32(buf[4:8]))
r.SetOutPort(outPort)
if err := r.Error(); err != nil {
return err
}
case OFPAT_SET_FIELD:
if len(buf) < 8 {
return openflow.ErrInvalidPacketLength
}
header := binary.BigEndian.Uint32(buf[4:8])
class := header >> 16 & 0xFFFF
if class != 0x8000 {
return errors.New("unsupported TLV class")
}
field := header >> 9 & 0x7F
switch field {
case OFPXMT_OFB_ETH_DST:
if len(buf) < 14 {
return openflow.ErrInvalidPacketLength
}
r.SetDstMAC(buf[8:14])
if err := r.Error(); err != nil {
return err
}
case OFPXMT_OFB_ETH_SRC:
if len(buf) < 14 {
return openflow.ErrInvalidPacketLength
}
r.SetSrcMAC(buf[8:14])
if err := r.Error(); err != nil {
return err
}
default:
// Do nothing
}
default:
// Do nothing
}
buf = buf[length:]
}
return nil
}
================================================
FILE: openflow/of13/barrier.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"github.com/superkkt/cherry/openflow"
)
type BarrierRequest struct {
openflow.Message
}
func NewBarrierRequest(xid uint32) openflow.BarrierRequest {
return &BarrierRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_BARRIER_REQUEST, xid),
}
}
func (r *BarrierRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type BarrierReply struct {
openflow.Message
}
func (r *BarrierReply) UnmarshalBinary(data []byte) error {
return r.Message.UnmarshalBinary(data)
}
================================================
FILE: openflow/of13/config.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"fmt"
"github.com/superkkt/cherry/openflow"
)
type Config struct {
err error
flags uint16
missSendLength uint16
}
func (r *Config) Flags() openflow.ConfigFlag {
switch r.flags {
case OFPC_FRAG_NORMAL:
return openflow.FragNormal
case OFPC_FRAG_DROP:
return openflow.FragDrop
case OFPC_FRAG_REASM:
return openflow.FragReasm
case OFPC_FRAG_MASK:
return openflow.FragMask
default:
panic(fmt.Sprintf("unexpected config flag: %v", r.flags))
}
}
func (r *Config) SetFlags(flags openflow.ConfigFlag) {
switch flags {
case openflow.FragNormal:
r.flags = OFPC_FRAG_NORMAL
case openflow.FragDrop:
r.flags = OFPC_FRAG_DROP
case openflow.FragReasm:
r.flags = OFPC_FRAG_REASM
case openflow.FragMask:
r.flags = OFPC_FRAG_MASK
default:
r.err = fmt.Errorf("SetFlags: unexpected config flag: %v", flags)
}
}
func (r *Config) MissSendLength() uint16 {
return r.missSendLength
}
func (r *Config) SetMissSendLength(length uint16) {
r.missSendLength = length
}
func (r *Config) Error() error {
return r.err
}
type SetConfig struct {
openflow.Message
Config
}
func NewSetConfig(xid uint32) openflow.SetConfig {
return &SetConfig{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_SET_CONFIG, xid),
Config: Config{
flags: OFPC_FRAG_NORMAL,
missSendLength: 0xFFFF,
},
}
}
func (r *SetConfig) MarshalBinary() ([]byte, error) {
if err := r.Error(); err != nil {
return nil, err
}
v := make([]byte, 4)
binary.BigEndian.PutUint16(v[0:2], r.flags)
binary.BigEndian.PutUint16(v[2:4], r.missSendLength)
r.SetPayload(v)
return r.Message.MarshalBinary()
}
type GetConfigRequest struct {
openflow.Message
}
func NewGetConfigRequest(xid uint32) openflow.GetConfigRequest {
return &GetConfigRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_GET_CONFIG_REQUEST, xid),
}
}
func (r *GetConfigRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type GetConfigReply struct {
openflow.Message
Config
}
func (r *GetConfigReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 4 {
return openflow.ErrInvalidPacketLength
}
r.flags = binary.BigEndian.Uint16(payload[0:2])
r.missSendLength = binary.BigEndian.Uint16(payload[2:4])
return nil
}
================================================
FILE: openflow/of13/const.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
const (
/* Immutable messages. */
OFPT_HELLO uint8 = iota /* Symmetric message */
OFPT_ERROR /* Symmetric message */
OFPT_ECHO_REQUEST /* Symmetric message */
OFPT_ECHO_REPLY /* Symmetric message */
OFPT_EXPERIMENTER /* Symmetric message */
/* Switch configuration messages. */
OFPT_FEATURES_REQUEST /* Controller/switch message */
OFPT_FEATURES_REPLY /* Controller/switch message */
OFPT_GET_CONFIG_REQUEST /* Controller/switch message */
OFPT_GET_CONFIG_REPLY /* Controller/switch message */
OFPT_SET_CONFIG /* Controller/switch message */
/* Asynchronous messages. */
OFPT_PACKET_IN /* Async message */
OFPT_FLOW_REMOVED /* Async message */
OFPT_PORT_STATUS /* Async message */
/* Controller command messages. */
OFPT_PACKET_OUT /* Controller/switch message */
OFPT_FLOW_MOD /* Controller/switch message */
OFPT_GROUP_MOD /* Controller/switch message */
OFPT_PORT_MOD /* Controller/switch message */
OFPT_TABLE_MOD /* Controller/switch message */
/* Multipart messages. */
OFPT_MULTIPART_REQUEST /* Controller/switch message */
OFPT_MULTIPART_REPLY /* Controller/switch message */
/* Barrier messages. */
OFPT_BARRIER_REQUEST /* Controller/switch message */
OFPT_BARRIER_REPLY /* Controller/switch message */
/* Queue Configuration messages. */
OFPT_QUEUE_GET_CONFIG_REQUEST /* Controller/switch message */
OFPT_QUEUE_GET_CONFIG_REPLY /* Controller/switch message */
/* Controller role change request messages. */
OFPT_ROLE_REQUEST /* Controller/switch message */
OFPT_ROLE_REPLY /* Controller/switch message */
/* Asynchronous message configuration. */
OFPT_GET_ASYNC_REQUEST /* Controller/switch message */
OFPT_GET_ASYNC_REPLY /* Controller/switch message */
OFPT_SET_ASYNC /* Controller/switch message */
/* Meters and rate limiters configuration messages. */
OFPT_METER_MOD /* Controller/switch message */
)
const (
OFPAT_OUTPUT = 0
OFPAT_SET_FIELD = 25
)
const (
/* Maximum number of physical and logical switch ports. */
OFPP_MAX = 0xffffff00
/* Reserved OpenFlow Port (fake output "ports"). */
OFPP_IN_PORT = 0xfffffff8
OFPP_TABLE = 0xfffffff9
OFPP_NORMAL = 0xfffffffa /* Process with normal L2/L3 switching. */
/* All physical ports in VLAN, except input port and those blocked or link down. */
OFPP_FLOOD = 0xfffffffb
OFPP_ALL = 0xfffffffc /* All physical ports except input port. */
OFPP_CONTROLLER = 0xfffffffd /* Send to controller. */
OFPP_LOCAL = 0xfffffffe /* Local openflow "port". */
OFPP_ANY = 0xffffffff /* Wildcard */
)
const (
OFPXMT_OFB_IN_PORT = iota
OFPXMT_OFB_IN_PHY_PORT
OFPXMT_OFB_METADATA
OFPXMT_OFB_ETH_DST
OFPXMT_OFB_ETH_SRC
OFPXMT_OFB_ETH_TYPE
OFPXMT_OFB_VLAN_VID
OFPXMT_OFB_VLAN_PCP
OFPXMT_OFB_IP_DSCP
OFPXMT_OFB_IP_ECN
OFPXMT_OFB_IP_PROTO
OFPXMT_OFB_IPV4_SRC
OFPXMT_OFB_IPV4_DST
OFPXMT_OFB_TCP_SRC
OFPXMT_OFB_TCP_DST
OFPXMT_OFB_UDP_SRC
OFPXMT_OFB_UDP_DST
OFPXMT_OFB_SCTP_SRC
OFPXMT_OFB_SCTP_DST
OFPXMT_OFB_ICMPV4_TYPE
OFPXMT_OFB_ICMPV4_CODE
OFPXMT_OFB_ARP_OP
OFPXMT_OFB_ARP_SPA
OFPXMT_OFB_ARP_TPA
OFPXMT_OFB_ARP_SHA
OFPXMT_OFB_ARP_THA
OFPXMT_OFB_IPV6_SRC
OFPXMT_OFB_IPV6_DST
OFPXMT_OFB_IPV6_FLABEL
OFPXMT_OFB_ICMPV6_TYPE
OFPXMT_OFB_ICMPV6_CODE
OFPXMT_OFB_IPV6_ND_TARGET
OFPXMT_OFB_IPV6_ND_SLL
OFPXMT_OFB_IPV6_ND_TLL
OFPXMT_OFB_MPLS_LABEL
OFPXMT_OFB_MPLS_TC
OFPXMT_OFP_MPLS_BOS
OFPXMT_OFB_PBB_ISID
OFPXMT_OFB_TUNNEL_ID
OFPXMT_OFB_IPV6_EXTHDR
)
const (
OFPMT_STANDARD = 0
OFPMT_OXM = 1
)
const (
OFPFC_ADD = 0 /* New flow. */
OFPFC_MODIFY = 1 /* Modify all matching flows. */
OFPFC_MODIFY_STRICT = 2 /* Modify entry strictly matching wildcards and priority. */
OFPFC_DELETE = 3 /* Delete all matching flows. */
OFPFC_DELETE_STRICT = 4 /* Delete entry strictly matching wildcards and priority */
)
const (
OFP_NO_BUFFER = 0xffffffff
)
const (
OFPFF_SEND_FLOW_REM = 1 << 0 /* Send flow removed message when flow expires or is deleted. */
OFPFF_CHECK_OVERLAP = 1 << 1 /* Check for overlapping entries first. */
OFPFF_RESET_COUNTS = 1 << 2 /* Reset flow packet and byte counts. */
OFPFF_NO_PKT_COUNTS = 1 << 3 /* Don't keep track of packet count. */
OFPFF_NO_BYT_COUNTS = 1 << 4 /* Don't keep track of byte count. */
)
const (
OFPPC_PORT_DOWN = 1 << 0 /* Port is administratively down. */
OFPPC_NO_RECV = 1 << 2
OFPPC_NO_FWD = 1 << 5
OFPPC_NO_PACKET_IN = 1 << 6
)
const (
OFPPS_LINK_DOWN = 1 << 0 /* No physical link present. */
OFPPS_BLOCKED = 1 << 1
OFPPS_LIVE = 1 << 2
)
const (
OFPPF_10MB_HD = 1 << 0
OFPPF_10MB_FD = 1 << 1
OFPPF_100MB_HD = 1 << 2
OFPPF_100MB_FD = 1 << 3
OFPPF_1GB_HD = 1 << 4
OFPPF_1GB_FD = 1 << 5
OFPPF_10GB_FD = 1 << 6
OFPPF_40GB_FD = 1 << 7
OFPPF_100GB_FD = 1 << 8
OFPPF_1TB_FD = 1 << 9
OFPPF_OTHER = 1 << 10
OFPPF_COPPER = 1 << 11
OFPPF_FIBER = 1 << 12
OFPPF_AUTONEG = 1 << 13
OFPPF_PAUSE = 1 << 14
OFPPF_PAUSE_ASYM = 1 << 15
)
const (
/* Description of this OpenFlow switch.
* The request body is empty.
* The reply body is struct ofp_desc. */
OFPMP_DESC = 0
/* Individual flow statistics.
* The request body is struct ofp_flow_stats_request.
* The reply body is an array of struct ofp_flow_stats. */
OFPMP_FLOW = 1
/* Aggregate flow statistics.
* The request body is struct ofp_aggregate_stats_request.
* The reply body is struct ofp_aggregate_stats_reply. */
OFPMP_AGGREGATE = 2
/* Flow table statistics.
* The request body is empty.
* The reply body is an array of struct ofp_table_stats. */
OFPMP_TABLE = 3
/* Port statistics.
* The request body is struct ofp_port_stats_request.
* The reply body is an array of struct ofp_port_stats. */
OFPMP_PORT_STATS = 4
/* Queue statistics for a port
* The request body is struct ofp_queue_stats_request.
* The reply body is an array of struct ofp_queue_stats */
OFPMP_QUEUE = 5
/* Group counter statistics.
* The request body is struct ofp_group_stats_request.
* The reply is an array of struct ofp_group_stats. */
OFPMP_GROUP = 6
/* Group description.
* The request body is empty.
* The reply body is an array of struct ofp_group_desc_stats. */
OFPMP_GROUP_DESC = 7
/* Group features.
* The request body is empty.
* The reply body is struct ofp_group_features. */
OFPMP_GROUP_FEATURES = 8
/* Meter statistics.
* The request body is struct ofp_meter_multipart_requests.
* The reply body is an array of struct ofp_meter_stats. */
OFPMP_METER = 9
/* Meter configuration.
* The request body is struct ofp_meter_multipart_requests.
* The reply body is an array of struct ofp_meter_config. */
OFPMP_METER_CONFIG = 10
/* Meter features.
* The request body is empty.
* The reply body is struct ofp_meter_features. */
OFPMP_METER_FEATURES = 11
/* Table features.
* The request body is either empty or contains an array of
* struct ofp_table_features containing the controller's
* desired view of the switch. If the switch is unable to
* set the specified view an error is returned.
* The reply body is an array of struct ofp_table_features. */
OFPMP_TABLE_FEATURES = 12
/* Port description.
* The request body is empty.
* The reply body is an array of struct ofp_port. */
OFPMP_PORT_DESC = 13
/* Experimenter extension.
* The request and reply bodies begin with
* struct ofp_experimenter_multipart_header.
* The request and reply bodies are otherwise experimenter-defined. */
OFPMP_EXPERIMENTER = 0xffff
)
const (
OFPG_ANY = 0xffffffff
)
const (
OFPC_FRAG_NORMAL = 0 /* No special handling for fragments. */
OFPC_FRAG_DROP = 1 << 0 /* Drop fragments. */
OFPC_FRAG_REASM = 1 << 1 /* Reassemble (only if OFPC_IP_REASM set). */
OFPC_FRAG_MASK = 3
)
const (
OFPPR_ADD = 0
OFPPR_DELETE = 1
OFPPR_MODIFY = 2
)
const (
OFPIT_GOTO_TABLE = 1 /* Setup the next table in the lookup pipeline */
OFPIT_WRITE_METADATA = 2 /* Setup the metadata field for use later in pipeline */
OFPIT_WRITE_ACTIONS = 3 /* Write the action(s) onto the datapath action set */
OFPIT_APPLY_ACTIONS = 4 /* Applies the action(s) immediately */
OFPIT_CLEAR_ACTIONS = 5 /* Clears all actions from the datapath action set */
OFPIT_METER = 6 /* Apply meter (rate limiter) */
OFPIT_EXPERIMENTER = 0xFFFF /* Experimenter instruction */
)
================================================
FILE: openflow/of13/echo.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"github.com/superkkt/cherry/openflow"
)
func NewEchoRequest(xid uint32) openflow.EchoRequest {
return &openflow.BaseEcho{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_ECHO_REQUEST, xid),
}
}
func NewEchoReply(xid uint32) openflow.EchoReply {
return &openflow.BaseEcho{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_ECHO_REPLY, xid),
}
}
================================================
FILE: openflow/of13/factory.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"fmt"
"sync/atomic"
"github.com/superkkt/cherry/openflow"
)
// Concrete factory
type Factory struct {
xid uint32
}
func NewFactory() openflow.Factory {
return &Factory{}
}
func (r *Factory) ProtocolVersion() uint8 {
return openflow.OF13_VERSION
}
func (r *Factory) getTransactionID() uint32 {
// Transaction ID will be started from 1, not 0.
return atomic.AddUint32(&r.xid, 1)
}
func (r *Factory) NewHello() (openflow.Hello, error) {
return NewHello(r.getTransactionID()), nil
}
func (r *Factory) NewEchoRequest() (openflow.EchoRequest, error) {
return NewEchoRequest(r.getTransactionID()), nil
}
func (r *Factory) NewEchoReply() (openflow.EchoReply, error) {
return NewEchoReply(r.getTransactionID()), nil
}
func (r *Factory) NewAction() (openflow.Action, error) {
return NewAction(), nil
}
func (r *Factory) NewMatch() (openflow.Match, error) {
return NewMatch(), nil
}
func (r *Factory) NewBarrierRequest() (openflow.BarrierRequest, error) {
return NewBarrierRequest(r.getTransactionID()), nil
}
func (r *Factory) NewBarrierReply() (openflow.BarrierReply, error) {
return new(BarrierReply), nil
}
func (r *Factory) NewSetConfig() (openflow.SetConfig, error) {
return NewSetConfig(r.getTransactionID()), nil
}
func (r *Factory) NewGetConfigRequest() (openflow.GetConfigRequest, error) {
return NewGetConfigRequest(r.getTransactionID()), nil
}
func (r *Factory) NewGetConfigReply() (openflow.GetConfigReply, error) {
return new(GetConfigReply), nil
}
func (r *Factory) NewFeaturesRequest() (openflow.FeaturesRequest, error) {
return NewFeaturesRequest(r.getTransactionID()), nil
}
func (r *Factory) NewFeaturesReply() (openflow.FeaturesReply, error) {
return new(FeaturesReply), nil
}
func getFlowModCmd(cmd openflow.FlowModCmd) uint8 {
var c uint8
switch cmd {
case openflow.FlowAdd:
c = OFPFC_ADD
case openflow.FlowModify:
c = OFPFC_MODIFY
case openflow.FlowDelete:
c = OFPFC_DELETE
default:
panic(fmt.Sprintf("unexpected FlowModCmd: %v", cmd))
}
return c
}
func (r *Factory) NewFlowMod(cmd openflow.FlowModCmd) (openflow.FlowMod, error) {
return NewFlowMod(r.getTransactionID(), getFlowModCmd(cmd)), nil
}
func (r *Factory) NewFlowRemoved() (openflow.FlowRemoved, error) {
return new(FlowRemoved), nil
}
func (r *Factory) NewPacketIn() (openflow.PacketIn, error) {
return new(PacketIn), nil
}
func (r *Factory) NewPacketOut() (openflow.PacketOut, error) {
return NewPacketOut(r.getTransactionID()), nil
}
func (r *Factory) NewPortStatus() (openflow.PortStatus, error) {
return new(PortStatus), nil
}
func (r *Factory) NewDescRequest() (openflow.DescRequest, error) {
return NewDescRequest(r.getTransactionID()), nil
}
func (r *Factory) NewDescReply() (openflow.DescReply, error) {
return new(DescReply), nil
}
func (r *Factory) NewFlowStatsRequest() (openflow.FlowStatsRequest, error) {
return NewFlowStatsRequest(r.getTransactionID()), nil
}
// TODO: NewFlowStatsReply() (openflow.FlowStatsReply, error) {
func (r *Factory) NewPortDescRequest() (openflow.PortDescRequest, error) {
return NewPortDescRequest(r.getTransactionID()), nil
}
func (r *Factory) NewPortDescReply() (openflow.PortDescReply, error) {
return new(PortDescReply), nil
}
func (r *Factory) NewTableFeaturesRequest() (openflow.TableFeaturesRequest, error) {
return NewTableFeaturesRequest(r.getTransactionID()), nil
}
func (r *Factory) NewError() (openflow.Error, error) {
return new(openflow.BaseError), nil
}
// TODO: NewTableFeaturesReply() (TableFeaturesReply, error)
func (r *Factory) NewInstruction() (openflow.Instruction, error) {
return new(Instruction), nil
}
func (r *Factory) NewQueueGetConfigRequest() (openflow.QueueGetConfigRequest, error) {
return NewQueueGetConfigRequest(r.getTransactionID()), nil
}
================================================
FILE: openflow/of13/features.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type FeaturesRequest struct {
openflow.Message
}
func NewFeaturesRequest(xid uint32) openflow.FeaturesRequest {
return &FeaturesRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_FEATURES_REQUEST, xid),
}
}
func (r *FeaturesRequest) MarshalBinary() ([]byte, error) {
return r.Message.MarshalBinary()
}
type FeaturesReply struct {
openflow.Message
dpid uint64
numBuffers uint32
numTables uint8
auxID uint8
capabilities uint32
}
func (r FeaturesReply) DPID() uint64 {
return r.dpid
}
func (r FeaturesReply) NumBuffers() uint32 {
return r.numBuffers
}
func (r FeaturesReply) NumTables() uint8 {
return r.numTables
}
func (r FeaturesReply) Capabilities() uint32 {
return r.capabilities
}
func (r FeaturesReply) Actions() uint32 {
// OpenFlow 1.3 does not have actions
return 0
}
func (r FeaturesReply) Ports() []openflow.Port {
// OpenFlow 1.3 does not have port
return nil
}
func (r FeaturesReply) AuxID() uint8 {
return r.auxID
}
func (r *FeaturesReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 20 {
return openflow.ErrInvalidPacketLength
}
r.dpid = binary.BigEndian.Uint64(payload[0:8])
r.numBuffers = binary.BigEndian.Uint32(payload[8:12])
r.numTables = payload[12]
r.auxID = payload[13]
r.capabilities = binary.BigEndian.Uint32(payload[16:20])
return nil
}
================================================
FILE: openflow/of13/flow_mod.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"errors"
"github.com/superkkt/cherry/openflow"
)
type FlowMod struct {
err error
openflow.Message
command uint8
cookie uint64
cookieMask uint64
tableID uint8
idleTimeout uint16
hardTimeout uint16
priority uint16
match openflow.Match
instruction openflow.Instruction
outPort openflow.OutPort
}
func NewFlowMod(xid uint32, cmd uint8) openflow.FlowMod {
// Default out_port value is OFPP_NONE (OFPP_ANY)
outPort := openflow.NewOutPort()
outPort.SetNone()
return &FlowMod{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_FLOW_MOD, xid),
command: cmd,
outPort: outPort,
}
}
func (r *FlowMod) Error() error {
return r.err
}
func (r *FlowMod) Cookie() uint64 {
return r.cookie
}
func (r *FlowMod) SetCookie(cookie uint64) {
r.cookie = cookie
}
func (r *FlowMod) CookieMask() uint64 {
return r.cookieMask
}
func (r *FlowMod) SetCookieMask(mask uint64) {
r.cookieMask = mask
}
func (r *FlowMod) TableID() uint8 {
return r.tableID
}
func (r *FlowMod) SetTableID(id uint8) {
r.tableID = id
}
func (r *FlowMod) IdleTimeout() uint16 {
return r.idleTimeout
}
func (r *FlowMod) SetIdleTimeout(timeout uint16) {
r.idleTimeout = timeout
}
func (r *FlowMod) HardTimeout() uint16 {
return r.hardTimeout
}
func (r *FlowMod) SetHardTimeout(timeout uint16) {
r.hardTimeout = timeout
}
func (r *FlowMod) Priority() uint16 {
return r.priority
}
func (r *FlowMod) SetPriority(priority uint16) {
r.priority = priority
}
func (r *FlowMod) FlowMatch() openflow.Match {
return r.match
}
func (r *FlowMod) SetFlowMatch(match openflow.Match) {
if match == nil {
panic("flow match is nil")
}
r.match = match
}
func (r *FlowMod) FlowInstruction() openflow.Instruction {
return r.instruction
}
func (r *FlowMod) SetFlowInstruction(inst openflow.Instruction) {
if inst == nil {
panic("flow instruction is nil")
}
r.instruction = inst
}
func (r *FlowMod) OutPort() openflow.OutPort {
return r.outPort
}
func (r *FlowMod) SetOutPort(p openflow.OutPort) {
r.outPort = p
}
func (r *FlowMod) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
v := make([]byte, 40)
binary.BigEndian.PutUint64(v[0:8], r.cookie)
binary.BigEndian.PutUint64(v[8:16], r.cookieMask)
v[16] = r.tableID
v[17] = r.command
binary.BigEndian.PutUint16(v[18:20], r.idleTimeout)
binary.BigEndian.PutUint16(v[20:22], r.hardTimeout)
binary.BigEndian.PutUint16(v[22:24], r.priority)
binary.BigEndian.PutUint32(v[24:28], OFP_NO_BUFFER)
if r.outPort.IsNone() {
binary.BigEndian.PutUint32(v[28:32], OFPP_ANY)
} else {
binary.BigEndian.PutUint32(v[28:32], r.outPort.Value())
}
binary.BigEndian.PutUint32(v[32:36], OFPP_ANY)
binary.BigEndian.PutUint16(v[36:38], OFPFF_SEND_FLOW_REM)
// v[38:40] is padding
if r.match == nil {
return nil, errors.New("empty flow match")
}
match, err := r.match.MarshalBinary()
if err != nil {
return nil, err
}
v = append(v, match...)
if r.instruction != nil {
ins, err := r.instruction.MarshalBinary()
if err != nil {
return nil, err
}
v = append(v, ins...)
}
r.SetPayload(v)
return r.Message.MarshalBinary()
}
================================================
FILE: openflow/of13/flow_removed.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type FlowRemoved struct {
openflow.Message
cookie uint64
priority uint16
reason uint8
tableID uint8
durationSec uint32
durationNanoSec uint32
idleTimeout uint16
hardTimeout uint16
packetCount uint64
byteCount uint64
match openflow.Match
}
func (r FlowRemoved) Cookie() uint64 {
return r.cookie
}
func (r FlowRemoved) Priority() uint16 {
return r.priority
}
func (r FlowRemoved) Reason() uint8 {
return r.reason
}
func (r FlowRemoved) TableID() uint8 {
return r.tableID
}
func (r FlowRemoved) DurationSec() uint32 {
return r.durationSec
}
func (r FlowRemoved) DurationNanoSec() uint32 {
return r.durationNanoSec
}
func (r FlowRemoved) IdleTimeout() uint16 {
return r.idleTimeout
}
func (r FlowRemoved) HardTimeout() uint16 {
return r.hardTimeout
}
func (r FlowRemoved) PacketCount() uint64 {
return r.packetCount
}
func (r FlowRemoved) ByteCount() uint64 {
return r.byteCount
}
func (r FlowRemoved) Match() openflow.Match {
return r.match
}
func (r *FlowRemoved) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 48 {
return openflow.ErrInvalidPacketLength
}
r.cookie = binary.BigEndian.Uint64(payload[0:8])
r.priority = binary.BigEndian.Uint16(payload[8:10])
r.reason = payload[10]
r.tableID = payload[11]
r.durationSec = binary.BigEndian.Uint32(payload[12:16])
r.durationNanoSec = binary.BigEndian.Uint32(payload[16:20])
r.idleTimeout = binary.BigEndian.Uint16(payload[20:22])
r.hardTimeout = binary.BigEndian.Uint16(payload[22:24])
r.packetCount = binary.BigEndian.Uint64(payload[24:32])
r.byteCount = binary.BigEndian.Uint64(payload[32:40])
r.match = NewMatch()
if err := r.match.UnmarshalBinary(payload[40:]); err != nil {
return err
}
return nil
}
================================================
FILE: openflow/of13/hello.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"github.com/superkkt/cherry/openflow"
)
func NewHello(xid uint32) openflow.Hello {
return &openflow.BaseHello{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_HELLO, xid),
}
}
================================================
FILE: openflow/of13/instruction.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding"
"encoding/binary"
"errors"
"github.com/superkkt/cherry/openflow"
)
type Instruction struct {
err error
value encoding.BinaryMarshaler
}
type gotoTable struct {
tableID uint8
}
func (r *gotoTable) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
binary.BigEndian.PutUint16(v[0:2], OFPIT_GOTO_TABLE)
binary.BigEndian.PutUint16(v[2:4], 8)
v[4] = r.tableID
// v[5:8] is padding
return v, nil
}
type writeAction struct {
action openflow.Action
}
func (r *writeAction) MarshalBinary() ([]byte, error) {
if r.action == nil {
return nil, errors.New("empty action")
}
action, err := r.action.MarshalBinary()
if err != nil {
return nil, err
}
v := make([]byte, 8)
v = append(v, action...)
binary.BigEndian.PutUint16(v[0:2], OFPIT_WRITE_ACTIONS)
binary.BigEndian.PutUint16(v[2:4], uint16(len(v)))
return v, nil
}
type applyAction struct {
action openflow.Action
}
func (r *applyAction) MarshalBinary() ([]byte, error) {
if r.action == nil {
return nil, errors.New("empty action")
}
action, err := r.action.MarshalBinary()
if err != nil {
return nil, err
}
v := make([]byte, 8)
v = append(v, action...)
binary.BigEndian.PutUint16(v[0:2], OFPIT_APPLY_ACTIONS)
binary.BigEndian.PutUint16(v[2:4], uint16(len(v)))
return v, nil
}
func (r *Instruction) Error() error {
return r.err
}
func (r *Instruction) GotoTable(tableID uint8) {
r.value = &gotoTable{tableID: tableID}
}
func (r *Instruction) WriteAction(act openflow.Action) {
if act == nil {
panic("act is nil")
}
r.value = &writeAction{action: act}
}
func (r *Instruction) ApplyAction(act openflow.Action) {
if act == nil {
panic("act is nil")
}
r.value = &applyAction{action: act}
}
func (r *Instruction) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
if r.value == nil {
return nil, errors.New("empty action of an instruction")
}
return r.value.MarshalBinary()
}
================================================
FILE: openflow/of13/match.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"sync"
"github.com/superkkt/cherry/openflow"
"github.com/pkg/errors"
)
type Match struct {
err error
mutex sync.Mutex
m map[uint]interface{}
}
// NewMatch returns a Match whose fields are all wildcarded
func NewMatch() openflow.Match {
return &Match{
m: make(map[uint]interface{}),
}
}
func (r *Match) Error() error {
return r.err
}
func (r *Match) SetWildcardSrcPort() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_TCP_SRC)
delete(r.m, OFPXMT_OFB_UDP_SRC)
}
func (r *Match) SetSrcPort(p uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
etherType, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingEtherType, "SetSrcPort")
return
}
// IPv4?
if etherType.(uint16) != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetSrcPort")
return
}
proto, ok := r.m[OFPXMT_OFB_IP_PROTO]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingIPProtocol, "SetSrcPort")
return
}
switch proto.(uint8) {
// TCP
case 0x06:
r.m[OFPXMT_OFB_TCP_SRC] = p
delete(r.m, OFPXMT_OFB_UDP_SRC)
// UDP
case 0x11:
r.m[OFPXMT_OFB_UDP_SRC] = p
delete(r.m, OFPXMT_OFB_TCP_SRC)
default:
r.err = errors.Wrap(openflow.ErrUnsupportedIPProtocol, "SetSrcPort")
return
}
}
func (r *Match) SrcPort() (wildcard bool, port uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_TCP_SRC]
if ok {
return false, v.(uint16)
}
v, ok = r.m[OFPXMT_OFB_UDP_SRC]
if ok {
return false, v.(uint16)
}
return true, 0
}
func (r *Match) SetWildcardDstPort() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_TCP_DST)
delete(r.m, OFPXMT_OFB_UDP_DST)
}
func (r *Match) SetDstPort(p uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
etherType, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingEtherType, "SetDstPort")
return
}
// IPv4?
if etherType.(uint16) != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetDstPort")
return
}
proto, ok := r.m[OFPXMT_OFB_IP_PROTO]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingIPProtocol, "SetDstPort")
return
}
switch proto.(uint8) {
// TCP
case 0x06:
r.m[OFPXMT_OFB_TCP_DST] = p
delete(r.m, OFPXMT_OFB_UDP_DST)
// UDP
case 0x11:
r.m[OFPXMT_OFB_UDP_DST] = p
delete(r.m, OFPXMT_OFB_TCP_DST)
default:
r.err = errors.Wrap(openflow.ErrUnsupportedIPProtocol, "SetDstPort")
return
}
}
func (r *Match) DstPort() (wildcard bool, port uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_TCP_DST]
if ok {
return false, v.(uint16)
}
v, ok = r.m[OFPXMT_OFB_UDP_DST]
if ok {
return false, v.(uint16)
}
return true, 0
}
func (r *Match) SetWildcardVLANID() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_VLAN_VID)
}
func (r *Match) SetVLANID(id uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.m[OFPXMT_OFB_VLAN_VID] = id
}
func (r *Match) VLANID() (wildcard bool, vlanID uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_VLAN_VID]
if ok {
return false, v.(uint16)
}
return true, 0
}
func (r *Match) SetWildcardVLANPriority() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_VLAN_PCP)
}
func (r *Match) SetVLANPriority(p uint8) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.m[OFPXMT_OFB_VLAN_PCP] = p
}
func (r *Match) VLANPriority() (wildcard bool, priority uint8) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_VLAN_PCP]
if ok {
return false, v.(uint8)
}
return true, 0
}
func (r *Match) SetWildcardIPProtocol() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_IP_PROTO)
}
func (r *Match) SetIPProtocol(p uint8) {
r.mutex.Lock()
defer r.mutex.Unlock()
etherType, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingEtherType, "SetIPProtocol")
return
}
// IPv4?
if etherType.(uint16) != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetIPProtocol")
return
}
r.m[OFPXMT_OFB_IP_PROTO] = p
}
func (r *Match) IPProtocol() (wildcard bool, protocol uint8) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_IP_PROTO]
if ok {
return false, v.(uint8)
}
return true, 0
}
func (r *Match) SetWildcardInPort() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_IN_PORT)
}
func (r *Match) SetInPort(port openflow.InPort) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.m[OFPXMT_OFB_IN_PORT] = uint32(port.Value())
}
func (r *Match) InPort() (wildcard bool, inport openflow.InPort) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_IN_PORT]
if ok {
inport = openflow.NewInPort()
inport.SetValue(v.(uint32))
return false, inport
}
return true, openflow.NewInPort()
}
func (r *Match) SetWildcardSrcMAC() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_ETH_SRC)
}
func (r *Match) SetSrcMAC(mac net.HardwareAddr) {
r.mutex.Lock()
defer r.mutex.Unlock()
if mac == nil {
panic("mac is nil")
}
if len(mac) < 6 {
r.err = errors.Wrap(openflow.ErrInvalidMACAddress, "SetSrcMAC")
return
}
r.m[OFPXMT_OFB_ETH_SRC] = mac
}
func (r *Match) SrcMAC() (wildcard bool, mac net.HardwareAddr) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_ETH_SRC]
if ok {
return false, v.(net.HardwareAddr)
}
return true, net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
}
func (r *Match) SetWildcardDstMAC() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_ETH_DST)
}
func (r *Match) SetDstMAC(mac net.HardwareAddr) {
r.mutex.Lock()
defer r.mutex.Unlock()
if mac == nil {
panic("mac is nil")
}
if len(mac) < 6 {
r.err = errors.Wrap(openflow.ErrInvalidMACAddress, "SetDstMAC")
return
}
r.m[OFPXMT_OFB_ETH_DST] = mac
}
func (r *Match) DstMAC() (wildcard bool, mac net.HardwareAddr) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_ETH_DST]
if ok {
return false, v.(net.HardwareAddr)
}
return true, net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0})
}
func (r *Match) SetSrcIP(ip *net.IPNet) {
r.mutex.Lock()
defer r.mutex.Unlock()
if ip == nil {
panic("ip is nil")
}
if ip.IP == nil || len(ip.IP) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidIPAddress, "SetSrcIP")
return
}
etherType, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingEtherType, "SetSrcIP")
return
}
// IPv4?
if etherType.(uint16) != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetSrcIP")
return
}
r.m[OFPXMT_OFB_IPV4_SRC] = ip
}
func (r *Match) SrcIP() *net.IPNet {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_IPV4_SRC]
if ok {
return v.(*net.IPNet)
}
return &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
}
}
func (r *Match) SetDstIP(ip *net.IPNet) {
r.mutex.Lock()
defer r.mutex.Unlock()
if ip == nil {
panic("ip is nil")
}
if ip.IP == nil || len(ip.IP) == 0 {
r.err = errors.Wrap(openflow.ErrInvalidIPAddress, "SetDstIP")
return
}
etherType, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if !ok {
r.err = errors.Wrap(openflow.ErrMissingEtherType, "SetDstIP")
return
}
// IPv4?
if etherType.(uint16) != 0x0800 {
r.err = errors.Wrap(openflow.ErrUnsupportedEtherType, "SetDstIP")
return
}
r.m[OFPXMT_OFB_IPV4_DST] = ip
}
func (r *Match) DstIP() *net.IPNet {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_IPV4_DST]
if ok {
return v.(*net.IPNet)
}
return &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
}
}
func (r *Match) SetWildcardEtherType() {
r.mutex.Lock()
defer r.mutex.Unlock()
delete(r.m, OFPXMT_OFB_ETH_TYPE)
}
func (r *Match) SetEtherType(t uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.m[OFPXMT_OFB_ETH_TYPE] = t
}
func (r *Match) EtherType() (wildcard bool, etherType uint16) {
r.mutex.Lock()
defer r.mutex.Unlock()
v, ok := r.m[OFPXMT_OFB_ETH_TYPE]
if ok {
return false, v.(uint16)
}
return true, 0
}
func marshalIPNetTLV(field uint8, ip *net.IPNet) ([]byte, error) {
data := make([]byte, 12)
// TLV header
var header uint32 = 0x8000<<16 | uint32(field)<<9 | 0x1<<8 | 8
binary.BigEndian.PutUint32(data[0:4], header)
ipv4 := ip.IP.To4()
if ipv4 == nil {
return nil, openflow.ErrInvalidIPAddress
}
copy(data[4:8], ipv4)
copy(data[8:12], ip.Mask)
return data, nil
}
func marshalHardwareAddrTLV(field uint8, mac net.HardwareAddr) ([]byte, error) {
data := make([]byte, 10)
// TLV header
var header uint32 = 0x8000<<16 | uint32(field)<<9 | 0x0<<8 | 6
binary.BigEndian.PutUint32(data[0:4], header)
copy(data[4:], mac)
return data, nil
}
func marshalUint8TLV(field uint8, v uint8) ([]byte, error) {
data := make([]byte, 5)
// TLV header
var header uint32 = 0x8000<<16 | uint32(field)<<9 | 0x0<<8 | 1
binary.BigEndian.PutUint32(data[0:4], header)
data[5] = v
return data, nil
}
func marshalUint16TLV(field uint8, v uint16) ([]byte, error) {
data := make([]byte, 6)
// TLV header
var header uint32 = 0x8000<<16 | uint32(field)<<9 | 0x0<<8 | 2
binary.BigEndian.PutUint32(data[0:4], header)
binary.BigEndian.PutUint16(data[4:6], v)
return data, nil
}
func marshalUint32TLV(field uint8, v uint32) ([]byte, error) {
data := make([]byte, 8)
// TLV header
var header uint32 = 0x8000<<16 | uint32(field)<<9 | 0x0<<8 | 4
binary.BigEndian.PutUint32(data[0:4], header)
binary.BigEndian.PutUint32(data[4:8], v)
return data, nil
}
func marshalTLV(id uint, v interface{}) ([]byte, error) {
switch id {
case OFPXMT_OFB_IN_PORT:
port := v.(uint32)
return marshalUint32TLV(OFPXMT_OFB_IN_PORT, port)
case OFPXMT_OFB_ETH_DST:
mac := v.(net.HardwareAddr)
return marshalHardwareAddrTLV(OFPXMT_OFB_ETH_DST, mac)
case OFPXMT_OFB_ETH_SRC:
mac := v.(net.HardwareAddr)
return marshalHardwareAddrTLV(OFPXMT_OFB_ETH_SRC, mac)
case OFPXMT_OFB_ETH_TYPE:
etherType := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_ETH_TYPE, etherType)
case OFPXMT_OFB_VLAN_VID:
vid := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_VLAN_VID, vid)
case OFPXMT_OFB_VLAN_PCP:
priority := v.(uint8)
return marshalUint8TLV(OFPXMT_OFB_VLAN_PCP, priority)
case OFPXMT_OFB_IP_PROTO:
protocol := v.(uint8)
return marshalUint8TLV(OFPXMT_OFB_IP_PROTO, protocol)
case OFPXMT_OFB_IPV4_SRC:
ip := v.(*net.IPNet)
return marshalIPNetTLV(OFPXMT_OFB_IPV4_SRC, ip)
case OFPXMT_OFB_IPV4_DST:
ip := v.(*net.IPNet)
return marshalIPNetTLV(OFPXMT_OFB_IPV4_DST, ip)
case OFPXMT_OFB_TCP_SRC:
port := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_TCP_SRC, port)
case OFPXMT_OFB_TCP_DST:
port := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_TCP_DST, port)
case OFPXMT_OFB_UDP_SRC:
port := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_UDP_SRC, port)
case OFPXMT_OFB_UDP_DST:
port := v.(uint16)
return marshalUint16TLV(OFPXMT_OFB_UDP_DST, port)
default:
panic(fmt.Sprintf("unexpected TLV type: %v", id))
}
}
func (r *Match) MarshalBinary() ([]byte, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.err != nil {
return nil, r.err
}
data := make([]byte, 4)
binary.BigEndian.PutUint16(data[0:2], OFPMT_OXM)
for k, v := range r.m {
tlv, err := marshalTLV(k, v)
if err != nil {
return nil, err
}
data = append(data, tlv...)
}
// ofp_match.length does not include padding
binary.BigEndian.PutUint16(data[2:4], uint16(len(data)))
// Add padding to align as a multiple of 8
rem := len(data) % 8
if rem > 0 {
data = append(data, bytes.Repeat([]byte{0}, 8-rem)...)
}
return data, nil
}
func (r *Match) unmarshalUint8TLV(field uint8, data []byte) error {
if len(data) < 5 {
return openflow.ErrInvalidPacketLength
}
r.m[uint(field)] = data[4]
return nil
}
func (r *Match) unmarshalUint16TLV(field uint8, data []byte) error {
if len(data) < 6 {
return openflow.ErrInvalidPacketLength
}
v := binary.BigEndian.Uint16(data[4:6])
r.m[uint(field)] = v
return nil
}
func (r *Match) unmarshalUint32TLV(field uint8, data []byte) error {
if len(data) < 8 {
return openflow.ErrInvalidPacketLength
}
v := binary.BigEndian.Uint32(data[4:8])
r.m[uint(field)] = v
return nil
}
func (r *Match) unmarshalHardwareAddrTLV(field uint8, data []byte) error {
if len(data) < 10 {
return openflow.ErrInvalidPacketLength
}
var mac net.HardwareAddr = make([]byte, 6)
copy(mac, data[4:10])
r.m[uint(field)] = mac
return nil
}
func (r *Match) unmarshalIPNetTLV(field uint8, hasmask uint8, data []byte) error {
length := 8
if hasmask == 1 {
length = 12
}
if len(data) < length {
return openflow.ErrInvalidPacketLength
}
ip := net.IPv4(data[4], data[5], data[6], data[7])
mask := []byte{0, 0, 0, 0}
if hasmask == 1 {
mask = []byte{data[8], data[9], data[10], data[11]}
}
ipnet := &net.IPNet{
IP: ip,
Mask: mask,
}
r.m[uint(field)] = ipnet
return nil
}
func (r *Match) unmarshalTLV(data []byte) error {
buf := data
// TLV header length is 4 bytes
for len(buf) >= 4 {
header := binary.BigEndian.Uint32(buf[0:4])
class := header >> 16 & 0xFFFF
if class != 0x8000 {
return errors.New("unsupported TLV class")
}
field := header >> 9 & 0x7F
hasmask := header >> 8 & 0x1
length := header & 0xFF
if len(buf) < int(4+length) {
return openflow.ErrInvalidPacketLength
}
switch field {
case OFPXMT_OFB_IN_PORT:
if err := r.unmarshalUint32TLV(OFPXMT_OFB_IN_PORT, buf); err != nil {
return err
}
case OFPXMT_OFB_ETH_DST:
if err := r.unmarshalHardwareAddrTLV(OFPXMT_OFB_ETH_DST, buf); err != nil {
return err
}
case OFPXMT_OFB_ETH_SRC:
if err := r.unmarshalHardwareAddrTLV(OFPXMT_OFB_ETH_SRC, buf); err != nil {
return err
}
case OFPXMT_OFB_ETH_TYPE:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_ETH_TYPE, buf); err != nil {
return err
}
case OFPXMT_OFB_VLAN_VID:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_VLAN_VID, buf); err != nil {
return err
}
case OFPXMT_OFB_VLAN_PCP:
if err := r.unmarshalUint8TLV(OFPXMT_OFB_VLAN_PCP, buf); err != nil {
return err
}
case OFPXMT_OFB_IP_PROTO:
if err := r.unmarshalUint8TLV(OFPXMT_OFB_IP_PROTO, buf); err != nil {
return err
}
case OFPXMT_OFB_IPV4_SRC:
if err := r.unmarshalIPNetTLV(OFPXMT_OFB_IPV4_SRC, uint8(hasmask), buf); err != nil {
return err
}
case OFPXMT_OFB_IPV4_DST:
if err := r.unmarshalIPNetTLV(OFPXMT_OFB_IPV4_DST, uint8(hasmask), buf); err != nil {
return err
}
case OFPXMT_OFB_TCP_SRC:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_TCP_SRC, buf); err != nil {
return err
}
case OFPXMT_OFB_TCP_DST:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_TCP_DST, buf); err != nil {
return err
}
case OFPXMT_OFB_UDP_SRC:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_UDP_SRC, buf); err != nil {
return err
}
case OFPXMT_OFB_UDP_DST:
if err := r.unmarshalUint16TLV(OFPXMT_OFB_UDP_DST, buf); err != nil {
return err
}
default:
// Do nothing
}
buf = buf[4+length:]
}
return nil
}
func (r *Match) UnmarshalBinary(data []byte) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if len(data) < 4 {
return openflow.ErrInvalidPacketLength
}
if binary.BigEndian.Uint16(data[0:2]) != OFPMT_OXM {
return openflow.ErrUnsupportedMatchType
}
length := binary.BigEndian.Uint16(data[2:4])
if len(data) < int(length) {
return openflow.ErrInvalidPacketLength
}
return r.unmarshalTLV(data[4:length])
}
================================================
FILE: openflow/of13/multipart_description.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"strings"
"github.com/superkkt/cherry/openflow"
)
type DescRequest struct {
openflow.Message
}
func NewDescRequest(xid uint32) openflow.DescRequest {
return &DescRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_MULTIPART_REQUEST, xid),
}
}
func (r *DescRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
// Multipart description request
binary.BigEndian.PutUint16(v[0:2], OFPMP_DESC)
// No flags and body
r.SetPayload(v)
return r.Message.MarshalBinary()
}
type DescReply struct {
openflow.Message
manufacturer string
hardware string
software string
serial string
description string
}
func (r DescReply) Manufacturer() string {
return r.manufacturer
}
func (r DescReply) Hardware() string {
return r.hardware
}
func (r DescReply) Software() string {
return r.software
}
func (r DescReply) Serial() string {
return r.serial
}
func (r DescReply) Description() string {
return r.description
}
func (r *DescReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 1064 {
return openflow.ErrInvalidPacketLength
}
r.manufacturer = strings.TrimRight(string(payload[8:264]), "\x00")
r.hardware = strings.TrimRight(string(payload[264:520]), "\x00")
r.software = strings.TrimRight(string(payload[520:776]), "\x00")
r.serial = strings.TrimRight(string(payload[776:808]), "\x00")
r.description = strings.TrimRight(string(payload[808:1064]), "\x00")
return nil
}
================================================
FILE: openflow/of13/multipart_flow_stats.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"errors"
"github.com/superkkt/cherry/openflow"
)
type FlowStatsRequest struct {
err error
openflow.Message
tableID uint8
cookie, cookieMask uint64
match openflow.Match
}
func NewFlowStatsRequest(xid uint32) openflow.FlowStatsRequest {
return &FlowStatsRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_MULTIPART_REQUEST, xid),
}
}
func (r *FlowStatsRequest) Error() error {
return r.err
}
func (r *FlowStatsRequest) Cookie() uint64 {
return r.cookie
}
func (r *FlowStatsRequest) SetCookie(cookie uint64) {
r.cookie = cookie
}
func (r *FlowStatsRequest) CookieMask() uint64 {
return r.cookieMask
}
func (r *FlowStatsRequest) SetCookieMask(mask uint64) {
r.cookieMask = mask
}
func (r *FlowStatsRequest) Match() openflow.Match {
return r.match
}
func (r *FlowStatsRequest) SetMatch(match openflow.Match) {
if match == nil {
panic("match is nil")
}
r.match = match
}
func (r *FlowStatsRequest) TableID() uint8 {
return r.tableID
}
// 0xFF means all table
func (r *FlowStatsRequest) SetTableID(id uint8) {
r.tableID = id
}
func (r *FlowStatsRequest) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
v := make([]byte, 40)
// Flow stats request
binary.BigEndian.PutUint16(v[0:2], OFPMP_FLOW)
v[8] = r.tableID
// v[9:12] is padding
binary.BigEndian.PutUint32(v[12:16], OFPP_ANY)
binary.BigEndian.PutUint32(v[16:20], OFPG_ANY)
// v[20:24] is padding
binary.BigEndian.PutUint64(v[24:32], r.cookie)
binary.BigEndian.PutUint64(v[32:40], r.cookieMask)
if r.match == nil {
return nil, errors.New("empty flow match")
}
match, err := r.match.MarshalBinary()
if err != nil {
return nil, err
}
v = append(v, match...)
r.SetPayload(v)
return r.Message.MarshalBinary()
}
// TODO: Implement FlowStatsReply
================================================
FILE: openflow/of13/multipart_port_description.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type PortDescRequest struct {
openflow.Message
}
func NewPortDescRequest(xid uint32) openflow.PortDescRequest {
return &PortDescRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_MULTIPART_REQUEST, xid),
}
}
func (r *PortDescRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
// Multipart description request
binary.BigEndian.PutUint16(v[0:2], OFPMP_PORT_DESC)
// No flags and body
r.SetPayload(v)
return r.Message.MarshalBinary()
}
type PortDescReply struct {
openflow.Message
ports []openflow.Port
}
func (r PortDescReply) Ports() []openflow.Port {
return r.ports
}
func (r *PortDescReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 8 {
return openflow.ErrInvalidPacketLength
}
nPorts := (len(payload) - 8) / 64
if nPorts == 0 {
return nil
}
r.ports = make([]openflow.Port, nPorts)
for i := 0; i < nPorts; i++ {
buf := payload[8+i*64:]
r.ports[i] = new(Port)
if err := r.ports[i].UnmarshalBinary(buf[0:64]); err != nil {
return err
}
}
return nil
}
================================================
FILE: openflow/of13/multipart_table_features.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type TableFeaturesRequest struct {
openflow.Message
}
func NewTableFeaturesRequest(xid uint32) openflow.TableFeaturesRequest {
return &TableFeaturesRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_MULTIPART_REQUEST, xid),
}
}
func (r *TableFeaturesRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
// Table features request
binary.BigEndian.PutUint16(v[0:2], OFPMP_TABLE_FEATURES)
// No flags and body
r.SetPayload(v)
return r.Message.MarshalBinary()
}
// TODO: Implement TableFeaturesReply
================================================
FILE: openflow/of13/packet_in.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type PacketIn struct {
openflow.Message
bufferID uint32
length uint16
inPort uint32
tableID uint8
reason uint8
cookie uint64
data []byte
}
func (r PacketIn) BufferID() uint32 {
return r.bufferID
}
func (r PacketIn) InPort() uint32 {
return r.inPort
}
func (r PacketIn) Data() []byte {
return r.data
}
func (r PacketIn) Length() uint16 {
return r.length
}
func (r PacketIn) TableID() uint8 {
return r.tableID
}
func (r PacketIn) Reason() uint8 {
return r.reason
}
func (r PacketIn) Cookie() uint64 {
return r.cookie
}
func (r *PacketIn) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 24 {
return openflow.ErrInvalidPacketLength
}
r.bufferID = binary.BigEndian.Uint32(payload[0:4])
r.length = binary.BigEndian.Uint16(payload[4:6])
r.reason = payload[6]
r.tableID = payload[7]
r.cookie = binary.BigEndian.Uint64(payload[8:16])
match := NewMatch()
if err := match.UnmarshalBinary(payload[16:]); err != nil {
return err
}
_, inport := match.InPort()
r.inPort = inport.Value()
matchLength := binary.BigEndian.Uint16(payload[18:20])
// Calculate padding length
rem := matchLength % 8
if rem > 0 {
matchLength += 8 - rem
}
dataOffset := 16 + matchLength + 2 // +2 is padding
if len(payload) >= int(dataOffset) {
// TODO: Check data size by comparing with r.Length
r.data = payload[dataOffset:]
}
return nil
}
================================================
FILE: openflow/of13/packet_out.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type PacketOut struct {
err error
openflow.Message
inPort openflow.InPort
action openflow.Action
data []byte
}
func NewPacketOut(xid uint32) openflow.PacketOut {
return &PacketOut{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_PACKET_OUT, xid),
}
}
func (r *PacketOut) Error() error {
return r.err
}
func (r *PacketOut) InPort() openflow.InPort {
return r.inPort
}
func (r *PacketOut) SetInPort(port openflow.InPort) {
r.inPort = port
}
func (r *PacketOut) Action() openflow.Action {
return r.action
}
func (r *PacketOut) SetAction(action openflow.Action) {
if action == nil {
panic("action is nil")
}
r.action = action
}
func (r *PacketOut) Data() []byte {
return r.data
}
func (r *PacketOut) SetData(data []byte) {
if data == nil {
panic("data is nil")
}
r.data = data
}
func (r *PacketOut) MarshalBinary() ([]byte, error) {
if r.err != nil {
return nil, r.err
}
action := make([]byte, 0)
if r.action != nil {
a, err := r.action.MarshalBinary()
if err != nil {
return nil, err
}
action = append(action, a...)
}
v := make([]byte, 16)
binary.BigEndian.PutUint32(v[0:4], OFP_NO_BUFFER)
port := r.inPort.Value()
if r.inPort.IsController() {
port = OFPP_CONTROLLER
}
binary.BigEndian.PutUint32(v[4:8], port)
binary.BigEndian.PutUint16(v[8:10], uint16(len(action)))
// v[10:16] is padding
v = append(v, action...)
if r.data != nil && len(r.data) > 0 {
v = append(v, r.data...)
}
r.SetPayload(v)
return r.Message.MarshalBinary()
}
================================================
FILE: openflow/of13/port.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"net"
"strings"
"github.com/superkkt/cherry/openflow"
)
type Port struct {
number uint32
mac net.HardwareAddr
name string
// Bitmap of OFPPC_* flags
config uint32
// Bitmap of OFPPS_* flags
state uint32
//
// Bitmaps of OFPPF_* that describe features. All bits zeroed if unsupported or unavailable.
//
current, advertised, supported, peer uint32
currentSpeed, maxSpeed uint32
}
func (r Port) Number() uint32 {
return r.number
}
func (r Port) MAC() net.HardwareAddr {
return r.mac
}
func (r Port) Name() string {
return r.name
}
func (r Port) IsPortDown() bool {
if r.config&OFPPC_PORT_DOWN != 0 {
return true
}
return false
}
func (r Port) IsLinkDown() bool {
if r.state&OFPPS_LINK_DOWN != 0 {
return true
}
return false
}
func (r Port) IsCopper() bool {
return r.current&OFPPF_COPPER != 0
}
func (r Port) IsFiber() bool {
return r.current&OFPPF_FIBER != 0
}
func (r Port) IsAutoNego() bool {
return r.current&OFPPF_AUTONEG != 0
}
func (r *Port) Speed() uint64 {
switch {
case r.current&OFPPF_10MB_HD != 0:
return 5
case r.current&OFPPF_10MB_FD != 0:
return 10
case r.current&OFPPF_100MB_HD != 0:
return 50
case r.current&OFPPF_100MB_FD != 0:
return 100
case r.current&OFPPF_1GB_HD != 0:
return 500
case r.current&OFPPF_1GB_FD != 0:
return 1000
case r.current&OFPPF_10GB_FD != 0:
return 10000
case r.current&OFPPF_40GB_FD != 0:
return 40000
case r.current&OFPPF_100GB_FD != 0:
return 100000
case r.current&OFPPF_1TB_FD != 0:
return 1000000
default:
return 0
}
}
func (r *Port) UnmarshalBinary(data []byte) error {
if len(data) < 64 {
return openflow.ErrInvalidPacketLength
}
r.number = binary.BigEndian.Uint32(data[0:4])
r.mac = make(net.HardwareAddr, 6)
copy(r.mac, data[8:14])
r.name = strings.TrimRight(string(data[16:32]), "\x00")
r.config = binary.BigEndian.Uint32(data[32:36])
r.state = binary.BigEndian.Uint32(data[36:40])
r.current = binary.BigEndian.Uint32(data[40:44])
r.advertised = binary.BigEndian.Uint32(data[44:48])
r.supported = binary.BigEndian.Uint32(data[48:52])
r.peer = binary.BigEndian.Uint32(data[52:56])
r.currentSpeed = binary.BigEndian.Uint32(data[56:60])
r.maxSpeed = binary.BigEndian.Uint32(data[60:64])
return nil
}
================================================
FILE: openflow/of13/port_status.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"github.com/superkkt/cherry/openflow"
)
type PortStatus struct {
openflow.Message
reason uint8
port openflow.Port
}
func (r PortStatus) Reason() openflow.PortReason {
switch r.reason {
case OFPPR_ADD:
return openflow.PortAdded
case OFPPR_DELETE:
return openflow.PortDeleted
case OFPPR_MODIFY:
return openflow.PortModified
default:
return openflow.PortReason(r.reason)
}
}
func (r PortStatus) Port() openflow.Port {
return r.port
}
func (r *PortStatus) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 72 {
return openflow.ErrInvalidPacketLength
}
r.reason = payload[0]
r.port = new(Port)
if err := r.port.UnmarshalBinary(payload[8:]); err != nil {
return err
}
return nil
}
================================================
FILE: openflow/of13/queue.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package of13
import (
"encoding/binary"
"github.com/superkkt/cherry/openflow"
)
type QueueGetConfigRequest struct {
openflow.Message
port openflow.OutPort
}
func NewQueueGetConfigRequest(xid uint32) openflow.QueueGetConfigRequest {
return &QueueGetConfigRequest{
Message: openflow.NewMessage(openflow.OF13_VERSION, OFPT_QUEUE_GET_CONFIG_REQUEST, xid),
}
}
func (r *QueueGetConfigRequest) Port() openflow.OutPort {
return r.port
}
func (r *QueueGetConfigRequest) SetPort(p openflow.OutPort) {
r.port = p
}
func (r *QueueGetConfigRequest) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
if r.port.IsNone() {
binary.BigEndian.PutUint32(v[0:4], OFPP_ANY)
} else {
binary.BigEndian.PutUint32(v[0:4], r.port.Value())
}
// v[4:8] is padding
r.SetPayload(v)
return r.Message.MarshalBinary()
}
// QueueGetConfigReply implementations by ksang:
type QueueProperty struct {
typ openflow.PropertyType
length uint16
rate uint16
experimenter uint32
data []byte
}
func NewQueueProperty() openflow.QueueProperty {
return &QueueProperty{}
}
func (r *QueueProperty) Type() openflow.PropertyType {
return r.typ
}
func (r *QueueProperty) Length() uint16 {
return r.length
}
func (r *QueueProperty) Rate() (uint16, error) {
if r.typ != openflow.OFPQT_MIN_RATE && r.typ != openflow.OFPQT_MAX_RATE {
return 0x0, openflow.ErrInvalidPropertyMethod
}
return r.rate, nil
}
func (r *QueueProperty) Experimenter() (uint32, error) {
if r.typ != openflow.OFPQT_EXPERIMENTER {
return 0x0, openflow.ErrInvalidPropertyMethod
}
return r.experimenter, nil
}
func (r *QueueProperty) Data() []byte {
return r.data
}
func (r *QueueProperty) UnmarshalBinary(data []byte) error {
if len(data) < 8 {
return openflow.ErrInvalidPacketLength
}
r.typ = openflow.PropertyType(binary.BigEndian.Uint16(data[0:2]))
r.length = binary.BigEndian.Uint16(data[2:4])
// data[4:8] is pad
if len(data) < int(r.length) || int(r.length) < 16 {
return openflow.ErrInvalidPacketLength
}
switch r.typ {
case openflow.OFPQT_MIN_RATE:
r.rate = binary.BigEndian.Uint16(data[8:10])
case openflow.OFPQT_MAX_RATE:
r.rate = binary.BigEndian.Uint16(data[8:10])
case openflow.OFPQT_EXPERIMENTER:
r.experimenter = binary.BigEndian.Uint32(data[8:12])
// data[12:16] is pad
r.data = make([]byte, int(r.length)-16)
copy(r.data, data[16:int(r.length)])
}
return nil
}
type Queue struct {
id uint32
port uint32
length uint16
property []openflow.QueueProperty
}
func (r *Queue) ID() uint32 {
return r.id
}
func (r *Queue) Port() uint32 {
return r.port
}
func (r *Queue) Length() uint16 {
return r.length
}
func (r *Queue) Property() []openflow.QueueProperty {
return r.property
}
func (r *Queue) UnmarshalBinary(data []byte) error {
if len(data) < 16 {
return openflow.ErrInvalidPacketLength
}
r.id = binary.BigEndian.Uint32(data[0:4])
r.port = binary.BigEndian.Uint32(data[4:8])
r.length = binary.BigEndian.Uint16(data[8:10])
// data[10:16] is pad
if len(data) < int(r.length) || int(r.length) < 16 {
return openflow.ErrInvalidPacketLength
}
for i := 16; i < int(r.length); {
p := NewQueueProperty()
if err := p.UnmarshalBinary(data[i:]); err != nil {
return err
}
r.property = append(r.property, p)
i += int(p.Length())
}
return nil
}
func NewQueue() openflow.Queue {
return &Queue{}
}
type QueueGetConfigReply struct {
openflow.Message
port uint32
queue []openflow.Queue
}
func (r *QueueGetConfigReply) Port() uint32 {
return r.port
}
func (r *QueueGetConfigReply) Queue() []openflow.Queue {
return r.queue
}
func (r *QueueGetConfigReply) UnmarshalBinary(data []byte) error {
if err := r.Message.UnmarshalBinary(data); err != nil {
return err
}
payload := r.Payload()
if payload == nil || len(payload) < 8 {
return openflow.ErrInvalidPacketLength
}
r.port = binary.BigEndian.Uint32(payload[0:4])
// Unmarshal Queues
for i := 8; i < len(payload); {
q := NewQueue()
if err := q.UnmarshalBinary(payload[i:]); err != nil {
return err
}
r.queue = append(r.queue, q)
i += int(q.Length())
}
return nil
}
================================================
FILE: openflow/packet_in.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type PacketIn interface {
Header
BufferID() uint32
Length() uint16
InPort() uint32
TableID() uint8
Reason() uint8
Cookie() uint64
Data() []byte
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/packet_out.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type PacketOut interface {
Action() Action
Data() []byte
encoding.BinaryMarshaler
Error() error
Header
InPort() InPort
SetAction(action Action)
SetData(data []byte)
SetInPort(port InPort)
}
================================================
FILE: openflow/port.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
"fmt"
"net"
)
const (
table = iota
flood
all
controller
inport
none
)
type OutPort struct {
logical uint8
value uint32
}
// NewOutPort returns output port whose default value is FLOOD
func NewOutPort() OutPort {
return OutPort{
logical: 0x1 << flood,
}
}
func (r *OutPort) SetTable() {
r.logical = 0x1 << table
}
func (r *OutPort) IsTable() bool {
return r.logical&(0x1<
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type PortDescRequest interface {
Header
encoding.BinaryMarshaler
}
type PortDescReply interface {
Header
Ports() []Port
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/port_status.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type PortReason uint8
const (
PortAdded PortReason = iota
PortDeleted
PortModified
)
type PortStatus interface {
Header
Reason() PortReason
Port() Port
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/queue.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type PropertyType uint16
const (
OFPQT_NONE PropertyType = iota
OFPQT_MIN_RATE
OFPQT_MAX_RATE
OFPQT_EXPERIMENTER = 0xffff
)
type Queue interface {
ID() uint32
Port() uint32
Length() uint16
Property() []QueueProperty
encoding.BinaryUnmarshaler
}
type QueueProperty interface {
Type() PropertyType
Length() uint16
Rate() (uint16, error)
Experimenter() (uint32, error)
Data() []byte
encoding.BinaryUnmarshaler
}
type QueueGetConfigRequest interface {
Header
Port() OutPort
SetPort(OutPort)
encoding.BinaryMarshaler
}
type QueueGetConfigReply interface {
Header
Port() uint32
Queue() []Queue
encoding.BinaryUnmarshaler
}
================================================
FILE: openflow/table_features.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package openflow
import (
"encoding"
)
type TableFeaturesRequest interface {
Header
encoding.BinaryMarshaler
}
// TODO: Implement TableFeaturesReply
================================================
FILE: openflow/transceiver/stream.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package transceiver
import (
"bufio"
"io"
"net"
"sync"
"time"
)
// Stream is a buffered I/O channel.
type Stream struct {
// Underlying socket.
channel io.ReadWriteCloser
reader struct {
mutex sync.Mutex
// Buffered reader on the underlying socket.
//
// NOTE:
// rd needs locking, otherwise Peek()'s result slice can be
// corrupted by subsequent Read() or ReadN() calls because
// the result slice is just a pointer to the reader's internal
// buffer, which will be overwritten by Read() and ReadN().
rd *bufio.Reader
timeout time.Duration
timestamp time.Time
}
writer struct {
mutex sync.Mutex
wr io.Writer
timeout time.Duration
timestamp time.Time
}
}
type deadline interface {
SetReadDeadline(time.Time) error
SetWriteDeadline(time.Time) error
}
// NewStream returns a new buffered I/O channel. channel is an underlying I/O channel that implements io.ReadWriteCloser.
func NewStream(channel io.ReadWriteCloser, bufSize int) *Stream {
c := new(Stream)
c.channel = channel
c.reader.rd = bufio.NewReaderSize(channel, bufSize)
c.writer.wr = channel
return c
}
type dummyAddr struct{}
func (r dummyAddr) Network() string {
return "DummyAddress"
}
func (r dummyAddr) String() string {
return ""
}
func (r *Stream) RemoteAddr() net.Addr {
type addr interface {
RemoteAddr() net.Addr
}
v, ok := r.channel.(addr)
if !ok {
return dummyAddr{}
}
return v.RemoteAddr()
}
// SetReadTimeout sets read timeout of the underlying socket if it implements deadline interface.
func (r *Stream) SetReadTimeout(t time.Duration) {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
r.reader.timeout = t
logger.Debugf("set read timeout to %v", t)
}
func (r *Stream) GetReadTimeout() time.Duration {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
return r.reader.timeout
}
// SetWriteTimeout sets write timeout of the underlying socket if it implements deadline interface.
func (r *Stream) SetWriteTimeout(t time.Duration) {
r.writer.mutex.Lock()
defer r.writer.mutex.Unlock()
r.writer.timeout = t
logger.Debugf("set write timeout to %v", t)
}
func (r *Stream) GetWriteTimeout() time.Duration {
r.writer.mutex.Lock()
defer r.writer.mutex.Unlock()
return r.writer.timeout
}
// Read is a wrapper function of bufio.Reader.Read().
func (r *Stream) Read(p []byte) (n int, err error) {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
r.setReadDeadline()
n, err = r.reader.rd.Read(p)
if err != nil {
return n, err
}
r.reader.timestamp = time.Now()
return n, nil
}
// NOTE: The caller should lock the reader mutex before calling this function.
func (r *Stream) setReadDeadline() {
// Directly use the underlying socket, instead of the reader, to set I/O timeout.
d, ok := r.channel.(deadline)
if !ok {
logger.Debug("socket does not support the read deadline interface!")
return
}
if r.reader.timeout > 0 {
d.SetReadDeadline(time.Now().Add(r.reader.timeout))
} else {
d.SetReadDeadline(time.Time{})
}
}
// Peek is a wrapper function of bufio.Reader.Peek().
func (r *Stream) Peek(n int) ([]byte, error) {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
if n <= 0 {
return []byte{}, nil
}
r.setReadDeadline()
v, err := r.reader.rd.Peek(n)
if err != nil {
return nil, err
}
// Deep copy of the peek result because v is a pointer to reader's internal
// buffer that may be corrupted by subsequent other read calls.
p := make([]byte, len(v))
copy(p, v)
return p, nil
}
// ReadN reads exactly n bytes from the underlying socket. It returns non-nil error if len(p) < n, and the data, whose
// length is len(p) bytes long, still remains in the socket buffer.
func (r *Stream) ReadN(n int) (p []byte, err error) {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
r.setReadDeadline()
// Wait until we have n-bytes data in the reader or timeout.
if _, err = r.reader.rd.Peek(n); err != nil {
return nil, err
}
p = make([]byte, n)
c, err := r.reader.rd.Read(p)
if err != nil {
return nil, err
}
if c != n {
panic("insufficient read")
}
r.reader.timestamp = time.Now()
return p, nil
}
// LastRead returns the timestamp of the last successful read operation except Peek().
func (r *Stream) LastRead() time.Time {
r.reader.mutex.Lock()
defer r.reader.mutex.Unlock()
return r.reader.timestamp
}
// Write is a wrapper function of net.Conn.Write().
func (r *Stream) Write(p []byte) (n int, err error) {
r.writer.mutex.Lock()
defer r.writer.mutex.Unlock()
r.setWriteDeadline()
n, err = r.writer.wr.Write(p)
if err != nil {
return n, err
}
r.writer.timestamp = time.Now()
return n, nil
}
// NOTE: The caller should lock the writer mutex before calling this function.
func (r *Stream) setWriteDeadline() {
// Directly use the underlying socket, instead of the writer, to set I/O timeout.
d, ok := r.channel.(deadline)
if !ok {
logger.Debug("socket does not support the write deadline interface!")
return
}
if r.writer.timeout > 0 {
d.SetWriteDeadline(time.Now().Add(r.writer.timeout))
} else {
d.SetWriteDeadline(time.Time{})
}
}
// LastWrite returns the timestamp of the last successful write operation.
func (r *Stream) LastWrite() time.Time {
r.writer.mutex.Lock()
defer r.writer.mutex.Unlock()
return r.writer.timestamp
}
// Close is a wrapper function of net.Conn.Close().
func (r *Stream) Close() error {
return r.channel.Close()
}
================================================
FILE: openflow/transceiver/transceiver.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package transceiver
import (
"context"
"encoding"
"encoding/binary"
"fmt"
"time"
"github.com/superkkt/cherry/openflow"
"github.com/superkkt/cherry/openflow/of10"
"github.com/superkkt/cherry/openflow/of13"
"github.com/pkg/errors"
"github.com/superkkt/go-logging"
)
var (
logger = logging.MustGetLogger("transceiver")
)
const (
// Allowed idle time before we send an echo request to a switch.
maxIdleTime = 10 * time.Second
// I/O timeouts (These timeouts should be less than maxIdleTime).
readTimeout = 1 * time.Second
writeTimeout = readTimeout * 2
)
type Writer interface {
Write(msg encoding.BinaryMarshaler) error
}
type WriteCloser interface {
Writer
Close() error
}
type Transceiver struct {
stream *Stream
observer Handler
version uint8
factory openflow.Factory
pingCounter uint
closed bool
}
type Handler interface {
OnHello(openflow.Factory, Writer, openflow.Hello) error
OnError(openflow.Factory, Writer, openflow.Error) error
OnFeaturesReply(openflow.Factory, Writer, openflow.FeaturesReply) error
OnGetConfigReply(openflow.Factory, Writer, openflow.GetConfigReply) error
OnDescReply(openflow.Factory, Writer, openflow.DescReply) error
OnPortDescReply(openflow.Factory, Writer, openflow.PortDescReply) error
OnPortStatus(openflow.Factory, Writer, openflow.PortStatus) error
OnFlowRemoved(openflow.Factory, Writer, openflow.FlowRemoved) error
OnPacketIn(openflow.Factory, Writer, openflow.PacketIn) error
OnBarrierReply(openflow.Factory, Writer, openflow.BarrierReply) error
}
func NewTransceiver(stream *Stream, handler Handler) *Transceiver {
if stream == nil {
panic("stream is nil")
}
if handler == nil {
panic("handler is nil")
}
return &Transceiver{
stream: stream,
observer: handler,
}
}
func (r *Transceiver) Version() (negotiated bool, version uint8) {
if r.version == 0 {
// Not yet negotiated
return false, 0
}
return true, r.version
}
func isTimeout(err error) bool {
type Timeout interface {
Timeout() bool
}
if v, ok := err.(Timeout); ok {
return v.Timeout()
}
return false
}
func (r *Transceiver) sendEchoRequest() error {
if r.pingCounter > 2 {
return errors.New("device does not respond to our echo request")
}
echo, err := r.factory.NewEchoRequest()
if err != nil {
return err
}
// We use current timestamp to check network latency between our controller and a switch.
timestamp, err := time.Now().GobEncode()
if err != nil {
return err
}
echo.SetData(timestamp)
if err := r.Write(echo); err != nil {
return errors.Wrap(err, "failed to send ECHO_REQUEST message")
}
r.pingCounter++
return nil
}
func (r *Transceiver) Run(ctx context.Context) error {
defer logger.Info("transceiver is closed")
r.stream.SetReadTimeout(readTimeout)
r.stream.SetWriteTimeout(writeTimeout)
readerCtx, cancelReader := context.WithCancel(ctx)
defer cancelReader()
reader := r.runReader(readerCtx)
// Negotiate the protocol version
packet, err := r.negotiate(ctx, reader)
if err != nil {
return errors.Wrap(err, "failed to negotiate the protocol version")
}
// Infinite loop
for {
// Dispatch the incoming packet
if err := r.dispatch(packet); err != nil {
if !isTemporaryErr(err) {
return err
}
// Ignore the temporary error. Just log the error and keep go on.
logger.Errorf("failed to dispatch the packet: %v", err)
}
// Read the next packet
var ok bool
select {
case <-ctx.Done():
logger.Info("context done")
return nil
case packet, ok = <-reader:
if !ok {
logger.Info("the reader channel is closed")
return nil
}
remain := len(reader)
if remain > 0 {
logger.Debugf("%v remaining unread packet(s) in the reader channel", remain)
}
}
}
}
func (r *Transceiver) negotiate(ctx context.Context, reader <-chan []byte) (packet []byte, err error) {
select {
case <-ctx.Done():
return nil, errors.New("context done")
case <-time.After(30 * time.Second):
return nil, errors.New("inactive for too long")
case packet, ok := <-reader:
if !ok {
return nil, errors.New("the reader channel is closed")
}
// The first message should be HELLO.
if packet[1] != 0x00 {
return nil, errors.New("missing HELLO message")
}
// Version negotiation
if packet[0] < openflow.OF13_VERSION {
r.version = openflow.OF10_VERSION
r.factory = of10.NewFactory()
logger.Info("negotiated to openflow version 1.0")
} else {
r.version = openflow.OF13_VERSION
r.factory = of13.NewFactory()
logger.Info("negotiated to openflow version 1.3")
}
// Return the initial packet to dispatch it.
return packet, nil
}
}
func (r *Transceiver) runReader(ctx context.Context) <-chan []byte {
// Buffered channel
c := make(chan []byte, 4096)
go func() {
// The channel c will be closed when this goroutine returns in order to notice the connection has been closed.
defer close(c)
defer logger.Info("transceiver reader is closed")
lastActivated := time.Now()
for {
select {
case <-ctx.Done():
logger.Info("context done")
return
default:
}
// Read the next packet
packet, err := r.readPacket()
if err != nil {
if !isTimeout(err) {
logger.Errorf("failed to read the next packet: %v", err)
return
}
// Timeout occurrs. Send a ping request if necessary.
if time.Now().After(lastActivated.Add(maxIdleTime)) {
if err := r.sendEchoRequest(); err != nil {
logger.Errorf("failed to send an echo request: %v", err)
return
}
}
continue
}
// Update the timestamp
lastActivated = time.Now()
ok, err := r.handleEcho(packet)
if err != nil {
logger.Errorf("failed to handle the echo request or response: %v", err)
return
}
if ok {
// Do not forward the echo request and response
// packets because this reader handles them.
continue
}
// Forward messages except the echo request and response.
select {
case c <- packet:
default:
// Drop the packet if we cannot immediately carry it.
logger.Warning("transceiver buffer full: drop the incoming packet!")
}
}
}()
return c
}
func isTemporaryErr(err error) bool {
e, ok := errors.Cause(err).(interface {
Temporary() bool
})
return ok && e.Temporary()
}
func (r *Transceiver) readPacket() ([]byte, error) {
header, err := r.stream.Peek(8) // peek ofp_header
if err != nil {
return nil, err
}
length := binary.BigEndian.Uint16(header[2:4])
if length < 8 {
return nil, openflow.ErrInvalidPacketLength
}
packet, err := r.stream.ReadN(int(length))
if err != nil {
return nil, err
}
return packet, nil
}
func (r *Transceiver) Write(msg encoding.BinaryMarshaler) error {
packet, err := msg.MarshalBinary()
if err != nil {
return err
}
if _, err := r.stream.Write(packet); err != nil {
return err
}
return nil
}
func (r *Transceiver) handleEcho(packet []byte) (ok bool, err error) {
switch packet[0] {
case openflow.OF10_VERSION:
return r.handleOF10Echo(packet)
case openflow.OF13_VERSION:
return r.handleOF13Echo(packet)
default:
return false, openflow.ErrUnsupportedVersion
}
}
func (r *Transceiver) handleOF10Echo(packet []byte) (ok bool, err error) {
switch packet[1] {
case of10.OFPT_ECHO_REQUEST:
return true, r.handleEchoRequest(packet)
case of10.OFPT_ECHO_REPLY:
return true, r.handleEchoReply(packet)
default:
// Do not anything for other types of the message
return false, nil
}
}
func (r *Transceiver) handleOF13Echo(packet []byte) (handled bool, err error) {
switch packet[1] {
case of13.OFPT_ECHO_REQUEST:
return true, r.handleEchoRequest(packet)
case of13.OFPT_ECHO_REPLY:
return true, r.handleEchoReply(packet)
default:
// Do not anything for other types of the message
return false, nil
}
}
func (r *Transceiver) dispatch(packet []byte) error {
if packet[0] != r.version {
return fmt.Errorf("mis-matched OpenFlow version: negotiated=%v, packet=%v", r.version, packet[0])
}
switch r.version {
case openflow.OF10_VERSION:
return r.handleOF10Message(packet)
case openflow.OF13_VERSION:
return r.handleOF13Message(packet)
default:
return openflow.ErrUnsupportedVersion
}
}
func (r *Transceiver) handleOF10Message(packet []byte) error {
switch packet[1] {
case of10.OFPT_HELLO:
return r.handleHello(packet)
case of10.OFPT_ERROR:
return r.handleError(packet)
case of10.OFPT_FEATURES_REPLY:
return r.handleFeaturesReply(packet)
case of10.OFPT_GET_CONFIG_REPLY:
return r.handleGetConfigReply(packet)
case of10.OFPT_STATS_REPLY:
switch binary.BigEndian.Uint16(packet[8:10]) {
case of10.OFPST_DESC:
return r.handleDescReply(packet)
default:
// Unsupported message. Do nothing.
return nil
}
case of10.OFPT_PORT_STATUS:
return r.handlePortStatus(packet)
case of10.OFPT_FLOW_REMOVED:
return r.handleFlowRemoved(packet)
case of10.OFPT_PACKET_IN:
return r.handlePacketIn(packet)
case of10.OFPT_BARRIER_REPLY:
return r.handleBarrierReply(packet)
default:
// Unsupported message. Do nothing.
return nil
}
}
func (r *Transceiver) handleOF13Message(packet []byte) error {
switch packet[1] {
case of13.OFPT_HELLO:
return r.handleHello(packet)
case of13.OFPT_ERROR:
return r.handleError(packet)
case of13.OFPT_FEATURES_REPLY:
return r.handleFeaturesReply(packet)
case of13.OFPT_GET_CONFIG_REPLY:
return r.handleGetConfigReply(packet)
case of13.OFPT_MULTIPART_REPLY:
switch binary.BigEndian.Uint16(packet[8:10]) {
case of13.OFPMP_DESC:
return r.handleDescReply(packet)
case of13.OFPMP_PORT_DESC:
return r.handlePortDescReply(packet)
default:
// Unsupported message. Do nothing.
return nil
}
case of13.OFPT_PORT_STATUS:
return r.handlePortStatus(packet)
case of13.OFPT_FLOW_REMOVED:
return r.handleFlowRemoved(packet)
case of13.OFPT_PACKET_IN:
return r.handlePacketIn(packet)
case of13.OFPT_BARRIER_REPLY:
return r.handleBarrierReply(packet)
default:
// Unsupported message. Do nothing.
return nil
}
}
func (r *Transceiver) handleEchoRequest(packet []byte) error {
msg, err := r.factory.NewEchoRequest()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
logger.Debug("received an ECHO_REQUEST packet")
reply, err := r.factory.NewEchoReply()
if err != nil {
return err
}
// Copy transaction ID and data from the incoming echo request message
reply.SetTransactionID(msg.TransactionID())
reply.SetData(msg.Data())
// Send the echo reply
if err := r.Write(reply); err != nil {
return errors.Wrap(err, "failed to send ECHO_REPLY message")
}
logger.Debug("sent an ECHO_REPLY packet")
return nil
}
func (r *Transceiver) handleEchoReply(packet []byte) error {
msg, err := r.factory.NewEchoReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
logger.Debug("received an ECHO_REPLY packet")
data := msg.Data()
if data == nil || len(data) != 8 {
// Some broken switch sends an unexpected echo reply data.
logger.Debug("unexpected ECHO_REPLY data: invalid data length")
} else {
timestamp := time.Time{}
if err := timestamp.GobDecode(data); err != nil {
logger.Debug("unexpected timestamp data in the ECHO_REPLY packet")
} else {
// Network latency
logger.Debugf("transceiver latency: %v", time.Now().Sub(timestamp))
}
}
// Reset the ping counter
r.pingCounter = 0
return nil
}
func (r *Transceiver) handleHello(packet []byte) error {
msg, err := r.factory.NewHello()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnHello(r.factory, r, msg)
}
func (r *Transceiver) handleError(packet []byte) error {
msg, err := r.factory.NewError()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnError(r.factory, r, msg)
}
func (r *Transceiver) handleFeaturesReply(packet []byte) error {
msg, err := r.factory.NewFeaturesReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnFeaturesReply(r.factory, r, msg)
}
func (r *Transceiver) handleGetConfigReply(packet []byte) error {
msg, err := r.factory.NewGetConfigReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnGetConfigReply(r.factory, r, msg)
}
func (r *Transceiver) handleDescReply(packet []byte) error {
msg, err := r.factory.NewDescReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnDescReply(r.factory, r, msg)
}
func (r *Transceiver) handlePortDescReply(packet []byte) error {
msg, err := r.factory.NewPortDescReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnPortDescReply(r.factory, r, msg)
}
func (r *Transceiver) handlePortStatus(packet []byte) error {
msg, err := r.factory.NewPortStatus()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnPortStatus(r.factory, r, msg)
}
func (r *Transceiver) handleFlowRemoved(packet []byte) error {
msg, err := r.factory.NewFlowRemoved()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnFlowRemoved(r.factory, r, msg)
}
func (r *Transceiver) handlePacketIn(packet []byte) error {
msg, err := r.factory.NewPacketIn()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnPacketIn(r.factory, r, msg)
}
func (r *Transceiver) handleBarrierReply(packet []byte) error {
msg, err := r.factory.NewBarrierReply()
if err != nil {
return err
}
if err := msg.UnmarshalBinary(packet); err != nil {
return err
}
return r.observer.OnBarrierReply(r.factory, r, msg)
}
func (r *Transceiver) Close() error {
if r.closed {
return nil
}
if err := r.stream.Close(); err != nil {
return err
}
r.closed = true
return nil
}
================================================
FILE: protocol/arp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"net"
)
type ARP struct {
HWType uint16
ProtoType uint16
HWLength uint8
ProtoLength uint8
Operation uint16
SHA net.HardwareAddr // Sender Hardware Address
SPA net.IP // Sender Protocol Address
THA net.HardwareAddr // Target Hardware Address
TPA net.IP // Target Protocol Address
}
func NewARPRequest(sha, tha net.HardwareAddr, spa, tpa net.IP) *ARP {
return &ARP{
HWType: 1, // Ethernet
ProtoType: 0x0800, // IPv4
HWLength: 6, // Size of Ethernet MAC address
ProtoLength: 4, // Size of IPv4 address
Operation: 1, // ARP request
SHA: sha,
SPA: spa,
THA: tha,
TPA: tpa,
}
}
func NewARPReply(sha, tha net.HardwareAddr, spa, tpa net.IP) *ARP {
return &ARP{
HWType: 1, // Ethernet
ProtoType: 0x0800, // IPv4
HWLength: 6, // Size of Ethernet MAC address
ProtoLength: 4, // Size of IPv4 address
Operation: 2, // ARP reply
SHA: sha,
SPA: spa,
THA: tha,
TPA: tpa,
}
}
func (r ARP) String() string {
return fmt.Sprintf("HWType=%v, ProtoType=%v, HWLength=%v, ProtoLength=%v, Operation=%v, SHA=%v, SPA=%v, THA=%v, TPA=%v", r.HWType, r.ProtoType, r.HWLength, r.ProtoLength, r.Operation, r.SHA, r.SPA, r.THA, r.TPA)
}
func (r ARP) MarshalBinary() ([]byte, error) {
if r.SHA == nil || r.SPA == nil || r.THA == nil || r.TPA == nil {
return nil, errors.New("invalid hardware or protocol address")
}
v := make([]byte, 28)
binary.BigEndian.PutUint16(v[0:2], r.HWType)
binary.BigEndian.PutUint16(v[2:4], r.ProtoType)
v[4] = r.HWLength
v[5] = r.ProtoLength
binary.BigEndian.PutUint16(v[6:8], r.Operation)
copy(v[8:14], r.SHA)
spa := r.SPA.To4()
if spa == nil {
return nil, errors.New("source protocol address is not an IPv4 address")
}
copy(v[14:18], spa)
copy(v[18:24], r.THA)
tpa := r.TPA.To4()
if tpa == nil {
return nil, errors.New("target protocol address is not an IPv4 address")
}
copy(v[24:28], tpa)
return v, nil
}
func (r *ARP) UnmarshalBinary(data []byte) error {
if len(data) < 28 {
return errors.New("invalid ARP packet length")
}
r.HWType = binary.BigEndian.Uint16(data[0:2])
r.ProtoType = binary.BigEndian.Uint16(data[2:4])
r.HWLength = data[4]
r.ProtoLength = data[5]
r.Operation = binary.BigEndian.Uint16(data[6:8])
r.SHA = data[8:14]
r.SPA = data[14:18]
r.THA = data[18:24]
r.TPA = data[24:28]
return nil
}
================================================
FILE: protocol/checksum.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
)
func aroundCarry(sum uint32) uint32 {
v := sum
for {
if (v >> 16) == 0 {
break
}
upper := (v >> 16) & 0xFFFF
lower := v & 0xFFFF
v = upper + lower
}
return v
}
func calculateChecksum(header []byte) uint16 {
v := header
if len(v)%2 != 0 {
v = append(v, byte(0))
}
var sum uint32 = 0
for i := 0; i < len(v); i += 2 {
sum += uint32(binary.BigEndian.Uint16(v[i : i+2]))
}
sum = aroundCarry(sum)
return ^uint16(sum)
}
================================================
FILE: protocol/dhcp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"strings"
"sync"
"time"
)
// See RFC 2131: Dynamic Host Configuration Protocol.
type DHCP struct {
Op DHCPOpcode // Message op code / message type.
Hops uint8
XID uint32 // Transaction ID.
Elapsed time.Duration // Elapsed since client began address acquisition or renewal process.
Flags uint16
CIAddr net.IP // Client IP address.
YIAddr net.IP // Your (client) IP address.
SIAddr net.IP // IP address of next server to use in bootstrap.
GIAddr net.IP // Relay agent IP address, used in booting via a relay agent.
CHAddr net.HardwareAddr // Client hardware address.
SName string // Optional server host name.
File string // Boot file name.
Options []DHCPOption // Optional parameters field.
m sync.Map // Map for options to search.
}
type DHCPOpcode uint8
const (
DHCPOpcodeRequest DHCPOpcode = 1
DHCPOpcodeReply DHCPOpcode = 2
)
func (r *DHCP) MarshalBinary() ([]byte, error) {
if r.Op != DHCPOpcodeRequest && r.Op != DHCPOpcodeReply {
return nil, fmt.Errorf("invalid Op value: %v", r.Op)
}
v := make([]byte, 236)
v[0] = byte(r.Op)
v[1] = 1 // HType for Ethernet.
v[2] = 6 // HLen for Ethernet.
v[3] = r.Hops
binary.BigEndian.PutUint32(v[4:8], r.XID)
binary.BigEndian.PutUint16(v[8:10], uint16(r.Elapsed.Seconds()))
binary.BigEndian.PutUint16(v[10:12], r.Flags)
if err := marshalIP(v[12:16], r.CIAddr); err != nil {
return nil, fmt.Errorf("CIAddr: %v", err)
}
if err := marshalIP(v[16:20], r.YIAddr); err != nil {
return nil, fmt.Errorf("YIAddr: %v", err)
}
if err := marshalIP(v[20:24], r.SIAddr); err != nil {
return nil, fmt.Errorf("SIAddr: %v", err)
}
if err := marshalIP(v[24:28], r.GIAddr); err != nil {
return nil, fmt.Errorf("GIAddr: %v", err)
}
if err := marshalMAC(v[28:44], r.CHAddr); err != nil {
return nil, fmt.Errorf("CHAddr: %v", err)
}
copy(v[44:107], r.SName)
// v[108] = 0x0 for null-terminated string.
copy(v[108:235], r.File)
// v[236] = 0x0 for null-terminated string.
// Magic cookie.
v = append(v, []byte{0x63, 0x82, 0x53, 0x63}...)
for _, opt := range r.Options {
clv, err := opt.MarshalBinary()
if err != nil {
return nil, err
}
v = append(v, clv...)
}
v = append(v, byte(255)) // End option mark.
// From RFC 2131: A DHCP client must be prepared to receive DHCP messages with an 'options' field of at least length 312 octets.
const minDHCPPacketLen = 312
if len(v) < minDHCPPacketLen {
v = append(v, bytes.Repeat([]byte{0}, minDHCPPacketLen-len(v))...) // Zero padding.
}
return v, nil
}
func (r *DHCP) UnmarshalBinary(data []byte) (err error) {
if len(data) < 236 {
return errors.New("invalid DHCP packet length")
}
r.Op = DHCPOpcode(data[0])
if r.Op != DHCPOpcodeRequest && r.Op != DHCPOpcodeReply {
return fmt.Errorf("unexpected DHCP Opcode: %v", r.Op)
}
if data[1] != 1 || data[2] != 6 {
return fmt.Errorf("unsupported hardware address type: HType=%v, HLen=%v", data[1], data[2])
}
r.Hops = data[3]
r.XID = binary.BigEndian.Uint32(data[4:8])
r.Elapsed = time.Duration(binary.BigEndian.Uint16(data[8:10])) * time.Second
r.Flags = binary.BigEndian.Uint16(data[10:12])
r.CIAddr = net.IPv4(data[12], data[13], data[14], data[15])
r.YIAddr = net.IPv4(data[16], data[17], data[18], data[19])
r.SIAddr = net.IPv4(data[20], data[21], data[22], data[23])
r.GIAddr = net.IPv4(data[24], data[25], data[26], data[27])
r.CHAddr, err = unmarshalMAC(data[28:44])
if err != nil {
return err
}
r.SName = unmarshalCString(data[44:108])
r.File = unmarshalCString(data[108:236])
// No options?
if len(data[236:]) == 0 {
return nil
}
opt := data[236:]
for len(opt) > 0 {
// End mark?
if opt[0] == 255 {
break
}
// Padding?
if opt[0] == 0 {
opt = opt[1:]
continue
}
// Magic cookie?
if opt[0] == 0x63 {
if len(opt) >= 4 && opt[1] == 0x82 && opt[2] == 0x53 && opt[3] == 0x63 {
opt = opt[4:]
continue
}
}
clv := DHCPOption{}
if err := clv.UnmarshalBinary(opt); err != nil {
return err
}
r.Options = append(r.Options, clv)
r.m.Store(clv.Code, clv)
opt = opt[2+len(clv.Value):] // +2 for Code and Length fields.
}
return nil
}
func (r *DHCP) Option(code uint8) (opt DHCPOption, ok bool) {
v, ok := r.m.Load(code)
if ok == false {
return DHCPOption{}, false
}
return v.(DHCPOption), true
}
func unmarshalCString(data []byte) string {
if len(data) == 0 {
return ""
}
idx := strings.Index(string(data), "\x00")
if idx < 0 {
// No null-terminated, just return the whole string.
return string(data)
}
return string(data[:idx])
}
func marshalIP(v []byte, addr net.IP) error {
if len(v) < 4 {
panic("invalid destination buffer length")
}
if addr == nil {
copy(v, []byte{0, 0, 0, 0})
return nil
}
ipv4 := addr.To4()
if ipv4 == nil {
return fmt.Errorf("unsuppoted IP address type: %v", addr)
}
copy(v, ipv4)
return nil
}
func marshalMAC(v []byte, mac net.HardwareAddr) error {
if len(v) < 6 {
panic("invalid destination buffer length")
}
if mac == nil {
copy(v, []byte{0, 0, 0, 0, 0, 0})
return nil
}
if len(mac) != 6 {
return fmt.Errorf("unsupported MAC address type: %v", mac)
}
copy(v, mac)
return nil
}
func unmarshalMAC(data []byte) (net.HardwareAddr, error) {
if len(data) < 6 {
return nil, errors.New("invalid MAC address length")
}
hw := make([]byte, 6)
copy(hw, data)
return hw, nil
}
// See RFC 2132: DHCP Options and BOOTP Vendor Extensions.
type DHCPOption struct {
Code uint8
Value []byte
}
func (r *DHCPOption) MarshalBinary() ([]byte, error) {
length := len(r.Value)
if length == 0 || length > 255 {
return nil, fmt.Errorf("invalid DHCP option packet length: code=%v, length=%v", r.Code, length)
}
v := make([]byte, 2+length)
v[0] = r.Code
v[1] = uint8(length)
copy(v[2:], r.Value)
return v, nil
}
func (r *DHCPOption) UnmarshalBinary(data []byte) error {
if len(data) < 2 {
return errors.New("invalid DHCP option packet header")
}
r.Code = data[0]
length := uint8(data[1])
if length == 0 || len(data)-2 < int(length) {
return fmt.Errorf("invalid DHCP option packet length: code=%v, length=%v", r.Code, length)
}
r.Value = data[2 : 2+length]
return nil
}
================================================
FILE: protocol/dhcp_test.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015-2019 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"bytes"
"encoding/hex"
"net"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func TestCodec(t *testing.T) {
samples := []struct {
Packet string
Expected DHCP
DecodeOnly bool
}{
{
Packet: "0101060000003d1d0000000000000000000000000000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013d0701000b8201fc4232040000000037040103062aff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
Expected: DHCP{
Op: DHCPOpcodeRequest,
Hops: 0,
XID: 0x3d1d,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4zero,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x01}},
{Code: 0x3d, Value: []byte{0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42}},
{Code: 0x32, Value: []byte{0x00, 0x00, 0x00, 0x00}},
{Code: 0x37, Value: []byte{0x01, 0x03, 0x06, 0x2a}},
},
},
DecodeOnly: false,
},
{
Packet: "0201060000003d1d0000000000000000c0a8000ac0a8000100000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501020104ffffff003a04000007083b0400000c4e330400000e103604c0a80001ff0000000000000000000000000000000000000000000000000000000000000000000000000000",
Expected: DHCP{
Op: DHCPOpcodeReply,
Hops: 0,
XID: 0x3d1d,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4(192, 168, 0, 10),
SIAddr: net.IPv4(192, 168, 0, 1),
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x02}},
{Code: 0x01, Value: []byte{0xff, 0xff, 0xff, 0x00}},
{Code: 0x3a, Value: []byte{0x00, 0x00, 0x07, 0x08}},
{Code: 0x3b, Value: []byte{0x00, 0x00, 0x0c, 0x4e}},
{Code: 0x33, Value: []byte{0x00, 0x00, 0x0e, 0x10}},
{Code: 0x36, Value: []byte{0xc0, 0xa8, 0x00, 0x01}},
},
},
DecodeOnly: false,
},
{
Packet: "0101060000003d1e0000000000000000000000000000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501033d0701000b8201fc423204c0a8000a3604c0a8000137040103062aff0000000000000000000000000000000000000000000000000000000000000000000000000000000000",
Expected: DHCP{
Op: DHCPOpcodeRequest,
Hops: 0,
XID: 0x3d1e,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4zero,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x03}},
{Code: 0x3d, Value: []byte{0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42}},
{Code: 0x32, Value: []byte{0xc0, 0xa8, 0x00, 0x0a}},
{Code: 0x36, Value: []byte{0xc0, 0xa8, 0x00, 0x01}},
{Code: 0x37, Value: []byte{0x01, 0x03, 0x06, 0x2a}},
},
},
DecodeOnly: false,
},
{
Packet: "0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000000000000000000000000000",
Expected: DHCP{
Op: DHCPOpcodeReply,
Hops: 0,
XID: 0x3d1e,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4(192, 168, 0, 10),
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x05}},
{Code: 0x3a, Value: []byte{0x00, 0x00, 0x07, 0x08}},
{Code: 0x3b, Value: []byte{0x00, 0x00, 0x0c, 0x4e}},
{Code: 0x33, Value: []byte{0x00, 0x00, 0x0e, 0x10}},
{Code: 0x36, Value: []byte{0xc0, 0xa8, 0x00, 0x01}},
{Code: 0x01, Value: []byte{0xff, 0xff, 0xff, 0x00}},
},
},
DecodeOnly: false,
},
{
Packet: "020106017771cf85000a0000000000000a0a08ebac16b2ea0a0a08f0000e8611c07500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501020104ffffff003604ac16b2ea33040000a8c003040a0a08fe06088fd104018fd10501420e3137322e32322e3137382e323334780501ac16b2ea3d10006e617468616e31636c69656e7469645a1f010100c878c45256402081313233348fe0cce2ee8596abb25817c480b2fd305216011420504f4e20312f312f30372f30313a312e302e31ff",
Expected: DHCP{
Op: DHCPOpcodeReply,
Hops: 1,
XID: 0x7771cf85,
Elapsed: 10 * time.Second,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4(10, 10, 8, 235),
SIAddr: net.IPv4(172, 22, 178, 234),
GIAddr: net.IPv4(10, 10, 8, 240),
CHAddr: []byte{0x00, 0x0e, 0x86, 0x11, 0xc0, 0x75},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x02}},
{Code: 0x01, Value: []byte{0xff, 0xff, 0xff, 0x00}},
{Code: 0x36, Value: []byte{0xac, 0x16, 0xb2, 0xea}},
{Code: 0x33, Value: []byte{0x00, 0x00, 0xa8, 0xc0}},
{Code: 0x03, Value: []byte{0x0a, 0x0a, 0x08, 0xfe}},
{Code: 0x06, Value: []byte{0x8f, 0xd1, 0x04, 0x01, 0x8f, 0xd1, 0x05, 0x01}},
{Code: 0x42, Value: []byte{0x31, 0x37, 0x32, 0x2e, 0x32, 0x32, 0x2e, 0x31, 0x37, 0x38, 0x2e, 0x32, 0x33, 0x34}},
{Code: 0x78, Value: []byte{0x01, 0xac, 0x16, 0xb2, 0xea}},
{Code: 0x3d, Value: []byte{0x00, 0x6e, 0x61, 0x74, 0x68, 0x61, 0x6e, 0x31, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x64}},
{Code: 0x5a, Value: []byte{0x01, 0x01, 0x00, 0xc8, 0x78, 0xc4, 0x52, 0x56, 0x40, 0x20, 0x81, 0x31, 0x32, 0x33, 0x34, 0x8f, 0xe0, 0xcc, 0xe2, 0xee, 0x85, 0x96, 0xab, 0xb2, 0x58, 0x17, 0xc4, 0x80, 0xb2, 0xfd, 0x30}},
{Code: 0x52, Value: []byte{0x01, 0x14, 0x20, 0x50, 0x4f, 0x4e, 0x20, 0x31, 0x2f, 0x31, 0x2f, 0x30, 0x37, 0x2f, 0x30, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x2e, 0x31}},
},
},
DecodeOnly: false,
},
{
// Unsupported option overload packet.
Packet: "01010600ac2effff000000000000000000000000000000000000000000006c82dc4e000000000000000000003814736e616d65206669656c64206f7665726c6f6164ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000381866696c65206e616d65206669656c64206f7665726c6f6164ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013902024e3704011c032b330400000e10340103380750616464696e67003d070100006c82dc4eff",
Expected: DHCP{
Op: DHCPOpcodeRequest,
Hops: 0,
XID: 0xac2effff,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4zero,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x00, 0x6c, 0x82, 0xdc, 0x4e},
SName: "8\x14sname field overload\xff",
File: "8\x18file name field overload\xff",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x01}},
{Code: 0x39, Value: []byte{0x02, 0x4e}},
{Code: 0x37, Value: []byte{0x01, 0x1c, 0x03, 0x2b}},
{Code: 0x33, Value: []byte{0x00, 0x00, 0x0e, 0x10}},
{Code: 0x34, Value: []byte{0x03}},
{Code: 0x38, Value: []byte{0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67}},
{Code: 0x3d, Value: []byte{0x01, 0x00, 0x00, 0x6c, 0x82, 0xdc, 0x4e}},
},
},
DecodeOnly: true,
},
{
// Unsupported option overload packet without the end mark.
Packet: "01010600ac2effff000000000000000000000000000000000000000000006c82dc4e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013902024e3704011c032b330400000e10340103380750616464696e67003d070100006c82dc4e00",
Expected: DHCP{
Op: DHCPOpcodeRequest,
Hops: 0,
XID: 0xac2effff,
Elapsed: 0,
Flags: 0,
CIAddr: net.IPv4zero,
YIAddr: net.IPv4zero,
SIAddr: net.IPv4zero,
GIAddr: net.IPv4zero,
CHAddr: []byte{0x00, 0x00, 0x6c, 0x82, 0xdc, 0x4e},
SName: "",
File: "",
Options: []DHCPOption{
{Code: 0x35, Value: []byte{0x01}},
{Code: 0x39, Value: []byte{0x02, 0x4e}},
{Code: 0x37, Value: []byte{0x01, 0x1c, 0x03, 0x2b}},
{Code: 0x33, Value: []byte{0x00, 0x00, 0x0e, 0x10}},
{Code: 0x34, Value: []byte{0x03}},
{Code: 0x38, Value: []byte{0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67}},
{Code: 0x3d, Value: []byte{0x01, 0x00, 0x00, 0x6c, 0x82, 0xdc, 0x4e}},
},
},
DecodeOnly: true,
},
}
for _, v := range samples {
p, err := hex.DecodeString(v.Packet)
if err != nil {
panic("invalid sample DHCP packet")
}
d := DHCP{}
if err := d.UnmarshalBinary(p); err != nil {
t.Fatalf("unexpected DHCP unmarshal error: %v", err)
}
if cmp.Equal(d, v.Expected, cmpopts.IgnoreUnexported(d, v.Expected)) == false {
t.Fatalf("unexpected unmarshaled DHCP packet: expected=%v, actual=%v, diff=%v", spew.Sdump(v.Expected), spew.Sdump(d), cmp.Diff(d, v.Expected, cmpopts.IgnoreUnexported(d, v.Expected)))
}
if v.DecodeOnly == true {
continue
}
m, err := d.MarshalBinary()
if err != nil {
t.Fatalf("unexpected DHCP marshal error: %v", err)
}
if bytes.Equal(m, p) == false {
t.Fatalf("unexpected marshaled DHCP packet: expected=%v, actual=%v", p, m)
}
}
}
================================================
FILE: protocol/ethernet.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
"net"
)
type Ethernet struct {
SrcMAC, DstMAC net.HardwareAddr
Type uint16
Payload []byte
}
func (r Ethernet) MarshalBinary() ([]byte, error) {
if r.SrcMAC == nil || r.DstMAC == nil {
return nil, errors.New("invalid MAC address")
}
if r.Payload == nil {
return nil, errors.New("nil payload")
}
v := make([]byte, 14+len(r.Payload))
copy(v[0:6], r.DstMAC)
copy(v[6:12], r.SrcMAC)
binary.BigEndian.PutUint16(v[12:14], r.Type)
if len(r.Payload) > 0 {
copy(v[14:], r.Payload)
}
return v, nil
}
func (r *Ethernet) UnmarshalBinary(data []byte) error {
if len(data) < 14 {
return errors.New("invalid ethernet frame length")
}
r.DstMAC = data[0:6]
r.SrcMAC = data[6:12]
r.Type = binary.BigEndian.Uint16(data[12:14])
// IEEE 802.1Q-tagged frame?
if r.Type == 0x8100 {
r.Type = binary.BigEndian.Uint16(data[16:18])
r.Payload = data[18:]
} else {
r.Payload = data[14:]
}
// FIXME: Add routines for JumboFrame
return nil
}
================================================
FILE: protocol/icmp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
)
type ICMP struct {
Type uint8
Code uint8
Checksum uint16
}
type ICMPEcho struct {
ICMP
ID uint16
Sequence uint16
Payload []byte
}
func NewICMPEchoRequest(id, seq uint16, payload []byte) *ICMPEcho {
return &ICMPEcho{
ICMP: ICMP{
Type: 8,
},
ID: id,
Sequence: seq,
Payload: payload,
}
}
func NewICMPEchoReply(id, seq uint16, payload []byte) *ICMPEcho {
return &ICMPEcho{
ID: id,
Sequence: seq,
Payload: payload,
}
}
func (r ICMPEcho) MarshalBinary() ([]byte, error) {
v := make([]byte, 8)
v[0] = r.Type
v[1] = r.Code
// v[2:4] is checksum
binary.BigEndian.PutUint16(v[4:6], r.ID)
binary.BigEndian.PutUint16(v[6:8], r.Sequence)
if r.Payload != nil {
v = append(v, r.Payload...)
}
checksum := calculateChecksum(v)
binary.BigEndian.PutUint16(v[2:4], checksum)
return v, nil
}
func (r *ICMPEcho) UnmarshalBinary(data []byte) error {
if len(data) < 8 {
return errors.New("invalid ICMP packet length")
}
if data[0] != 8 && data[0] != 0 {
return errors.New("packet is not an ICMP echo message")
}
r.Type = data[0]
r.Code = data[1]
r.Checksum = binary.BigEndian.Uint16(data[2:4])
r.ID = binary.BigEndian.Uint16(data[4:6])
r.Sequence = binary.BigEndian.Uint16(data[6:8])
if len(data) > 8 {
r.Payload = data[8:]
}
return nil
}
================================================
FILE: protocol/ipv4.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
"net"
)
// TODO: Test this module!!
type IPv4 struct {
Version uint8
IHL uint8
DSCP uint8
ECN uint8
Length uint16
ID uint16
Flags uint8
Offset uint16
TTL uint8
Protocol uint8
Checksum uint16
SrcIP net.IP
DstIP net.IP
Payload []byte
}
func NewIPv4(src, dst net.IP, protocol uint8, payload []byte) *IPv4 {
switch protocol {
case 1: // ICMP
case 2: // IGMP
case 6: // TCP
case 17: // UDP
case 41: // IPv6 encapsulation
case 89: // OSPF
case 132: // SCTP
default:
panic("unknown protocol number")
}
if len(payload) > 0xFFFF-20 {
panic("payload is too long")
}
return &IPv4{
Version: 4,
IHL: 5, // 20 bytes
Length: uint16(len(payload) + 20), // Payload + Header
// FIXME: Should we set ID as a random number?
Flags: 0x2, // Don't Fragment
TTL: 64,
Protocol: protocol,
SrcIP: src,
DstIP: dst,
Payload: payload,
}
}
func (r IPv4) MarshalBinary() ([]byte, error) {
if r.SrcIP == nil || r.DstIP == nil {
return nil, errors.New("nil IP address")
}
header := make([]byte, 20)
header[0] = (r.Version&0xF)<<4 | r.IHL&0xF
header[1] = (r.DSCP&0x3F)<<2 | r.ECN&0x3
binary.BigEndian.PutUint16(header[2:4], r.Length)
binary.BigEndian.PutUint16(header[4:6], r.ID)
binary.BigEndian.PutUint16(header[6:8], (uint16(r.Flags)&0x7)<<13|r.Offset&0x1FFF)
header[8] = r.TTL
header[9] = r.Protocol
// header[10:12] = checksum
srcIP := r.SrcIP.To4()
if srcIP == nil {
return nil, errors.New("source IP address is not an IPv4 address")
}
copy(header[12:16], srcIP)
dstIP := r.DstIP.To4()
if dstIP == nil {
return nil, errors.New("destination IP address is not an IPv4 address")
}
copy(header[16:20], dstIP)
checksum := calculateChecksum(header)
binary.BigEndian.PutUint16(header[10:12], checksum)
if r.Payload == nil {
return header, nil
}
return append(header, r.Payload...), nil
}
func (r *IPv4) UnmarshalBinary(data []byte) error {
if len(data) < 20 {
return errors.New("invalid IPv4 packet length")
}
r.Version = (data[0] >> 4) & 0xF
r.IHL = data[0] & 0xF
r.DSCP = (data[1] >> 2) & 0x3F
r.ECN = data[1] & 0x3
r.Length = binary.BigEndian.Uint16(data[2:4])
r.ID = binary.BigEndian.Uint16(data[4:6])
v := binary.BigEndian.Uint16(data[6:8])
r.Flags = uint8((v >> 13) & 0x7)
r.Offset = v & 0x1FFF
r.TTL = data[8]
r.Protocol = data[9]
r.Checksum = binary.BigEndian.Uint16(data[10:12])
r.SrcIP = data[12:16]
r.DstIP = data[16:20]
headerLen := int(r.IHL) * 4
if len(data) > headerLen {
r.Payload = data[headerLen:]
}
return nil
}
================================================
FILE: protocol/lldp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
)
type LLDPChassisID struct {
SubType uint8
Data []byte
}
type LLDPPortID struct {
SubType uint8
Data []byte
}
type LLDP struct {
ChassisID LLDPChassisID
PortID LLDPPortID
TTL uint16
}
func (r *LLDP) marshalChassisID() ([]byte, error) {
if r.ChassisID.Data == nil {
return nil, errors.New("nil chassis ID")
}
if len(r.ChassisID.Data) > 255 {
return nil, errors.New("too long chassis ID")
}
var header uint16
length := len(r.ChassisID.Data) + 1
header = uint16(1<<9 | (length & 0x1FF))
v := make([]byte, length+2)
binary.BigEndian.PutUint16(v[0:2], header)
v[2] = r.ChassisID.SubType
copy(v[3:], r.ChassisID.Data)
return v, nil
}
func (r *LLDP) marshalPortID() ([]byte, error) {
if r.PortID.Data == nil {
return nil, errors.New("nil port ID")
}
if len(r.PortID.Data) > 255 {
return nil, errors.New("too long port ID")
}
var header uint16
length := len(r.PortID.Data) + 1
header = uint16(2<<9 | (length & 0x1FF))
v := make([]byte, length+2)
binary.BigEndian.PutUint16(v[0:2], header)
v[2] = r.PortID.SubType
copy(v[3:], r.PortID.Data)
return v, nil
}
func (r *LLDP) marshalTTL() ([]byte, error) {
var header uint16
length := 2
header = uint16(3<<9 | (length & 0x1FF))
v := make([]byte, 4)
binary.BigEndian.PutUint16(v[0:2], header)
binary.BigEndian.PutUint16(v[2:4], r.TTL)
return v, nil
}
func (r *LLDP) MarshalBinary() ([]byte, error) {
v := make([]byte, 0)
chassis, err := r.marshalChassisID()
if err != nil {
return nil, err
}
v = append(v, chassis...)
port, err := r.marshalPortID()
if err != nil {
return nil, err
}
v = append(v, port...)
ttl, err := r.marshalTTL()
if err != nil {
return nil, err
}
v = append(v, ttl...)
// End of TLV
v = append(v, []byte{0, 0}...)
return v, nil
}
func (r *LLDP) unmarshalChassisID(data []byte) (n int, err error) {
length := len(data)
if length < 2 {
return 0, errors.New("invalid chassis ID TLV length")
}
header := binary.BigEndian.Uint16(data[0:2])
tlvType := (header >> 9) & 0x7F
if tlvType != 1 {
return 0, errors.New("invalid chassis ID TLV type")
}
tlvLength := header & 0x1FF
if length < int(tlvLength+2) {
return 0, errors.New("invalid chassis ID TLV length")
}
r.ChassisID = LLDPChassisID{
SubType: data[2],
Data: data[3 : 3+tlvLength-1],
}
return int(tlvLength + 2), nil
}
func (r *LLDP) unmarshalPortID(data []byte) (n int, err error) {
length := len(data)
if length < 2 {
return 0, errors.New("invalid port ID TLV length")
}
header := binary.BigEndian.Uint16(data[0:2])
tlvType := (header >> 9) & 0x7F
if tlvType != 2 {
return 0, errors.New("invalid port ID TLV type")
}
tlvLength := header & 0x1FF
if length < int(tlvLength+2) {
return 0, errors.New("invalid port ID TLV length")
}
r.PortID = LLDPPortID{
SubType: data[2],
Data: data[3 : 3+tlvLength-1],
}
return int(tlvLength + 2), nil
}
func (r *LLDP) unmarshalTTL(data []byte) (n int, err error) {
length := len(data)
if length < 2 {
return 0, errors.New("invalid TTL TLV length")
}
header := binary.BigEndian.Uint16(data[0:2])
tlvType := (header >> 9) & 0x7F
if tlvType != 3 {
return 0, errors.New("invalid TTL TLV type")
}
tlvLength := header & 0x1FF
if length < int(tlvLength+2) {
return 0, errors.New("invalid TTL TLV length")
}
r.TTL = binary.BigEndian.Uint16(data[2:4])
return int(tlvLength + 2), nil
}
func (r *LLDP) UnmarshalBinary(data []byte) error {
offset := 0
length := len(data)
// From IEEE 802.1AB-2009:
//
// a) Three mandatory TLVs shall be included at the beginning of each LLDPDU and shall be in the order shown.
// 1) Chassis ID TLV
// 2) Port ID TLV
// 3) Time To Live TLV
// b) Optional TLVs as selected by network management (may be inserted in any order).
n, err := r.unmarshalChassisID(data)
if err != nil {
return err
}
offset += n
if length < offset {
return errors.New("invalid LLDP packet length")
}
n, err = r.unmarshalPortID(data[offset:])
if err != nil {
return err
}
offset += n
if length < offset {
return errors.New("invalid LLDP packet length")
}
_, err = r.unmarshalTTL(data[offset:])
if err != nil {
return err
}
return nil
}
================================================
FILE: protocol/tcp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"net"
)
type TCP struct {
srcIP net.IP
dstIP net.IP
SrcPort uint16
DstPort uint16
Sequence uint32
Acknowledgment uint32
// From LSB, FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, and NS.
Flags uint16
WindowSize uint16
Checksum uint16
Urgent uint16
Payload []byte
}
// TCP checksum needs a pseudo header that has src and dst IPv4 addresses.
func (r *TCP) SetPseudoHeader(src, dst net.IP) {
r.srcIP = src
r.dstIP = dst
}
func (r TCP) MarshalBinary() ([]byte, error) {
length := 20
if r.Payload != nil {
length += len(r.Payload)
}
if length > (0xFFFF - 20 /* IPv4 header */) {
return nil, fmt.Errorf("too long TCP packet: length=%v", length)
}
v := make([]byte, length)
binary.BigEndian.PutUint16(v[0:2], r.SrcPort)
binary.BigEndian.PutUint16(v[2:4], r.DstPort)
binary.BigEndian.PutUint32(v[4:8], r.Sequence)
binary.BigEndian.PutUint32(v[8:12], r.Acknowledgment)
v[12] = uint8(0x5<<4 | (r.Flags >> 8 & 0x1))
v[13] = uint8(r.Flags & 0xFF)
binary.BigEndian.PutUint16(v[14:16], r.WindowSize)
// v[16:18] is checksum
binary.BigEndian.PutUint16(v[18:20], r.Urgent)
if r.Payload != nil {
copy(v[20:], r.Payload)
}
if r.srcIP == nil || r.dstIP == nil {
return nil, errors.New("nil pseudo IP addresses")
}
pseudo := make([]byte, 12)
srcIP := r.srcIP.To4()
if srcIP == nil {
return nil, errors.New("source IP address is not an IPv4 address")
}
copy(pseudo[0:4], srcIP)
dstIP := r.dstIP.To4()
if dstIP == nil {
return nil, errors.New("destination IP address is not an IPv4 address")
}
copy(pseudo[4:8], dstIP)
pseudo[9] = 6 // TCP
binary.BigEndian.PutUint16(pseudo[10:12], uint16(length))
checksum := calculateChecksum(append(pseudo, v...))
binary.BigEndian.PutUint16(v[16:18], checksum)
return v, nil
}
func (r *TCP) UnmarshalBinary(data []byte) error {
if len(data) < 20 {
return errors.New("invalid TCP packet length")
}
r.SrcPort = binary.BigEndian.Uint16(data[0:2])
r.DstPort = binary.BigEndian.Uint16(data[2:4])
r.Sequence = binary.BigEndian.Uint32(data[4:8])
r.Acknowledgment = binary.BigEndian.Uint32(data[8:12])
offset := int((data[12] >> 4)) * 4
r.Flags = uint16((data[12]&0x1)<<8 | data[13])
r.WindowSize = binary.BigEndian.Uint16(data[14:16])
r.Checksum = binary.BigEndian.Uint16(data[16:18])
r.Urgent = binary.BigEndian.Uint16(data[18:20])
if len(data) > offset {
r.Payload = data[offset:]
}
return nil
}
================================================
FILE: protocol/udp.go
================================================
/*
* Cherry - An OpenFlow Controller
*
* Copyright (C) 2015 Samjung Data Service, Inc. All rights reserved.
* Kitae Kim
*
* 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 2 of the License, or
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"net"
)
type UDP struct {
srcIP net.IP
dstIP net.IP
SrcPort uint16
DstPort uint16
Length uint16
Checksum uint16
Payload []byte
}
// UDP checksum needs a pseudo header that has src and dst IPv4 addresses.
func (r *UDP) SetPseudoHeader(src, dst net.IP) {
r.srcIP = src
r.dstIP = dst
}
func (r UDP) MarshalBinary() ([]byte, error) {
length := 8
if r.Payload != nil {
length += len(r.Payload)
}
if length > (0xFFFF - 20 /* IPv4 header */) {
return nil, fmt.Errorf("too long UDP packet: length=%v", length)
}
v := make([]byte, length)
binary.BigEndian.PutUint16(v[0:2], r.SrcPort)
binary.BigEndian.PutUint16(v[2:4], r.DstPort)
binary.BigEndian.PutUint16(v[4:6], uint16(length))
// v[6:8] is checksum
if r.Payload != nil && len(r.Payload) > 0 {
copy(v[8:], r.Payload)
}
if r.srcIP == nil || r.dstIP == nil {
return nil, errors.New("nil pseudo IP addresses")
}
pseudo := make([]byte, 12)
srcIP := r.srcIP.To4()
if srcIP == nil {
return nil, errors.New("source IP address is not an IPv4 address")
}
copy(pseudo[0:4], srcIP)
dstIP := r.dstIP.To4()
if dstIP == nil {
return nil, errors.New("destination IP address is not an IPv4 address")
}
copy(pseudo[4:8], dstIP)
pseudo[9] = 17 // UDP
binary.BigEndian.PutUint16(pseudo[10:12], uint16(length))
checksum := calculateChecksum(append(pseudo, v...))
binary.BigEndian.PutUint16(v[6:8], checksum)
return v, nil
}
func (r *UDP) UnmarshalBinary(data []byte) error {
if len(data) < 8 {
return errors.New("invalid UDP packet length")
}
r.SrcPort = binary.BigEndian.Uint16(data[0:2])
r.DstPort = binary.BigEndian.Uint16(data[2:4])
r.Length = binary.BigEndian.Uint16(data[4:6])
r.Checksum = binary.BigEndian.Uint16(data[6:8])
if len(data) > 8 {
r.Payload = data[8:]
}
return nil
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/LICENSE
================================================
Copyright (c) 2013-2016 Antoine Imbert
The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/access_log_apache.go
================================================
package rest
import (
"bytes"
"fmt"
"log"
"net"
"os"
"strings"
"text/template"
"time"
)
// TODO Future improvements:
// * support %{strftime}t ?
// * support %{}o to print headers
// AccessLogFormat defines the format of the access log record.
// This implementation is a subset of Apache mod_log_config.
// (See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html)
//
// %b content length in bytes, - if 0
// %B content length in bytes
// %D response elapsed time in microseconds
// %h remote address
// %H server protocol
// %l identd logname, not supported, -
// %m http method
// %P process id
// %q query string
// %r first line of the request
// %s status code
// %S status code preceeded by a terminal color
// %t time of the request
// %T response elapsed time in seconds, 3 decimals
// %u remote user, - if missing
// %{User-Agent}i user agent, - if missing
// %{Referer}i referer, - is missing
//
// Some predefined formats are provided as contants.
type AccessLogFormat string
const (
// CommonLogFormat is the Common Log Format (CLF).
CommonLogFormat = "%h %l %u %t \"%r\" %s %b"
// CombinedLogFormat is the NCSA extended/combined log format.
CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
// DefaultLogFormat is the default format, colored output and response time, convenient for development.
DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
)
// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that should be in the wrapped
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogApacheMiddleware struct {
// Logger points to the logger object used by this middleware, it defaults to
// log.New(os.Stderr, "", 0).
Logger *log.Logger
// Format defines the format of the access log record. See AccessLogFormat for the details.
// It defaults to DefaultLogFormat.
Format AccessLogFormat
textTemplate *template.Template
}
// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
// set the default Logger
if mw.Logger == nil {
mw.Logger = log.New(os.Stderr, "", 0)
}
// set default format
if mw.Format == "" {
mw.Format = DefaultLogFormat
}
mw.convertFormat()
return func(w ResponseWriter, r *Request) {
// call the handler
h(w, r)
util := &accessLogUtil{w, r}
mw.Logger.Print(mw.executeTextTemplate(util))
}
}
var apacheAdapter = strings.NewReplacer(
"%b", "{{.BytesWritten | dashIf0}}",
"%B", "{{.BytesWritten}}",
"%D", "{{.ResponseTime | microseconds}}",
"%h", "{{.ApacheRemoteAddr}}",
"%H", "{{.R.Proto}}",
"%l", "-",
"%m", "{{.R.Method}}",
"%P", "{{.Pid}}",
"%q", "{{.ApacheQueryString}}",
"%r", "{{.R.Method}} {{.R.URL.RequestURI}} {{.R.Proto}}",
"%s", "{{.StatusCode}}",
"%S", "\033[{{.StatusCode | statusCodeColor}}m{{.StatusCode}}",
"%t", "{{if .StartTime}}{{.StartTime.Format \"02/Jan/2006:15:04:05 -0700\"}}{{end}}",
"%T", "{{if .ResponseTime}}{{.ResponseTime.Seconds | printf \"%.3f\"}}{{end}}",
"%u", "{{.RemoteUser | dashIfEmptyStr}}",
"%{User-Agent}i", "{{.R.UserAgent | dashIfEmptyStr}}",
"%{Referer}i", "{{.R.Referer | dashIfEmptyStr}}",
)
// Convert the Apache access log format into a text/template
func (mw *AccessLogApacheMiddleware) convertFormat() {
tmplText := apacheAdapter.Replace(string(mw.Format))
funcMap := template.FuncMap{
"dashIfEmptyStr": func(value string) string {
if value == "" {
return "-"
}
return value
},
"dashIf0": func(value int64) string {
if value == 0 {
return "-"
}
return fmt.Sprintf("%d", value)
},
"microseconds": func(dur *time.Duration) string {
if dur != nil {
return fmt.Sprintf("%d", dur.Nanoseconds()/1000)
}
return ""
},
"statusCodeColor": func(statusCode int) string {
if statusCode >= 400 && statusCode < 500 {
return "1;33"
} else if statusCode >= 500 {
return "0;31"
}
return "0;32"
},
}
var err error
mw.textTemplate, err = template.New("accessLog").Funcs(funcMap).Parse(tmplText)
if err != nil {
panic(err)
}
}
// Execute the text template with the data derived from the request, and return a string.
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
buf := bytes.NewBufferString("")
err := mw.textTemplate.Execute(buf, util)
if err != nil {
panic(err)
}
return buf.String()
}
// accessLogUtil provides a collection of utility functions that devrive data from the Request object.
// This object is used to provide data to the Apache Style template and the the JSON log record.
type accessLogUtil struct {
W ResponseWriter
R *Request
}
// As stored by the auth middlewares.
func (u *accessLogUtil) RemoteUser() string {
if u.R.Env["REMOTE_USER"] != nil {
return u.R.Env["REMOTE_USER"].(string)
}
return ""
}
// If qs exists then return it with a leadin "?", apache log style.
func (u *accessLogUtil) ApacheQueryString() string {
if u.R.URL.RawQuery != "" {
return "?" + u.R.URL.RawQuery
}
return ""
}
// When the request entered the timer middleware.
func (u *accessLogUtil) StartTime() *time.Time {
if u.R.Env["START_TIME"] != nil {
return u.R.Env["START_TIME"].(*time.Time)
}
return nil
}
// If remoteAddr is set then return is without the port number, apache log style.
func (u *accessLogUtil) ApacheRemoteAddr() string {
remoteAddr := u.R.RemoteAddr
if remoteAddr != "" {
if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
return ip
}
}
return ""
}
// As recorded by the recorder middleware.
func (u *accessLogUtil) StatusCode() int {
if u.R.Env["STATUS_CODE"] != nil {
return u.R.Env["STATUS_CODE"].(int)
}
return 0
}
// As mesured by the timer middleware.
func (u *accessLogUtil) ResponseTime() *time.Duration {
if u.R.Env["ELAPSED_TIME"] != nil {
return u.R.Env["ELAPSED_TIME"].(*time.Duration)
}
return nil
}
// Process id.
func (u *accessLogUtil) Pid() int {
return os.Getpid()
}
// As recorded by the recorder middleware.
func (u *accessLogUtil) BytesWritten() int64 {
if u.R.Env["BYTES_WRITTEN"] != nil {
return u.R.Env["BYTES_WRITTEN"].(int64)
}
return 0
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/access_log_json.go
================================================
package rest
import (
"encoding/json"
"log"
"os"
"time"
)
// AccessLogJsonMiddleware produces the access log with records written as JSON. This middleware
// depends on TimerMiddleware and RecorderMiddleware that must be in the wrapped middlewares. It
// also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogJsonMiddleware struct {
// Logger points to the logger object used by this middleware, it defaults to
// log.New(os.Stderr, "", 0).
Logger *log.Logger
}
// MiddlewareFunc makes AccessLogJsonMiddleware implement the Middleware interface.
func (mw *AccessLogJsonMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
// set the default Logger
if mw.Logger == nil {
mw.Logger = log.New(os.Stderr, "", 0)
}
return func(w ResponseWriter, r *Request) {
// call the handler
h(w, r)
mw.Logger.Printf("%s", makeAccessLogJsonRecord(r).asJson())
}
}
// AccessLogJsonRecord is the data structure used by AccessLogJsonMiddleware to create the JSON
// records. (Public for documentation only, no public method uses it)
type AccessLogJsonRecord struct {
Timestamp *time.Time
StatusCode int
ResponseTime *time.Duration
HttpMethod string
RequestURI string
RemoteUser string
UserAgent string
}
func makeAccessLogJsonRecord(r *Request) *AccessLogJsonRecord {
var timestamp *time.Time
if r.Env["START_TIME"] != nil {
timestamp = r.Env["START_TIME"].(*time.Time)
}
var statusCode int
if r.Env["STATUS_CODE"] != nil {
statusCode = r.Env["STATUS_CODE"].(int)
}
var responseTime *time.Duration
if r.Env["ELAPSED_TIME"] != nil {
responseTime = r.Env["ELAPSED_TIME"].(*time.Duration)
}
var remoteUser string
if r.Env["REMOTE_USER"] != nil {
remoteUser = r.Env["REMOTE_USER"].(string)
}
return &AccessLogJsonRecord{
Timestamp: timestamp,
StatusCode: statusCode,
ResponseTime: responseTime,
HttpMethod: r.Method,
RequestURI: r.URL.RequestURI(),
RemoteUser: remoteUser,
UserAgent: r.UserAgent(),
}
}
func (r *AccessLogJsonRecord) asJson() []byte {
b, err := json.Marshal(r)
if err != nil {
panic(err)
}
return b
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/api.go
================================================
package rest
import (
"net/http"
)
// Api defines a stack of Middlewares and an App.
type Api struct {
stack []Middleware
app App
}
// NewApi makes a new Api object. The Middleware stack is empty, and the App is nil.
func NewApi() *Api {
return &Api{
stack: []Middleware{},
app: nil,
}
}
// Use pushes one or multiple middlewares to the stack for middlewares
// maintained in the Api object.
func (api *Api) Use(middlewares ...Middleware) {
api.stack = append(api.stack, middlewares...)
}
// SetApp sets the App in the Api object.
func (api *Api) SetApp(app App) {
api.app = app
}
// MakeHandler wraps all the Middlewares of the stack and the App together, and returns an
// http.Handler ready to be used. If the Middleware stack is empty the App is used directly. If the
// App is nil, a HandlerFunc that does nothing is used instead.
func (api *Api) MakeHandler() http.Handler {
var appFunc HandlerFunc
if api.app != nil {
appFunc = api.app.AppFunc()
} else {
appFunc = func(w ResponseWriter, r *Request) {}
}
return http.HandlerFunc(
adapterFunc(
WrapMiddlewares(api.stack, appFunc),
),
)
}
// Defines a stack of middlewares convenient for development. Among other things:
// console friendly logging, JSON indentation, error stack strace in the response.
var DefaultDevStack = []Middleware{
&AccessLogApacheMiddleware{},
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{
EnableResponseStackTrace: true,
},
&JsonIndentMiddleware{},
&ContentTypeCheckerMiddleware{},
}
// Defines a stack of middlewares convenient for production. Among other things:
// Apache CombinedLogFormat logging, gzip compression.
var DefaultProdStack = []Middleware{
&AccessLogApacheMiddleware{
Format: CombinedLogFormat,
},
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{},
&GzipMiddleware{},
&ContentTypeCheckerMiddleware{},
}
// Defines a stack of middlewares that should be common to most of the middleware stacks.
var DefaultCommonStack = []Middleware{
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{},
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/auth_basic.go
================================================
package rest
import (
"encoding/base64"
"errors"
"log"
"net/http"
"strings"
)
// AuthBasicMiddleware provides a simple AuthBasic implementation. On failure, a 401 HTTP response
//is returned. On success, the wrapped middleware is called, and the userId is made available as
// request.Env["REMOTE_USER"].(string)
type AuthBasicMiddleware struct {
// Realm name to display to the user. Required.
Realm string
// Callback function that should perform the authentication of the user based on userId and
// password. Must return true on success, false on failure. Required.
Authenticator func(userId string, password string) bool
// Callback function that should perform the authorization of the authenticated user. Called
// only after an authentication success. Must return true on success, false on failure.
// Optional, default to success.
Authorizator func(userId string, request *Request) bool
}
// MiddlewareFunc makes AuthBasicMiddleware implement the Middleware interface.
func (mw *AuthBasicMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
if mw.Realm == "" {
log.Fatal("Realm is required")
}
if mw.Authenticator == nil {
log.Fatal("Authenticator is required")
}
if mw.Authorizator == nil {
mw.Authorizator = func(userId string, request *Request) bool {
return true
}
}
return func(writer ResponseWriter, request *Request) {
authHeader := request.Header.Get("Authorization")
if authHeader == "" {
mw.unauthorized(writer)
return
}
providedUserId, providedPassword, err := mw.decodeBasicAuthHeader(authHeader)
if err != nil {
Error(writer, "Invalid authentication", http.StatusBadRequest)
return
}
if !mw.Authenticator(providedUserId, providedPassword) {
mw.unauthorized(writer)
return
}
if !mw.Authorizator(providedUserId, request) {
mw.unauthorized(writer)
return
}
request.Env["REMOTE_USER"] = providedUserId
handler(writer, request)
}
}
func (mw *AuthBasicMiddleware) unauthorized(writer ResponseWriter) {
writer.Header().Set("WWW-Authenticate", "Basic realm="+mw.Realm)
Error(writer, "Not Authorized", http.StatusUnauthorized)
}
func (mw *AuthBasicMiddleware) decodeBasicAuthHeader(header string) (user string, password string, err error) {
parts := strings.SplitN(header, " ", 2)
if !(len(parts) == 2 && parts[0] == "Basic") {
return "", "", errors.New("Invalid authentication")
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return "", "", errors.New("Invalid base64")
}
creds := strings.SplitN(string(decoded), ":", 2)
if len(creds) != 2 {
return "", "", errors.New("Invalid authentication")
}
return creds[0], creds[1], nil
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/content_type_checker.go
================================================
package rest
import (
"mime"
"net/http"
"strings"
)
// ContentTypeCheckerMiddleware verifies the request Content-Type header and returns a
// StatusUnsupportedMediaType (415) HTTP error response if it's incorrect. The expected
// Content-Type is 'application/json' if the content is non-null. Note: If a charset parameter
// exists, it MUST be UTF-8.
type ContentTypeCheckerMiddleware struct{}
// MiddlewareFunc makes ContentTypeCheckerMiddleware implement the Middleware interface.
func (mw *ContentTypeCheckerMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
return func(w ResponseWriter, r *Request) {
mediatype, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
charset, ok := params["charset"]
if !ok {
charset = "UTF-8"
}
// per net/http doc, means that the length is known and non-null
if r.ContentLength > 0 &&
!(mediatype == "application/json" && strings.ToUpper(charset) == "UTF-8") {
Error(w,
"Bad Content-Type or charset, expected 'application/json'",
http.StatusUnsupportedMediaType,
)
return
}
// call the wrapped handler
handler(w, r)
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/cors.go
================================================
package rest
import (
"net/http"
"strconv"
"strings"
)
// Possible improvements:
// If AllowedMethods["*"] then Access-Control-Allow-Methods is set to the requested methods
// If AllowedHeaderss["*"] then Access-Control-Allow-Headers is set to the requested headers
// Put some presets in AllowedHeaders
// Put some presets in AccessControlExposeHeaders
// CorsMiddleware provides a configurable CORS implementation.
type CorsMiddleware struct {
allowedMethods map[string]bool
allowedMethodsCsv string
allowedHeaders map[string]bool
allowedHeadersCsv string
// Reject non CORS requests if true. See CorsInfo.IsCors.
RejectNonCorsRequests bool
// Function excecuted for every CORS requests to validate the Origin. (Required)
// Must return true if valid, false if invalid.
// For instance: simple equality, regexp, DB lookup, ...
OriginValidator func(origin string, request *Request) bool
// List of allowed HTTP methods. Note that the comparison will be made in
// uppercase to avoid common mistakes. And that the
// Access-Control-Allow-Methods response header also uses uppercase.
// (see CorsInfo.AccessControlRequestMethod)
AllowedMethods []string
// List of allowed HTTP Headers. Note that the comparison will be made with
// noarmalized names (http.CanonicalHeaderKey). And that the response header
// also uses normalized names.
// (see CorsInfo.AccessControlRequestHeaders)
AllowedHeaders []string
// List of headers used to set the Access-Control-Expose-Headers header.
AccessControlExposeHeaders []string
// User to se the Access-Control-Allow-Credentials response header.
AccessControlAllowCredentials bool
// Used to set the Access-Control-Max-Age response header, in seconds.
AccessControlMaxAge int
}
// MiddlewareFunc makes CorsMiddleware implement the Middleware interface.
func (mw *CorsMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
// precompute as much as possible at init time
mw.allowedMethods = map[string]bool{}
normedMethods := []string{}
for _, allowedMethod := range mw.AllowedMethods {
normed := strings.ToUpper(allowedMethod)
mw.allowedMethods[normed] = true
normedMethods = append(normedMethods, normed)
}
mw.allowedMethodsCsv = strings.Join(normedMethods, ",")
mw.allowedHeaders = map[string]bool{}
normedHeaders := []string{}
for _, allowedHeader := range mw.AllowedHeaders {
normed := http.CanonicalHeaderKey(allowedHeader)
mw.allowedHeaders[normed] = true
normedHeaders = append(normedHeaders, normed)
}
mw.allowedHeadersCsv = strings.Join(normedHeaders, ",")
return func(writer ResponseWriter, request *Request) {
corsInfo := request.GetCorsInfo()
// non CORS requests
if !corsInfo.IsCors {
if mw.RejectNonCorsRequests {
Error(writer, "Non CORS request", http.StatusForbidden)
return
}
// continue, execute the wrapped middleware
handler(writer, request)
return
}
// Validate the Origin
if mw.OriginValidator(corsInfo.Origin, request) == false {
Error(writer, "Invalid Origin", http.StatusForbidden)
return
}
if corsInfo.IsPreflight {
// check the request methods
if mw.allowedMethods[corsInfo.AccessControlRequestMethod] == false {
Error(writer, "Invalid Preflight Request", http.StatusForbidden)
return
}
// check the request headers
for _, requestedHeader := range corsInfo.AccessControlRequestHeaders {
if mw.allowedHeaders[requestedHeader] == false {
Error(writer, "Invalid Preflight Request", http.StatusForbidden)
return
}
}
writer.Header().Set("Access-Control-Allow-Methods", mw.allowedMethodsCsv)
writer.Header().Set("Access-Control-Allow-Headers", mw.allowedHeadersCsv)
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
if mw.AccessControlAllowCredentials == true {
writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
writer.Header().Set("Access-Control-Max-Age", strconv.Itoa(mw.AccessControlMaxAge))
writer.WriteHeader(http.StatusOK)
return
}
// Non-preflight requests
for _, exposed := range mw.AccessControlExposeHeaders {
writer.Header().Add("Access-Control-Expose-Headers", exposed)
}
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
if mw.AccessControlAllowCredentials == true {
writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
// continure, execute the wrapped middleware
handler(writer, request)
return
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/doc.go
================================================
// A quick and easy way to setup a RESTful JSON API
//
// http://ant0ine.github.io/go-json-rest/
//
// Go-Json-Rest is a thin layer on top of net/http that helps building RESTful JSON APIs easily.
// It provides fast and scalable request routing using a Trie based implementation, helpers to deal
// with JSON requests and responses, and middlewares for functionalities like CORS, Auth, Gzip,
// Status, ...
//
// Example:
//
// package main
//
// import (
// "github.com/ant0ine/go-json-rest/rest"
// "log"
// "net/http"
// )
//
// type User struct {
// Id string
// Name string
// }
//
// func GetUser(w rest.ResponseWriter, req *rest.Request) {
// user := User{
// Id: req.PathParam("id"),
// Name: "Antoine",
// }
// w.WriteJson(&user)
// }
//
// func main() {
// api := rest.NewApi()
// api.Use(rest.DefaultDevStack...)
// router, err := rest.MakeRouter(
// rest.Get("/users/:id", GetUser),
// )
// if err != nil {
// log.Fatal(err)
// }
// api.SetApp(router)
// log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
// }
//
//
package rest
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/gzip.go
================================================
package rest
import (
"bufio"
"compress/gzip"
"net"
"net/http"
"strings"
)
// GzipMiddleware is responsible for compressing the payload with gzip and setting the proper
// headers when supported by the client. It must be wrapped by TimerMiddleware for the
// compression time to be captured. And It must be wrapped by RecorderMiddleware for the
// compressed BYTES_WRITTEN to be captured.
type GzipMiddleware struct{}
// MiddlewareFunc makes GzipMiddleware implement the Middleware interface.
func (mw *GzipMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
return func(w ResponseWriter, r *Request) {
// gzip support enabled
canGzip := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
// client accepts gzip ?
writer := &gzipResponseWriter{w, false, canGzip, nil}
defer func() {
// need to close gzip writer
if writer.gzipWriter != nil {
writer.gzipWriter.Close()
}
}()
// call the handler with the wrapped writer
h(writer, r)
}
}
// Private responseWriter intantiated by the gzip middleware.
// It encodes the payload with gzip and set the proper headers.
// It implements the following interfaces:
// ResponseWriter
// http.ResponseWriter
// http.Flusher
// http.CloseNotifier
// http.Hijacker
type gzipResponseWriter struct {
ResponseWriter
wroteHeader bool
canGzip bool
gzipWriter *gzip.Writer
}
// Set the right headers for gzip encoded responses.
func (w *gzipResponseWriter) WriteHeader(code int) {
// Always set the Vary header, even if this particular request
// is not gzipped.
w.Header().Add("Vary", "Accept-Encoding")
if w.canGzip {
w.Header().Set("Content-Encoding", "gzip")
}
w.ResponseWriter.WriteHeader(code)
w.wroteHeader = true
}
// Make sure the local Write is called.
func (w *gzipResponseWriter) WriteJson(v interface{}) error {
b, err := w.EncodeJson(v)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
return nil
}
// Make sure the local WriteHeader is called, and call the parent Flush.
// Provided in order to implement the http.Flusher interface.
func (w *gzipResponseWriter) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
flusher := w.ResponseWriter.(http.Flusher)
flusher.Flush()
}
// Call the parent CloseNotify.
// Provided in order to implement the http.CloseNotifier interface.
func (w *gzipResponseWriter) CloseNotify() <-chan bool {
notifier := w.ResponseWriter.(http.CloseNotifier)
return notifier.CloseNotify()
}
// Provided in order to implement the http.Hijacker interface.
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker := w.ResponseWriter.(http.Hijacker)
return hijacker.Hijack()
}
// Make sure the local WriteHeader is called, and encode the payload if necessary.
// Provided in order to implement the http.ResponseWriter interface.
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
writer := w.ResponseWriter.(http.ResponseWriter)
if w.canGzip {
// Write can be called multiple times for a given response.
// (see the streaming example:
// https://github.com/ant0ine/go-json-rest-examples/tree/master/streaming)
// The gzipWriter is instantiated only once, and flushed after
// each write.
if w.gzipWriter == nil {
w.gzipWriter = gzip.NewWriter(writer)
}
count, errW := w.gzipWriter.Write(b)
errF := w.gzipWriter.Flush()
if errW != nil {
return count, errW
}
if errF != nil {
return count, errF
}
return count, nil
}
return writer.Write(b)
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/if.go
================================================
package rest
import (
"log"
)
// IfMiddleware evaluates at runtime a condition based on the current request, and decides to
// execute one of the other Middleware based on this boolean.
type IfMiddleware struct {
// Runtime condition that decides of the execution of IfTrue of IfFalse.
Condition func(r *Request) bool
// Middleware to run when the condition is true. Note that the middleware is initialized
// weather if will be used or not. (Optional, pass-through if not set)
IfTrue Middleware
// Middleware to run when the condition is false. Note that the middleware is initialized
// weather if will be used or not. (Optional, pass-through if not set)
IfFalse Middleware
}
// MiddlewareFunc makes TimerMiddleware implement the Middleware interface.
func (mw *IfMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
if mw.Condition == nil {
log.Fatal("IfMiddleware Condition is required")
}
var ifTrueHandler HandlerFunc
if mw.IfTrue != nil {
ifTrueHandler = mw.IfTrue.MiddlewareFunc(h)
} else {
ifTrueHandler = h
}
var ifFalseHandler HandlerFunc
if mw.IfFalse != nil {
ifFalseHandler = mw.IfFalse.MiddlewareFunc(h)
} else {
ifFalseHandler = h
}
return func(w ResponseWriter, r *Request) {
if mw.Condition(r) {
ifTrueHandler(w, r)
} else {
ifFalseHandler(w, r)
}
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/json_indent.go
================================================
package rest
import (
"bufio"
"encoding/json"
"net"
"net/http"
)
// JsonIndentMiddleware provides JSON encoding with indentation.
// It could be convenient to use it during development.
// It works by "subclassing" the responseWriter provided by the wrapping middleware,
// replacing the writer.EncodeJson and writer.WriteJson implementations,
// and making the parent implementations ignored.
type JsonIndentMiddleware struct {
// prefix string, as in json.MarshalIndent
Prefix string
// indentation string, as in json.MarshalIndent
Indent string
}
// MiddlewareFunc makes JsonIndentMiddleware implement the Middleware interface.
func (mw *JsonIndentMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
if mw.Indent == "" {
mw.Indent = " "
}
return func(w ResponseWriter, r *Request) {
writer := &jsonIndentResponseWriter{w, false, mw.Prefix, mw.Indent}
// call the wrapped handler
handler(writer, r)
}
}
// Private responseWriter intantiated by the middleware.
// It implements the following interfaces:
// ResponseWriter
// http.ResponseWriter
// http.Flusher
// http.CloseNotifier
// http.Hijacker
type jsonIndentResponseWriter struct {
ResponseWriter
wroteHeader bool
prefix string
indent string
}
// Replace the parent EncodeJson to provide indentation.
func (w *jsonIndentResponseWriter) EncodeJson(v interface{}) ([]byte, error) {
b, err := json.MarshalIndent(v, w.prefix, w.indent)
if err != nil {
return nil, err
}
return b, nil
}
// Make sure the local EncodeJson and local Write are called.
// Does not call the parent WriteJson.
func (w *jsonIndentResponseWriter) WriteJson(v interface{}) error {
b, err := w.EncodeJson(v)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
return nil
}
// Call the parent WriteHeader.
func (w *jsonIndentResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
w.wroteHeader = true
}
// Make sure the local WriteHeader is called, and call the parent Flush.
// Provided in order to implement the http.Flusher interface.
func (w *jsonIndentResponseWriter) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
flusher := w.ResponseWriter.(http.Flusher)
flusher.Flush()
}
// Call the parent CloseNotify.
// Provided in order to implement the http.CloseNotifier interface.
func (w *jsonIndentResponseWriter) CloseNotify() <-chan bool {
notifier := w.ResponseWriter.(http.CloseNotifier)
return notifier.CloseNotify()
}
// Provided in order to implement the http.Hijacker interface.
func (w *jsonIndentResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker := w.ResponseWriter.(http.Hijacker)
return hijacker.Hijack()
}
// Make sure the local WriteHeader is called, and call the parent Write.
// Provided in order to implement the http.ResponseWriter interface.
func (w *jsonIndentResponseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
writer := w.ResponseWriter.(http.ResponseWriter)
return writer.Write(b)
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/jsonp.go
================================================
package rest
import (
"bufio"
"net"
"net/http"
)
// JsonpMiddleware provides JSONP responses on demand, based on the presence
// of a query string argument specifying the callback name.
type JsonpMiddleware struct {
// Name of the query string parameter used to specify the
// the name of the JS callback used for the padding.
// Defaults to "callback".
CallbackNameKey string
}
// MiddlewareFunc returns a HandlerFunc that implements the middleware.
func (mw *JsonpMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
if mw.CallbackNameKey == "" {
mw.CallbackNameKey = "callback"
}
return func(w ResponseWriter, r *Request) {
callbackName := r.URL.Query().Get(mw.CallbackNameKey)
// TODO validate the callbackName ?
if callbackName != "" {
// the client request JSONP, instantiate JsonpMiddleware.
writer := &jsonpResponseWriter{w, false, callbackName}
// call the handler with the wrapped writer
h(writer, r)
} else {
// do nothing special
h(w, r)
}
}
}
// Private responseWriter intantiated by the JSONP middleware.
// It adds the padding to the payload and set the proper headers.
// It implements the following interfaces:
// ResponseWriter
// http.ResponseWriter
// http.Flusher
// http.CloseNotifier
// http.Hijacker
type jsonpResponseWriter struct {
ResponseWriter
wroteHeader bool
callbackName string
}
// Overwrite the Content-Type to be text/javascript
func (w *jsonpResponseWriter) WriteHeader(code int) {
w.Header().Set("Content-Type", "text/javascript")
w.ResponseWriter.WriteHeader(code)
w.wroteHeader = true
}
// Make sure the local Write is called.
func (w *jsonpResponseWriter) WriteJson(v interface{}) error {
b, err := w.EncodeJson(v)
if err != nil {
return err
}
// JSONP security fix (http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/)
w.Header().Set("Content-Disposition", "filename=f.txt")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Write([]byte("/**/" + w.callbackName + "("))
w.Write(b)
w.Write([]byte(")"))
return nil
}
// Make sure the local WriteHeader is called, and call the parent Flush.
// Provided in order to implement the http.Flusher interface.
func (w *jsonpResponseWriter) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
flusher := w.ResponseWriter.(http.Flusher)
flusher.Flush()
}
// Call the parent CloseNotify.
// Provided in order to implement the http.CloseNotifier interface.
func (w *jsonpResponseWriter) CloseNotify() <-chan bool {
notifier := w.ResponseWriter.(http.CloseNotifier)
return notifier.CloseNotify()
}
// Provided in order to implement the http.Hijacker interface.
func (w *jsonpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker := w.ResponseWriter.(http.Hijacker)
return hijacker.Hijack()
}
// Make sure the local WriteHeader is called.
// Provided in order to implement the http.ResponseWriter interface.
func (w *jsonpResponseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
writer := w.ResponseWriter.(http.ResponseWriter)
return writer.Write(b)
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/middleware.go
================================================
package rest
import (
"net/http"
)
// HandlerFunc defines the handler function. It is the go-json-rest equivalent of http.HandlerFunc.
type HandlerFunc func(ResponseWriter, *Request)
// App defines the interface that an object should implement to be used as an app in this framework
// stack. The App is the top element of the stack, the other elements being middlewares.
type App interface {
AppFunc() HandlerFunc
}
// AppSimple is an adapter type that makes it easy to write an App with a simple function.
// eg: rest.NewApi(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) { ... }))
type AppSimple HandlerFunc
// AppFunc makes AppSimple implement the App interface.
func (as AppSimple) AppFunc() HandlerFunc {
return HandlerFunc(as)
}
// Middleware defines the interface that objects must implement in order to wrap a HandlerFunc and
// be used in the middleware stack.
type Middleware interface {
MiddlewareFunc(handler HandlerFunc) HandlerFunc
}
// MiddlewareSimple is an adapter type that makes it easy to write a Middleware with a simple
// function. eg: api.Use(rest.MiddlewareSimple(func(h HandlerFunc) Handlerfunc { ... }))
type MiddlewareSimple func(handler HandlerFunc) HandlerFunc
// MiddlewareFunc makes MiddlewareSimple implement the Middleware interface.
func (ms MiddlewareSimple) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
return ms(handler)
}
// WrapMiddlewares calls the MiddlewareFunc methods in the reverse order and returns an HandlerFunc
// ready to be executed. This can be used to wrap a set of middlewares, post routing, on a per Route
// basis.
func WrapMiddlewares(middlewares []Middleware, handler HandlerFunc) HandlerFunc {
wrapped := handler
for i := len(middlewares) - 1; i >= 0; i-- {
wrapped = middlewares[i].MiddlewareFunc(wrapped)
}
return wrapped
}
// Handle the transition between net/http and go-json-rest objects.
// It intanciates the rest.Request and rest.ResponseWriter, ...
func adapterFunc(handler HandlerFunc) http.HandlerFunc {
return func(origWriter http.ResponseWriter, origRequest *http.Request) {
// instantiate the rest objects
request := &Request{
origRequest,
nil,
map[string]interface{}{},
}
writer := &responseWriter{
origWriter,
false,
}
// call the wrapped handler
handler(writer, request)
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/powered_by.go
================================================
package rest
const xPoweredByDefault = "go-json-rest"
// PoweredByMiddleware adds the "X-Powered-By" header to the HTTP response.
type PoweredByMiddleware struct {
// If specified, used as the value for the "X-Powered-By" response header.
// Defaults to "go-json-rest".
XPoweredBy string
}
// MiddlewareFunc makes PoweredByMiddleware implement the Middleware interface.
func (mw *PoweredByMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
poweredBy := xPoweredByDefault
if mw.XPoweredBy != "" {
poweredBy = mw.XPoweredBy
}
return func(w ResponseWriter, r *Request) {
w.Header().Add("X-Powered-By", poweredBy)
// call the handler
h(w, r)
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/recorder.go
================================================
package rest
import (
"bufio"
"net"
"net/http"
)
// RecorderMiddleware keeps a record of the HTTP status code of the response,
// and the number of bytes written.
// The result is available to the wrapping handlers as request.Env["STATUS_CODE"].(int),
// and as request.Env["BYTES_WRITTEN"].(int64)
type RecorderMiddleware struct{}
// MiddlewareFunc makes RecorderMiddleware implement the Middleware interface.
func (mw *RecorderMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
return func(w ResponseWriter, r *Request) {
writer := &recorderResponseWriter{w, 0, false, 0}
// call the handler
h(writer, r)
r.Env["STATUS_CODE"] = writer.statusCode
r.Env["BYTES_WRITTEN"] = writer.bytesWritten
}
}
// Private responseWriter intantiated by the recorder middleware.
// It keeps a record of the HTTP status code of the response.
// It implements the following interfaces:
// ResponseWriter
// http.ResponseWriter
// http.Flusher
// http.CloseNotifier
// http.Hijacker
type recorderResponseWriter struct {
ResponseWriter
statusCode int
wroteHeader bool
bytesWritten int64
}
// Record the status code.
func (w *recorderResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
w.statusCode = code
w.wroteHeader = true
}
// Make sure the local Write is called.
func (w *recorderResponseWriter) WriteJson(v interface{}) error {
b, err := w.EncodeJson(v)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
return nil
}
// Make sure the local WriteHeader is called, and call the parent Flush.
// Provided in order to implement the http.Flusher interface.
func (w *recorderResponseWriter) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
flusher := w.ResponseWriter.(http.Flusher)
flusher.Flush()
}
// Call the parent CloseNotify.
// Provided in order to implement the http.CloseNotifier interface.
func (w *recorderResponseWriter) CloseNotify() <-chan bool {
notifier := w.ResponseWriter.(http.CloseNotifier)
return notifier.CloseNotify()
}
// Provided in order to implement the http.Hijacker interface.
func (w *recorderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker := w.ResponseWriter.(http.Hijacker)
return hijacker.Hijack()
}
// Make sure the local WriteHeader is called, and call the parent Write.
// Provided in order to implement the http.ResponseWriter interface.
func (w *recorderResponseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
writer := w.ResponseWriter.(http.ResponseWriter)
written, err := writer.Write(b)
w.bytesWritten += int64(written)
return written, err
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/recover.go
================================================
package rest
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"runtime/debug"
)
// RecoverMiddleware catches the panic errors that occur in the wrapped HandleFunc,
// and convert them to 500 responses.
type RecoverMiddleware struct {
// Custom logger used for logging the panic errors,
// optional, defaults to log.New(os.Stderr, "", 0)
Logger *log.Logger
// If true, the log records will be printed as JSON. Convenient for log parsing.
EnableLogAsJson bool
// If true, when a "panic" happens, the error string and the stack trace will be
// printed in the 500 response body.
EnableResponseStackTrace bool
}
// MiddlewareFunc makes RecoverMiddleware implement the Middleware interface.
func (mw *RecoverMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
// set the default Logger
if mw.Logger == nil {
mw.Logger = log.New(os.Stderr, "", 0)
}
return func(w ResponseWriter, r *Request) {
// catch user code's panic, and convert to http response
defer func() {
if reco := recover(); reco != nil {
trace := debug.Stack()
// log the trace
message := fmt.Sprintf("%s\n%s", reco, trace)
mw.logError(message)
// write error response
if mw.EnableResponseStackTrace {
Error(w, message, http.StatusInternalServerError)
} else {
Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
}()
// call the handler
h(w, r)
}
}
func (mw *RecoverMiddleware) logError(message string) {
if mw.EnableLogAsJson {
record := map[string]string{
"error": message,
}
b, err := json.Marshal(&record)
if err != nil {
panic(err)
}
mw.Logger.Printf("%s", b)
} else {
mw.Logger.Print(message)
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/request.go
================================================
package rest
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
var (
// ErrJsonPayloadEmpty is returned when the JSON payload is empty.
ErrJsonPayloadEmpty = errors.New("JSON payload is empty")
)
// Request inherits from http.Request, and provides additional methods.
type Request struct {
*http.Request
// Map of parameters that have been matched in the URL Path.
PathParams map[string]string
// Environment used by middlewares to communicate.
Env map[string]interface{}
}
// PathParam provides a convenient access to the PathParams map.
func (r *Request) PathParam(name string) string {
return r.PathParams[name]
}
// DecodeJsonPayload reads the request body and decodes the JSON using json.Unmarshal.
func (r *Request) DecodeJsonPayload(v interface{}) error {
content, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
return err
}
if len(content) == 0 {
return ErrJsonPayloadEmpty
}
err = json.Unmarshal(content, v)
if err != nil {
return err
}
return nil
}
// BaseUrl returns a new URL object with the Host and Scheme taken from the request.
// (without the trailing slash in the host)
func (r *Request) BaseUrl() *url.URL {
scheme := r.URL.Scheme
if scheme == "" {
scheme = "http"
}
host := r.Host
if len(host) > 0 && host[len(host)-1] == '/' {
host = host[:len(host)-1]
}
return &url.URL{
Scheme: scheme,
Host: host,
}
}
// UrlFor returns the URL object from UriBase with the Path set to path, and the query
// string built with queryParams.
func (r *Request) UrlFor(path string, queryParams map[string][]string) *url.URL {
baseUrl := r.BaseUrl()
baseUrl.Path = path
if queryParams != nil {
query := url.Values{}
for k, v := range queryParams {
for _, vv := range v {
query.Add(k, vv)
}
}
baseUrl.RawQuery = query.Encode()
}
return baseUrl
}
// CorsInfo contains the CORS request info derived from a rest.Request.
type CorsInfo struct {
IsCors bool
IsPreflight bool
Origin string
OriginUrl *url.URL
// The header value is converted to uppercase to avoid common mistakes.
AccessControlRequestMethod string
// The header values are normalized with http.CanonicalHeaderKey.
AccessControlRequestHeaders []string
}
// GetCorsInfo derives CorsInfo from Request.
func (r *Request) GetCorsInfo() *CorsInfo {
origin := r.Header.Get("Origin")
var originUrl *url.URL
var isCors bool
if origin == "" {
isCors = false
} else if origin == "null" {
isCors = true
} else {
var err error
originUrl, err = url.ParseRequestURI(origin)
isCors = err == nil && r.Host != originUrl.Host
}
reqMethod := r.Header.Get("Access-Control-Request-Method")
reqHeaders := []string{}
rawReqHeaders := r.Header[http.CanonicalHeaderKey("Access-Control-Request-Headers")]
for _, rawReqHeader := range rawReqHeaders {
// net/http does not handle comma delimited headers for us
for _, reqHeader := range strings.Split(rawReqHeader, ",") {
reqHeaders = append(reqHeaders, http.CanonicalHeaderKey(strings.TrimSpace(reqHeader)))
}
}
isPreflight := isCors && r.Method == "OPTIONS" && reqMethod != ""
return &CorsInfo{
IsCors: isCors,
IsPreflight: isPreflight,
Origin: origin,
OriginUrl: originUrl,
AccessControlRequestMethod: strings.ToUpper(reqMethod),
AccessControlRequestHeaders: reqHeaders,
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/response.go
================================================
package rest
import (
"bufio"
"encoding/json"
"net"
"net/http"
)
// A ResponseWriter interface dedicated to JSON HTTP response.
// Note, the responseWriter object instantiated by the framework also implements many other interfaces
// accessible by type assertion: http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker.
type ResponseWriter interface {
// Identical to the http.ResponseWriter interface
Header() http.Header
// Use EncodeJson to generate the payload, write the headers with http.StatusOK if
// they are not already written, then write the payload.
// The Content-Type header is set to "application/json", unless already specified.
WriteJson(v interface{}) error
// Encode the data structure to JSON, mainly used to wrap ResponseWriter in
// middlewares.
EncodeJson(v interface{}) ([]byte, error)
// Similar to the http.ResponseWriter interface, with additional JSON related
// headers set.
WriteHeader(int)
}
// This allows to customize the field name used in the error response payload.
// It defaults to "Error" for compatibility reason, but can be changed before starting the server.
// eg: rest.ErrorFieldName = "errorMessage"
var ErrorFieldName = "Error"
// Error produces an error response in JSON with the following structure, '{"Error":"My error message"}'
// The standard plain text net/http Error helper can still be called like this:
// http.Error(w, "error message", code)
func Error(w ResponseWriter, error string, code int) {
w.WriteHeader(code)
err := w.WriteJson(map[string]string{ErrorFieldName: error})
if err != nil {
panic(err)
}
}
// NotFound produces a 404 response with the following JSON, '{"Error":"Resource not found"}'
// The standard plain text net/http NotFound helper can still be called like this:
// http.NotFound(w, r.Request)
func NotFound(w ResponseWriter, r *Request) {
Error(w, "Resource not found", http.StatusNotFound)
}
// Private responseWriter intantiated by the resource handler.
// It implements the following interfaces:
// ResponseWriter
// http.ResponseWriter
// http.Flusher
// http.CloseNotifier
// http.Hijacker
type responseWriter struct {
http.ResponseWriter
wroteHeader bool
}
func (w *responseWriter) WriteHeader(code int) {
if w.Header().Get("Content-Type") == "" {
// Per spec, UTF-8 is the default, and the charset parameter should not
// be necessary. But some clients (eg: Chrome) think otherwise.
// Since json.Marshal produces UTF-8, setting the charset parameter is a
// safe option.
w.Header().Set("Content-Type", "application/json; charset=utf-8")
}
w.ResponseWriter.WriteHeader(code)
w.wroteHeader = true
}
func (w *responseWriter) EncodeJson(v interface{}) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
return b, nil
}
// Encode the object in JSON and call Write.
func (w *responseWriter) WriteJson(v interface{}) error {
b, err := w.EncodeJson(v)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
return nil
}
// Provided in order to implement the http.ResponseWriter interface.
func (w *responseWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
return w.ResponseWriter.Write(b)
}
// Provided in order to implement the http.Flusher interface.
func (w *responseWriter) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
flusher := w.ResponseWriter.(http.Flusher)
flusher.Flush()
}
// Provided in order to implement the http.CloseNotifier interface.
func (w *responseWriter) CloseNotify() <-chan bool {
notifier := w.ResponseWriter.(http.CloseNotifier)
return notifier.CloseNotify()
}
// Provided in order to implement the http.Hijacker interface.
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker := w.ResponseWriter.(http.Hijacker)
return hijacker.Hijack()
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/route.go
================================================
package rest
import (
"strings"
)
// Route defines a route as consumed by the router. It can be instantiated directly, or using one
// of the shortcut methods: rest.Get, rest.Post, rest.Put, rest.Patch and rest.Delete.
type Route struct {
// Any HTTP method. It will be used as uppercase to avoid common mistakes.
HttpMethod string
// A string like "/resource/:id.json".
// Placeholders supported are:
// :paramName that matches any char to the first '/' or '.'
// #paramName that matches any char to the first '/'
// *paramName that matches everything to the end of the string
// (placeholder names must be unique per PathExp)
PathExp string
// Code that will be executed when this route is taken.
Func HandlerFunc
}
// MakePath generates the path corresponding to this Route and the provided path parameters.
// This is used for reverse route resolution.
func (route *Route) MakePath(pathParams map[string]string) string {
path := route.PathExp
for paramName, paramValue := range pathParams {
paramPlaceholder := ":" + paramName
relaxedPlaceholder := "#" + paramName
splatPlaceholder := "*" + paramName
r := strings.NewReplacer(paramPlaceholder, paramValue, splatPlaceholder, paramValue, relaxedPlaceholder, paramValue)
path = r.Replace(path)
}
return path
}
// Head is a shortcut method that instantiates a HEAD route. See the Route object the parameters definitions.
// Equivalent to &Route{"HEAD", pathExp, handlerFunc}
func Head(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "HEAD",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Get is a shortcut method that instantiates a GET route. See the Route object the parameters definitions.
// Equivalent to &Route{"GET", pathExp, handlerFunc}
func Get(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "GET",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Post is a shortcut method that instantiates a POST route. See the Route object the parameters definitions.
// Equivalent to &Route{"POST", pathExp, handlerFunc}
func Post(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "POST",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Put is a shortcut method that instantiates a PUT route. See the Route object the parameters definitions.
// Equivalent to &Route{"PUT", pathExp, handlerFunc}
func Put(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "PUT",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Patch is a shortcut method that instantiates a PATCH route. See the Route object the parameters definitions.
// Equivalent to &Route{"PATCH", pathExp, handlerFunc}
func Patch(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "PATCH",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Delete is a shortcut method that instantiates a DELETE route. Equivalent to &Route{"DELETE", pathExp, handlerFunc}
func Delete(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "DELETE",
PathExp: pathExp,
Func: handlerFunc,
}
}
// Options is a shortcut method that instantiates an OPTIONS route. See the Route object the parameters definitions.
// Equivalent to &Route{"OPTIONS", pathExp, handlerFunc}
func Options(pathExp string, handlerFunc HandlerFunc) *Route {
return &Route{
HttpMethod: "OPTIONS",
PathExp: pathExp,
Func: handlerFunc,
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/router.go
================================================
package rest
import (
"errors"
"github.com/ant0ine/go-json-rest/rest/trie"
"net/http"
"net/url"
"strings"
)
type router struct {
Routes []*Route
disableTrieCompression bool
index map[*Route]int
trie *trie.Trie
}
// MakeRouter returns the router app. Given a set of Routes, it dispatches the request to the
// HandlerFunc of the first route that matches. The order of the Routes matters.
func MakeRouter(routes ...*Route) (App, error) {
r := &router{
Routes: routes,
}
err := r.start()
if err != nil {
return nil, err
}
return r, nil
}
// Handle the REST routing and run the user code.
func (rt *router) AppFunc() HandlerFunc {
return func(writer ResponseWriter, request *Request) {
// find the route
route, params, pathMatched := rt.findRouteFromURL(request.Method, request.URL)
if route == nil {
if pathMatched {
// no route found, but path was matched: 405 Method Not Allowed
Error(writer, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// no route found, the path was not matched: 404 Not Found
NotFound(writer, request)
return
}
// a route was found, set the PathParams
request.PathParams = params
// run the user code
handler := route.Func
handler(writer, request)
}
}
// This is run for each new request, perf is important.
func escapedPath(urlObj *url.URL) string {
// the escape method of url.URL should be public
// that would avoid this split.
parts := strings.SplitN(urlObj.RequestURI(), "?", 2)
return parts[0]
}
var preEscape = strings.NewReplacer("*", "__SPLAT_PLACEHOLDER__", "#", "__RELAXED_PLACEHOLDER__")
var postEscape = strings.NewReplacer("__SPLAT_PLACEHOLDER__", "*", "__RELAXED_PLACEHOLDER__", "#")
// This is run at init time only.
func escapedPathExp(pathExp string) (string, error) {
// PathExp validation
if pathExp == "" {
return "", errors.New("empty PathExp")
}
if pathExp[0] != '/' {
return "", errors.New("PathExp must start with /")
}
if strings.Contains(pathExp, "?") {
return "", errors.New("PathExp must not contain the query string")
}
// Get the right escaping
// XXX a bit hacky
pathExp = preEscape.Replace(pathExp)
urlObj, err := url.Parse(pathExp)
if err != nil {
return "", err
}
// get the same escaping as find requests
pathExp = urlObj.RequestURI()
pathExp = postEscape.Replace(pathExp)
return pathExp, nil
}
// This validates the Routes and prepares the Trie data structure.
// It must be called once the Routes are defined and before trying to find Routes.
// The order matters, if multiple Routes match, the first defined will be used.
func (rt *router) start() error {
rt.trie = trie.New()
rt.index = map[*Route]int{}
for i, route := range rt.Routes {
// work with the PathExp urlencoded.
pathExp, err := escapedPathExp(route.PathExp)
if err != nil {
return err
}
// insert in the Trie
err = rt.trie.AddRoute(
strings.ToUpper(route.HttpMethod), // work with the HttpMethod in uppercase
pathExp,
route,
)
if err != nil {
return err
}
// index
rt.index[route] = i
}
if rt.disableTrieCompression == false {
rt.trie.Compress()
}
return nil
}
// return the result that has the route defined the earliest
func (rt *router) ofFirstDefinedRoute(matches []*trie.Match) *trie.Match {
minIndex := -1
var bestMatch *trie.Match
for _, result := range matches {
route := result.Route.(*Route)
routeIndex := rt.index[route]
if minIndex == -1 || routeIndex < minIndex {
minIndex = routeIndex
bestMatch = result
}
}
return bestMatch
}
// Return the first matching Route and the corresponding parameters for a given URL object.
func (rt *router) findRouteFromURL(httpMethod string, urlObj *url.URL) (*Route, map[string]string, bool) {
// lookup the routes in the Trie
matches, pathMatched := rt.trie.FindRoutesAndPathMatched(
strings.ToUpper(httpMethod), // work with the httpMethod in uppercase
escapedPath(urlObj), // work with the path urlencoded
)
// short cuts
if len(matches) == 0 {
// no route found
return nil, nil, pathMatched
}
if len(matches) == 1 {
// one route found
return matches[0].Route.(*Route), matches[0].Params, pathMatched
}
// multiple routes found, pick the first defined
result := rt.ofFirstDefinedRoute(matches)
return result.Route.(*Route), result.Params, pathMatched
}
// Parse the url string (complete or just the path) and return the first matching Route and the corresponding parameters.
func (rt *router) findRoute(httpMethod, urlStr string) (*Route, map[string]string, bool, error) {
// parse the url
urlObj, err := url.Parse(urlStr)
if err != nil {
return nil, nil, false, err
}
route, params, pathMatched := rt.findRouteFromURL(httpMethod, urlObj)
return route, params, pathMatched, nil
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/status.go
================================================
package rest
import (
"fmt"
"log"
"os"
"sync"
"time"
)
// StatusMiddleware keeps track of various stats about the processed requests.
// It depends on request.Env["STATUS_CODE"] and request.Env["ELAPSED_TIME"],
// recorderMiddleware and timerMiddleware must be in the wrapped middlewares.
type StatusMiddleware struct {
lock sync.RWMutex
start time.Time
pid int
responseCounts map[string]int
totalResponseTime time.Time
}
// MiddlewareFunc makes StatusMiddleware implement the Middleware interface.
func (mw *StatusMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
mw.start = time.Now()
mw.pid = os.Getpid()
mw.responseCounts = map[string]int{}
mw.totalResponseTime = time.Time{}
return func(w ResponseWriter, r *Request) {
// call the handler
h(w, r)
if r.Env["STATUS_CODE"] == nil {
log.Fatal("StatusMiddleware: Env[\"STATUS_CODE\"] is nil, " +
"RecorderMiddleware may not be in the wrapped Middlewares.")
}
statusCode := r.Env["STATUS_CODE"].(int)
if r.Env["ELAPSED_TIME"] == nil {
log.Fatal("StatusMiddleware: Env[\"ELAPSED_TIME\"] is nil, " +
"TimerMiddleware may not be in the wrapped Middlewares.")
}
responseTime := r.Env["ELAPSED_TIME"].(*time.Duration)
mw.lock.Lock()
mw.responseCounts[fmt.Sprintf("%d", statusCode)]++
mw.totalResponseTime = mw.totalResponseTime.Add(*responseTime)
mw.lock.Unlock()
}
}
// Status contains stats and status information. It is returned by GetStatus.
// These information can be made available as an API endpoint, see the "status"
// example to install the following status route.
// GET /.status returns something like:
//
// {
// "Pid": 21732,
// "UpTime": "1m15.926272s",
// "UpTimeSec": 75.926272,
// "Time": "2013-03-04 08:00:27.152986 +0000 UTC",
// "TimeUnix": 1362384027,
// "StatusCodeCount": {
// "200": 53,
// "404": 11
// },
// "TotalCount": 64,
// "TotalResponseTime": "16.777ms",
// "TotalResponseTimeSec": 0.016777,
// "AverageResponseTime": "262.14us",
// "AverageResponseTimeSec": 0.00026214
// }
type Status struct {
Pid int
UpTime string
UpTimeSec float64
Time string
TimeUnix int64
StatusCodeCount map[string]int
TotalCount int
TotalResponseTime string
TotalResponseTimeSec float64
AverageResponseTime string
AverageResponseTimeSec float64
}
// GetStatus computes and returns a Status object based on the request informations accumulated
// since the start of the process.
func (mw *StatusMiddleware) GetStatus() *Status {
mw.lock.RLock()
now := time.Now()
uptime := now.Sub(mw.start)
totalCount := 0
for _, count := range mw.responseCounts {
totalCount += count
}
totalResponseTime := mw.totalResponseTime.Sub(time.Time{})
averageResponseTime := time.Duration(0)
if totalCount > 0 {
avgNs := int64(totalResponseTime) / int64(totalCount)
averageResponseTime = time.Duration(avgNs)
}
status := &Status{
Pid: mw.pid,
UpTime: uptime.String(),
UpTimeSec: uptime.Seconds(),
Time: now.String(),
TimeUnix: now.Unix(),
StatusCodeCount: mw.responseCounts,
TotalCount: totalCount,
TotalResponseTime: totalResponseTime.String(),
TotalResponseTimeSec: totalResponseTime.Seconds(),
AverageResponseTime: averageResponseTime.String(),
AverageResponseTimeSec: averageResponseTime.Seconds(),
}
mw.lock.RUnlock()
return status
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/timer.go
================================================
package rest
import (
"time"
)
// TimerMiddleware computes the elapsed time spent during the execution of the wrapped handler.
// The result is available to the wrapping handlers as request.Env["ELAPSED_TIME"].(*time.Duration),
// and as request.Env["START_TIME"].(*time.Time)
type TimerMiddleware struct{}
// MiddlewareFunc makes TimerMiddleware implement the Middleware interface.
func (mw *TimerMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
return func(w ResponseWriter, r *Request) {
start := time.Now()
r.Env["START_TIME"] = &start
// call the handler
h(w, r)
end := time.Now()
elapsed := end.Sub(start)
r.Env["ELAPSED_TIME"] = &elapsed
}
}
================================================
FILE: vendor/github.com/ant0ine/go-json-rest/rest/trie/impl.go
================================================
// Special Trie implementation for HTTP routing.
//
// This Trie implementation is designed to support strings that includes
// :param and *splat parameters. Strings that are commonly used to represent
// the Path in HTTP routing. This implementation also maintain for each Path
// a map of HTTP Methods associated with the Route.
//
// You probably don't need to use this package directly.
//
package trie
import (
"errors"
"fmt"
)
func splitParam(remaining string) (string, string) {
i := 0
for len(remaining) > i && remaining[i] != '/' && remaining[i] != '.' {
i++
}
return remaining[:i], remaining[i:]
}
func splitRelaxed(remaining string) (string, string) {
i := 0
for len(remaining) > i && remaining[i] != '/' {
i++
}
return remaining[:i], remaining[i:]
}
type node struct {
HttpMethodToRoute map[string]interface{}
Children map[string]*node
ChildrenKeyLen int
ParamChild *node
ParamName string
RelaxedChild *node
RelaxedName string
SplatChild *node
SplatName string
}
func (n *node) addRoute(httpMethod, pathExp string, route interface{}, usedParams []string) error {
if len(pathExp) == 0 {
// end of the path, leaf node, update the map
if n.HttpMethodToRoute == nil {
n.HttpMethodToRoute = map[string]interface{}{
httpMethod: route,
}
return nil
} else {
if n.HttpMethodToRoute[httpMethod] != nil {
return errors.New("node.Route already set, duplicated path and method")
}
n.HttpMethodToRoute[httpMethod] = route
return nil
}
}
token := pathExp[0:1]
remaining := pathExp[1:]
var nextNode *node
if token[0] == ':' {
// :param case
var name string
name, remaining = splitParam(remaining)
// Check param name is unique
for _, e := range usedParams {
if e == name {
return errors.New(
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
)
}
}
usedParams = append(usedParams, name)
if n.ParamChild == nil {
n.ParamChild = &node{}
n.ParamName = name
} else {
if n.ParamName != name {
return errors.New(
fmt.Sprintf(
"Routes sharing a common placeholder MUST name it consistently: %s != %s",
n.ParamName,
name,
),
)
}
}
nextNode = n.ParamChild
} else if token[0] == '#' {
// #param case
var name string
name, remaining = splitRelaxed(remaining)
// Check param name is unique
for _, e := range usedParams {
if e == name {
return errors.New(
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
)
}
}
usedParams = append(usedParams, name)
if n.RelaxedChild == nil {
n.RelaxedChild = &node{}
n.RelaxedName = name
} else {
if n.RelaxedName != name {
return errors.New(
fmt.Sprintf(
"Routes sharing a common placeholder MUST name it consistently: %s != %s",
n.RelaxedName,
name,
),
)
}
}
nextNode = n.RelaxedChild
} else if token[0] == '*' {
// *splat case
name := remaining
remaining = ""
// Check param name is unique
for _, e := range usedParams {
if e == name {
return errors.New(
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
)
}
}
if n.SplatChild == nil {
n.SplatChild = &node{}
n.SplatName = name
}
nextNode = n.SplatChild
} else {
// general case
if n.Children == nil {
n.Children = map[string]*node{}
n.ChildrenKeyLen = 1
}
if n.Children[token] == nil {
n.Children[token] = &node{}
}
nextNode = n.Children[token]
}
return nextNode.addRoute(httpMethod, remaining, route, usedParams)
}
func (n *node) compress() {
// *splat branch
if n.SplatChild != nil {
n.SplatChild.compress()
}
// :param branch
if n.ParamChild != nil {
n.ParamChild.compress()
}
// #param branch
if n.RelaxedChild != nil {
n.RelaxedChild.compress()
}
// main branch
if len(n.Children) == 0 {
return
}
// compressable ?
canCompress := true
for _, node := range n.Children {
if node.HttpMethodToRoute != nil || node.SplatChild != nil || node.ParamChild != nil || node.RelaxedChild != nil {
canCompress = false
}
}
// compress
if canCompress {
merged := map[string]*node{}
for key, node := range n.Children {
for gdKey, gdNode := range node.Children {
mergedKey := key + gdKey
merged[mergedKey] = gdNode
}
}
n.Children = merged
n.ChildrenKeyLen++
n.compress()
// continue
} else {
for _, node := range n.Children {
node.compress()
}
}
}
func printFPadding(padding int, format string, a ...interface{}) {
for i := 0; i < padding; i++ {
fmt.Print(" ")
}
fmt.Printf(format, a...)
}
// Private function for now
func (n *node) printDebug(level int) {
level++
// *splat branch
if n.SplatChild != nil {
printFPadding(level, "*splat\n")
n.SplatChild.printDebug(level)
}
// :param branch
if n.ParamChild != nil {
printFPadding(level, ":param\n")
n.ParamChild.printDebug(level)
}
// #param branch
if n.RelaxedChild != nil {
printFPadding(level, "#relaxed\n")
n.RelaxedChild.printDebug(level)
}
// main branch
for key, node := range n.Children {
printFPadding(level, "\"%s\"\n", key)
node.printDebug(level)
}
}
// utility for the node.findRoutes recursive method
type paramMatch struct {
name string
value string
}
type findContext struct {
paramStack []paramMatch
matchFunc func(httpMethod, path string, node *node)
}
func newFindContext() *findContext {
return &findContext{
paramStack: []paramMatch{},
}
}
func (fc *findContext) pushParams(name, value string) {
fc.paramStack = append(
fc.paramStack,
paramMatch{name, value},
)
}
func (fc *findContext) popParams() {
fc.paramStack = fc.paramStack[:len(fc.paramStack)-1]
}
func (fc *findContext) paramsAsMap() map[string]string {
r := map[string]string{}
for _, param := range fc.paramStack {
if r[param.name] != "" {
// this is checked at addRoute time, and should never happen.
panic(fmt.Sprintf(
"placeholder %s already found, placeholder names should be unique per route",
param.name,
))
}
r[param.name] = param.value
}
return r
}
type Match struct {
// Same Route as in AddRoute
Route interface{}
// map of params matched for this result
Params map[string]string
}
func (n *node) find(httpMethod, path string, context *findContext) {
if n.HttpMethodToRoute != nil && path == "" {
context.matchFunc(httpMethod, path, n)
}
if len(path) == 0 {
return
}
// *splat branch
if n.SplatChild != nil {
context.pushParams(n.SplatName, path)
n.SplatChild.find(httpMethod, "", context)
context.popParams()
}
// :param branch
if n.ParamChild != nil {
value, remaining := splitParam(path)
context.pushParams(n.ParamName, value)
n.ParamChild.find(httpMethod, remaining, context)
context.popParams()
}
// #param branch
if n.RelaxedChild != nil {
value, remaining := splitRelaxed(path)
context.pushParams(n.RelaxedName, value)
n.RelaxedChild.find(httpMethod, remaining, context)
context.popParams()
}
// main branch
length := n.ChildrenKeyLen
if len(path) < length {
return
}
token := path[0:length]
remaining := path[length:]
if n.Children[token] != nil {
n.Children[token].find(httpMethod, remaining, context)
}
}
type Trie struct {
root *node
}
// Instanciate a Trie with an empty node as the root.
func New() *Trie {
return &Trie{
root: &node{},
}
}
// Insert the route in the Trie following or creating the nodes corresponding to the path.
func (t *Trie) AddRoute(httpMethod, pathExp string, route interface{}) error {
return t.root.addRoute(httpMethod, pathExp, route, []string{})
}
// Reduce the size of the tree, must be done after the last AddRoute.
func (t *Trie) Compress() {
t.root.compress()
}
// Private function for now.
func (t *Trie) printDebug() {
fmt.Print("\n")
t.root.printDebug(0)
fmt.Print("\n")
}
// Given a path and an http method, return all the matching routes.
func (t *Trie) FindRoutes(httpMethod, path string) []*Match {
context := newFindContext()
matches := []*Match{}
context.matchFunc = func(httpMethod, path string, node *node) {
if node.HttpMethodToRoute[httpMethod] != nil {
// path and method match, found a route !
matches = append(
matches,
&Match{
Route: node.HttpMethodToRoute[httpMethod],
Params: context.paramsAsMap(),
},
)
}
}
t.root.find(httpMethod, path, context)
return matches
}
// Same as FindRoutes, but return in addition a boolean indicating if the path was matched.
// Useful to return 405
func (t *Trie) FindRoutesAndPathMatched(httpMethod, path string) ([]*Match, bool) {
context := newFindContext()
pathMatched := false
matches := []*Match{}
context.matchFunc = func(httpMethod, path string, node *node) {
pathMatched = true
if node.HttpMethodToRoute[httpMethod] != nil {
// path and method match, found a route !
matches = append(
matches,
&Match{
Route: node.HttpMethodToRoute[httpMethod],
Params: context.paramsAsMap(),
},
)
}
}
t.root.find(httpMethod, path, context)
return matches, pathMatched
}
// Given a path, and whatever the http method, return all the matching routes.
func (t *Trie) FindRoutesForPath(path string) []*Match {
context := newFindContext()
matches := []*Match{}
context.matchFunc = func(httpMethod, path string, node *node) {
params := context.paramsAsMap()
for _, route := range node.HttpMethodToRoute {
matches = append(
matches,
&Match{
Route: route,
Params: params,
},
)
}
}
t.root.find("", path, context)
return matches
}
================================================
FILE: vendor/github.com/boombuler/barcode/.gitignore
================================================
.vscode/
================================================
FILE: vendor/github.com/boombuler/barcode/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014 Florian Sundermann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: vendor/github.com/boombuler/barcode/README.md
================================================
[](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Introduction ##
This is a package for GO which can be used to create different types of barcodes.
## Supported Barcode Types ##
* 2 of 5
* Aztec Code
* Codabar
* Code 128
* Code 39
* Code 93
* Datamatrix
* EAN 13
* EAN 8
* PDF 417
* QR Code
## Example ##
This is a simple example on how to create a QR-Code and write it to a png-file
```go
package main
import (
"image/png"
"os"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
)
func main() {
// Create the barcode
qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)
// Scale the barcode to 200x200 pixels
qrCode, _ = barcode.Scale(qrCode, 200, 200)
// create the output file
file, _ := os.Create("qrcode.png")
defer file.Close()
// encode the barcode as png
png.Encode(file, qrCode)
}
```
## Documentation ##
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
To create a barcode use the Encode function from one of the subpackages.
================================================
FILE: vendor/github.com/boombuler/barcode/barcode.go
================================================
package barcode
import "image"
const (
TypeAztec = "Aztec"
TypeCodabar = "Codabar"
TypeCode128 = "Code 128"
TypeCode39 = "Code 39"
TypeCode93 = "Code 93"
TypeDataMatrix = "DataMatrix"
TypeEAN8 = "EAN 8"
TypeEAN13 = "EAN 13"
TypePDF = "PDF417"
TypeQR = "QR Code"
Type2of5 = "2 of 5"
Type2of5Interleaved = "2 of 5 (interleaved)"
)
// Contains some meta information about a barcode
type Metadata struct {
// the name of the barcode kind
CodeKind string
// contains 1 for 1D barcodes or 2 for 2D barcodes
Dimensions byte
}
// a rendered and encoded barcode
type Barcode interface {
image.Image
// returns some meta information about the barcode
Metadata() Metadata
// the data that was encoded in this barcode
Content() string
}
// Additional interface that some barcodes might implement to provide
// the value of its checksum.
type BarcodeIntCS interface {
Barcode
CheckSum() int
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/alphanumeric.go
================================================
package qr
import (
"errors"
"fmt"
"strings"
"github.com/boombuler/barcode/utils"
)
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
func stringToAlphaIdx(content string) <-chan int {
result := make(chan int)
go func() {
for _, r := range content {
idx := strings.IndexRune(charSet, r)
result <- idx
if idx < 0 {
break
}
}
close(result)
}()
return result
}
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentLenIsOdd := len(content)%2 == 1
contentBitCount := (len(content) / 2) * 11
if contentLenIsOdd {
contentBitCount += 6
}
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(alphaNumericMode), 4)
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
encoder := stringToAlphaIdx(content)
for idx := 0; idx < len(content)/2; idx++ {
c1 := <-encoder
c2 := <-encoder
if c1 < 0 || c2 < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c1*45+c2, 11)
}
if contentLenIsOdd {
c := <-encoder
if c < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c, 6)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/automatic.go
================================================
package qr
import (
"fmt"
"github.com/boombuler/barcode/utils"
)
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
bits, vi, _ := Numeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = Unicode.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/blocks.go
================================================
package qr
type block struct {
data []byte
ecc []byte
}
type blockList []*block
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[b] = blk
}
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
}
return result
}
func (bl blockList) interleave(vi *versionInfo) []byte {
var maxCodewordCount int
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
} else {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
}
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
result := make([]byte, 0, resultLen)
for i := 0; i < maxCodewordCount; i++ {
for b := 0; b < len(bl); b++ {
if len(bl[b].data) > i {
result = append(result, bl[b].data[i])
}
}
}
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
for b := 0; b < len(bl); b++ {
result = append(result, bl[b].ecc[i])
}
}
return result
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/encoder.go
================================================
// Package qr can be used to create QR barcodes.
package qr
import (
"image"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
// Encoding mode for QR Codes.
type Encoding byte
const (
// Auto will choose ths best matching encoding
Auto Encoding = iota
// Numeric encoding only encodes numbers [0-9]
Numeric
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
AlphaNumeric
// Unicode encoding encodes the string as utf-8
Unicode
// only for testing purpose
unknownEncoding
)
func (e Encoding) getEncoder() encodeFn {
switch e {
case Auto:
return encodeAuto
case Numeric:
return encodeNumeric
case AlphaNumeric:
return encodeAlphaNumeric
case Unicode:
return encodeUnicode
}
return nil
}
func (e Encoding) String() string {
switch e {
case Auto:
return "Auto"
case Numeric:
return "Numeric"
case AlphaNumeric:
return "AlphaNumeric"
case Unicode:
return "Unicode"
}
return ""
}
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
bits, vi, err := mode.getEncoder()(content, level)
if err != nil {
return nil, err
}
blocks := splitToBlocks(bits.IterateBytes(), vi)
data := blocks.interleave(vi)
result := render(data, vi)
result.content = content
return result, nil
}
func render(data []byte, vi *versionInfo) *qrcode {
dim := vi.modulWidth()
results := make([]*qrcode, 8)
for i := 0; i < 8; i++ {
results[i] = newBarcode(dim)
}
occupied := newBarcode(dim)
setAll := func(x int, y int, val bool) {
occupied.Set(x, y, true)
for i := 0; i < 8; i++ {
results[i].Set(x, y, val)
}
}
drawFinderPatterns(vi, setAll)
drawAlignmentPatterns(occupied, vi, setAll)
//Timing Pattern:
var i int
for i = 0; i < dim; i++ {
if !occupied.Get(i, 6) {
setAll(i, 6, i%2 == 0)
}
if !occupied.Get(6, i) {
setAll(6, i, i%2 == 0)
}
}
// Dark Module
setAll(8, dim-8, true)
drawVersionInfo(vi, setAll)
drawFormatInfo(vi, -1, occupied.Set)
for i := 0; i < 8; i++ {
drawFormatInfo(vi, i, results[i].Set)
}
// Write the data
var curBitNo int
for pos := range iterateModules(occupied) {
var curBit bool
if curBitNo < len(data)*8 {
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
} else {
curBit = false
}
for i := 0; i < 8; i++ {
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
}
curBitNo++
}
lowestPenalty := ^uint(0)
lowestPenaltyIdx := -1
for i := 0; i < 8; i++ {
p := results[i].calcPenalty()
if p < lowestPenalty {
lowestPenalty = p
lowestPenaltyIdx = i
}
}
return results[lowestPenaltyIdx]
}
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
switch mask {
case 0:
val = val != (((y + x) % 2) == 0)
break
case 1:
val = val != ((y % 2) == 0)
break
case 2:
val = val != ((x % 3) == 0)
break
case 3:
val = val != (((y + x) % 3) == 0)
break
case 4:
val = val != (((y/2 + x/3) % 2) == 0)
break
case 5:
val = val != (((y*x)%2)+((y*x)%3) == 0)
break
case 6:
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
break
case 7:
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
}
set(x, y, val)
}
func iterateModules(occupied *qrcode) <-chan image.Point {
result := make(chan image.Point)
allPoints := make(chan image.Point)
go func() {
curX := occupied.dimension - 1
curY := occupied.dimension - 1
isUpward := true
for true {
if isUpward {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY--
if curY < 0 {
curY = 0
curX -= 2
if curX == 6 {
curX--
}
if curX < 0 {
break
}
isUpward = false
}
} else {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY++
if curY >= occupied.dimension {
curY = occupied.dimension - 1
curX -= 2
if curX == 6 {
curX--
}
isUpward = true
if curX < 0 {
break
}
}
}
}
close(allPoints)
}()
go func() {
for pt := range allPoints {
if !occupied.Get(pt.X, pt.Y) {
result <- pt
}
}
close(result)
}()
return result
}
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
dim := vi.modulWidth()
drawPattern := func(xoff int, yoff int) {
for x := -1; x < 8; x++ {
for y := -1; y < 8; y++ {
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
set(x+xoff, y+yoff, val)
}
}
}
}
drawPattern(0, 0)
drawPattern(0, dim-7)
drawPattern(dim-7, 0)
}
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
drawPattern := func(xoff int, yoff int) {
for x := -2; x <= 2; x++ {
for y := -2; y <= 2; y++ {
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
set(x+xoff, y+yoff, val)
}
}
}
positions := vi.alignmentPatternPlacements()
for _, x := range positions {
for _, y := range positions {
if occupied.Get(x, y) {
continue
}
drawPattern(x, y)
}
}
}
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
L: {
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
},
M: {
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
},
Q: {
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
},
H: {
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
},
}
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
var formatInfo []bool
if usedMask == -1 {
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
} else {
formatInfo = formatInfos[vi.Level][usedMask]
}
if len(formatInfo) == 15 {
dim := vi.modulWidth()
set(0, 8, formatInfo[0])
set(1, 8, formatInfo[1])
set(2, 8, formatInfo[2])
set(3, 8, formatInfo[3])
set(4, 8, formatInfo[4])
set(5, 8, formatInfo[5])
set(7, 8, formatInfo[6])
set(8, 8, formatInfo[7])
set(8, 7, formatInfo[8])
set(8, 5, formatInfo[9])
set(8, 4, formatInfo[10])
set(8, 3, formatInfo[11])
set(8, 2, formatInfo[12])
set(8, 1, formatInfo[13])
set(8, 0, formatInfo[14])
set(8, dim-1, formatInfo[0])
set(8, dim-2, formatInfo[1])
set(8, dim-3, formatInfo[2])
set(8, dim-4, formatInfo[3])
set(8, dim-5, formatInfo[4])
set(8, dim-6, formatInfo[5])
set(8, dim-7, formatInfo[6])
set(dim-8, 8, formatInfo[7])
set(dim-7, 8, formatInfo[8])
set(dim-6, 8, formatInfo[9])
set(dim-5, 8, formatInfo[10])
set(dim-4, 8, formatInfo[11])
set(dim-3, 8, formatInfo[12])
set(dim-2, 8, formatInfo[13])
set(dim-1, 8, formatInfo[14])
}
}
var versionInfoBitsByVersion = map[byte][]bool{
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
}
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
if ok && len(versionInfoBits) > 0 {
for i := 0; i < len(versionInfoBits); i++ {
x := (vi.modulWidth() - 11) + i%3
y := i / 3
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
}
}
}
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
bl.AddBit(false)
}
for bl.Len()%8 != 0 {
bl.AddBit(false)
}
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
if i%2 == 0 {
bl.AddByte(236)
} else {
bl.AddByte(17)
}
}
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/errorcorrection.go
================================================
package qr
import (
"github.com/boombuler/barcode/utils"
)
type errorCorrection struct {
rs *utils.ReedSolomonEncoder
}
var ec = newErrorCorrection()
func newErrorCorrection() *errorCorrection {
fld := utils.NewGaloisField(285, 256, 0)
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
}
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
dataInts := make([]int, len(data))
for i := 0; i < len(data); i++ {
dataInts[i] = int(data[i])
}
res := ec.rs.Encode(dataInts, int(eccCount))
result := make([]byte, len(res))
for i := 0; i < len(res); i++ {
result[i] = byte(res[i])
}
return result
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/numeric.go
================================================
package qr
import (
"errors"
"fmt"
"strconv"
"github.com/boombuler/barcode/utils"
)
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentBitCount := (len(content) / 3) * 10
switch len(content) % 3 {
case 1:
contentBitCount += 4
case 2:
contentBitCount += 7
}
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(numericMode), 4)
res.AddBits(len(content), vi.charCountBits(numericMode))
for pos := 0; pos < len(content); pos += 3 {
var curStr string
if pos+3 <= len(content) {
curStr = content[pos : pos+3]
} else {
curStr = content[pos:]
}
i, err := strconv.Atoi(curStr)
if err != nil || i < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
}
var bitCnt byte
switch len(curStr) % 3 {
case 0:
bitCnt = 10
case 1:
bitCnt = 4
break
case 2:
bitCnt = 7
break
}
res.AddBits(i, bitCnt)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/qrcode.go
================================================
package qr
import (
"image"
"image/color"
"math"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type qrcode struct {
dimension int
data *utils.BitList
content string
}
func (qr *qrcode) Content() string {
return qr.content
}
func (qr *qrcode) Metadata() barcode.Metadata {
return barcode.Metadata{barcode.TypeQR, 2}
}
func (qr *qrcode) ColorModel() color.Model {
return color.Gray16Model
}
func (qr *qrcode) Bounds() image.Rectangle {
return image.Rect(0, 0, qr.dimension, qr.dimension)
}
func (qr *qrcode) At(x, y int) color.Color {
if qr.Get(x, y) {
return color.Black
}
return color.White
}
func (qr *qrcode) Get(x, y int) bool {
return qr.data.GetBit(x*qr.dimension + y)
}
func (qr *qrcode) Set(x, y int, val bool) {
qr.data.SetBit(x*qr.dimension+y, val)
}
func (qr *qrcode) calcPenalty() uint {
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
}
func (qr *qrcode) calcPenaltyRule1() uint {
var result uint
for x := 0; x < qr.dimension; x++ {
checkForX := false
var cntX uint
checkForY := false
var cntY uint
for y := 0; y < qr.dimension; y++ {
if qr.Get(x, y) == checkForX {
cntX++
} else {
checkForX = !checkForX
if cntX >= 5 {
result += cntX - 2
}
cntX = 1
}
if qr.Get(y, x) == checkForY {
cntY++
} else {
checkForY = !checkForY
if cntY >= 5 {
result += cntY - 2
}
cntY = 1
}
}
if cntX >= 5 {
result += cntX - 2
}
if cntY >= 5 {
result += cntY - 2
}
}
return result
}
func (qr *qrcode) calcPenaltyRule2() uint {
var result uint
for x := 0; x < qr.dimension-1; x++ {
for y := 0; y < qr.dimension-1; y++ {
check := qr.Get(x, y)
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
result += 3
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule3() uint {
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
var result uint
for x := 0; x <= qr.dimension-len(pattern1); x++ {
for y := 0; y < qr.dimension; y++ {
pattern1XFound := true
pattern2XFound := true
pattern1YFound := true
pattern2YFound := true
for i := 0; i < len(pattern1); i++ {
iv := qr.Get(x+i, y)
if iv != pattern1[i] {
pattern1XFound = false
}
if iv != pattern2[i] {
pattern2XFound = false
}
iv = qr.Get(y, x+i)
if iv != pattern1[i] {
pattern1YFound = false
}
if iv != pattern2[i] {
pattern2YFound = false
}
}
if pattern1XFound || pattern2XFound {
result += 40
}
if pattern1YFound || pattern2YFound {
result += 40
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule4() uint {
totalNum := qr.data.Len()
trueCnt := 0
for i := 0; i < totalNum; i++ {
if qr.data.GetBit(i) {
trueCnt++
}
}
percDark := float64(trueCnt) * 100 / float64(totalNum)
floor := math.Abs(math.Floor(percDark/5) - 10)
ceil := math.Abs(math.Ceil(percDark/5) - 10)
return uint(math.Min(floor, ceil) * 10)
}
func newBarcode(dim int) *qrcode {
res := new(qrcode)
res.dimension = dim
res.data = utils.NewBitList(dim * dim)
return res
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/unicode.go
================================================
package qr
import (
"errors"
"github.com/boombuler/barcode/utils"
)
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
data := []byte(content)
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
// required ECI header...
res := new(utils.BitList)
res.AddBits(int(byteMode), 4)
res.AddBits(len(content), vi.charCountBits(byteMode))
for _, b := range data {
res.AddByte(b)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}
================================================
FILE: vendor/github.com/boombuler/barcode/qr/versioninfo.go
================================================
package qr
import "math"
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
type ErrorCorrectionLevel byte
const (
// L recovers 7% of data
L ErrorCorrectionLevel = iota
// M recovers 15% of data
M
// Q recovers 25% of data
Q
// H recovers 30% of data
H
)
func (ecl ErrorCorrectionLevel) String() string {
switch ecl {
case L:
return "L"
case M:
return "M"
case Q:
return "Q"
case H:
return "H"
}
return "unknown"
}
type encodingMode byte
const (
numericMode encodingMode = 1
alphaNumericMode encodingMode = 2
byteMode encodingMode = 4
kanjiMode encodingMode = 8
)
type versionInfo struct {
Version byte
Level ErrorCorrectionLevel
ErrorCorrectionCodewordsPerBlock byte
NumberOfBlocksInGroup1 byte
DataCodeWordsPerBlockInGroup1 byte
NumberOfBlocksInGroup2 byte
DataCodeWordsPerBlockInGroup2 byte
}
var versionInfos = []*versionInfo{
&versionInfo{1, L, 7, 1, 19, 0, 0},
&versionInfo{1, M, 10, 1, 16, 0, 0},
&versionInfo{1, Q, 13, 1, 13, 0, 0},
&versionInfo{1, H, 17, 1, 9, 0, 0},
&versionInfo{2, L, 10, 1, 34, 0, 0},
&versionInfo{2, M, 16, 1, 28, 0, 0},
&versionInfo{2, Q, 22, 1, 22, 0, 0},
&versionInfo{2, H, 28, 1, 16, 0, 0},
&versionInfo{3, L, 15, 1, 55, 0, 0},
&versionInfo{3, M, 26, 1, 44, 0, 0},
&versionInfo{3, Q, 18, 2, 17, 0, 0},
&versionInfo{3, H, 22, 2, 13, 0, 0},
&versionInfo{4, L, 20, 1, 80, 0, 0},
&versionInfo{4, M, 18, 2, 32, 0, 0},
&versionInfo{4, Q, 26, 2, 24, 0, 0},
&versionInfo{4, H, 16, 4, 9, 0, 0},
&versionInfo{5, L, 26, 1, 108, 0, 0},
&versionInfo{5, M, 24, 2, 43, 0, 0},
&versionInfo{5, Q, 18, 2, 15, 2, 16},
&versionInfo{5, H, 22, 2, 11, 2, 12},
&versionInfo{6, L, 18, 2, 68, 0, 0},
&versionInfo{6, M, 16, 4, 27, 0, 0},
&versionInfo{6, Q, 24, 4, 19, 0, 0},
&versionInfo{6, H, 28, 4, 15, 0, 0},
&versionInfo{7, L, 20, 2, 78, 0, 0},
&versionInfo{7, M, 18, 4, 31, 0, 0},
&versionInfo{7, Q, 18, 2, 14, 4, 15},
&versionInfo{7, H, 26, 4, 13, 1, 14},
&versionInfo{8, L, 24, 2, 97, 0, 0},
&versionInfo{8, M, 22, 2, 38, 2, 39},
&versionInfo{8, Q, 22, 4, 18, 2, 19},
&versionInfo{8, H, 26, 4, 14, 2, 15},
&versionInfo{9, L, 30, 2, 116, 0, 0},
&versionInfo{9, M, 22, 3, 36, 2, 37},
&versionInfo{9, Q, 20, 4, 16, 4, 17},
&versionInfo{9, H, 24, 4, 12, 4, 13},
&versionInfo{10, L, 18, 2, 68, 2, 69},
&versionInfo{10, M, 26, 4, 43, 1, 44},
&versionInfo{10, Q, 24, 6, 19, 2, 20},
&versionInfo{10, H, 28, 6, 15, 2, 16},
&versionInfo{11, L, 20, 4, 81, 0, 0},
&versionInfo{11, M, 30, 1, 50, 4, 51},
&versionInfo{11, Q, 28, 4, 22, 4, 23},
&versionInfo{11, H, 24, 3, 12, 8, 13},
&versionInfo{12, L, 24, 2, 92, 2, 93},
&versionInfo{12, M, 22, 6, 36, 2, 37},
&versionInfo{12, Q, 26, 4, 20, 6, 21},
&versionInfo{12, H, 28, 7, 14, 4, 15},
&versionInfo{13, L, 26, 4, 107, 0, 0},
&versionInfo{13, M, 22, 8, 37, 1, 38},
&versionInfo{13, Q, 24, 8, 20, 4, 21},
&versionInfo{13, H, 22, 12, 11, 4, 12},
&versionInfo{14, L, 30, 3, 115, 1, 116},
&versionInfo{14, M, 24, 4, 40, 5, 41},
&versionInfo{14, Q, 20, 11, 16, 5, 17},
&versionInfo{14, H, 24, 11, 12, 5, 13},
&versionInfo{15, L, 22, 5, 87, 1, 88},
&versionInfo{15, M, 24, 5, 41, 5, 42},
&versionInfo{15, Q, 30, 5, 24, 7, 25},
&versionInfo{15, H, 24, 11, 12, 7, 13},
&versionInfo{16, L, 24, 5, 98, 1, 99},
&versionInfo{16, M, 28, 7, 45, 3, 46},
&versionInfo{16, Q, 24, 15, 19, 2, 20},
&versionInfo{16, H, 30, 3, 15, 13, 16},
&versionInfo{17, L, 28, 1, 107, 5, 108},
&versionInfo{17, M, 28, 10, 46, 1, 47},
&versionInfo{17, Q, 28, 1, 22, 15, 23},
&versionInfo{17, H, 28, 2, 14, 17, 15},
&versionInfo{18, L, 30, 5, 120, 1, 121},
&versionInfo{18, M, 26, 9, 43, 4, 44},
&versionInfo{18, Q, 28, 17, 22, 1, 23},
&versionInfo{18, H, 28, 2, 14, 19, 15},
&versionInfo{19, L, 28, 3, 113, 4, 114},
&versionInfo{19, M, 26, 3, 44, 11, 45},
&versionInfo{19, Q, 26, 17, 21, 4, 22},
&versionInfo{19, H, 26, 9, 13, 16, 14},
&versionInfo{20, L, 28, 3, 107, 5, 108},
&versionInfo{20, M, 26, 3, 41, 13, 42},
&versionInfo{20, Q, 30, 15, 24, 5, 25},
&versionInfo{20, H, 28, 15, 15, 10, 16},
&versionInfo{21, L, 28, 4, 116, 4, 117},
&versionInfo{21, M, 26, 17, 42, 0, 0},
&versionInfo{21, Q, 28, 17, 22, 6, 23},
&versionInfo{21, H, 30, 19, 16, 6, 17},
&versionInfo{22, L, 28, 2, 111, 7, 112},
&versionInfo{22, M, 28, 17, 46, 0, 0},
&versionInfo{22, Q, 30, 7, 24, 16, 25},
&versionInfo{22, H, 24, 34, 13, 0, 0},
&versionInfo{23, L, 30, 4, 121, 5, 122},
&versionInfo{23, M, 28, 4, 47, 14, 48},
&versionInfo{23, Q, 30, 11, 24, 14, 25},
&versionInfo{23, H, 30, 16, 15, 14, 16},
&versionInfo{24, L, 30, 6, 117, 4, 118},
&versionInfo{24, M, 28, 6, 45, 14, 46},
&versionInfo{24, Q, 30, 11, 24, 16, 25},
&versionInfo{24, H, 30, 30, 16, 2, 17},
&versionInfo{25, L, 26, 8, 106, 4, 107},
&versionInfo{25, M, 28, 8, 47, 13, 48},
&versionInfo{25, Q, 30, 7, 24, 22, 25},
&versionInfo{25, H, 30, 22, 15, 13, 16},
&versionInfo{26, L, 28, 10, 114, 2, 115},
&versionInfo{26, M, 28, 19, 46, 4, 47},
&versionInfo{26, Q, 28, 28, 22, 6, 23},
&versionInfo{26, H, 30, 33, 16, 4, 17},
&versionInfo{27, L, 30, 8, 122, 4, 123},
&versionInfo{27, M, 28, 22, 45, 3, 46},
&versionInfo{27, Q, 30, 8, 23, 26, 24},
&versionInfo{27, H, 30, 12, 15, 28, 16},
&versionInfo{28, L, 30, 3, 117, 10, 118},
&versionInfo{28, M, 28, 3, 45, 23, 46},
&versionInfo{28, Q, 30, 4, 24, 31, 25},
&versionInfo{28, H, 30, 11, 15, 31, 16},
&versionInfo{29, L, 30, 7, 116, 7, 117},
&versionInfo{29, M, 28, 21, 45, 7, 46},
&versionInfo{29, Q, 30, 1, 23, 37, 24},
&versionInfo{29, H, 30, 19, 15, 26, 16},
&versionInfo{30, L, 30, 5, 115, 10, 116},
&versionInfo{30, M, 28, 19, 47, 10, 48},
&versionInfo{30, Q, 30, 15, 24, 25, 25},
&versionInfo{30, H, 30, 23, 15, 25, 16},
&versionInfo{31, L, 30, 13, 115, 3, 116},
&versionInfo{31, M, 28, 2, 46, 29, 47},
&versionInfo{31, Q, 30, 42, 24, 1, 25},
&versionInfo{31, H, 30, 23, 15, 28, 16},
&versionInfo{32, L, 30, 17, 115, 0, 0},
&versionInfo{32, M, 28, 10, 46, 23, 47},
&versionInfo{32, Q, 30, 10, 24, 35, 25},
&versionInfo{32, H, 30, 19, 15, 35, 16},
&versionInfo{33, L, 30, 17, 115, 1, 116},
&versionInfo{33, M, 28, 14, 46, 21, 47},
&versionInfo{33, Q, 30, 29, 24, 19, 25},
&versionInfo{33, H, 30, 11, 15, 46, 16},
&versionInfo{34, L, 30, 13, 115, 6, 116},
&versionInfo{34, M, 28, 14, 46, 23, 47},
&versionInfo{34, Q, 30, 44, 24, 7, 25},
&versionInfo{34, H, 30, 59, 16, 1, 17},
&versionInfo{35, L, 30, 12, 121, 7, 122},
&versionInfo{35, M, 28, 12, 47, 26, 48},
&versionInfo{35, Q, 30, 39, 24, 14, 25},
&versionInfo{35, H, 30, 22, 15, 41, 16},
&versionInfo{36, L, 30, 6, 121, 14, 122},
&versionInfo{36, M, 28, 6, 47, 34, 48},
&versionInfo{36, Q, 30, 46, 24, 10, 25},
&versionInfo{36, H, 30, 2, 15, 64, 16},
&versionInfo{37, L, 30, 17, 122, 4, 123},
&versionInfo{37, M, 28, 29, 46, 14, 47},
&versionInfo{37, Q, 30, 49, 24, 10, 25},
&versionInfo{37, H, 30, 24, 15, 46, 16},
&versionInfo{38, L, 30, 4, 122, 18, 123},
&versionInfo{38, M, 28, 13, 46, 32, 47},
&versionInfo{38, Q, 30, 48, 24, 14, 25},
&versionInfo{38, H, 30, 42, 15, 32, 16},
&versionInfo{39, L, 30, 20, 117, 4, 118},
&versionInfo{39, M, 28, 40, 47, 7, 48},
&versionInfo{39, Q, 30, 43, 24, 22, 25},
&versionInfo{39, H, 30, 10, 15, 67, 16},
&versionInfo{40, L, 30, 19, 118, 6, 119},
&versionInfo{40, M, 28, 18, 47, 31, 48},
&versionInfo{40, Q, 30, 34, 24, 34, 25},
&versionInfo{40, H, 30, 20, 15, 61, 16},
}
func (vi *versionInfo) totalDataBytes() int {
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
return (g1Data + g2Data)
}
func (vi *versionInfo) charCountBits(m encodingMode) byte {
switch m {
case numericMode:
if vi.Version < 10 {
return 10
} else if vi.Version < 27 {
return 12
}
return 14
case alphaNumericMode:
if vi.Version < 10 {
return 9
} else if vi.Version < 27 {
return 11
}
return 13
case byteMode:
if vi.Version < 10 {
return 8
}
return 16
case kanjiMode:
if vi.Version < 10 {
return 8
} else if vi.Version < 27 {
return 10
}
return 12
default:
return 0
}
}
func (vi *versionInfo) modulWidth() int {
return ((int(vi.Version) - 1) * 4) + 21
}
func (vi *versionInfo) alignmentPatternPlacements() []int {
if vi.Version == 1 {
return make([]int, 0)
}
first := 6
last := vi.modulWidth() - 7
space := float64(last - first)
count := int(math.Ceil(space/28)) + 1
result := make([]int, count)
result[0] = first
result[len(result)-1] = last
if count > 2 {
step := int(math.Ceil(float64(last-first) / float64(count-1)))
if step%2 == 1 {
frac := float64(last-first) / float64(count-1)
_, x := math.Modf(frac)
if x >= 0.5 {
frac = math.Ceil(frac)
} else {
frac = math.Floor(frac)
}
if int(frac)%2 == 0 {
step--
} else {
step++
}
}
for i := 1; i <= count-2; i++ {
result[i] = last - (step * (count - 1 - i))
}
}
return result
}
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
dataBits = dataBits + 4 // mode indicator
for _, vi := range versionInfos {
if vi.Level == ecl {
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
return vi
}
}
}
return nil
}
================================================
FILE: vendor/github.com/boombuler/barcode/scaledbarcode.go
================================================
package barcode
import (
"errors"
"fmt"
"image"
"image/color"
"math"
)
type wrapFunc func(x, y int) color.Color
type scaledBarcode struct {
wrapped Barcode
wrapperFunc wrapFunc
rect image.Rectangle
}
type intCSscaledBC struct {
scaledBarcode
}
func (bc *scaledBarcode) Content() string {
return bc.wrapped.Content()
}
func (bc *scaledBarcode) Metadata() Metadata {
return bc.wrapped.Metadata()
}
func (bc *scaledBarcode) ColorModel() color.Model {
return bc.wrapped.ColorModel()
}
func (bc *scaledBarcode) Bounds() image.Rectangle {
return bc.rect
}
func (bc *scaledBarcode) At(x, y int) color.Color {
return bc.wrapperFunc(x, y)
}
func (bc *intCSscaledBC) CheckSum() int {
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
return cs.CheckSum()
}
return 0
}
// Scale returns a resized barcode with the given width and height.
func Scale(bc Barcode, width, height int) (Barcode, error) {
switch bc.Metadata().Dimensions {
case 1:
return scale1DCode(bc, width, height)
case 2:
return scale2DCode(bc, width, height)
}
return nil, errors.New("unsupported barcode format")
}
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
result := &scaledBarcode{
wrapped: wrapped,
wrapperFunc: wrapperFunc,
rect: rect,
}
if _, ok := wrapped.(BarcodeIntCS); ok {
return &intCSscaledBC{*result}
}
return result
}
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
}
offsetX := (width - (orgWidth * factor)) / 2
offsetY := (height - (orgHeight * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX || y < offsetY {
return color.White
}
x = (x - offsetX) / factor
y = (y - offsetY) / factor
if x >= orgWidth || y >= orgHeight {
return color.White
}
return bc.At(x, y)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
factor := int(float64(width) / float64(orgWidth))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
}
offsetX := (width - (orgWidth * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX {
return color.White
}
x = (x - offsetX) / factor
if x >= orgWidth {
return color.White
}
return bc.At(x, 0)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/base1dcode.go
================================================
// Package utils contain some utilities which are needed to create barcodes
package utils
import (
"image"
"image/color"
"github.com/boombuler/barcode"
)
type base1DCode struct {
*BitList
kind string
content string
}
type base1DCodeIntCS struct {
base1DCode
checksum int
}
func (c *base1DCode) Content() string {
return c.content
}
func (c *base1DCode) Metadata() barcode.Metadata {
return barcode.Metadata{c.kind, 1}
}
func (c *base1DCode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *base1DCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.Len(), 1)
}
func (c *base1DCode) At(x, y int) color.Color {
if c.GetBit(x) {
return color.Black
}
return color.White
}
func (c *base1DCodeIntCS) CheckSum() int {
return c.checksum
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
return &base1DCode{bars, codeKind, content}
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/bitlist.go
================================================
package utils
// BitList is a list that contains bits
type BitList struct {
count int
data []int32
}
// NewBitList returns a new BitList with the given length
// all bits are initialize with false
func NewBitList(capacity int) *BitList {
bl := new(BitList)
bl.count = capacity
x := 0
if capacity%32 != 0 {
x = 1
}
bl.data = make([]int32, capacity/32+x)
return bl
}
// Len returns the number of contained bits
func (bl *BitList) Len() int {
return bl.count
}
func (bl *BitList) grow() {
growBy := len(bl.data)
if growBy < 128 {
growBy = 128
} else if growBy >= 1024 {
growBy = 1024
}
nd := make([]int32, len(bl.data)+growBy)
copy(nd, bl.data)
bl.data = nd
}
// AddBit appends the given bits to the end of the list
func (bl *BitList) AddBit(bits ...bool) {
for _, bit := range bits {
itmIndex := bl.count / 32
for itmIndex >= len(bl.data) {
bl.grow()
}
bl.SetBit(bl.count, bit)
bl.count++
}
}
// SetBit sets the bit at the given index to the given value
func (bl *BitList) SetBit(index int, value bool) {
itmIndex := index / 32
itmBitShift := 31 - (index % 32)
if value {
bl.data[itmIndex] = bl.data[itmIndex] | 1<> uint(itmBitShift)) & 1) == 1
}
// AddByte appends all 8 bits of the given byte to the end of the list
func (bl *BitList) AddByte(b byte) {
for i := 7; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
func (bl *BitList) AddBits(b int, count byte) {
for i := int(count) - 1; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// GetBytes returns all bits of the BitList as a []byte
func (bl *BitList) GetBytes() []byte {
len := bl.count >> 3
if (bl.count % 8) != 0 {
len++
}
result := make([]byte, len)
for i := 0; i < len; i++ {
shift := (3 - (i % 4)) * 8
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
}
return result
}
// IterateBytes iterates through all bytes contained in the BitList
func (bl *BitList) IterateBytes() <-chan byte {
res := make(chan byte)
go func() {
c := bl.count
shift := 24
i := 0
for c > 0 {
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
shift -= 8
if shift < 0 {
shift = 24
i++
}
c -= 8
}
close(res)
}()
return res
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/galoisfield.go
================================================
package utils
// GaloisField encapsulates galois field arithmetics
type GaloisField struct {
Size int
Base int
ALogTbl []int
LogTbl []int
}
// NewGaloisField creates a new galois field
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
result := new(GaloisField)
result.Size = fieldSize
result.Base = b
result.ALogTbl = make([]int, fieldSize)
result.LogTbl = make([]int, fieldSize)
x := 1
for i := 0; i < fieldSize; i++ {
result.ALogTbl[i] = x
x = x * 2
if x >= fieldSize {
x = (x ^ pp) & (fieldSize - 1)
}
}
for i := 0; i < fieldSize; i++ {
result.LogTbl[result.ALogTbl[i]] = int(i)
}
return result
}
func (gf *GaloisField) Zero() *GFPoly {
return NewGFPoly(gf, []int{0})
}
// AddOrSub add or substract two numbers
func (gf *GaloisField) AddOrSub(a, b int) int {
return a ^ b
}
// Multiply multiplys two numbers
func (gf *GaloisField) Multiply(a, b int) int {
if a == 0 || b == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
}
// Divide divides two numbers
func (gf *GaloisField) Divide(a, b int) int {
if b == 0 {
panic("divide by zero")
} else if a == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
}
func (gf *GaloisField) Invers(num int) int {
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/gfpoly.go
================================================
package utils
type GFPoly struct {
gf *GaloisField
Coefficients []int
}
func (gp *GFPoly) Degree() int {
return len(gp.Coefficients) - 1
}
func (gp *GFPoly) Zero() bool {
return gp.Coefficients[0] == 0
}
// GetCoefficient returns the coefficient of x ^ degree
func (gp *GFPoly) GetCoefficient(degree int) int {
return gp.Coefficients[gp.Degree()-degree]
}
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
if gp.Zero() {
return other
} else if other.Zero() {
return gp
}
smallCoeff := gp.Coefficients
largeCoeff := other.Coefficients
if len(smallCoeff) > len(largeCoeff) {
largeCoeff, smallCoeff = smallCoeff, largeCoeff
}
sumDiff := make([]int, len(largeCoeff))
lenDiff := len(largeCoeff) - len(smallCoeff)
copy(sumDiff, largeCoeff[:lenDiff])
for i := lenDiff; i < len(largeCoeff); i++ {
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
}
return NewGFPoly(gp.gf, sumDiff)
}
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
if coeff == 0 {
return gp.gf.Zero()
}
size := len(gp.Coefficients)
result := make([]int, size+degree)
for i := 0; i < size; i++ {
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
}
return NewGFPoly(gp.gf, result)
}
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
if gp.Zero() || other.Zero() {
return gp.gf.Zero()
}
aCoeff := gp.Coefficients
aLen := len(aCoeff)
bCoeff := other.Coefficients
bLen := len(bCoeff)
product := make([]int, aLen+bLen-1)
for i := 0; i < aLen; i++ {
ac := int(aCoeff[i])
for j := 0; j < bLen; j++ {
bc := int(bCoeff[j])
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
}
}
return NewGFPoly(gp.gf, product)
}
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
quotient = gp.gf.Zero()
remainder = gp
fld := gp.gf
denomLeadTerm := other.GetCoefficient(other.Degree())
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
degreeDiff := remainder.Degree() - other.Degree()
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
term := other.MultByMonominal(degreeDiff, scale)
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
quotient = quotient.AddOrSubstract(itQuot)
remainder = remainder.AddOrSubstract(term)
}
return
}
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
if coeff == 0 {
return field.Zero()
}
result := make([]int, degree+1)
result[0] = coeff
return NewGFPoly(field, result)
}
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
for len(coefficients) > 1 && coefficients[0] == 0 {
coefficients = coefficients[1:]
}
return &GFPoly{field, coefficients}
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/reedsolomon.go
================================================
package utils
import (
"sync"
)
type ReedSolomonEncoder struct {
gf *GaloisField
polynomes []*GFPoly
m *sync.Mutex
}
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
return &ReedSolomonEncoder{
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
}
}
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
rs.m.Lock()
defer rs.m.Unlock()
if degree >= len(rs.polynomes) {
last := rs.polynomes[len(rs.polynomes)-1]
for d := len(rs.polynomes); d <= degree; d++ {
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
rs.polynomes = append(rs.polynomes, next)
last = next
}
}
return rs.polynomes[degree]
}
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
generator := rs.getPolynomial(eccCount)
info := NewGFPoly(rs.gf, data)
info = info.MultByMonominal(eccCount, 1)
_, remainder := info.Divide(generator)
result := make([]int, eccCount)
numZero := int(eccCount) - len(remainder.Coefficients)
copy(result[numZero:], remainder.Coefficients)
return result
}
================================================
FILE: vendor/github.com/boombuler/barcode/utils/runeint.go
================================================
package utils
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
// If the rune is outside of this range -1 is returned.
func RuneToInt(r rune) int {
if r >= '0' && r <= '9' {
return int(r - '0')
}
return -1
}
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
// of this range 'F' is returned!
func IntToRune(i int) rune {
if i >= 0 && i <= 9 {
return rune(i + '0')
}
return 'F'
}
================================================
FILE: vendor/github.com/davecgh/go-spew/LICENSE
================================================
ISC License
Copyright (c) 2012-2016 Dave Collins
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/bypass.go
================================================
// Copyright (c) 2015-2016 Dave Collins
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex.
// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
type flag uintptr
var (
// flagRO indicates whether the value field of a reflect.Value
// is read-only.
flagRO flag
// flagAddr indicates whether the address of the reflect.Value's
// value may be taken.
flagAddr flag
)
// flagKindMask holds the bits that make up the kind
// part of the flags field. In all the supported versions,
// it is in the lower 5 bits.
const flagKindMask = flag(0x1f)
// Different versions of Go have used different
// bit layouts for the flags type. This table
// records the known combinations.
var okFlags = []struct {
ro, addr flag
}{{
// From Go 1.4 to 1.5
ro: 1 << 5,
addr: 1 << 7,
}, {
// Up to Go tip.
ro: 1<<5 | 1<<6,
addr: 1 << 8,
}}
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
// flagField returns a pointer to the flag field of a reflect.Value.
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) reflect.Value {
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
return v
}
flagFieldPtr := flagField(&v)
*flagFieldPtr &^= flagRO
*flagFieldPtr |= flagAddr
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
type t0 int
var t struct {
A t0
// t0 will have flagEmbedRO set.
t0
// a will have flagStickyRO set
a t0
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
vt0 := reflect.ValueOf(t).FieldByName("t0")
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagPublic := *flagField(&vA)
flagWithRO := *flagField(&va) | *flagField(&vt0)
flagRO = flagPublic ^ flagWithRO
// Infer flagAddr from the difference between a value
// taken from a pointer and not.
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
flagNoPtr := *flagField(&vA)
flagPtr := *flagField(&vPtrA)
flagAddr = flagNoPtr ^ flagPtr
// Check that the inferred flags tally with one of the known versions.
for _, f := range okFlags {
if flagRO == f.ro && flagAddr == f.addr {
return
}
}
panic("reflect.Value read-only flag has changed semantics")
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
================================================
// Copyright (c) 2015-2016 Dave Collins
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe !go1.4
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/common.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("")
maxNewlineBytes = []byte("\n")
maxShortBytes = []byte("")
circularBytes = []byte("")
circularShortBytes = []byte("")
invalidAngleBytes = []byte("")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/config.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/doc.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr)
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/dump.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound:
d.w.Write(nilAngleBytes)
case cycleFound:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/format.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
f.fs.Write(nilAngleBytes)
case cycleFound:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}
================================================
FILE: vendor/github.com/davecgh/go-spew/spew/spew.go
================================================
/*
* Copyright (c) 2013-2016 Dave Collins
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/.gitignore
================================================
# Setup a Global .gitignore for OS and editor generated files:
# https://help.github.com/articles/ignoring-files
# git config --global core.excludesfile ~/.gitignore_global
.vagrant
*.sublime-project
================================================
FILE: vendor/github.com/fsnotify/fsnotify/.travis.yml
================================================
sudo: false
language: go
go:
- 1.5.4
- 1.6.1
- tip
matrix:
allow_failures:
- go: tip
before_script:
- go get -u github.com/golang/lint/golint
script:
- go test -v --race ./...
after_script:
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
- test -z "$(golint ./... | tee /dev/stderr)"
- go vet ./...
os:
- linux
- osx
notifications:
email: false
================================================
FILE: vendor/github.com/fsnotify/fsnotify/AUTHORS
================================================
# Names should be added to this file as
# Name or Organization
# The email address is not required for organizations.
# You can update this list using the following command:
#
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
# Please keep the list sorted.
Adrien Bustany
Amit Krishnan
Bjørn Erik Pedersen
Caleb Spare
Case Nelson
Chris Howey
Christoffer Buchholz
Daniel Wagner-Hall
Dave Cheney
Evan Phoenix
Francisco Souza
Hari haran
John C Barstow
Kelvin Fo
Ken-ichirou MATSUZAWA
Matt Layher
Nathan Youngman
Paul Hammond
Pawel Knap
Pieter Droogendijk
Pursuit92
Riku Voipio
Rob Figueiredo
Soge Zhang
Tiffany Jernigan
Tilak Sharma
Travis Cline
Tudor Golubenco
Yukang
bronze1man
debrando
henrikedwards
铁哥
================================================
FILE: vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
================================================
# Changelog
## v1.3.0 / 2016-04-19
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
## v1.2.10 / 2016-03-02
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
## v1.2.9 / 2016-01-13
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
## v1.2.8 / 2015-12-17
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
* inotify: fix race in test
* enable race detection for continuous integration (Linux, Mac, Windows)
## v1.2.5 / 2015-10-17
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
## v1.2.1 / 2015-10-14
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
## v1.2.0 / 2015-02-08
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
## v1.1.1 / 2015-02-05
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
## v1.1.0 / 2014-12-12
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
* add low-level functions
* only need to store flags on directories
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
* done can be an unbuffered channel
* remove calls to os.NewSyscallError
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v1.0.4 / 2014-09-07
* kqueue: add dragonfly to the build tags.
* Rename source code files, rearrange code so exported APIs are at the top.
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
## v1.0.3 / 2014-08-19
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
## v1.0.2 / 2014-08-17
* [Fix] Missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
## v1.0.0 / 2014-08-15
* [API] Remove AddWatch on Windows, use Add.
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
* Minor updates based on feedback from golint.
## dev / 2014-07-09
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
## dev / 2014-07-04
* kqueue: fix incorrect mutex used in Close()
* Update example to demonstrate usage of Op.
## dev / 2014-06-28
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
* Fix for String() method on Event (thanks Alex Brainman)
* Don't build on Plan 9 or Solaris (thanks @4ad)
## dev / 2014-06-21
* Events channel of type Event rather than *Event.
* [internal] use syscall constants directly for inotify and kqueue.
* [internal] kqueue: rename events to kevents and fileEvent to event.
## dev / 2014-06-19
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
* [internal] remove cookie from Event struct (unused).
* [internal] Event struct has the same definition across every OS.
* [internal] remove internal watch and removeWatch methods.
## dev / 2014-06-12
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
* [API] Pluralized channel names: Events and Errors.
* [API] Renamed FileEvent struct to Event.
* [API] Op constants replace methods like IsCreate().
## dev / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## dev / 2014-05-23
* [API] Remove current implementation of WatchFlags.
* current implementation doesn't take advantage of OS for efficiency
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
* no tests for the current implementation
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
## v0.9.3 / 2014-12-31
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v0.9.2 / 2014-08-17
* [Backport] Fix missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
## v0.9.1 / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## v0.9.0 / 2014-01-17
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
## v0.8.12 / 2013-11-13
* [API] Remove FD_SET and friends from Linux adapter
## v0.8.11 / 2013-11-02
* [Doc] Add Changelog [#72][] (thanks @nathany)
* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
## v0.8.10 / 2013-10-19
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
* [Doc] specify OS-specific limits in README (thanks @debrando)
## v0.8.9 / 2013-09-08
* [Doc] Contributing (thanks @nathany)
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
* [Doc] GoCI badge in README (Linux only) [#60][]
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
## v0.8.8 / 2013-06-17
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
## v0.8.7 / 2013-06-03
* [API] Make syscall flags internal
* [Fix] inotify: ignore event changes
* [Fix] race in symlink test [#45][] (reported by @srid)
* [Fix] tests on Windows
* lower case error messages
## v0.8.6 / 2013-05-23
* kqueue: Use EVT_ONLY flag on Darwin
* [Doc] Update README with full example
## v0.8.5 / 2013-05-09
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
## v0.8.4 / 2013-04-07
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
## v0.8.3 / 2013-03-13
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
## v0.8.2 / 2013-02-07
* [Doc] add Authors
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
## v0.8.1 / 2013-01-09
* [Fix] Windows path separators
* [Doc] BSD License
## v0.8.0 / 2012-11-09
* kqueue: directory watching improvements (thanks @vmirage)
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
## v0.7.4 / 2012-10-09
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
* [Fix] kqueue: modify after recreation of file
## v0.7.3 / 2012-09-27
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
* [Fix] kqueue: no longer get duplicate CREATE events
## v0.7.2 / 2012-09-01
* kqueue: events for created directories
## v0.7.1 / 2012-07-14
* [Fix] for renaming files
## v0.7.0 / 2012-07-02
* [Feature] FSNotify flags
* [Fix] inotify: Added file name back to event path
## v0.6.0 / 2012-06-06
* kqueue: watch files after directory created (thanks @tmc)
## v0.5.1 / 2012-05-22
* [Fix] inotify: remove all watches before Close()
## v0.5.0 / 2012-05-03
* [API] kqueue: return errors during watch instead of sending over channel
* kqueue: match symlink behavior on Linux
* inotify: add `DELETE_SELF` (requested by @taralx)
* [Fix] kqueue: handle EINTR (reported by @robfig)
* [Doc] Godoc example [#1][] (thanks @davecheney)
## v0.4.0 / 2012-03-30
* Go 1 released: build with go tool
* [Feature] Windows support using winfsnotify
* Windows does not have attribute change notifications
* Roll attribute notifications into IsModify
## v0.3.0 / 2012-02-19
* kqueue: add files when watch directory
## v0.2.0 / 2011-12-30
* update to latest Go weekly code
## v0.1.0 / 2011-10-19
* kqueue: add watch on file creation to match inotify
* kqueue: create file event
* inotify: ignore `IN_IGNORED` events
* event String()
* linux: common FileEvent functions
* initial commit
[#79]: https://github.com/howeyc/fsnotify/pull/79
[#77]: https://github.com/howeyc/fsnotify/pull/77
[#72]: https://github.com/howeyc/fsnotify/issues/72
[#71]: https://github.com/howeyc/fsnotify/issues/71
[#70]: https://github.com/howeyc/fsnotify/issues/70
[#63]: https://github.com/howeyc/fsnotify/issues/63
[#62]: https://github.com/howeyc/fsnotify/issues/62
[#60]: https://github.com/howeyc/fsnotify/issues/60
[#59]: https://github.com/howeyc/fsnotify/issues/59
[#49]: https://github.com/howeyc/fsnotify/issues/49
[#45]: https://github.com/howeyc/fsnotify/issues/45
[#40]: https://github.com/howeyc/fsnotify/issues/40
[#36]: https://github.com/howeyc/fsnotify/issues/36
[#33]: https://github.com/howeyc/fsnotify/issues/33
[#29]: https://github.com/howeyc/fsnotify/issues/29
[#25]: https://github.com/howeyc/fsnotify/issues/25
[#24]: https://github.com/howeyc/fsnotify/issues/24
[#21]: https://github.com/howeyc/fsnotify/issues/21
================================================
FILE: vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
================================================
# Contributing
## Issues
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
* Please indicate the platform you are using fsnotify on.
* A code example to reproduce the problem is appreciated.
## Pull Requests
### Contributor License Agreement
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
Please indicate that you have signed the CLA in your pull request.
### How fsnotify is Developed
* Development is done on feature branches.
* Tests are run on BSD, Linux, OS X and Windows.
* Pull requests are reviewed and [applied to master][am] using [hub][].
* Maintainers may modify or squash commits rather than asking contributors to.
* To issue a new release, the maintainers will:
* Update the CHANGELOG
* Tag a version, which will become available through gopkg.in.
### How to Fork
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Ensure everything works and the tests pass (see below)
4. Commit your changes (`git commit -am 'Add some feature'`)
Contribute upstream:
1. Fork fsnotify on GitHub
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
3. Push to the branch (`git push fork my-new-feature`)
4. Create a new Pull Request on GitHub
This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
### Testing
fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
* When you're done, you will want to halt or destroy the Vagrant boxes.
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
### Maintainers
Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/).
[hub]: https://github.com/github/hub
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
================================================
FILE: vendor/github.com/fsnotify/fsnotify/LICENSE
================================================
Copyright (c) 2012 The Go Authors. All rights reserved.
Copyright (c) 2012 fsnotify Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: vendor/github.com/fsnotify/fsnotify/README.md
================================================
# File system notifications for Go
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [](http://gocover.io/github.com/fsnotify/fsnotify)
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
```console
go get -u golang.org/x/sys/...
```
Cross platform: Windows, Linux, BSD and OS X.
|Adapter |OS |Status |
|----------|----------|----------|
|inotify |Linux 2.6.27 or later, Android\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|kqueue |BSD, OS X, iOS\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|FSEvents |OS X |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|fanotify |Linux 2.6.37+ | |
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
\* Android and iOS are untested.
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) for usage. Consult the [Wiki](https://github.com/fsnotify/fsnotify/wiki) for the FAQ and further information.
## API stability
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
## Contributing
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
## Example
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
## Related Projects
* [notify](https://github.com/rjeczalik/notify)
* [fsevents](https://github.com/fsnotify/fsevents)
================================================
FILE: vendor/github.com/fsnotify/fsnotify/fen.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build solaris
package fsnotify
import (
"errors"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
return nil
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
return nil
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/fsnotify.go
================================================
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
// Package fsnotify provides a platform-independent interface for file system notifications.
package fsnotify
import (
"bytes"
"fmt"
)
// Event represents a single file system notification.
type Event struct {
Name string // Relative path to the file or directory.
Op Op // File operation that triggered the event.
}
// Op describes a set of file operations.
type Op uint32
// These are the generalized file operations that can trigger a notification.
const (
Create Op = 1 << iota
Write
Remove
Rename
Chmod
)
// String returns a string representation of the event in the form
// "file: REMOVE|WRITE|..."
func (e Event) String() string {
// Use a buffer for efficient string concatenation
var buffer bytes.Buffer
if e.Op&Create == Create {
buffer.WriteString("|CREATE")
}
if e.Op&Remove == Remove {
buffer.WriteString("|REMOVE")
}
if e.Op&Write == Write {
buffer.WriteString("|WRITE")
}
if e.Op&Rename == Rename {
buffer.WriteString("|RENAME")
}
if e.Op&Chmod == Chmod {
buffer.WriteString("|CHMOD")
}
// If buffer remains empty, return no event names
if buffer.Len() == 0 {
return fmt.Sprintf("%q: ", e.Name)
}
// Return a list of event names, with leading pipe character stripped
return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:])
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/inotify.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"unsafe"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
mu sync.Mutex // Map access
cv *sync.Cond // sync removing on rm_watch with IN_IGNORE
fd int
poller *fdPoller
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
doneResp chan struct{} // Channel to respond to Close
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
// Create inotify fd
fd, errno := unix.InotifyInit()
if fd == -1 {
return nil, errno
}
// Create epoll
poller, err := newFdPoller(fd)
if err != nil {
unix.Close(fd)
return nil, err
}
w := &Watcher{
fd: fd,
poller: poller,
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
}
w.cv = sync.NewCond(&w.mu)
go w.readEvents()
return w, nil
}
func (w *Watcher) isClosed() bool {
select {
case <-w.done:
return true
default:
return false
}
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed() {
return nil
}
// Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)
// Wake up goroutine
w.poller.wake()
// Wait for goroutine to close
<-w.doneResp
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
name = filepath.Clean(name)
if w.isClosed() {
return errors.New("inotify instance already closed")
}
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
var flags uint32 = agnosticEvents
w.mu.Lock()
watchEntry, found := w.watches[name]
w.mu.Unlock()
if found {
watchEntry.flags |= flags
flags |= unix.IN_MASK_ADD
}
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
if wd == -1 {
return errno
}
w.mu.Lock()
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
w.paths[wd] = name
w.mu.Unlock()
return nil
}
// Remove stops watching the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
// Fetch the watch.
w.mu.Lock()
defer w.mu.Unlock()
watch, ok := w.watches[name]
// Remove it from inotify.
if !ok {
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
}
// inotify_rm_watch will return EINVAL if the file has been deleted;
// the inotify will already have been removed.
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
// by another thread and we have not received IN_IGNORE event.
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
// TODO: Perhaps it's not helpful to return an error here in every case.
// the only two possible errors are:
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
return errno
}
// wait until ignoreLinux() deleting maps
exists := true
for exists {
w.cv.Wait()
_, exists = w.watches[name]
}
return nil
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Events channel
func (w *Watcher) readEvents() {
var (
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
n int // Number of bytes read with read()
errno error // Syscall errno
ok bool // For poller.wait
)
defer close(w.doneResp)
defer close(w.Errors)
defer close(w.Events)
defer unix.Close(w.fd)
defer w.poller.close()
for {
// See if we have been closed.
if w.isClosed() {
return
}
ok, errno = w.poller.wait()
if errno != nil {
select {
case w.Errors <- errno:
case <-w.done:
return
}
continue
}
if !ok {
continue
}
n, errno = unix.Read(w.fd, buf[:])
// If a signal interrupted execution, see if we've been asked to close, and try again.
// http://man7.org/linux/man-pages/man7/signal.7.html :
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
if errno == unix.EINTR {
continue
}
// unix.Read might have been woken up by Close. If so, we're done.
if w.isClosed() {
return
}
if n < unix.SizeofInotifyEvent {
var err error
if n == 0 {
// If EOF is received. This should really never happen.
err = io.EOF
} else if n < 0 {
// If an error occurred while reading.
err = errno
} else {
// Read was too short.
err = errors.New("notify: short read in readEvents()")
}
select {
case w.Errors <- err:
case <-w.done:
return
}
continue
}
var offset uint32
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
for offset <= uint32(n-unix.SizeofInotifyEvent) {
// Point "raw" to the event in the buffer
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
mask := uint32(raw.Mask)
nameLen := uint32(raw.Len)
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
w.mu.Lock()
name := w.paths[int(raw.Wd)]
w.mu.Unlock()
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
event := newEvent(name, mask)
// Send the events that are not ignored on the events channel
if !event.ignoreLinux(w, raw.Wd, mask) {
select {
case w.Events <- event:
case <-w.done:
return
}
}
// Move to the next event in the buffer
offset += unix.SizeofInotifyEvent + nameLen
}
}
}
// Certain types of events can be "ignored" and not sent over the Events
// channel. Such as events marked ignore by the kernel, or MODIFY events
// against files that do not exist.
func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool {
// Ignore anything the inotify API says to ignore
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
w.mu.Lock()
defer w.mu.Unlock()
name := w.paths[int(wd)]
delete(w.paths, int(wd))
delete(w.watches, name)
w.cv.Broadcast()
return true
}
// If the event is not a DELETE or RENAME, the file must exist.
// Otherwise the event is ignored.
// *Note*: this was put in place because it was seen that a MODIFY
// event was sent after the DELETE. This ignores that MODIFY and
// assumes a DELETE will come or has come if the file doesn't exist.
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
_, statErr := os.Lstat(e.Name)
return os.IsNotExist(statErr)
}
return false
}
// newEvent returns an platform-independent Event based on an inotify mask.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
e.Op |= Create
}
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
e.Op |= Remove
}
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
e.Op |= Write
}
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
e.Op |= Rename
}
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
e.Op |= Chmod
}
return e
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/inotify_poller.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"golang.org/x/sys/unix"
)
type fdPoller struct {
fd int // File descriptor (as returned by the inotify_init() syscall)
epfd int // Epoll file descriptor
pipe [2]int // Pipe for waking up
}
func emptyPoller(fd int) *fdPoller {
poller := new(fdPoller)
poller.fd = fd
poller.epfd = -1
poller.pipe[0] = -1
poller.pipe[1] = -1
return poller
}
// Create a new inotify poller.
// This creates an inotify handler, and an epoll handler.
func newFdPoller(fd int) (*fdPoller, error) {
var errno error
poller := emptyPoller(fd)
defer func() {
if errno != nil {
poller.close()
}
}()
poller.fd = fd
// Create epoll fd
poller.epfd, errno = unix.EpollCreate1(0)
if poller.epfd == -1 {
return nil, errno
}
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
if errno != nil {
return nil, errno
}
// Register inotify fd with epoll
event := unix.EpollEvent{
Fd: int32(poller.fd),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
if errno != nil {
return nil, errno
}
// Register pipe fd with epoll
event = unix.EpollEvent{
Fd: int32(poller.pipe[0]),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
if errno != nil {
return nil, errno
}
return poller, nil
}
// Wait using epoll.
// Returns true if something is ready to be read,
// false if there is not.
func (poller *fdPoller) wait() (bool, error) {
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
// I don't know whether epoll_wait returns the number of events returned,
// or the total number of events ready.
// I decided to catch both by making the buffer one larger than the maximum.
events := make([]unix.EpollEvent, 7)
for {
n, errno := unix.EpollWait(poller.epfd, events, -1)
if n == -1 {
if errno == unix.EINTR {
continue
}
return false, errno
}
if n == 0 {
// If there are no events, try again.
continue
}
if n > 6 {
// This should never happen. More events were returned than should be possible.
return false, errors.New("epoll_wait returned more events than I know what to do with")
}
ready := events[:n]
epollhup := false
epollerr := false
epollin := false
for _, event := range ready {
if event.Fd == int32(poller.fd) {
if event.Events&unix.EPOLLHUP != 0 {
// This should not happen, but if it does, treat it as a wakeup.
epollhup = true
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the file descriptor, we should pretend
// something is ready to read, and let unix.Read pick up the error.
epollerr = true
}
if event.Events&unix.EPOLLIN != 0 {
// There is data to read.
epollin = true
}
}
if event.Fd == int32(poller.pipe[0]) {
if event.Events&unix.EPOLLHUP != 0 {
// Write pipe descriptor was closed, by us. This means we're closing down the
// watcher, and we should wake up.
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the pipe file descriptor.
// This is an absolute mystery, and should never ever happen.
return false, errors.New("Error on the pipe descriptor.")
}
if event.Events&unix.EPOLLIN != 0 {
// This is a regular wakeup, so we have to clear the buffer.
err := poller.clearWake()
if err != nil {
return false, err
}
}
}
}
if epollhup || epollerr || epollin {
return true, nil
}
return false, nil
}
}
// Close the write end of the poller.
func (poller *fdPoller) wake() error {
buf := make([]byte, 1)
n, errno := unix.Write(poller.pipe[1], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is full, poller will wake.
return nil
}
return errno
}
return nil
}
func (poller *fdPoller) clearWake() error {
// You have to be woken up a LOT in order to get to 100!
buf := make([]byte, 100)
n, errno := unix.Read(poller.pipe[0], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is empty, someone else cleared our wake.
return nil
}
return errno
}
return nil
}
// Close all poller file descriptors, but not the one passed to it.
func (poller *fdPoller) close() {
if poller.pipe[1] != -1 {
unix.Close(poller.pipe[1])
}
if poller.pipe[0] != -1 {
unix.Close(poller.pipe[0])
}
if poller.epfd != -1 {
unix.Close(poller.epfd)
}
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/kqueue.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly darwin
package fsnotify
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
done chan bool // Channel for sending a "quit message" to the reader goroutine
kq int // File descriptor (as returned by the kqueue() syscall).
mu sync.Mutex // Protects access to watcher data
watches map[string]int // Map of watched file descriptors (key: path).
externalWatches map[string]bool // Map of watches added by user of the library.
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
isClosed bool // Set to true when Close() is first called
}
type pathInfo struct {
name string
isDir bool
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
kq, err := kqueue()
if err != nil {
return nil, err
}
w := &Watcher{
kq: kq,
watches: make(map[string]int),
dirFlags: make(map[string]uint32),
paths: make(map[int]pathInfo),
fileExists: make(map[string]bool),
externalWatches: make(map[string]bool),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan bool),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return nil
}
w.isClosed = true
w.mu.Unlock()
// copy paths to remove while locked
w.mu.Lock()
var pathsToRemove = make([]string, 0, len(w.watches))
for name := range w.watches {
pathsToRemove = append(pathsToRemove, name)
}
w.mu.Unlock()
// unlock before calling Remove, which also locks
var err error
for _, name := range pathsToRemove {
if e := w.Remove(name); e != nil && err == nil {
err = e
}
}
// Send "quit" message to the reader goroutine:
w.done <- true
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
w.mu.Lock()
w.externalWatches[name] = true
w.mu.Unlock()
_, err := w.addWatch(name, noteAllEvents)
return err
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
w.mu.Lock()
watchfd, ok := w.watches[name]
w.mu.Unlock()
if !ok {
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
}
const registerRemove = unix.EV_DELETE
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
return err
}
unix.Close(watchfd)
w.mu.Lock()
isDir := w.paths[watchfd].isDir
delete(w.watches, name)
delete(w.paths, watchfd)
delete(w.dirFlags, name)
w.mu.Unlock()
// Find all watched paths that are in this directory that are not external.
if isDir {
var pathsToRemove []string
w.mu.Lock()
for _, path := range w.paths {
wdir, _ := filepath.Split(path.name)
if filepath.Clean(wdir) == name {
if !w.externalWatches[path.name] {
pathsToRemove = append(pathsToRemove, path.name)
}
}
}
w.mu.Unlock()
for _, name := range pathsToRemove {
// Since these are internal, not much sense in propagating error
// to the user, as that will just confuse them with an error about
// a path they did not explicitly watch themselves.
w.Remove(name)
}
}
return nil
}
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
// keventWaitTime to block on each read from kevent
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
// addWatch adds name to the watched file set.
// The flags are interpreted as described in kevent(2).
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
var isDir bool
// Make ./name and name equivalent
name = filepath.Clean(name)
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return "", errors.New("kevent instance already closed")
}
watchfd, alreadyWatching := w.watches[name]
// We already have a watch, but we can still override flags.
if alreadyWatching {
isDir = w.paths[watchfd].isDir
}
w.mu.Unlock()
if !alreadyWatching {
fi, err := os.Lstat(name)
if err != nil {
return "", err
}
// Don't watch sockets.
if fi.Mode()&os.ModeSocket == os.ModeSocket {
return "", nil
}
// Don't watch named pipes.
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
return "", nil
}
// Follow Symlinks
// Unfortunately, Linux can add bogus symlinks to watch list without
// issue, and Windows can't do symlinks period (AFAIK). To maintain
// consistency, we will act like everything is fine. There will simply
// be no file events for broken symlinks.
// Hence the returns of nil on errors.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
name, err = filepath.EvalSymlinks(name)
if err != nil {
return "", nil
}
w.mu.Lock()
_, alreadyWatching = w.watches[name]
w.mu.Unlock()
if alreadyWatching {
return name, nil
}
fi, err = os.Lstat(name)
if err != nil {
return "", nil
}
}
watchfd, err = unix.Open(name, openMode, 0700)
if watchfd == -1 {
return "", err
}
isDir = fi.IsDir()
}
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
unix.Close(watchfd)
return "", err
}
if !alreadyWatching {
w.mu.Lock()
w.watches[name] = watchfd
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
w.mu.Unlock()
}
if isDir {
// Watch the directory if it has not been watched before,
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
w.mu.Lock()
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
// Store flags so this watch can be updated later
w.dirFlags[name] = flags
w.mu.Unlock()
if watchDir {
if err := w.watchDirectoryFiles(name); err != nil {
return "", err
}
}
}
return name, nil
}
// readEvents reads from kqueue and converts the received kevents into
// Event values that it sends down the Events channel.
func (w *Watcher) readEvents() {
eventBuffer := make([]unix.Kevent_t, 10)
for {
// See if there is a message on the "done" channel
select {
case <-w.done:
err := unix.Close(w.kq)
if err != nil {
w.Errors <- err
}
close(w.Events)
close(w.Errors)
return
default:
}
// Get new events
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
// EINTR is okay, the syscall was interrupted before timeout expired.
if err != nil && err != unix.EINTR {
w.Errors <- err
continue
}
// Flush the events we received to the Events channel
for len(kevents) > 0 {
kevent := &kevents[0]
watchfd := int(kevent.Ident)
mask := uint32(kevent.Fflags)
w.mu.Lock()
path := w.paths[watchfd]
w.mu.Unlock()
event := newEvent(path.name, mask)
if path.isDir && !(event.Op&Remove == Remove) {
// Double check to make sure the directory exists. This can happen when
// we do a rm -fr on a recursively watched folders and we receive a
// modification event first but the folder has been deleted and later
// receive the delete event
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
// mark is as delete event
event.Op |= Remove
}
}
if event.Op&Rename == Rename || event.Op&Remove == Remove {
w.Remove(event.Name)
w.mu.Lock()
delete(w.fileExists, event.Name)
w.mu.Unlock()
}
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
w.sendDirectoryChangeEvents(event.Name)
} else {
// Send the event on the Events channel
w.Events <- event
}
if event.Op&Remove == Remove {
// Look for a file that may have overwritten this.
// For example, mv f1 f2 will delete f2, then create f2.
if path.isDir {
fileDir := filepath.Clean(event.Name)
w.mu.Lock()
_, found := w.watches[fileDir]
w.mu.Unlock()
if found {
// make sure the directory exists before we watch for changes. When we
// do a recursive watch and perform rm -fr, the parent directory might
// have gone missing, ignore the missing directory and let the
// upcoming delete event remove the watch from the parent directory.
if _, err := os.Lstat(fileDir); err == nil {
w.sendDirectoryChangeEvents(fileDir)
}
}
} else {
filePath := filepath.Clean(event.Name)
if fileInfo, err := os.Lstat(filePath); err == nil {
w.sendFileCreatedEventIfNew(filePath, fileInfo)
}
}
}
// Move to next event
kevents = kevents[1:]
}
}
}
// newEvent returns an platform-independent Event based on kqueue Fflags.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
e.Op |= Remove
}
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
e.Op |= Write
}
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
e.Op |= Rename
}
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
e.Op |= Chmod
}
return e
}
func newCreateEvent(name string) Event {
return Event{Name: name, Op: Create}
}
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
return err
}
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
}
return nil
}
// sendDirectoryEvents searches the directory for newly created files
// and sends them over the event channel. This functionality is to have
// the BSD version of fsnotify match Linux inotify which provides a
// create event for files created in a watched directory.
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
w.Errors <- err
}
// Search for new files
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
if err != nil {
return
}
}
}
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
w.mu.Lock()
_, doesExist := w.fileExists[filePath]
w.mu.Unlock()
if !doesExist {
// Send create event
w.Events <- newCreateEvent(filePath)
}
// like watchDirectoryFiles (but without doing another ReadDir)
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
return nil
}
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
if fileInfo.IsDir() {
// mimic Linux providing delete events for subdirectories
// but preserve the flags used if currently watching subdirectory
w.mu.Lock()
flags := w.dirFlags[name]
w.mu.Unlock()
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
return w.addWatch(name, flags)
}
// watch file to mimic Linux inotify
return w.addWatch(name, noteAllEvents)
}
// kqueue creates a new kernel event queue and returns a descriptor.
func kqueue() (kq int, err error) {
kq, err = unix.Kqueue()
if kq == -1 {
return kq, err
}
return kq, nil
}
// register events with the queue
func register(kq int, fds []int, flags int, fflags uint32) error {
changes := make([]unix.Kevent_t, len(fds))
for i, fd := range fds {
// SetKevent converts int to the platform-specific types:
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
changes[i].Fflags = fflags
}
// register the events
success, err := unix.Kevent(kq, changes, nil, nil)
if success == -1 {
return err
}
return nil
}
// read retrieves pending events, or waits until an event occurs.
// A timeout of nil blocks indefinitely, while 0 polls the queue.
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
n, err := unix.Kevent(kq, nil, events, timeout)
if err != nil {
return nil, err
}
return events[0:n], nil
}
// durationToTimespec prepares a timeout value
func durationToTimespec(d time.Duration) unix.Timespec {
return unix.NsecToTimespec(d.Nanoseconds())
}
================================================
FILE: vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly
package fsnotify
import "golang.org/x/sys/unix"
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
================================================
FILE: vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package fsnotify
import "golang.org/x/sys/unix"
// note: this constant is not defined on BSD
const openMode = unix.O_EVTONLY
================================================
FILE: vendor/github.com/fsnotify/fsnotify/windows.go
================================================
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package fsnotify
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"unsafe"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
isClosed bool // Set to true when Close() is first called
mu sync.Mutex // Map access
port syscall.Handle // Handle to completion port
watches watchMap // Map of watches (key: i-number)
input chan *input // Inputs to the reader are sent on this channel
quit chan chan<- error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
w := &Watcher{
port: port,
watches: make(watchMap),
input: make(chan *input, 1),
Events: make(chan Event, 50),
Errors: make(chan error),
quit: make(chan chan<- error, 1),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Send "quit" message to the reader goroutine
ch := make(chan error)
w.quit <- ch
if err := w.wakeupReader(); err != nil {
return err
}
return <-ch
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
if w.isClosed {
return errors.New("watcher already closed")
}
in := &input{
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
in := &input{
op: opRemoveWatch,
path: filepath.Clean(name),
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
const (
// Options for AddWatch
sysFSONESHOT = 0x80000000
sysFSONLYDIR = 0x1000000
// Events
sysFSACCESS = 0x1
sysFSALLEVENTS = 0xfff
sysFSATTRIB = 0x4
sysFSCLOSE = 0x18
sysFSCREATE = 0x100
sysFSDELETE = 0x200
sysFSDELETESELF = 0x400
sysFSMODIFY = 0x2
sysFSMOVE = 0xc0
sysFSMOVEDFROM = 0x40
sysFSMOVEDTO = 0x80
sysFSMOVESELF = 0x800
// Special events
sysFSIGNORED = 0x8000
sysFSQOVERFLOW = 0x4000
)
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
e.Op |= Create
}
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
e.Op |= Remove
}
if mask&sysFSMODIFY == sysFSMODIFY {
e.Op |= Write
}
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
e.Op |= Rename
}
if mask&sysFSATTRIB == sysFSATTRIB {
e.Op |= Chmod
}
return e
}
const (
opAddWatch = iota
opRemoveWatch
)
const (
provisional uint64 = 1 << (32 + iota)
)
type input struct {
op int
path string
flags uint32
reply chan error
}
type inode struct {
handle syscall.Handle
volume uint32
index uint64
}
type watch struct {
ov syscall.Overlapped
ino *inode // i-number
path string // Directory path
mask uint64 // Directory itself is being watched with these notify flags
names map[string]uint64 // Map of names being watched and their notify flags
rename string // Remembers the old name while renaming a file
buf [4096]byte
}
type indexMap map[uint64]*watch
type watchMap map[uint32]indexMap
func (w *Watcher) wakeupReader() error {
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
if e != nil {
return os.NewSyscallError("PostQueuedCompletionStatus", e)
}
return nil
}
func getDir(pathname string) (dir string, err error) {
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
if e != nil {
return "", os.NewSyscallError("GetFileAttributes", e)
}
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
dir = pathname
} else {
dir, _ = filepath.Split(pathname)
dir = filepath.Clean(dir)
}
return
}
func getIno(path string) (ino *inode, err error) {
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
syscall.FILE_LIST_DIRECTORY,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
if e != nil {
return nil, os.NewSyscallError("CreateFile", e)
}
var fi syscall.ByHandleFileInformation
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
syscall.CloseHandle(h)
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
}
ino = &inode{
handle: h,
volume: fi.VolumeSerialNumber,
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
}
return ino, nil
}
// Must run within the I/O thread.
func (m watchMap) get(ino *inode) *watch {
if i := m[ino.volume]; i != nil {
return i[ino.index]
}
return nil
}
// Must run within the I/O thread.
func (m watchMap) set(ino *inode, watch *watch) {
i := m[ino.volume]
if i == nil {
i = make(indexMap)
m[ino.volume] = i
}
i[ino.index] = watch
}
// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
if flags&sysFSONLYDIR != 0 && pathname != dir {
return nil
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watchEntry := w.watches.get(ino)
w.mu.Unlock()
if watchEntry == nil {
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
syscall.CloseHandle(ino.handle)
return os.NewSyscallError("CreateIoCompletionPort", e)
}
watchEntry = &watch{
ino: ino,
path: dir,
names: make(map[string]uint64),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
w.mu.Unlock()
flags |= provisional
} else {
syscall.CloseHandle(ino.handle)
}
if pathname == dir {
watchEntry.mask |= flags
} else {
watchEntry.names[filepath.Base(pathname)] |= flags
}
if err = w.startRead(watchEntry); err != nil {
return err
}
if pathname == dir {
watchEntry.mask &= ^provisional
} else {
watchEntry.names[filepath.Base(pathname)] &= ^provisional
}
return nil
}
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watch := w.watches.get(ino)
w.mu.Unlock()
if watch == nil {
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
}
if pathname == dir {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
watch.mask = 0
} else {
name := filepath.Base(pathname)
w.sendEvent(watch.path+"\\"+name, watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
return w.startRead(watch)
}
// Must run within the I/O thread.
func (w *Watcher) deleteWatch(watch *watch) {
for name, mask := range watch.names {
if mask&provisional == 0 {
w.sendEvent(watch.path+"\\"+name, mask&sysFSIGNORED)
}
delete(watch.names, name)
}
if watch.mask != 0 {
if watch.mask&provisional == 0 {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
}
watch.mask = 0
}
}
// Must run within the I/O thread.
func (w *Watcher) startRead(watch *watch) error {
if e := syscall.CancelIo(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CancelIo", e)
w.deleteWatch(watch)
}
mask := toWindowsFlags(watch.mask)
for _, m := range watch.names {
mask |= toWindowsFlags(m)
}
if mask == 0 {
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CloseHandle", e)
}
w.mu.Lock()
delete(w.watches[watch.ino.volume], watch.ino.index)
w.mu.Unlock()
return nil
}
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
if e != nil {
err := os.NewSyscallError("ReadDirectoryChanges", e)
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
// Watched directory was probably removed
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
err = nil
}
w.deleteWatch(watch)
w.startRead(watch)
return err
}
return nil
}
// readEvents reads from the I/O completion port, converts the
// received events into Event objects and sends them via the Events channel.
// Entry point to the I/O thread.
func (w *Watcher) readEvents() {
var (
n, key uint32
ov *syscall.Overlapped
)
runtime.LockOSThread()
for {
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
watch := (*watch)(unsafe.Pointer(ov))
if watch == nil {
select {
case ch := <-w.quit:
w.mu.Lock()
var indexes []indexMap
for _, index := range w.watches {
indexes = append(indexes, index)
}
w.mu.Unlock()
for _, index := range indexes {
for _, watch := range index {
w.deleteWatch(watch)
w.startRead(watch)
}
}
var err error
if e := syscall.CloseHandle(w.port); e != nil {
err = os.NewSyscallError("CloseHandle", e)
}
close(w.Events)
close(w.Errors)
ch <- err
return
case in := <-w.input:
switch in.op {
case opAddWatch:
in.reply <- w.addWatch(in.path, uint64(in.flags))
case opRemoveWatch:
in.reply <- w.remWatch(in.path)
}
default:
}
continue
}
switch e {
case syscall.ERROR_MORE_DATA:
if watch == nil {
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
} else {
// The i/o succeeded but the buffer is full.
// In theory we should be building up a full packet.
// In practice we can get away with just carrying on.
n = uint32(unsafe.Sizeof(watch.buf))
}
case syscall.ERROR_ACCESS_DENIED:
// Watched directory was probably removed
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
w.deleteWatch(watch)
w.startRead(watch)
continue
case syscall.ERROR_OPERATION_ABORTED:
// CancelIo was called on this handle
continue
default:
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
continue
case nil:
}
var offset uint32
for {
if n == 0 {
w.Events <- newEvent("", sysFSQOVERFLOW)
w.Errors <- errors.New("short read in readEvents()")
break
}
// Point "raw" to the event in the buffer
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
fullname := watch.path + "\\" + name
var mask uint64
switch raw.Action {
case syscall.FILE_ACTION_REMOVED:
mask = sysFSDELETESELF
case syscall.FILE_ACTION_MODIFIED:
mask = sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
watch.rename = name
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
if watch.names[watch.rename] != 0 {
watch.names[name] |= watch.names[watch.rename]
delete(watch.names, watch.rename)
mask = sysFSMOVESELF
}
}
sendNameEvent := func() {
if w.sendEvent(fullname, watch.names[name]&mask) {
if watch.names[name]&sysFSONESHOT != 0 {
delete(watch.names, name)
}
}
}
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
sendNameEvent()
}
if raw.Action == syscall.FILE_ACTION_REMOVED {
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
fullname = watch.path + "\\" + watch.rename
sendNameEvent()
}
// Move to the next event in the buffer
if raw.NextEntryOffset == 0 {
break
}
offset += raw.NextEntryOffset
// Error!
if offset >= n {
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
break
}
}
if err := w.startRead(watch); err != nil {
w.Errors <- err
}
}
}
func (w *Watcher) sendEvent(name string, mask uint64) bool {
if mask == 0 {
return false
}
event := newEvent(name, uint32(mask))
select {
case ch := <-w.quit:
w.quit <- ch
case w.Events <- event:
}
return true
}
func toWindowsFlags(mask uint64) uint32 {
var m uint32
if mask&sysFSACCESS != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
}
if mask&sysFSMODIFY != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
}
if mask&sysFSATTRIB != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
}
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
}
return m
}
func toFSnotifyFlags(action uint32) uint64 {
switch action {
case syscall.FILE_ACTION_ADDED:
return sysFSCREATE
case syscall.FILE_ACTION_REMOVED:
return sysFSDELETE
case syscall.FILE_ACTION_MODIFIED:
return sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
return sysFSMOVEDFROM
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
return sysFSMOVEDTO
}
return 0
}
================================================
FILE: vendor/github.com/go-sql-driver/mysql/.gitignore
================================================
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db
.idea
================================================
FILE: vendor/github.com/go-sql-driver/mysql/.travis.yml
================================================
sudo: false
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- master
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
before_script:
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
- sudo service mysql restart
- .travis/wait_mysql.sh
- mysql -e 'create database gotest;'
matrix:
include:
- env: DB=MYSQL57
sudo: required
dist: trusty
go: 1.10.x
services:
- docker
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- docker pull mysql:5.7
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh
before_script:
- export MYSQL_TEST_USER=gotest
- export MYSQL_TEST_PASS=secret
- export MYSQL_TEST_ADDR=127.0.0.1:3307
- export MYSQL_TEST_CONCURRENT=1
- env: DB=MARIA55
sudo: required
dist: trusty
go: 1.10.x
services:
- docker
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- docker pull mariadb:5.5
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh
before_script:
- export MYSQL_TEST_USER=gotest
- export MYSQL_TEST_PASS=secret
- export MYSQL_TEST_ADDR=127.0.0.1:3307
- export MYSQL_TEST_CONCURRENT=1
- env: DB=MARIA10_1
sudo: required
dist: trusty
go: 1.10.x
services:
- docker
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- docker pull mariadb:10.1
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
- sleep 30
- cp .travis/docker.cnf ~/.my.cnf
- mysql --print-defaults
- .travis/wait_mysql.sh
before_script:
- export MYSQL_TEST_USER=gotest
- export MYSQL_TEST_PASS=secret
- export MYSQL_TEST_ADDR=127.0.0.1:3307
- export MYSQL_TEST_CONCURRENT=1
script:
- go test -v -covermode=count -coverprofile=coverage.out
- go vet ./...
- .travis/gofmt.sh
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
================================================
FILE: vendor/github.com/go-sql-driver/mysql/AUTHORS
================================================
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
# If you are submitting a patch, please add your name or the name of the
# organization which holds the copyright to this list in alphabetical order.
# Names should be added to this file as
# Name
# The email address is not required for organizations.
# Please keep the list sorted.
# Individual Persons
Aaron Hopkins
Achille Roussel
Alexey Palazhchenko
Andrew Reid
Arne Hormann
Asta Xie
Bulat Gaifullin
Carlos Nieto
Chris Moos
Daniel Montoya