├── .ruby-version ├── fixtures ├── open_flow10 │ ├── hello.raw │ ├── echo_reply.raw │ ├── vendor.raw │ ├── barrier_reply.raw │ ├── barrier_request.raw │ ├── echo_request.raw │ ├── bad_action_eperm.raw │ ├── features_request.raw │ ├── bad_action_bad_length.raw │ ├── bad_action_bad_queue.raw │ ├── bad_action_bad_type.raw │ ├── bad_action_bad_vendor.raw │ ├── bad_action_too_many.raw │ ├── bad_request.raw │ ├── error.raw │ ├── flow_mod_failed_eperm.raw │ ├── get_config_request.raw │ ├── table_stats_request.raw │ ├── bad_action_bad_argument.raw │ ├── bad_action_bad_out_port.raw │ ├── bad_action_bad_vendor_type.raw │ ├── description_stats_request.raw │ ├── flow_mod_failed_overlap.raw │ ├── queue_get_config_request.raw │ ├── flow_mod_failed_all_tables_full.raw │ ├── flow_mod_failed_bad_command.raw │ ├── flow_mod_failed_unsupported.raw │ ├── hello_failed.raw │ ├── port_mod.raw │ ├── flow_mod_failed_bad_emerg_timeout.raw │ ├── aggregate_stats_reply.raw │ ├── nxast_learn.raw │ ├── packet_out.raw │ ├── port_status.raw │ ├── set_config.raw │ ├── features_reply.raw │ ├── flow_mod_add.raw │ ├── flow_removed.raw │ ├── nx_packet_in.raw │ ├── flow_mod_delete.raw │ ├── flow_mod_modify.raw │ ├── flow_stats_reply.raw │ ├── get_config_reply.raw │ ├── nx_flow_mod_add.raw │ ├── packet_in_cbench.raw │ ├── port_stats_reply.raw │ ├── flow_stats_request.raw │ ├── nx_flow_mod_delete.raw │ ├── nx_flow_mod_modify.raw │ ├── packet_in_arp_reply.raw │ ├── port_stats_request.raw │ ├── queue_stats_request.raw │ ├── table_stats_reply.raw │ ├── vendor_stats_request.raw │ ├── aggregate_stats_request.raw │ ├── flow_mod_delete_strict.raw │ ├── flow_mod_modify_strict.raw │ ├── queue_get_config_reply.raw │ ├── nx_flow_mod_delete_strict.raw │ ├── nx_flow_mod_modify_strict.raw │ ├── packet_in.rb │ ├── packet_in_arp_request.rb │ └── description_stats_reply.raw ├── open_flow13 │ ├── action_group.raw │ ├── action_pop_mpls.raw │ ├── action_pop_pbb.raw │ ├── action_pop_vlan.raw │ ├── action_push_pbb.raw │ ├── oxm_no_fields.raw │ ├── action_copy_ttl_in.raw │ ├── action_copy_ttl_out.raw │ ├── action_dec_mpls_ttl.raw │ ├── action_dec_nw_ttl.raw │ ├── action_push_mpls.raw │ ├── action_push_vlan.raw │ ├── action_set_queue.raw │ ├── echo_reply_no_body.raw │ ├── echo_request_no_body.raw │ ├── features_request.raw │ ├── instruction_meter.raw │ ├── bad_request.raw │ ├── hello_no_version_bitmap.raw │ ├── instruction_clear_actions.raw │ ├── instruction_goto_table.raw │ ├── hello_version_bitmap.raw │ ├── table_stats_request.raw │ ├── echo_reply_body.raw │ ├── hello_failed.raw │ ├── echo_request_body.raw │ ├── features_reply.raw │ ├── instruction_write_metadata.raw │ ├── packet_in.raw │ ├── packet_out.raw │ ├── apply_actions.raw │ ├── oxm_tcp_field.raw │ ├── oxm_udp_field.raw │ ├── send_out_port.raw │ ├── action_set_field.raw │ ├── oxm_arp_op_field.raw │ ├── oxm_ip_ecn_field.raw │ ├── action_set_mpls_ttl.raw │ ├── action_set_nw_ttl.raw │ ├── oxm_arp_sha_field.raw │ ├── oxm_arp_spa_field.raw │ ├── oxm_arp_tha_field.raw │ ├── oxm_arp_tpa_field.raw │ ├── oxm_in_port_field.raw │ ├── oxm_invalid_field.raw │ ├── oxm_ip_dscp_field.raw │ ├── oxm_metadata_field.raw │ ├── oxm_tunnel_id_field.raw │ ├── oxm_vlan_pcp_field.raw │ ├── oxm_vlan_vid_field.raw │ ├── table_stats_reply.raw │ ├── oxm_ether_type_field.raw │ ├── oxm_icmpv4_code_field.raw │ ├── oxm_icmpv4_type_field.raw │ ├── oxm_in_phy_port_field.raw │ ├── oxm_ipv4_source_field.raw │ ├── oxm_ipv6_source_field.raw │ ├── oxm_sctp_source_field.raw │ ├── oxm_tcp_source_field.raw │ ├── oxm_udp_source_field.raw │ ├── flow_add_apply_no_match.raw │ ├── oxm_ether_source_field.raw │ ├── oxm_masked_arp_sha_field.raw │ ├── oxm_masked_arp_spa_field.raw │ ├── oxm_masked_arp_tha_field.raw │ ├── oxm_masked_arp_tpa_field.raw │ ├── instruction_write_actions.raw │ ├── oxm_ipv4_destination_field.raw │ ├── oxm_ipv6_destination_field.raw │ ├── oxm_masked_tunnel_id_field.raw │ ├── oxm_metadata_masked_field.raw │ ├── oxm_sctp_destination_field.raw │ ├── oxm_tcp_destination_field.raw │ ├── oxm_udp_destination_field.raw │ ├── flow_mod_add_apply_no_match.raw │ ├── oxm_ether_destination_field.raw │ ├── oxm_masked_ether_source_field.raw │ ├── oxm_masked_ipv4_source_field.raw │ ├── oxm_masked_ipv6_source_field.raw │ ├── flow_mod_no_match_or_instructions.raw │ ├── oxm_masked_ether_destination_field.raw │ ├── oxm_masked_ipv4_destination_field.raw │ ├── oxm_masked_ipv6_destination_field.raw │ └── oxm_experimenter_stratos_basic_dot11.raw ├── dhcp.pcap ├── icmpv6.pcap ├── lldp.minimal.pcap ├── lldp.detailed.pcap ├── udp_no_payload.raw ├── udp_with_payload.raw ├── ethernet_header │ ├── ethernet_header.rb │ └── vlan_ethernet_header.rb ├── ipv4_header │ └── ipv4_header.rb ├── arp │ ├── arp_reply.rb │ └── arp_request.rb └── icmp │ ├── icmp_reply.rb │ └── icmp_request.rb ├── .rspec ├── .hound.yml ├── .projectile ├── features ├── support │ ├── env.rb │ └── hooks.rb ├── step_definitions │ ├── .rubocop.yml │ ├── Guardfile │ ├── README.md │ ├── Rakefile │ ├── ruby_steps.rb │ ├── Gemfile │ ├── .travis.yml │ ├── virtual_link_steps.rb │ ├── dump_flows_steps.rb │ └── .gitignore ├── open_flow13 │ ├── decrement_ip_ttl.feature │ ├── set_ip_ttl.feature │ ├── copy_ttl_inwards.feature │ ├── copy_ttl_outwards.feature │ ├── set_metadata.feature │ ├── set_arp_operation.feature │ ├── set_arp_sender_protocol_address.feature │ ├── set_source_mac_address.feature │ ├── stats_request.feature │ ├── set_destination_mac_address.feature │ ├── set_arp_sender_hardware_address.feature │ ├── nicira_conjunction.feature │ ├── nicira_stack_pop.feature │ ├── nicira_stack_push.feature │ ├── features_request.feature │ ├── hello.feature │ ├── nicira_send_out_port.feature │ ├── meter.feature │ ├── goto_table.feature │ └── bad_request.feature ├── open_flow │ ├── nicira_resubmit.feature │ ├── header.feature │ └── nicira_resubmit_table.feature ├── open_flow10 │ ├── strip_vlan_header.feature │ ├── set_vlan_vid.feature │ ├── set_tos.feature │ ├── set_vlan_priority.feature │ ├── vendor_action.feature │ ├── set_transport_source_port.feature │ ├── set_transport_destination_port.feature │ ├── enqueue.feature │ ├── set_source_ip_address.feature │ ├── set_ip_destination_address.feature │ ├── set_source_mac_address.feature │ ├── set_destination_mac_address.feature │ ├── aggregate_stats_reply.feature │ ├── flow_stats_reply.feature │ ├── bad_request.feature │ ├── hello_failed.feature │ ├── features_request.feature │ ├── description_stats_reply.feature │ ├── hello.feature │ ├── table_stats_request.feature │ ├── port_stats_request.feature │ ├── barrier_request.feature │ ├── port_status.feature │ ├── description_stats_request.feature │ ├── barrier_reply.feature │ └── queue_stats_request.feature ├── open_flow_version.feature ├── parser.feature └── .nav ├── lib ├── pio │ ├── version.rb │ ├── open_flow10 │ │ ├── features.rb │ │ ├── hello.rb │ │ ├── features │ │ │ └── request.rb │ │ ├── barrier │ │ │ ├── reply.rb │ │ │ └── request.rb │ │ ├── strip_vlan_header.rb │ │ ├── echo │ │ │ ├── reply.rb │ │ │ └── request.rb │ │ ├── error.rb │ │ ├── set_source_ip_address.rb │ │ ├── set_destination_ip_address.rb │ │ ├── match10.rb │ │ ├── table_stats │ │ │ └── request.rb │ │ ├── set_source_mac_address.rb │ │ ├── queue_stats │ │ │ └── request.rb │ │ ├── port_status │ │ │ └── reason.rb │ │ ├── set_destination_mac_address.rb │ │ ├── description_stats │ │ │ ├── request.rb │ │ │ └── reply.rb │ │ ├── port16.rb │ │ ├── exact_match.rb │ │ ├── flow_removed │ │ │ └── reason.rb │ │ ├── stats_type.rb │ │ ├── packet_out.rb │ │ ├── packet_in │ │ │ └── reason.rb │ │ ├── aggregate_stats │ │ │ ├── reply.rb │ │ │ └── request.rb │ │ ├── port_stats │ │ │ └── request.rb │ │ ├── flow_mod │ │ │ └── command.rb │ │ ├── error │ │ │ ├── error_type10.rb │ │ │ ├── hello_failed.rb │ │ │ ├── bad_request.rb │ │ │ └── bad_request │ │ │ │ └── bad_request_code.rb │ │ ├── set_vlan_vid.rb │ │ ├── flow_removed.rb │ │ ├── flow_stats │ │ │ └── request.rb │ │ ├── set_vlan_priority.rb │ │ ├── flow_mod.rb │ │ ├── set_tos.rb │ │ ├── stats_reply.rb │ │ ├── vendor_action.rb │ │ └── stats_request.rb │ ├── parse_error.rb │ ├── monkey_patch │ │ ├── bindata_record.rb │ │ ├── integer │ │ │ ├── base_conversions.rb │ │ │ └── ranges.rb │ │ ├── bindata_string.rb │ │ ├── integer.rb │ │ ├── uint.rb │ │ └── uint │ │ │ └── base_conversions.rb │ ├── dhcp │ │ ├── offer.rb │ │ ├── request.rb │ │ ├── discover.rb │ │ ├── ack.rb │ │ ├── boot_reply.rb │ │ ├── boot_request.rb │ │ ├── client_id.rb │ │ ├── parameter_list.rb │ │ └── frame.rb │ ├── open_flow │ │ ├── flow_match.rb │ │ ├── instruction.rb │ │ ├── version.rb │ │ ├── buffer_id.rb │ │ ├── hello_failed_code.rb │ │ ├── nicira_action.rb │ │ ├── transaction_id.rb │ │ ├── nicira_resubmit.rb │ │ ├── header.rb │ │ ├── datapath_id.rb │ │ ├── parser.rb │ │ ├── nicira_resubmit_table.rb │ │ └── error_message.rb │ ├── lldp │ │ ├── system_name_value.rb │ │ ├── end_of_lldpdu_value.rb │ │ ├── port_description_value.rb │ │ ├── system_description_value.rb │ │ ├── system_capabilities_value.rb │ │ ├── organizationally_specific_value.rb │ │ ├── ttl_tlv.rb │ │ ├── management_address_value.rb │ │ ├── port_id_tlv.rb │ │ └── options.rb │ ├── payload.rb │ ├── open_flow13 │ │ ├── decrement_ip_ttl.rb │ │ ├── features │ │ │ ├── request.rb │ │ │ └── reply.rb │ │ ├── set_ip_ttl.rb │ │ ├── echo │ │ │ ├── reply.rb │ │ │ └── request.rb │ │ ├── copy_ttl_inwards.rb │ │ ├── copy_ttl_outwards.rb │ │ ├── error.rb │ │ ├── port32.rb │ │ ├── set_metadata.rb │ │ ├── error │ │ │ ├── hello_failed.rb │ │ │ └── error_type13.rb │ │ ├── set_arp_operation.rb │ │ ├── set_source_mac_address.rb │ │ ├── send_out_port.rb │ │ ├── set_destination_mac_address.rb │ │ ├── set_arp_sender_protocol_address.rb │ │ ├── set_arp_sender_hardware_address.rb │ │ ├── nicira_conjunction.rb │ │ ├── stats_request.rb │ │ ├── meter.rb │ │ └── goto_table.rb │ ├── instance_inspector.rb │ ├── icmp │ │ ├── reply.rb │ │ ├── request.rb │ │ └── message.rb │ ├── udp.rb │ ├── ethernet_frame.rb │ ├── arp.rb │ ├── icmp.rb │ ├── type │ │ ├── ipv6_address.rb │ │ ├── ether_type.rb │ │ ├── mac_address.rb │ │ └── ip_address.rb │ ├── arp │ │ ├── reply.rb │ │ ├── message.rb │ │ └── request.rb │ ├── message_type_selector.rb │ ├── class_inspector.rb │ └── parser.rb └── pio.rb ├── spec ├── spec_helper.rb └── pio │ ├── open_flow10 │ ├── hello_spec.rb │ ├── packet_out_spec.rb │ ├── echo │ │ ├── reply_spec.rb │ │ └── request_spec.rb │ ├── features │ │ ├── request_spec.rb │ │ └── reply_spec.rb │ ├── packet_in_spec.rb │ ├── strip_vlan_header_spec.rb │ ├── set_source_ip_address_spec.rb │ ├── set_tos_spec.rb │ ├── set_destination_ip_address_spec.rb │ ├── error │ │ └── hello_failed_spec.rb │ ├── set_source_mac_address_spec.rb │ ├── phy_port16_spec.rb │ ├── set_destination_mac_address_spec.rb │ ├── set_vlan_vid_spec.rb │ └── set_vlan_priority_spec.rb │ ├── open_flow13 │ ├── packet_out_spec.rb │ ├── echo_reply_spec.rb │ ├── echo_request_spec.rb │ ├── error │ │ ├── bad_request_spec.rb │ │ └── hello_failed_spec.rb │ ├── features │ │ ├── reply_spec.rb │ │ └── request_spec.rb │ ├── meter_spec.rb │ ├── goto_table_spec.rb │ ├── packet_in_spec.rb │ ├── write_metadata_spec.rb │ └── nicira_send_out_port_spec.rb │ ├── monkey_patch │ └── integer_spec.rb │ ├── open_flow │ ├── nicira_resubmit_spec.rb │ └── nicira_resubmit_table_spec.rb │ └── open_flow_spec.rb ├── tasks ├── README.md ├── rspec.rake ├── rubocop.rake ├── relish.rake ├── reek.rake ├── gem.rake ├── cucumber.rake ├── flay.rake ├── .gitignore └── flog.rake ├── cucumber.yml ├── .travis.yml ├── .gitignore ├── .rubocop.yml ├── .reek ├── Gemfile ├── pio.gemspec └── Rakefile /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.4 2 | -------------------------------------------------------------------------------- /fixtures/open_flow10/hello.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/echo_reply.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/vendor.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/barrier_reply.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/barrier_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/echo_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_group.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_pop_mpls.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_pop_pbb.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_pop_vlan.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_push_pbb.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_no_fields.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_eperm.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/features_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_copy_ttl_in.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_copy_ttl_out.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_dec_mpls_ttl.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_dec_nw_ttl.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_push_mpls.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_push_vlan.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/action_set_queue.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/echo_reply_no_body.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/echo_request_no_body.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/features_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/instruction_meter.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --order random 3 | --color -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_length.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_queue.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_type.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_vendor.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_too_many.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/error.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_eperm.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/get_config_request.raw: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /fixtures/open_flow10/table_stats_request.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow13/bad_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/hello_no_version_bitmap.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/instruction_clear_actions.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/instruction_goto_table.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_argument.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_out_port.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/bad_action_bad_vendor_type.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/description_stats_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_overlap.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/queue_get_config_request.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow13/hello_version_bitmap.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow13/table_stats_request.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_all_tables_full.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_bad_command.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_unsupported.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow10/hello_failed.raw: -------------------------------------------------------------------------------- 1 | error description -------------------------------------------------------------------------------- /fixtures/open_flow10/port_mod.raw: -------------------------------------------------------------------------------- 1 |  "3DUf -------------------------------------------------------------------------------- /fixtures/open_flow13/echo_reply_body.raw: -------------------------------------------------------------------------------- 1 | hogehogehogehogehoge -------------------------------------------------------------------------------- /fixtures/open_flow13/hello_failed.raw: -------------------------------------------------------------------------------- 1 | error description -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | enabled: true 3 | config_file: .rubocop.yml 4 | -------------------------------------------------------------------------------- /.projectile: -------------------------------------------------------------------------------- 1 | -/.yardoc 2 | -/coverage 3 | -/doc 4 | -/pkg 5 | -/tmp 6 | -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_failed_bad_emerg_timeout.raw: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /fixtures/open_flow13/echo_request_body.raw: -------------------------------------------------------------------------------- 1 | hogehogehogehogehoge -------------------------------------------------------------------------------- /fixtures/open_flow13/features_reply.raw: -------------------------------------------------------------------------------- 1 |  o -------------------------------------------------------------------------------- /fixtures/open_flow13/instruction_write_metadata.raw: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fixtures/dhcp.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/dhcp.pcap -------------------------------------------------------------------------------- /fixtures/open_flow10/aggregate_stats_reply.raw: -------------------------------------------------------------------------------- 1 | $ -------------------------------------------------------------------------------- /fixtures/icmpv6.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/icmpv6.pcap -------------------------------------------------------------------------------- /fixtures/lldp.minimal.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/lldp.minimal.pcap -------------------------------------------------------------------------------- /fixtures/lldp.detailed.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/lldp.detailed.pcap -------------------------------------------------------------------------------- /fixtures/udp_no_payload.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/udp_no_payload.raw -------------------------------------------------------------------------------- /fixtures/udp_with_payload.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/udp_with_payload.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/packet_in.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/packet_in.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nxast_learn.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nxast_learn.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/packet_out.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/packet_out.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/port_status.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/port_status.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/set_config.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/set_config.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/packet_out.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/packet_out.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/features_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/features_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_add.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_mod_add.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_removed.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_removed.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_packet_in.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_packet_in.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/apply_actions.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/apply_actions.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_tcp_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_tcp_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_udp_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_udp_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/send_out_port.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/send_out_port.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_delete.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_mod_delete.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_modify.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_mod_modify.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_stats_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_stats_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/get_config_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/get_config_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_flow_mod_add.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_flow_mod_add.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/packet_in_cbench.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/packet_in_cbench.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/port_stats_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/port_stats_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/action_set_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/action_set_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_arp_op_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_arp_op_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ip_ecn_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ip_ecn_field.raw -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aruba/cucumber' 4 | require 'pio' 5 | require 'pio/pcap' 6 | -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_stats_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_stats_request.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_flow_mod_delete.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_flow_mod_delete.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_flow_mod_modify.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_flow_mod_modify.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/packet_in_arp_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/packet_in_arp_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/port_stats_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/port_stats_request.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/queue_stats_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/queue_stats_request.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/table_stats_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/table_stats_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/action_set_mpls_ttl.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/action_set_mpls_ttl.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/action_set_nw_ttl.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/action_set_nw_ttl.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_arp_sha_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_arp_sha_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_arp_spa_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_arp_spa_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_arp_tha_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_arp_tha_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_arp_tpa_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_arp_tpa_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_in_port_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_in_port_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_invalid_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_invalid_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ip_dscp_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ip_dscp_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_metadata_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_metadata_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_tunnel_id_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_tunnel_id_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_vlan_pcp_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_vlan_pcp_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_vlan_vid_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_vlan_vid_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/table_stats_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/table_stats_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/vendor_stats_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/vendor_stats_request.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ether_type_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ether_type_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_icmpv4_code_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_icmpv4_code_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_icmpv4_type_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_icmpv4_type_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_in_phy_port_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_in_phy_port_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ipv4_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ipv4_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ipv6_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ipv6_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_sctp_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_sctp_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_tcp_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_tcp_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_udp_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_udp_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/aggregate_stats_request.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/aggregate_stats_request.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_delete_strict.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_mod_delete_strict.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/flow_mod_modify_strict.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/flow_mod_modify_strict.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/queue_get_config_reply.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/queue_get_config_reply.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/flow_add_apply_no_match.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/flow_add_apply_no_match.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ether_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ether_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_arp_sha_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_arp_sha_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_arp_spa_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_arp_spa_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_arp_tha_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_arp_tha_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_arp_tpa_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_arp_tpa_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_flow_mod_delete_strict.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_flow_mod_delete_strict.raw -------------------------------------------------------------------------------- /fixtures/open_flow10/nx_flow_mod_modify_strict.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow10/nx_flow_mod_modify_strict.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/instruction_write_actions.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/instruction_write_actions.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ipv4_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ipv4_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ipv6_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ipv6_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_tunnel_id_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_tunnel_id_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_metadata_masked_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_metadata_masked_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_sctp_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_sctp_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_tcp_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_tcp_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_udp_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_udp_destination_field.raw -------------------------------------------------------------------------------- /lib/pio/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Base module. 4 | module Pio 5 | # gem version. 6 | VERSION = '0.30.2' 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspec/given' 4 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 5 | -------------------------------------------------------------------------------- /fixtures/open_flow13/flow_mod_add_apply_no_match.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/flow_mod_add_apply_no_match.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_ether_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_ether_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ether_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ether_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ipv4_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ipv4_source_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ipv6_source_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ipv6_source_field.raw -------------------------------------------------------------------------------- /features/step_definitions/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/StringLiterals: 2 | EnforcedStyle: single_quotes 3 | 4 | Style/DotPosition: 5 | EnforcedStyle: trailing 6 | -------------------------------------------------------------------------------- /fixtures/open_flow13/flow_mod_no_match_or_instructions.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/flow_mod_no_match_or_instructions.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ether_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ether_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ipv4_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ipv4_destination_field.raw -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_masked_ipv6_destination_field.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_masked_ipv6_destination_field.raw -------------------------------------------------------------------------------- /lib/pio/open_flow10/features.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/features/reply' 4 | require 'pio/open_flow10/features/request' 5 | -------------------------------------------------------------------------------- /fixtures/open_flow13/oxm_experimenter_stratos_basic_dot11.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trema/pio/HEAD/fixtures/open_flow13/oxm_experimenter_stratos_basic_dot11.raw -------------------------------------------------------------------------------- /tasks/README.md: -------------------------------------------------------------------------------- 1 | Rake Tasks 2 | ========== 3 | 4 | A collection of rake tasks for use with [Trema][trema] and Trema apps. 5 | 6 | [trema]: https://github.com/trema/trema_ruby 7 | -------------------------------------------------------------------------------- /lib/pio/parse_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | # Raised when the packet data is in wrong format. 5 | class ParseError < StandardError; end 6 | end 7 | -------------------------------------------------------------------------------- /features/step_definitions/Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | guard :rubocop do 4 | watch(/.+\.rb$/) 5 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 6 | end 7 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/bindata_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module BinData 4 | # Add BinData::Record#to_binary 5 | class Record 6 | alias to_binary to_binary_s 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/hello_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/hello' 4 | 5 | describe Pio::OpenFlow10::Hello do 6 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::Hello) 7 | end 8 | -------------------------------------------------------------------------------- /features/support/hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Before('@open_flow10') do 4 | Pio::OpenFlow.version = :OpenFlow10 5 | end 6 | 7 | Before('@open_flow13') do 8 | Pio::OpenFlow.version = :OpenFlow13 9 | end 10 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new 6 | rescue LoadError 7 | task :spec do 8 | $stderr.puts 'RSpec is disabled' 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /tasks/rubocop.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rubocop/rake_task' 5 | RuboCop::RakeTask.new 6 | rescue LoadError 7 | task :rubocop do 8 | $stderr.puts 'RuboCop is disabled' 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | std_opts = "--format progress features --tags ~@wip --no-source -r features" 3 | wip_opts = "--color -r features --tags @wip" 4 | %> 5 | default: <%= std_opts %> 6 | wip: --wip <%= wip_opts %> features 7 | none: --format pretty 8 | -------------------------------------------------------------------------------- /fixtures/ethernet_header/ethernet_header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac 5 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac 6 | 0x08, 0x00, # ether_type 7 | ].pack('C*') 8 | -------------------------------------------------------------------------------- /lib/pio.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/parse_error' 4 | 5 | require 'pio/arp' 6 | require 'pio/dhcp' 7 | require 'pio/icmp' 8 | require 'pio/lldp' 9 | require 'pio/mac' 10 | require 'pio/udp' 11 | require 'pio/open_flow' 12 | -------------------------------------------------------------------------------- /lib/pio/dhcp/offer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/boot_reply' 4 | 5 | module Pio 6 | class Dhcp 7 | # Dhcp Offer packet generator 8 | class Offer < BootReply 9 | TYPE = 2 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pio/dhcp/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/boot_request' 4 | 5 | module Pio 6 | class Dhcp 7 | # Dhcp Request packet generator 8 | class Request < BootRequest 9 | TYPE = 3 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pio/dhcp/discover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/boot_request' 4 | 5 | module Pio 6 | class Dhcp 7 | # Dhcp Discover packet generator 8 | class Discover < BootRequest 9 | TYPE = 1 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /features/step_definitions/README.md: -------------------------------------------------------------------------------- 1 | Cucumber Step Definitions 2 | ========================= 3 | 4 | A collection of step definitions for use with [Trema][trema] and Trema apps. 5 | These steps are highly specific to Trema. 6 | 7 | [trema]: https://github.com/trema/trema_ruby 8 | -------------------------------------------------------------------------------- /lib/pio/dhcp/ack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/boot_reply' 4 | 5 | module Pio 6 | # Dhcp parser and generator. 7 | class Dhcp 8 | # Dhcp Ack packet generator 9 | class Ack < BootReply 10 | TYPE = 5 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /tasks/relish.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | desc 'Push features to relish' 4 | task 'relish:push' do 5 | if Kernel.const_defined?(:RELISH_PROJECT) 6 | sh "relish push #{RELISH_PROJECT}" 7 | else 8 | $stderr.puts 'Relish is disabled' 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /features/step_definitions/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | task default: :rubocop 4 | 5 | begin 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new 8 | rescue LoadError 9 | task :rubocop do 10 | $stderr.puts 'RuboCop is disabled' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/integer/base_conversions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MonkeyPatch 4 | module Integer 5 | # to_hex etc. 6 | module BaseConversions 7 | def to_hex 8 | format '0x%02x', self 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pio/open_flow/flow_match.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/descendants_tracker' 4 | 5 | module Pio 6 | module OpenFlow 7 | # Flow match 8 | class FlowMatch 9 | extend ActiveSupport::DescendantsTracker 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/hello.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Hello message 8 | class Hello < OpenFlow::Message 9 | open_flow_header version: 1, type: 0 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/packet_out_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/packet_out' 4 | 5 | describe Pio::OpenFlow10::PacketOut do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::PacketOut) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/packet_out_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/packet_out' 4 | 5 | describe Pio::OpenFlow13::PacketOut do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow13::PacketOut) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/echo/reply_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/echo/reply' 4 | 5 | describe Pio::OpenFlow10::Echo::Reply do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::Echo::Reply) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/echo_reply_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/echo/reply' 4 | 5 | describe Pio::OpenFlow13::Echo::Reply do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow13::Echo::Reply) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /features/step_definitions/ruby_steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | When(/^I eval the following Ruby code:$/) do |ruby_code| 4 | @result = Pio.module_eval(ruby_code) 5 | end 6 | 7 | Then(/^the result of eval should be:$/) do |expected| 8 | expect(@result.to_s).to eq expected 9 | end 10 | -------------------------------------------------------------------------------- /lib/pio/open_flow/instruction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/descendants_tracker' 4 | 5 | module Pio 6 | module OpenFlow 7 | # Flow instruction 8 | class Instruction 9 | extend ActiveSupport::DescendantsTracker 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/echo/request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/echo/request' 4 | 5 | describe Pio::OpenFlow10::Echo::Request do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::Echo::Request) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/echo_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/echo/request' 4 | 5 | describe Pio::OpenFlow13::Echo::Request do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow13::Echo::Request) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/error/bad_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/error/bad_request' 4 | 5 | describe Pio::OpenFlow13::Error::BadRequest do 6 | it_should_behave_like('an OpenFlow message', 7 | Pio::OpenFlow13::Error::BadRequest) 8 | end 9 | -------------------------------------------------------------------------------- /tasks/reek.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'reek/rake/task' 5 | Reek::Rake::Task.new do |t| 6 | t.fail_on_error = false 7 | t.verbose = false 8 | end 9 | rescue LoadError 10 | task :reek do 11 | $stderr.puts 'Reek is disabled' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pio/lldp/system_name_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # TLV value field of system name TLV 8 | class SystemNameValue < BinData::Record 9 | endian :big 10 | 11 | stringz :system_name 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/bindata_string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module BinData 4 | # Add BinData::String#to_bytes 5 | class String 6 | def to_bytes 7 | to_s.unpack('H*').pop.scan(/[0-9a-f]{2}/).map do |each| 8 | "0x#{each}" 9 | end.join(', ') 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | before_install: 4 | - gem update bundler 5 | 6 | bundler_args: --without development 7 | 8 | rvm: 9 | - 2.3.4 10 | - 2.4.1 11 | - ruby-head 12 | 13 | matrix: 14 | allow_failures: 15 | - rvm: ruby-head 16 | fast_finish: true 17 | 18 | script: bundle exec rake travis 19 | -------------------------------------------------------------------------------- /lib/pio/lldp/end_of_lldpdu_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # End Of LLDPDU 8 | class EndOfLldpduValue < BinData::Record 9 | endian :big 10 | 11 | stringz :tlv_info_string, length: 0, value: '' 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/pio/lldp/port_description_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # TLV value field of port description TLV 8 | class PortDescriptionValue < BinData::Record 9 | endian :big 10 | 11 | stringz :port_description 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/step_definitions/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | group :development, :test do 6 | gem 'guard', require: false 7 | gem 'guard-bundler', require: false 8 | gem 'guard-rubocop', require: false 9 | gem 'rake', require: false 10 | gem 'rubocop', require: false 11 | end 12 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/integer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/monkey_patch/integer/base_conversions' 4 | require 'pio/monkey_patch/integer/ranges' 5 | 6 | # Monkey patch to Ruby's Integer class. 7 | class Integer 8 | include MonkeyPatch::Integer::BaseConversions 9 | include MonkeyPatch::Integer::Ranges 10 | end 11 | -------------------------------------------------------------------------------- /lib/pio/lldp/system_description_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # TLV value field of system description TLV 8 | class SystemDescriptionValue < BinData::Record 9 | endian :big 10 | 11 | stringz :system_description 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/step_definitions/.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | script: bundle exec rake 4 | 5 | bundler_args: --without development 6 | 7 | rvm: 8 | - 2.0 9 | - 2.1 10 | - 2.2 11 | - ruby-head 12 | 13 | before_install: 14 | - gem update bundler 15 | 16 | matrix: 17 | allow_failures: 18 | - rvm: ruby-head 19 | fast_finish: true 20 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/features/request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/features/request.rb' 4 | 5 | describe Pio::OpenFlow10::Features::Request do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', 8 | Pio::OpenFlow10::Features::Request) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /features/open_flow13/decrement_ip_ttl.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: DecrementIpTtl 3 | 4 | Scenario: new 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::DecrementIpTtl.new 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 24 | 12 | -------------------------------------------------------------------------------- /fixtures/ethernet_header/vlan_ethernet_header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac 5 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac 6 | 0x81, 0x00, # ether_type 7 | 0b101_0_000001100100, # vlan_pcp, vlan_cfi, vlan_vid 8 | 0x81, 0x00, # ether_type_vlan 9 | ].pack('C14nC2') 10 | -------------------------------------------------------------------------------- /fixtures/open_flow10/packet_in.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0x01, # version 5 | 0x0a, # type 6 | 0x00, 0x12, # _length 7 | 0x00, 0x00, 0x00, 0x00, # transaction_id 8 | 0xff, 0xff, 0xff, 0x00, # buffer_id 9 | 0x00, 0x00, # total_length 10 | 0x00, 0x01, # in_port 11 | 0x00, # reason 12 | 0x00, # padding 13 | ].pack('C18') 14 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/features/reply_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/features/reply' 4 | 5 | describe Pio::OpenFlow10::Features::Reply do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message with Datapath ID', 8 | Pio::OpenFlow10::Features::Reply) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/features/reply_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/features/reply' 4 | 5 | describe Pio::OpenFlow13::Features::Reply do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message with Datapath ID', 8 | Pio::OpenFlow13::Features::Reply) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/pio/payload.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | # Get payload fields. 5 | module Payload 6 | def binary_after(field_name) 7 | all_fields = field_names(true) 8 | all_fields[(all_fields.index(field_name) + 1)..-1].map do |each| 9 | __send__(each).to_binary_s 10 | end.join 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /features/open_flow/nicira_resubmit.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: NiciraResubmit 3 | 4 | Scenario: new(1) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::NiciraResubmit.new(1) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | in_port | 1 | 12 | -------------------------------------------------------------------------------- /features/step_definitions/virtual_link_steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable LineLength 4 | 5 | Then(/^virtual links should not exist$/) do 6 | step %(I run `bash -c 'ifconfig | grep "^link[0-9]\+-[0-9]\+" > virtual_links.txt'`) 7 | step 'the file "virtual_links.txt" should not contain "link"' 8 | end 9 | 10 | # rubocop:enable LineLength 11 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/meter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/meter' 4 | 5 | describe Pio::OpenFlow13::Meter do 6 | describe '.new' do 7 | When(:meter) { Pio::OpenFlow13::Meter.new(options) } 8 | 9 | context 'with 1' do 10 | Given(:options) { 1 } 11 | Then { meter.meter_id == 1 } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/open_flow13/set_ip_ttl.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetIpTtl 3 | 4 | Scenario: new(10) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetIpTtl.new(10) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 23 | 12 | | ttl | 10 | 13 | -------------------------------------------------------------------------------- /lib/pio/lldp/system_capabilities_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # TLV value field of system capabilities TLV 8 | class SystemCapabilitiesValue < BinData::Record 9 | endian :big 10 | 11 | uint16be :system_capabilities 12 | uint16be :enabled_capabilities 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /.idea/ 5 | /InstalledFiles 6 | /bin/ 7 | /coverage/ 8 | /pkg/ 9 | /spec/reports/ 10 | /test/tmp/ 11 | /test/version_tmp/ 12 | /tmp/ 13 | /vendor/ 14 | 15 | ## Documentation cache and generated files: 16 | /.yardoc/ 17 | /_yardoc/ 18 | /doc/ 19 | /rdoc/ 20 | pio.html 21 | 22 | ## Environment normalisation: 23 | /.bundle/ 24 | /lib/bundler/man/ 25 | -------------------------------------------------------------------------------- /lib/pio/dhcp/boot_reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/message' 4 | require 'pio/dhcp/boot_reply_options' 5 | 6 | module Pio 7 | class Dhcp 8 | # DHCP Reply Frame Base Class 9 | class BootReply < Message 10 | MESSAGE_TYPE = 2 11 | 12 | # DHCP Options Class. 13 | class Options < Dhcp::BootReplyOptions; end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/lldp/organizationally_specific_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # The V of Organizationally specfic TLV. 8 | class OrganizationallySpecificValue < BinData::Record 9 | endian :big 10 | 11 | uint24be :oui 12 | uint8 :subtype 13 | stringz :information 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/uint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/monkey_patch/uint/base_conversions' 5 | 6 | # BinData's base module 7 | module BinData 8 | Uint8.__send__ :include, MonkeyPatch::Uint::BaseConversions 9 | Uint16be.__send__ :include, MonkeyPatch::Uint::BaseConversions 10 | Uint32be.__send__ :include, MonkeyPatch::Uint::BaseConversions 11 | end 12 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/features/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Features Request and Reply message. 8 | class Features 9 | # Features Request message. 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 1, type: 5 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/goto_table_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/goto_table' 4 | 5 | describe Pio::OpenFlow13::GotoTable do 6 | describe '.new' do 7 | When(:goto_table) { Pio::OpenFlow13::GotoTable.new(options) } 8 | 9 | context 'with 1' do 10 | Given(:options) { 1 } 11 | Then { goto_table.table_id == 1 } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/pio/dhcp/boot_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/message' 4 | require 'pio/dhcp/boot_request_options' 5 | 6 | module Pio 7 | class Dhcp 8 | # DHCP Request Frame Base Class 9 | class BootRequest < Message 10 | MESSAGE_TYPE = 1 11 | 12 | # DHCP Options Class. 13 | class Options < Dhcp::BootRequestOptions; end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /features/open_flow10/strip_vlan_header.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: StripVlanHeader 3 | 4 | Scenario: new 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::StripVlanHeader.new 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 3 | 12 | | action_length | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /features/open_flow13/copy_ttl_inwards.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: CopyTtlInwards 3 | 4 | Copies TTL "inwards" -- from outermost to next-to-outermost 5 | 6 | Scenario: new 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::CopyTtlInwards.new 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | action_type | 12 | 14 | -------------------------------------------------------------------------------- /features/open_flow13/copy_ttl_outwards.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: CopyTtlOutwards 3 | 4 | Copies TTL "outwards" -- from next-to-outermost to outermost 5 | 6 | Scenario: new 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::CopyTtlOutwards.new 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | action_type | 11 | 14 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/decrement_ip_ttl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Decrements IP TTL 8 | class DecrementIpTtl < OpenFlow::Action 9 | action_header action_type: 24, action_length: 8 10 | string :padding, length: 4 11 | 12 | def initialize 13 | super({}) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /tasks/gem.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | unless Dir.glob(File.join(__dir__, '../*.gemspec')).empty? 5 | require 'bundler/gem_tasks' 6 | end 7 | rescue LoadError 8 | task :build do 9 | $stderr.puts 'Bundler is disabled' 10 | end 11 | task :install do 12 | $stderr.puts 'Bundler is disabled' 13 | end 14 | task :release do 15 | $stderr.puts 'Bundler is disabled' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /features/open_flow13/set_metadata.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetMetadata 3 | 4 | Scenario: new(0x123) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetMetadata.new(0x123) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | metadata.to_hex | 0x123 | 14 | -------------------------------------------------------------------------------- /lib/pio/dhcp/client_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/type/mac_address' 4 | 5 | module Pio 6 | class Dhcp 7 | # Dhcp Client Id Format 8 | class ClientId < BinData::Primitive 9 | bit8 :hardwaretype, value: 1 10 | mac_address :mac 11 | 12 | def get 13 | mac 14 | end 15 | 16 | def set(value) 17 | self.mac = value 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/barrier/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 Barrier messages 8 | module Barrier 9 | # OpenFlow 1.0 Barrier Request message 10 | class Reply < OpenFlow::Message 11 | open_flow_header version: 1, type: 19 12 | string :body, value: '' 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/features/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Features Request and Reply message. 8 | class Features 9 | # Features Request message. 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 4, type: 5 12 | string :body, value: '' 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_ip_ttl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Sets IP TTL 8 | class SetIpTtl < OpenFlow::Action 9 | action_header action_type: 23, action_length: 8 10 | uint8 :ttl 11 | string :padding, length: 3 12 | 13 | def initialize(ttl) 14 | super(ttl: ttl) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /features/open_flow10/set_vlan_vid.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetVlanVid 3 | 4 | Scenario: new(1) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetVlanVid.new(1) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 1 | 12 | | action_length | 8 | 13 | | vlan_id | 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/barrier/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 Barrier messages 8 | module Barrier 9 | # OpenFlow 1.0 Barrier Request message 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 1, type: 18 12 | string :body, value: '' 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/echo/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow13 7 | module Echo 8 | # OpenFlow 1.3 Echo Reply message. 9 | class Reply < OpenFlow::Message 10 | open_flow_header version: 4, type: 3 11 | string :body, read_length: -> { length - 8 } 12 | 13 | alias user_data body 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /features/open_flow_version.feature: -------------------------------------------------------------------------------- 1 | Feature: OpenFlow.version 2 | Scenario: OpenFlow 1.0 3 | Given I switch the Pio::OpenFlow version to "OpenFlow10" 4 | When I get the OpenFlow version string 5 | Then the version string should be "OpenFlow10" 6 | 7 | Scenario: OpenFlow 1.3 8 | Given I switch the Pio::OpenFlow version to "OpenFlow13" 9 | When I get the OpenFlow version string 10 | Then the version string should be "OpenFlow13" 11 | -------------------------------------------------------------------------------- /features/open_flow10/set_tos.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetTos 3 | 4 | Scenario: new(0b11111100) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetTos.new(0b11111100) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 8 | 12 | | action_length | 8 | 13 | | type_of_service | 252 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow10/set_vlan_priority.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetVlanPriority 3 | 4 | Scenario: new(1) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetVlanPriority.new(1) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 2 | 12 | | action_length | 8 | 13 | | vlan_priority | 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow10/vendor_action.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: VendorAction 3 | 4 | Scenario: new(1) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::VendorAction.new(1) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type.to_hex | 0xffff | 12 | | length | 8 | 13 | | vendor | 1 | 14 | -------------------------------------------------------------------------------- /features/parser.feature: -------------------------------------------------------------------------------- 1 | Feature: Parser 2 | Scenario: parse icmpv6.pcap 3 | When I parse a file named "icmpv6.pcap" with "Pio::Parser" class 4 | Then the message #1 have the following fields and values: 5 | | field | value | 6 | | class | Pio::EthernetFrame | 7 | | destination_mac | 00:60:97:07:69:ea | 8 | | source_mac | 00:00:86:05:80:da | 9 | | ether_type | 34525 | 10 | -------------------------------------------------------------------------------- /lib/pio/instance_inspector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | # Introduces #inspect method 5 | module InstanceInspector 6 | def inspect 7 | "#<#{self.class.name} " + 8 | field_names.map do |each| 9 | next if each == :padding 10 | next unless __send__("#{each}?") 11 | "#{each}: #{__send__(each).inspect}" 12 | end.compact.join(', ') + 13 | '>' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/echo/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow13 7 | module Echo 8 | # OpenFlow 1.3 Echo Request message. 9 | class Request < OpenFlow::Message 10 | open_flow_header version: 4, type: 2 11 | string :body, read_length: -> { length - header_length } 12 | 13 | alias user_data body 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/strip_vlan_header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # An action to strip the 802.1q header. 8 | class StripVlanHeader < OpenFlow::Action 9 | action_header action_type: 3, action_length: 8 10 | string :padding, length: 4 11 | hide :padding 12 | 13 | def initialize 14 | super({}) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/copy_ttl_inwards.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Copies TTL "inwards" -- from outermost to next-to-outermost 8 | class CopyTtlInwards < OpenFlow::Action 9 | action_header action_type: 12, action_length: 8 10 | string :padding, length: 4 11 | 12 | def initialize 13 | super({}) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/copy_ttl_outwards.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Copies TTL "outwards" -- from next-to-outermost to outermost 8 | class CopyTtlOutwards < OpenFlow::Action 9 | action_header action_type: 11, action_length: 8 10 | string :padding, length: 4 11 | 12 | def initialize 13 | super({}) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'cucumber/rake/task' 5 | Cucumber::Rake::Task.new 6 | Cucumber::Rake::Task.new('cucumber:travis') do |task| 7 | task.cucumber_opts = '--tags ~@wip --tags ~@sudo --tags ~@shell' 8 | end 9 | rescue LoadError 10 | task :cucumber do 11 | $stderr.puts 'Cucumber is disabled' 12 | end 13 | task 'cucumber:travis' do 14 | $stderr.puts 'Cucumber is disabled' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /tasks/flay.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rake/tasklib' 5 | require 'flay' 6 | require 'flay_task' 7 | FlayTask.new do |t| 8 | t.dirs = FileList['lib/**/*.rb'].map do |each| 9 | each[%r{[^/]+}] 10 | end.uniq 11 | t.threshold = FLAY_THRESHOLD if Kernel.const_defined?(:FLAY_THRESHOLD) 12 | t.verbose = true 13 | end 14 | rescue LoadError 15 | task :flay do 16 | $stderr.puts 'Flay is disabled' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /features/open_flow10/set_transport_source_port.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetTransportSourcePort 3 | 4 | Scenario: new(100) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetTransportSourcePort.new(100) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 9 | 12 | | action_length | 8 | 13 | | port | 100 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow13/set_arp_operation.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetArpOperation 3 | 4 | Scenario: new(Pio::Arp::Reply::OPERATION) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetArpOperation.new(Pio::Arp::Reply.operation) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | operation | 2 | 14 | -------------------------------------------------------------------------------- /lib/pio/dhcp/parameter_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Dhcp 7 | # DHCP ParameterList Format. 8 | class ParameterList < BinData::Primitive 9 | array :octets, 10 | type: :uint8, 11 | initial_length: :tlv_info_length 12 | 13 | def set(value) 14 | self.octets = value 15 | end 16 | 17 | def get 18 | octets 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/uint/base_conversions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MonkeyPatch 4 | module Uint 5 | # Uint8#to_bytes, Uintxxbe#to_bytes 6 | module BaseConversions 7 | def to_bytes 8 | /Uint(8|\d+be)$/=~ self.class.name 9 | nbyte = Regexp.last_match(1).to_i / 4 10 | format("%0#{nbyte}x", to_i).scan(/.{1,2}/).map do |each| 11 | '0x' + each 12 | end.join(', ') 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/packet_in_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/packet_in' 4 | 5 | describe Pio::OpenFlow10::PacketIn do 6 | Given(:packet_in) { Pio::OpenFlow10::PacketIn.new } 7 | 8 | describe '.new' do 9 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::PacketIn) 10 | end 11 | 12 | describe 'datapath_id=' do 13 | When { packet_in.datapath_id = 1 } 14 | Then { packet_in.datapath_id == 1 } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/packet_in_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/packet_in' 4 | 5 | describe Pio::OpenFlow13::PacketIn do 6 | Given(:packet_in) { Pio::OpenFlow13::PacketIn.new } 7 | 8 | describe '.new' do 9 | it_should_behave_like('an OpenFlow message', Pio::OpenFlow13::PacketIn) 10 | end 11 | 12 | describe 'datapath_id=' do 13 | When { packet_in.datapath_id = 1 } 14 | Then { packet_in.datapath_id == 1 } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /fixtures/ipv4_header/ipv4_header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0b0100_0101, # ip_version, ip_header_length 5 | 0x00, # ip_type_of_service 6 | 0x00, 0x14, # ip_total_length 7 | 0x00, 0x00, # ip_identifier 8 | 0b000_0000000000000, # ip_flag, ip_fragment 9 | 0x80, # ip_ttl 10 | 0x00, # ip_protocol 11 | 0x30, 0xe1, # ip_header_checksum 12 | 0x01, 0x02, 0x03, 0x04, # source_ip_address 13 | 0x04, 0x03, 0x02, 0x01, # destination_ip_address 14 | ].pack('C6nC12') 15 | -------------------------------------------------------------------------------- /features/open_flow10/set_transport_destination_port.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetTransportDestinationPort 3 | 4 | Scenario: new(100) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetTransportDestinationPort.new(100) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 10 | 12 | | action_length | 8 | 13 | | port | 100 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow10/enqueue.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Enqueue 3 | 4 | Scenario: new(port: 1, queue_id: 2) 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::Enqueue.new(port: 1, queue_id: 2) 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 11 | 12 | | action_length | 16 | 13 | | port | 1 | 14 | | queue_id | 2 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/echo/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 Echo Request and Reply message. 8 | module Echo 9 | # OpenFlow 1.0 Echo Reply message. 10 | class Reply < OpenFlow::Message 11 | open_flow_header version: 1, type: 3 12 | string :body, read_length: -> { length - header_length } 13 | 14 | alias user_data body 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /features/open_flow10/set_source_ip_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetSourceIpAddress 3 | 4 | Scenario: new('192.168.0.1') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetSourceIpAddress.new('192.168.0.1') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 6 | 12 | | action_length | 8 | 13 | | ip_address | 192.168.0.1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow13/set_arp_sender_protocol_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetArpSenderProtocolAddress 3 | 4 | Scenario: new('192.168.1.1') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetArpSenderProtocolAddress.new('192.168.1.1') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | ip_address | 192.168.1.1 | 14 | -------------------------------------------------------------------------------- /lib/pio/icmp/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/icmp/message' 4 | 5 | module Pio 6 | class Icmp 7 | # ICMP Reply packet generator 8 | class Reply < Message 9 | option :icmp_type, value: 0 10 | option :source_mac 11 | option :destination_mac 12 | option :source_ip_address 13 | option :destination_ip_address 14 | option :icmp_identifier 15 | option :icmp_sequence_number 16 | option :echo_data, default: '' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/echo/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 Echo Request and Reply message. 8 | module Echo 9 | # OpenFlow 1.0 Echo Request message. 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 1, type: 2 12 | string :body, read_length: -> { length - header_length } 13 | 14 | alias user_data body 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/error_message' 4 | require 'pio/open_flow10/error/error_type10' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # Error message parser 9 | module Error 10 | extend OpenFlow::ErrorMessage 11 | 12 | # Error message body parser. 13 | class BodyParser < BinData::Record 14 | endian :big 15 | error_type10 :error_type 16 | uint16 :error_code 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pio/udp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/ethernet_header' 5 | require 'pio/ipv4_header' 6 | require 'pio/udp_header' 7 | 8 | module Pio 9 | # UDP packet format 10 | class Udp < BinData::Record 11 | include Ethernet 12 | include IPv4 13 | include UdpHeader 14 | 15 | endian :big 16 | ethernet_header ether_type: Ethernet::Type::IPV4 17 | ipv4_header ip_protocol: ProtocolNumber::UDP 18 | udp_header 19 | rest :udp_payload 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/ethernet_frame.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/ethernet_header' 5 | require 'pio/class_inspector' 6 | require 'pio/instance_inspector' 7 | require 'pio/ruby_dumper' 8 | 9 | module Pio 10 | # Ethernet frame parser 11 | class EthernetFrame < BinData::Record 12 | extend ClassInspector 13 | include Ethernet 14 | include InstanceInspector 15 | include RubyDumper 16 | 17 | endian :big 18 | 19 | ethernet_header 20 | rest :rest 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /features/open_flow10/set_ip_destination_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetDestinationIpAddress 3 | 4 | Scenario: new('192.168.0.1') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::OpenFlow10::SetDestinationIpAddress.new('192.168.0.1') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 7 | 12 | | action_length | 8 | 13 | | ip_address | 192.168.0.1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow13/set_source_mac_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetSourceMacAddress 3 | 4 | Scenario: new('11:22:33:44:55:66') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetSourceMacAddress.new('11:22:33:44:55:66') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | mac_address | 11:22:33:44:55:66 | 14 | -------------------------------------------------------------------------------- /features/open_flow13/stats_request.feature: -------------------------------------------------------------------------------- 1 | Feature: StatsRequest 2 | Background: 3 | Given I use OpenFlow 1.3 4 | 5 | @wip 6 | Scenario: new 7 | When I create an OpenFlow message with: 8 | """ 9 | Pio::StatsRequest.new(stats_type: :table) 10 | """ 11 | Then the message has the following fields and values: 12 | | field | value | 13 | | version | 4 | 14 | | transaction_id | 0 | 15 | | xid | 0 | 16 | | stats_type | :table | 17 | -------------------------------------------------------------------------------- /lib/pio/arp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/arp/format' 4 | require 'pio/arp/reply' 5 | require 'pio/arp/request' 6 | require 'pio/parse_error' 7 | 8 | module Pio 9 | # ARP parser and generator. 10 | class Arp 11 | def self.read(raw_data) 12 | format = Format.read(raw_data) 13 | { Request.operation => Request, 14 | Reply.operation => Reply }.fetch(format.operation).create(format) 15 | rescue 16 | raise Pio::ParseError, $ERROR_INFO.message 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_source_ip_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/type/ip_address' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # An action to modify the IPv4 source address of a packet. 9 | class SetSourceIpAddress < OpenFlow::Action 10 | action_header action_type: 6, action_length: 8 11 | ip_address :ip_address 12 | 13 | def initialize(ip_address) 14 | super ip_address: ip_address 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | TargetRubyVersion: 2.3 5 | DisplayCopNames: true 6 | Include: 7 | - '**/Gemfile' 8 | - '**/Rakefile' 9 | - examples 10 | - pio.gemspec 11 | - tasks/*.rake 12 | Exclude: 13 | - bin/* 14 | - vendor/**/* 15 | 16 | Metrics/LineLength: 17 | Exclude: 18 | - 'fixtures/**/*.rb' 19 | - 'spec/support/*.rb' 20 | 21 | Style/StringLiterals: 22 | EnforcedStyle: single_quotes 23 | 24 | Style/DotPosition: 25 | EnforcedStyle: trailing 26 | -------------------------------------------------------------------------------- /lib/pio/icmp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/icmp/format' 4 | require 'pio/icmp/reply' 5 | require 'pio/icmp/request' 6 | require 'pio/parse_error' 7 | 8 | module Pio 9 | # Icmp parser and generator. 10 | class Icmp 11 | def self.read(raw_data) 12 | format = Format.read(raw_data) 13 | { Request.icmp_type => Request, 14 | Reply.icmp_type => Reply }.fetch(format.icmp_type).create(format) 15 | rescue 16 | raise Pio::ParseError, $ERROR_INFO.message 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /features/open_flow10/set_source_mac_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetSourceMacAddress 3 | 4 | Scenario: new('11:22:33:44:55:66') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetSourceMacAddress.new('11:22:33:44:55:66') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 4 | 12 | | action_length | 16 | 13 | | mac_address | 11:22:33:44:55:66 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/step_definitions/dump_flows_steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable LineLength 4 | 5 | Then(/^the switch "(.*?)" has (\d+) flow entr(?:y|ies)$/) do |switch, num_entries| 6 | command = "trema dump_flows #{switch} -S." 7 | step "I run `#{command}`" 8 | expect(output_from(command).split("\n").size - 1).to eq(num_entries.to_i) 9 | end 10 | 11 | Then(/^the switch "(.*?)" has no flow entry$/) do |switch| 12 | step %(the switch "#{switch}" has 0 flow entry) 13 | end 14 | 15 | # rubocop:enable LineLength 16 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_destination_ip_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/type/ip_address' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # An action to modify the IPv4 source address of a packet. 9 | class SetDestinationIpAddress < OpenFlow::Action 10 | action_header action_type: 7, action_length: 8 11 | ip_address :ip_address 12 | 13 | def initialize(ip_address) 14 | super ip_address: ip_address 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pio/type/ipv6_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'ipaddr' 5 | 6 | module Pio 7 | module Type 8 | # IPv6 address 9 | class Ipv6Address < BinData::Primitive 10 | endian :big 11 | 12 | uint128 :ipv6_address 13 | 14 | def set(value) 15 | self.ipv6_address = IPAddr.new(value, Socket::Constants::AF_INET6) 16 | end 17 | 18 | def get 19 | IPAddr.new(ipv6_address, Socket::Constants::AF_INET6).to_s 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /features/open_flow13/set_destination_mac_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetDestinationMacAddress 3 | 4 | Scenario: new('11:22:33:44:55:66') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetDestinationMacAddress.new('11:22:33:44:55:66') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | mac_address | 11:22:33:44:55:66 | 14 | -------------------------------------------------------------------------------- /lib/pio/icmp/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/icmp/message' 4 | 5 | module Pio 6 | class Icmp 7 | # ICMP Request packet generator 8 | class Request < Message 9 | option :icmp_type, value: 8 10 | option :source_mac 11 | option :destination_mac 12 | option :source_ip_address 13 | option :destination_ip_address 14 | option :icmp_identifier, default: 0 15 | option :icmp_sequence_number, default: 0 16 | option :echo_data, default: '' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pio/open_flow/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow 5 | # OpenFlow version 6 | class Version < BinData::Primitive 7 | VERSIONS = { 1 => :OpenFlow10, 4 => :OpenFlow13 }.freeze 8 | 9 | uint8 :version 10 | 11 | def get 12 | VERSIONS.fetch(version) 13 | end 14 | 15 | def set(value) 16 | self.version = VERSIONS.invert.fetch(value) 17 | end 18 | 19 | def to_bytes 20 | version.to_hex 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.reek: -------------------------------------------------------------------------------- 1 | UncommunicativeModuleName: 2 | accept: ['Pio::Hello13', 3 | 'Pio::OpenFlow10::Error::ErrorType10', 4 | 'Pio::OpenFlow10::Match10', 5 | 'Pio::OpenFlow10::MatchOpenFlow10', 6 | 'Pio::OpenFlow10::PhyPort16', 7 | 'Pio::OpenFlow10::Port16', 8 | 'Pio::OpenFlow10::Port32', 9 | 'Pio::OpenFlow13', 10 | 'Pio::OpenFlow13::Error::ErrorType13', 11 | 'Pio::OpenFlow13::Port32', 12 | 'Pio::OpenFlow10'] 13 | 14 | TooManyStatements: 15 | enabled: false 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'rake' 8 | 9 | group :development, :test do 10 | gem 'aruba', require: false 11 | gem 'cucumber', require: false 12 | gem 'flay', require: false 13 | gem 'flog', require: false 14 | gem 'reek', require: false 15 | gem 'rspec', require: false 16 | gem 'rspec-given', require: false 17 | gem 'rubocop', require: false 18 | end 19 | 20 | group :development do 21 | gem 'pry', require: false 22 | gem 'relish', require: false 23 | end 24 | -------------------------------------------------------------------------------- /features/open_flow10/set_destination_mac_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: SetDestinationMacAddress 3 | 4 | Scenario: new('11:22:33:44:55:66') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetDestinationMacAddress.new('11:22:33:44:55:66') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 5 | 12 | | action_length | 16 | 13 | | mac_address | 11:22:33:44:55:66 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/open_flow13/set_arp_sender_hardware_address.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: SetArpSenderHardwareAddress 3 | 4 | Scenario: new('00:00:de:ad:be:ef') 5 | When I create an OpenFlow action with: 6 | """ 7 | Pio::SetArpSenderHardwareAddress.new('00:00:de:ad:be:ef') 8 | """ 9 | Then the action has the following fields and values: 10 | | field | value | 11 | | action_type | 25 | 12 | | action_length | 16 | 13 | | mac_address | 00:00:de:ad:be:ef | 14 | -------------------------------------------------------------------------------- /lib/pio/open_flow/buffer_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow 5 | # Buffered packet to apply to, or :no_buffer. 6 | class BufferId < BinData::Primitive 7 | NO_BUFFER = 0xffffffff 8 | 9 | endian :big 10 | uint32 :buffer_id, initial_value: NO_BUFFER 11 | 12 | def get 13 | buffer_id == NO_BUFFER ? :no_buffer : buffer_id 14 | end 15 | 16 | def set(value) 17 | self.buffer_id = (value == :no_buffer ? NO_BUFFER : value) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/lldp/ttl_tlv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # Time to live TLV 8 | class TtlTlv < BinData::Primitive 9 | endian :big 10 | 11 | bit7 :tlv_type, value: 3 12 | bit9 :tlv_info_length, value: 2 13 | string :ttl, read_length: :tlv_info_length 14 | 15 | def get 16 | BinData::Int16be.read(ttl) 17 | end 18 | 19 | def set(value) 20 | self.ttl = BinData::Int16be.new(value).to_binary_s 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /features/open_flow13/nicira_conjunction.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: NiciraConjunction 3 | 4 | Provides conjunctive flow match. 5 | 6 | Scenario: new(clause: 1, n_clauses: 2, conjunction_id: 1) 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::NiciraConjunction.new(clause: 1, n_clauses: 2, conjunction_id: 1) 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | clause | 1 | 14 | | n_clauses | 2 | 15 | | conjunction_id | 1 | 16 | -------------------------------------------------------------------------------- /lib/pio/open_flow/hello_failed_code.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | module OpenFlow 7 | # enum ofp_hello_failed_code 8 | class HelloFailedCode < BinData::Primitive 9 | ERROR_CODES = { incompatible: 0, permissions_error: 1 }.freeze 10 | 11 | endian :big 12 | uint16 :error_code 13 | 14 | def get 15 | ERROR_CODES.invert.fetch(error_code) 16 | end 17 | 18 | def set(value) 19 | self.error_code = ERROR_CODES.fetch(value) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/match10.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/match' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Pio::MatchFormat wrapper. 8 | class Match10 < BinData::Primitive 9 | endian :big 10 | 11 | string :match, 12 | read_length: 40, 13 | initial_value: Pio::OpenFlow10::Match.new.to_binary_s 14 | 15 | def set(object) 16 | self.match = object.to_binary_s 17 | end 18 | 19 | def get 20 | Pio::OpenFlow10::Match.read match 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pio/open_flow/nicira_action.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow 7 | # NXAST_* actions 8 | class NiciraAction < Action 9 | def self.nicira_action_header(options) 10 | module_eval do 11 | action_header action_type: options.fetch(:action_type), 12 | action_length: options.fetch(:action_length) 13 | uint32 :vendor, value: 0x2320 14 | uint16 :subtype, value: options.fetch(:subtype) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/table_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/stats_type' 4 | require 'pio/open_flow/message' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # OpenFlow 1.0 Table Stats messages 9 | module TableStats 10 | # OpenFlow 1.0 Table Stats Request message 11 | class Request < OpenFlow::Message 12 | open_flow_header version: 1, type: 16, length: 12 13 | stats_type :stats_type, value: -> { :table } 14 | uint16 :flags 15 | string :body, value: '' 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /features/open_flow/header.feature: -------------------------------------------------------------------------------- 1 | Feature: OpenFlow::Header 2 | 3 | Scenario: OpenFlow::Header#to_hex 4 | When I create an OpenFlow message with: 5 | """ 6 | Pio::OpenFlow::Header.new(version: :OpenFlow10, 7 | type: 10, 8 | message_length: 18, 9 | transaction_id: 0xff) 10 | """ 11 | Then the message has the following fields and values: 12 | | field | value | 13 | | to_bytes | 0x01, 0x0a, 0x00, 0x12, 0x00, 0x00, 0x00, 0xff | 14 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_source_mac_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/type/mac_address' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # An action to modify the source Ethernet address of a packet. 9 | class SetSourceMacAddress < OpenFlow::Action 10 | action_header action_type: 4, action_length: 16 11 | mac_address :mac_address 12 | string :padding, length: 6 13 | hide :padding 14 | 15 | def initialize(mac_address) 16 | super mac_address: mac_address 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/features/request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/features/request' 4 | 5 | describe Pio::OpenFlow13::Features::Request do 6 | describe '.new' do 7 | it_should_behave_like('an OpenFlow message', 8 | Pio::OpenFlow13::Features::Request) 9 | 10 | context "with unknown_opt: 'abcde'" do 11 | When(:message) do 12 | Pio::OpenFlow13::Features::Request.new(unknown_opt: 'abcde') 13 | end 14 | Then { message == Failure(RuntimeError, 'Unknown option: unknown_opt') } 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/queue_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Queue Stats messages 8 | class QueueStats 9 | # Queue Stats Request message 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 1, type: 16, length: 20 12 | 13 | stats_type :stats_type, value: -> { :queue } 14 | uint16 :flags 15 | port16 :port 16 | string :padding, length: 2 17 | hide :padding 18 | uint32 :queue_id 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/port_status/reason.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | # OpenFlow 1.0 Port Status message 6 | class PortStatus < OpenFlow::Message 7 | # What changed about the physical port 8 | class Reason < BinData::Primitive 9 | REASONS = { add: 0, delete: 1, modify: 2 }.freeze 10 | 11 | uint8 :reason 12 | 13 | def get 14 | REASONS.invert.fetch(reason) 15 | end 16 | 17 | def set(value) 18 | self.reason = REASONS.fetch(value) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_destination_mac_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/type/mac_address' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # An action to modify the destination Ethernet address of a packet. 9 | class SetDestinationMacAddress < OpenFlow::Action 10 | action_header action_type: 5, action_length: 16 11 | mac_address :mac_address 12 | string :padding, length: 6 13 | hide :padding 14 | 15 | def initialize(mac_address) 16 | super mac_address: mac_address 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pio/lldp/management_address_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # TLV value field of management address TLV 8 | class ManagementAddressValue < BinData::Record 9 | endian :big 10 | 11 | uint8 :string_length 12 | uint8 :subtype 13 | string :management_address, read_length: -> { string_length - 1 } 14 | uint8 :interface_numbering_subtype 15 | uint32 :interface_number 16 | uint8 :oid_string_length 17 | string :object_identifier, read_length: -> { oid_string_length } 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/description_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/stats_type' 4 | require 'pio/open_flow/message' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # OpenFlow 1.0 Description Stats messages 9 | module DescriptionStats 10 | # OpenFlow 1.0 Description Stats Request message 11 | class Request < OpenFlow::Message 12 | open_flow_header version: 1, type: 16, length: 12 13 | stats_type :stats_type, value: -> { :description } 14 | uint16 :flags 15 | string :body, value: '' 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pio/monkey_patch/integer/ranges.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MonkeyPatch 4 | module Integer 5 | # Defines Integer#nbit? methods. 6 | module Ranges 7 | def unsigned_8bit? 8 | _within_range? 8 9 | end 10 | 11 | def unsigned_16bit? 12 | _within_range? 16 13 | end 14 | 15 | def unsigned_32bit? 16 | _within_range? 32 17 | end 18 | 19 | def unsigned_64bit? 20 | _within_range? 64 21 | end 22 | 23 | def _within_range?(nbit) 24 | (0 <= self) && (self < 2**nbit) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /fixtures/arp/arp_reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # destination_mac 5 | 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # source_mac 6 | 0x08, 0x06, # ether_type 7 | 0x00, 0x01, # hardware_type 8 | 0x08, 0x00, # protocol_type 9 | 0x06, # hardware_length 10 | 0x04, # protocol_length 11 | 0x00, 0x02, # operation 12 | 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # sender_hardware_address 13 | 0xc0, 0xa8, 0x53, 0xfe, # sender_protocol_address 14 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # target_hardware_address 15 | 0xc0, 0xa8, 0x53, 0x03, # target_protocol_address 16 | ].pack('C*') 17 | -------------------------------------------------------------------------------- /fixtures/arp/arp_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac 5 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac 6 | 0x08, 0x06, # ether_type 7 | 0x00, 0x01, # hardware_type 8 | 0x08, 0x00, # protocol_type 9 | 0x06, # hardware_length 10 | 0x04, # protocol_length 11 | 0x00, 0x01, # operation 12 | 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # sender_hardware_address 13 | 0xc0, 0xa8, 0x53, 0x03, # sender_protocol_address 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # target_hardware_address 15 | 0xc0, 0xa8, 0x53, 0xfe, # target_protocol_address 16 | ].pack('C*') 17 | -------------------------------------------------------------------------------- /lib/pio/arp/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/arp/message' 4 | require 'pio/instance_inspector' 5 | require 'pio/mac' 6 | 7 | module Pio 8 | class Arp 9 | # ARP Reply packet generator 10 | class Reply < Message 11 | include InstanceInspector 12 | 13 | option :operation, value: 2 14 | option :source_mac 15 | option :destination_mac 16 | option :sender_hardware_address, value: :source_mac 17 | option :target_hardware_address, value: :destination_mac 18 | option :sender_protocol_address 19 | option :target_protocol_address 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow/transaction_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/monkey_patch/integer' 5 | 6 | module Pio 7 | module OpenFlow 8 | # Transaction ID (uint32) 9 | class TransactionId < BinData::Primitive 10 | endian :big 11 | 12 | uint32 :xid 13 | 14 | def set(value) 15 | unless value.unsigned_32bit? 16 | raise(ArgumentError, 17 | 'Transaction ID should be an unsigned 32-bit integer.') 18 | end 19 | self.xid = value 20 | end 21 | 22 | def get 23 | xid 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pio/message_type_selector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'English' 4 | require 'pio/parse_error' 5 | 6 | module Pio 7 | # Macros for defining message types. 8 | module MessageTypeSelector 9 | def message_type(options) 10 | const_set(:MESSAGE_TYPE, options) 11 | end 12 | 13 | def read(raw_data) 14 | format = const_get(:Format).read(raw_data) 15 | message = const_get(:MESSAGE_TYPE)[format.message_type].allocate 16 | message.instance_variable_set :@format, format 17 | message 18 | rescue 19 | raise Pio::ParseError, $ERROR_INFO.message 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/port16.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/port' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Port numbering (16bit). 8 | class Port16 < OpenFlow::Port 9 | port_size_in_bytes 16 10 | 11 | max_port_number 0xff00 12 | 13 | reserved_ports(in_port: 0xfff8, 14 | table: 0xfff9, 15 | normal: 0xfffa, 16 | flood: 0xfffb, 17 | all: 0xfffc, 18 | controller: 0xfffd, 19 | local: 0xfffe, 20 | none: 0xffff) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow/nicira_resubmit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/nicira_action' 4 | 5 | module Pio 6 | module OpenFlow 7 | # NXAST_RESUBMIT action 8 | class NiciraResubmit < OpenFlow::NiciraAction 9 | nicira_action_header action_type: 0xffff, 10 | action_length: 16, 11 | subtype: 1 12 | uint16 :in_port 13 | string :padding, length: 4 14 | hide :padding 15 | 16 | def initialize(port_number) 17 | super(in_port: port_number) 18 | end 19 | end 20 | end 21 | NiciraResubmit = OpenFlow::NiciraResubmit 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/exact_match.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/match' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 exact match 8 | class ExactMatch < OpenFlow::FlowMatch 9 | def initialize(packet_in) 10 | @match = packet_in.data.to_exact_match(packet_in.in_port) 11 | rescue NoMethodError 12 | raise NotImplementedError, 13 | "#{packet_in.data.class} is not yet supported by ExactMatch." 14 | end 15 | 16 | def method_missing(method, *args, &block) 17 | @match.__send__ method, *args, &block 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/flow_removed/reason.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | # Flow Removed message 6 | class FlowRemoved < OpenFlow::Message 7 | # Why was this flow removed? 8 | # (enum ofp_flow_removed_reason) 9 | class Reason < BinData::Primitive 10 | REASONS = { idle_timeout: 0, hard_timeout: 1, delete: 2 }.freeze 11 | 12 | uint8 :reason 13 | 14 | def get 15 | REASONS.invert.fetch(reason) 16 | end 17 | 18 | def set(value) 19 | self.reason = REASONS.fetch(value) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/error_message' 4 | require 'pio/open_flow13/error/bad_request' 5 | require 'pio/open_flow13/error/error_type13' 6 | require 'pio/open_flow13/error/hello_failed' 7 | 8 | module Pio 9 | module OpenFlow13 10 | # Error message parser 11 | module Error 12 | mattr_reader(:type) { 1 } 13 | 14 | extend OpenFlow::ErrorMessage 15 | 16 | # Error message body parser. 17 | class BodyParser < BinData::Record 18 | endian :big 19 | error_type13 :error_type 20 | uint16 :error_code 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pio/arp/message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/arp/format' 4 | require 'pio/message' 5 | 6 | module Pio 7 | class Arp 8 | # Base class of ARP Request and Reply 9 | class Message < Pio::Message 10 | def self.create(format) 11 | allocate.tap do |message| 12 | message.instance_variable_set :@format, format 13 | end 14 | end 15 | 16 | def initialize(user_options) 17 | @format = Arp::Format.new(parse_options(user_options)) 18 | end 19 | 20 | def method_missing(method, *args) 21 | @format.__send__ method, *args 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/pio/monkey_patch/integer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/monkey_patch/integer' 4 | 5 | describe MonkeyPatch::Integer::BaseConversions do 6 | describe '0#to_hex' do 7 | When(:result) { 0.to_hex } 8 | Then { result == '0x00' } 9 | end 10 | 11 | describe '1#to_hex' do 12 | When(:result) { 1.to_hex } 13 | Then { result == '0x01' } 14 | end 15 | 16 | describe '250#to_hex' do 17 | When(:result) { 250.to_hex } 18 | Then { result == '0xfa' } 19 | end 20 | 21 | describe '4207849484#to_hex' do 22 | When(:result) { 4_207_849_484.to_hex } 23 | Then { result == '0xfaceb00c' } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /features/open_flow10/aggregate_stats_reply.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: AggregateStats::Reply 3 | 4 | Scenario: read 5 | When I parse a file named "open_flow10/aggregate_stats_reply.raw" with "AggregateStats::Reply" class 6 | Then the message has the following fields and values: 7 | | field | value | 8 | | version | 1 | 9 | | transaction_id | 15 | 10 | | xid | 15 | 11 | | stats_type | :aggregate | 12 | | flags | 0 | 13 | | packet_count | 0 | 14 | | byte_count | 0 | 15 | | flow_count | 0 | 16 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/stats_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | # enum ofp_stats_types 6 | class StatsType < BinData::Primitive 7 | STATS_TYPES = { 8 | description: 0, 9 | flow: 1, 10 | aggregate: 2, 11 | table: 3, 12 | port: 4, 13 | queue: 5, 14 | vendor: 0xffff 15 | }.freeze 16 | 17 | endian :big 18 | uint16 :command 19 | 20 | def get 21 | STATS_TYPES.invert.fetch(command) 22 | end 23 | 24 | def set(value) 25 | self.command = STATS_TYPES.fetch(value) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/pio/open_flow/nicira_resubmit_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/nicira_resubmit' 4 | 5 | describe Pio::OpenFlow::NiciraResubmit do 6 | describe '.new' do 7 | When(:nicira_resubmit) do 8 | Pio::OpenFlow::NiciraResubmit.new(port_number) 9 | end 10 | 11 | context 'with 1' do 12 | Given(:port_number) { 1 } 13 | 14 | Then { nicira_resubmit.action_type == 0xffff } 15 | Then { nicira_resubmit.action_length == 16 } 16 | Then { nicira_resubmit.vendor == 0x2320 } 17 | Then { nicira_resubmit.subtype == 1 } 18 | Then { nicira_resubmit.in_port == 1 } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/arp/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/arp/message' 4 | require 'pio/instance_inspector' 5 | require 'pio/mac' 6 | 7 | module Pio 8 | class Arp 9 | # ARP Request packet generator 10 | class Request < Message 11 | include InstanceInspector 12 | 13 | option :operation, value: 1 14 | option :source_mac 15 | option :destination_mac, default: 'ff:ff:ff:ff:ff:ff' 16 | option :sender_hardware_address, value: :source_mac 17 | option :target_hardware_address, default: '00:00:00:00:00:00' 18 | option :sender_protocol_address 19 | option :target_protocol_address 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/packet_out.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/actions' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # OpenFlow 1.0 Packet-Out message 9 | class PacketOut < OpenFlow::Message 10 | open_flow_header version: 1, 11 | type: 13, 12 | length: -> { 16 + actions_length + raw_data.length } 13 | uint32 :buffer_id 14 | uint16 :in_port 15 | uint16 :actions_length, initial_value: -> { actions.binary.length } 16 | actions10 :actions, length: -> { actions_length } 17 | rest :raw_data 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /features/open_flow10/flow_stats_reply.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: FlowStats::Reply 3 | Scenario: new 4 | When I create an OpenFlow message with: 5 | """ 6 | Pio::FlowStats::Reply.new 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | version | 1 | 11 | | transaction_id | 0 | 12 | | xid | 0 | 13 | | stats_type | :flow | 14 | | stats.size | 0 | 15 | 16 | @wip 17 | Scenario: new(more options) 18 | When I create an OpenFlow message with: 19 | """ 20 | Pio::FlowStats::Reply.new(more options) 21 | """ 22 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/port32.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/port' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # Port numbering (32bit). 8 | class Port32 < OpenFlow::Port 9 | port_size_in_bytes 32 10 | 11 | max_port_number 0xffffffff00 12 | 13 | reserved_ports(in_port: 0xfffffff8, 14 | table: 0xfffffff9, 15 | normal: 0xfffffffa, 16 | flood: 0xfffffffb, 17 | all: 0xfffffffc, 18 | controller: 0xfffffffd, 19 | local: 0xfffffffe, 20 | any: 0xffffffff) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set metadata 9 | class SetMetadata < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::ArpOperation::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 8 16 | uint64 :metadata 17 | 18 | def initialize(metadata) 19 | super metadata: metadata 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow/header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/monkey_patch/uint' 5 | require 'pio/open_flow/transaction_id' 6 | require 'pio/open_flow/version' 7 | 8 | module Pio 9 | module OpenFlow 10 | # OpenFlow message header parser 11 | class Header < BinData::Record 12 | endian :big 13 | 14 | version :version 15 | uint8 :type 16 | uint16 :message_length 17 | transaction_id :transaction_id 18 | rest :body 19 | 20 | def to_bytes 21 | [version, 22 | type, 23 | message_length, 24 | transaction_id].map(&:to_bytes).join(', ') 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/packet_in/reason.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | class PacketIn < OpenFlow::Message 6 | # Why is this packet being sent to the controller? 7 | # (enum ofp_packet_in_reason) 8 | class Reason < BinData::Primitive 9 | REASONS = { no_match: 0, action: 1 }.freeze 10 | 11 | uint8 :reason 12 | 13 | def get 14 | REASONS.invert.fetch(reason) 15 | end 16 | 17 | def set(value) 18 | self.reason = REASONS.fetch(value) 19 | end 20 | 21 | def to_bytes 22 | reason.to_hex 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/strip_vlan_header_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/strip_vlan_header' 4 | 5 | describe Pio::OpenFlow10::StripVlanHeader do 6 | Given(:strip_vlan_header) { Pio::OpenFlow10::StripVlanHeader.new } 7 | 8 | describe '#action_type' do 9 | When(:action_type) { strip_vlan_header.action_type } 10 | Then { action_type == 3 } 11 | end 12 | 13 | describe '#action_length' do 14 | When(:action_length) { strip_vlan_header.action_length } 15 | Then { action_length == 8 } 16 | end 17 | 18 | describe '#to_binary' do 19 | When(:binary) { strip_vlan_header.to_binary } 20 | Then { binary.length == 8 } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/aggregate_stats/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/stats_type' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # Aggregate Stats messages 9 | module AggregateStats 10 | # Aggregate Stats Reply message 11 | class Reply < OpenFlow::Message 12 | open_flow_header version: 1, type: 17, length: 32 13 | stats_type :stats_type, value: -> { :aggregate } 14 | uint16 :flags 15 | uint64 :packet_count 16 | uint64 :byte_count 17 | uint32 :flow_count 18 | string :padding, length: 4 19 | hide :padding 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/port_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # Port Stats messages 8 | class PortStats 9 | # Port Stats Request message 10 | class Request < OpenFlow::Message 11 | open_flow_header version: 1, type: 16, length: 20 12 | 13 | stats_type :stats_type, value: -> { :port } 14 | uint16 :flags 15 | port16 :port 16 | string :padding, length: 6 17 | hide :padding 18 | 19 | def initialize(port, user_options = {}) 20 | super({ port: port }.merge user_options) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/class_inspector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/string/inflections' 4 | 5 | module Pio 6 | # Introduces Class.inspect method 7 | module ClassInspector 8 | # rubocop:disable LineLength 9 | def inspect 10 | field_and_type = fields.each_with_object([]) do |each, result| 11 | next if each.name == :padding 12 | result << [each.name, 13 | each.prototype.instance_variable_get(:@obj_class).name.demodulize.sub(/be$/, '').underscore] 14 | end 15 | signature = field_and_type.map { |field, type| "#{field}: #{type}" }.join(', ') 16 | "#{self}(#{signature})" 17 | end 18 | # rubocop:enable LineLength 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/error/hello_failed.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/error_message' 4 | require 'pio/open_flow/hello_failed_code' 5 | require 'pio/open_flow/message' 6 | require 'pio/open_flow13/error/error_type13' 7 | 8 | module Pio 9 | module OpenFlow13 10 | module Error 11 | # Hello Failed error message 12 | class HelloFailed < OpenFlow::Message 13 | open_flow_header version: 4, 14 | type: OpenFlow::ErrorMessage.type, 15 | length: -> { 12 + description.length } 16 | error_type13 :error_type 17 | hello_failed_code :error_code 18 | rest :description 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /tasks/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | -------------------------------------------------------------------------------- /lib/pio/open_flow/datapath_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/monkey_patch/integer' 5 | 6 | module Pio 7 | module OpenFlow 8 | # Datapath unique ID. The lower 48-bits are for a MAC address, 9 | # while the upper 16-bits are implementer-defined. 10 | class DatapathId < BinData::Primitive 11 | endian :big 12 | 13 | uint64 :datapath_id 14 | 15 | def set(value) 16 | unless value.unsigned_64bit? 17 | raise(ArgumentError, 18 | 'Datapath ID should be an unsigned 64-bit integer.') 19 | end 20 | self.datapath_id = value 21 | end 22 | 23 | def get 24 | datapath_id.to_i 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pio/open_flow/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10' 4 | require 'pio/open_flow13' 5 | 6 | module Pio 7 | module OpenFlow 8 | # Collection class of OpenFlow message parser class 9 | class Parser 10 | def self.find_by_type!(type) 11 | message_class = [Hello, Error, Echo::Request, Echo::Reply, 12 | Features::Request, Features::Reply, PacketIn, 13 | PacketOut, FlowMod, PortStatus, Stats::Request, 14 | Stats::Reply, Barrier::Request, Barrier::Reply] 15 | message_class.each_with_object({}) do |each, hash| 16 | hash[each.type] = each 17 | end.fetch(type) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/flow_mod/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | # OpenFlow 1.0 Flow Mod message. 6 | class FlowMod < OpenFlow::Message 7 | # enum ofp_flow_mod_command 8 | class Command < BinData::Primitive 9 | COMMANDS = { 10 | add: 0, 11 | modify: 1, 12 | modify_strict: 2, 13 | delete: 3, 14 | delete_strict: 4 15 | }.freeze 16 | 17 | endian :big 18 | uint16 :command 19 | 20 | def get 21 | COMMANDS.invert.fetch(command) 22 | end 23 | 24 | def set(value) 25 | self.command = COMMANDS.fetch(value) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /fixtures/open_flow10/packet_in_arp_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0x01, # version 5 | 0x0a, # type 6 | 0x00, 0x52, # _length 7 | 0x00, 0x00, 0x00, 0x00, # transaction_id 8 | 0xff, 0xff, 0xff, 0x00, # buffer_id 9 | 0x00, 0x40, # total_length 10 | 0x00, 0x01, # in_port 11 | 0x00, # reason 12 | 0x00, # padding 13 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # raw_data 14 | ].pack('C82') 15 | -------------------------------------------------------------------------------- /lib/pio/type/ether_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | module Type 7 | # Ether type 8 | class EtherType < BinData::Primitive 9 | endian :big 10 | 11 | uint16 :ether_type 12 | 13 | def set(value) 14 | self.ether_type = value 15 | end 16 | 17 | def get 18 | ether_type 19 | end 20 | 21 | # This method smells of :reek:UncommunicativeVariableName 22 | def to_bytes 23 | byte1 = format('%02x', (self & 0xff00) >> 8) 24 | byte2 = format('%02x', self & 0xff) 25 | "0x#{byte1}, 0x#{byte2}" 26 | end 27 | 28 | def inspect 29 | Kernel.format '0x%04x', self 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /features/step_definitions/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /InstalledFiles 5 | /coverage/ 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | /vendor/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalisation: 25 | /.bundle/ 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | -------------------------------------------------------------------------------- /features/open_flow10/bad_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Error::BadRequest 3 | 4 | Request was not understood error 5 | 6 | Scenario: new (error_code: :bad_version, raw_data: EchoRequest 1.3) 7 | When I create an OpenFlow message with: 8 | """ 9 | Pio::Error::BadRequest.new(error_code: :bad_version, 10 | raw_data: Pio::OpenFlow13::Echo::Request.new.to_binary) 11 | """ 12 | Then the message has the following fields and values: 13 | | field | value | 14 | | transaction_id | 0 | 15 | | xid | 0 | 16 | | error_type | :bad_request | 17 | | error_code | :bad_version | 18 | | raw_data.length | 8 | 19 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_arp_operation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set ARP operation field 9 | class SetArpOperation < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::ArpOperation::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 2 16 | uint16 :operation 17 | string :padding, length: 6 18 | hide :padding 19 | 20 | def initialize(operation) 21 | super operation: operation 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/description_stats/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/stats_type' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # OpenFlow 1.0 Description Stats messages 8 | module DescriptionStats 9 | # OpenFlow 1.0 Description Stats Reply message 10 | class Reply < OpenFlow::Message 11 | open_flow_header version: 1, type: 17, length: 1068 12 | stats_type :stats_type, value: -> { :description } 13 | uint16 :flags 14 | 15 | stringz :manufacturer, length: 256 16 | stringz :hardware, length: 256 17 | stringz :software, length: 256 18 | stringz :serial_number, length: 32 19 | stringz :datapath, length: 256 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/error/error_type10.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow10 5 | module Error 6 | # enum ofp_error_type 7 | class ErrorType10 < BinData::Primitive 8 | ERROR_TYPES = { 9 | hello_failed: 0, 10 | bad_request: 1, 11 | bad_action: 2, 12 | flow_mod_failed: 3, 13 | port_mod_failed: 4, 14 | queue_operation_failed: 5 15 | }.freeze 16 | 17 | endian :big 18 | uint16 :error_type 19 | 20 | def get 21 | ERROR_TYPES.invert.fetch(error_type) 22 | end 23 | 24 | def set(value) 25 | self.error_type = ERROR_TYPES.fetch(value) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /features/open_flow10/hello_failed.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Error::HelloFailed 3 | 4 | Hello protocol failed error 5 | 6 | Scenario: new(error_code: :incompatible, description: 'error description') 7 | When I create an OpenFlow message with: 8 | """ 9 | Pio::Error::HelloFailed.new(error_code: :incompatible, 10 | description: 'error description') 11 | """ 12 | Then the message has the following fields and values: 13 | | field | value | 14 | | transaction_id | 0 | 15 | | xid | 0 | 16 | | error_type | :hello_failed | 17 | | error_code | :incompatible | 18 | | description | error description | 19 | 20 | -------------------------------------------------------------------------------- /lib/pio/open_flow/nicira_resubmit_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/nicira_action' 4 | 5 | module Pio 6 | module OpenFlow 7 | # NXAST_RESUBMIT_TABLE action 8 | class NiciraResubmitTable < OpenFlow::NiciraAction 9 | nicira_action_header action_type: 0xffff, 10 | action_length: 16, 11 | subtype: 14 12 | uint16 :in_port 13 | uint8 :table, initial_value: 0xff 14 | string :padding, length: 3 15 | hide :padding 16 | 17 | def initialize(options) 18 | raise ':in_port option is a mandatory' unless options.key?(:in_port) 19 | super options 20 | end 21 | end 22 | end 23 | NiciraResubmitTable = OpenFlow::NiciraResubmitTable 24 | end 25 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_vlan_vid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # An action to modify the VLAN ID of a packet. 8 | class SetVlanVid < OpenFlow::Action 9 | action_header action_type: 1, action_length: 8 10 | uint16 :vlan_id 11 | string :padding, length: 2 12 | hide :padding 13 | 14 | def initialize(number) 15 | vlan_id = number.to_i 16 | unless vlan_id >= 1 && vlan_id <= 4095 17 | raise ArgumentError, 'VLAN ID must be between 1 and 4095 inclusive' 18 | end 19 | super(vlan_id: vlan_id) 20 | rescue NoMethodError 21 | raise TypeError, 'VLAN ID must be an Integer.' 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_source_mac_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set a header field. 9 | class SetSourceMacAddress < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::SourceMacAddress::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 6 16 | mac_address :mac_address 17 | string :padding, length: 2 18 | hide :padding 19 | 20 | def initialize(mac_address) 21 | super mac_address: mac_address 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /features/open_flow/nicira_resubmit_table.feature: -------------------------------------------------------------------------------- 1 | Feature: NiciraResubmitTable 2 | 3 | Scenario: new(in_port: 1, table: 1) 4 | When I create an OpenFlow action with: 5 | """ 6 | Pio::NiciraResubmitTable.new(in_port: 1, table: 1) 7 | """ 8 | Then the action has the following fields and values: 9 | | field | value | 10 | | in_port | 1 | 11 | | table | 1 | 12 | 13 | Scenario: new(in_port: 1) 14 | When I create an OpenFlow action with: 15 | """ 16 | Pio::NiciraResubmitTable.new(in_port: 1) 17 | """ 18 | Then the action has the following fields and values: 19 | | field | value | 20 | | in_port | 1 | 21 | | table.to_hex | 0xff | 22 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/flow_removed.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/match10' 5 | require 'pio/open_flow10/flow_removed/reason' 6 | 7 | module Pio 8 | module OpenFlow10 9 | # Flow Removed message 10 | class FlowRemoved < OpenFlow::Message 11 | open_flow_header version: 1, type: 11, length: 88 12 | match10 :match 13 | uint64 :cookie 14 | uint16 :priority 15 | reason :reason 16 | string :padding1, length: 1 17 | hide :padding1 18 | uint32 :duration_sec 19 | uint32 :duration_nsec 20 | uint16 :idle_timeout 21 | string :padding2, length: 2 22 | hide :padding2 23 | uint64 :packet_count 24 | uint64 :byte_count 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/flow_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/match10' 5 | require 'pio/open_flow10/stats_type' 6 | 7 | module Pio 8 | module OpenFlow10 9 | # OpenFlow 1.0 FlowStats messages 10 | module FlowStats 11 | # OpenFlow 1.0 Flow Stats Request message 12 | class Request < OpenFlow::Message 13 | open_flow_header version: 1, type: 16, length: 56 14 | 15 | stats_type :stats_type, value: -> { :flow } 16 | uint16 :flags 17 | match10 :match 18 | uint8 :table_id, initial_value: 0xff 19 | string :padding, length: 1 20 | hide :padding 21 | port16 :out_port, initial_value: -> { :none } 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/type/mac_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/mac' 5 | require 'pio/monkey_patch/integer' 6 | require 'pio/monkey_patch/uint' 7 | 8 | module Pio 9 | module Type 10 | # MAC address 11 | class MacAddress < BinData::Primitive 12 | array :octets, type: :uint8, initial_length: 6 13 | 14 | def set(value) 15 | self.octets = Mac.new(value).to_a 16 | end 17 | 18 | def get 19 | Mac.new(octets.reduce('') do |str, each| 20 | str + format('%02x', each) 21 | end.hex) 22 | end 23 | 24 | def to_bytes 25 | octets.map(&:to_hex).join(', ') 26 | end 27 | 28 | def inspect 29 | %("#{get}") 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/send_out_port.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/port32' 5 | 6 | # Base module. 7 | module Pio 8 | module OpenFlow13 9 | # Output to switch port. 10 | class SendOutPort < OpenFlow::Action 11 | NO_BUFFER = 0xffff 12 | 13 | action_header action_type: 0, action_length: 16 14 | port32 :port 15 | uint16 :max_length, initial_value: NO_BUFFER 16 | uint48 :padding 17 | 18 | def initialize(port) 19 | super(port: port) 20 | end 21 | 22 | def max_length 23 | case @format.max_length 24 | when NO_BUFFER 25 | :no_buffer 26 | else 27 | @format.max_length 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /tasks/flog.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'flog' 5 | 6 | desc 'Analyze for code complexity' 7 | task :flog do 8 | flog = Flog.new(continue: true) 9 | flog.flog(*FileList['lib/**/*.rb']) 10 | threshold = 10 11 | 12 | bad_methods = flog.totals.select do |name, score| 13 | /##{flog.no_method}$/ !~ name && score > threshold 14 | end 15 | bad_methods.sort { |a, b| a[1] <=> b[1] }.reverse_each do |name, score| 16 | printf "%8.1f: %s\n", score, name 17 | end 18 | unless bad_methods.empty? 19 | $stderr.puts "#{bad_methods.size} methods "\ 20 | "have a complexity > #{threshold}" 21 | end 22 | end 23 | rescue LoadError 24 | task :flog do 25 | $stderr.puts 'Flog is disabled' 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_destination_mac_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set a header field. 9 | class SetDestinationMacAddress < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::DestinationMacAddress::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 6 16 | mac_address :mac_address 17 | string :padding, length: 2 18 | hide :padding 19 | 20 | def initialize(mac_address) 21 | super mac_address: mac_address 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/pio/open_flow/nicira_resubmit_table_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/nicira_resubmit_table' 4 | 5 | describe Pio::OpenFlow::NiciraResubmitTable do 6 | describe '.new' do 7 | When(:nicira_resubmit_table) do 8 | Pio::OpenFlow::NiciraResubmitTable.new(options) 9 | end 10 | 11 | context 'with in_port: 1, table: 1' do 12 | Given(:options) { { in_port: 1, table: 1 } } 13 | 14 | Then { nicira_resubmit_table.action_type == 0xffff } 15 | Then { nicira_resubmit_table.action_length == 16 } 16 | Then { nicira_resubmit_table.vendor == 0x2320 } 17 | Then { nicira_resubmit_table.subtype == 14 } 18 | Then { nicira_resubmit_table.in_port == 1 } 19 | Then { nicira_resubmit_table.table == 1 } 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/error/hello_failed.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/error_message' 4 | require 'pio/open_flow/hello_failed_code' 5 | require 'pio/open_flow/message' 6 | require 'pio/open_flow10/error/error_type10' 7 | 8 | module Pio 9 | module OpenFlow10 10 | module Error 11 | # Hello failed error. 12 | class HelloFailed < OpenFlow::Message 13 | open_flow_header version: 1, 14 | type: OpenFlow::ErrorMessage.type, 15 | length: -> { header_length + 4 + description.length } 16 | 17 | error_type10 :error_type, value: -> { :hello_failed } 18 | hello_failed_code :error_code 19 | string :description, read_length: -> { length - header_length - 4 } 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_vlan_priority.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | 5 | module Pio 6 | module OpenFlow10 7 | # An action to modify the VLAN priority of a packet. 8 | class SetVlanPriority < OpenFlow::Action 9 | action_header action_type: 2, action_length: 8 10 | uint16 :vlan_priority 11 | string :padding, length: 2 12 | hide :padding 13 | 14 | def initialize(number) 15 | priority = number.to_i 16 | if priority < 0 || priority > 7 17 | raise ArgumentError, 'VLAN priority must be between 0 and 7 inclusive' 18 | end 19 | super(vlan_priority: priority) 20 | rescue NoMethodError 21 | raise TypeError, 'VLAN priority must be an Integer.' 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/error/bad_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/error_message' 4 | require 'pio/open_flow/message' 5 | require 'pio/open_flow10/error/bad_request/bad_request_code' 6 | require 'pio/open_flow10/error/error_type10' 7 | 8 | module Pio 9 | module OpenFlow10 10 | module Error 11 | # Bad request error. 12 | class BadRequest < OpenFlow::Message 13 | open_flow_header version: 1, 14 | type: OpenFlow::ErrorMessage.type, 15 | length: -> { header_length + 4 + raw_data.length } 16 | error_type10 :error_type, value: -> { :bad_request } 17 | bad_request_code :error_code 18 | string :raw_data, read_length: -> { length - header_length - 4 } 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_arp_sender_protocol_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set ARP sender protocol address field 9 | class SetArpSenderProtocolAddress < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::ArpSenderProtocolAddress::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 4 16 | ip_address :ip_address 17 | string :padding, length: 4 18 | hide :padding 19 | 20 | def initialize(ip_address) 21 | super ip_address: ip_address 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/set_arp_sender_hardware_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/action' 4 | require 'pio/open_flow13/match' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Set ARP sender hardware address field 9 | class SetArpSenderHardwareAddress < OpenFlow::Action 10 | action_header action_type: 25, action_length: 16 11 | 12 | uint16 :oxm_class, value: Match::OpenFlowBasicValue::OXM_CLASS 13 | bit7 :oxm_field, value: Match::ArpSenderHardwareAddress::OXM_FIELD 14 | bit1 :oxm_hasmask, value: 0 15 | uint8 :oxm_length, value: 6 16 | mac_address :mac_address 17 | string :padding, length: 2 18 | hide :padding 19 | 20 | def initialize(mac_address) 21 | super mac_address: mac_address 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/aggregate_stats/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/match10' 5 | require 'pio/open_flow10/port16' 6 | require 'pio/open_flow10/stats_type' 7 | 8 | module Pio 9 | module OpenFlow10 10 | # OpenFlow 1.0 Aggregate Stats messages 11 | module AggregateStats 12 | # OpenFlow 1.0 Aggregate Stats Request message 13 | class Request < OpenFlow::Message 14 | open_flow_header version: 1, type: 16, length: 56 15 | stats_type :stats_type, value: -> { :aggregate } 16 | uint16 :flags 17 | match10 :match 18 | uint8 :table_id, initial_value: 0xff 19 | string :padding, length: 1 20 | hide :padding 21 | port16 :out_port, initial_value: -> { :none } 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/nicira_conjunction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/nicira_action' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # NXAST_CONJUNCTION action 8 | class NiciraConjunction < OpenFlow::NiciraAction 9 | nicira_action_header action_type: 0xffff, 10 | action_length: 16, 11 | subtype: 34 12 | uint8 :_clause 13 | uint8 :n_clauses 14 | uint32 :conjunction_id 15 | 16 | def initialize(options) 17 | super(_clause: options[:clause] - 1, 18 | n_clauses: options[:n_clauses], 19 | conjunction_id: options[:conjunction_id]) 20 | end 21 | 22 | def clause 23 | _clause + 1 24 | end 25 | end 26 | end 27 | NiciraConjunction = OpenFlow13::NiciraConjunction 28 | end 29 | -------------------------------------------------------------------------------- /features/open_flow13/nicira_stack_pop.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: NiciraStackPop 3 | 4 | Pops field[offset: offset + n_bits] from top of the stack. 5 | 6 | Scenario: new(:reg0) 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::NiciraStackPop.new(:reg0) 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | offset | 0 | 14 | | n_bits | 32 | 15 | | field | :reg0 | 16 | 17 | Scenario: new(:reg0, n_bits: 16, offset: 16) 18 | When I create an OpenFlow action with: 19 | """ 20 | Pio::NiciraStackPop.new(:reg0, n_bits: 16, offset: 16) 21 | """ 22 | Then the action has the following fields and values: 23 | | field | value | 24 | | offset | 16 | 25 | | n_bits | 16 | 26 | | field | :reg0 | 27 | -------------------------------------------------------------------------------- /features/open_flow10/features_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Features::Request 3 | 4 | Upon OpenFlow channel establishment, the controller sends a 5 | Features::Request message. 6 | 7 | Scenario: new 8 | When I create an OpenFlow message with: 9 | """ 10 | Pio::Features::Request.new 11 | """ 12 | Then the message has the following fields and values: 13 | | field | value | 14 | | transaction_id | 0 | 15 | | xid | 0 | 16 | 17 | Scenario: new(transaction_id: 123) 18 | When I create an OpenFlow message with: 19 | """ 20 | Pio::Features::Request.new(transaction_id: 123) 21 | """ 22 | Then the message has the following fields and values: 23 | | field | value | 24 | | transaction_id | 123 | 25 | | xid | 123 | 26 | -------------------------------------------------------------------------------- /features/open_flow13/nicira_stack_push.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: NiciraStackPush 3 | 4 | Pushes field[offset: offset + n_bits] to top of the stack. 5 | 6 | Scenario: new(:reg0) 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::NiciraStackPush.new(:reg0) 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | offset | 0 | 14 | | n_bits | 32 | 15 | | field | :reg0 | 16 | 17 | Scenario: new(:reg0, n_bits: 16, offset: 16) 18 | When I create an OpenFlow action with: 19 | """ 20 | Pio::NiciraStackPush.new(:reg0, n_bits: 16, offset: 16) 21 | """ 22 | Then the action has the following fields and values: 23 | | field | value | 24 | | offset | 16 | 25 | | n_bits | 16 | 26 | | field | :reg0 | 27 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/write_metadata_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/write_metadata' 4 | 5 | describe Pio::OpenFlow13::WriteMetadata do 6 | describe '.new' do 7 | When(:write_metadata) { Pio::OpenFlow13::WriteMetadata.new(options) } 8 | 9 | context 'with metadata: 1' do 10 | Given(:options) do 11 | { 12 | metadata: 0x1 13 | } 14 | end 15 | Then { write_metadata.metadata == 1 } 16 | Then { write_metadata.metadata_mask.zero? } 17 | end 18 | 19 | context 'with metadata: 1, metadata_mask: 1' do 20 | Given(:options) do 21 | { 22 | metadata: 0x1, 23 | metadata_mask: 0x1 24 | } 25 | end 26 | Then { write_metadata.metadata == 1 } 27 | Then { write_metadata.metadata_mask == 1 } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_source_ip_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_source_ip_address' 4 | 5 | describe Pio::OpenFlow10::SetSourceIpAddress do 6 | describe '.new' do 7 | context "with '1.2.3.4'" do 8 | When(:set_source_ip_addr) do 9 | Pio::OpenFlow10::SetSourceIpAddress.new('1.2.3.4') 10 | end 11 | 12 | describe '#ip_address' do 13 | Then { set_source_ip_addr.ip_address == '1.2.3.4' } 14 | end 15 | 16 | describe '#action_type' do 17 | Then { set_source_ip_addr.action_type == 6 } 18 | end 19 | 20 | describe '#action_length' do 21 | Then { set_source_ip_addr.action_length == 8 } 22 | end 23 | 24 | describe '#to_binary' do 25 | Then { set_source_ip_addr.to_binary.length == 8 } 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_tos_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_tos' 4 | 5 | describe Pio::OpenFlow10::SetTos do 6 | describe '.new' do 7 | context 'with 32' do 8 | When(:set_tos) { Pio::OpenFlow10::SetTos.new(32) } 9 | 10 | describe '#type_of_service' do 11 | Then { set_tos.type_of_service == 32 } 12 | end 13 | 14 | describe '#action_type' do 15 | Then { set_tos.action_type == 8 } 16 | end 17 | 18 | describe '#action_length' do 19 | Then { set_tos.action_length == 8 } 20 | end 21 | 22 | describe '#to_binary' do 23 | Then { set_tos.to_binary.length == 8 } 24 | end 25 | end 26 | 27 | context 'with 1' do 28 | When(:set_tos) { Pio::OpenFlow10::SetTos.new(1) } 29 | Then { set_tos == Failure(ArgumentError) } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /features/open_flow13/features_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: Features::Request 3 | Scenario: new 4 | When I create an OpenFlow message with: 5 | """ 6 | Pio::Features::Request.new 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | version | 4 | 11 | | transaction_id | 0 | 12 | | xid | 0 | 13 | | body | | 14 | 15 | Scenario: new(transaction_id: 123) 16 | When I create an OpenFlow message with: 17 | """ 18 | Pio::Features::Request.new(transaction_id: 123) 19 | """ 20 | Then the message has the following fields and values: 21 | | field | value | 22 | | version | 4 | 23 | | transaction_id | 123 | 24 | | xid | 123 | 25 | | body | | 26 | -------------------------------------------------------------------------------- /lib/pio/type/ip_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'pio/ipv4_address' 5 | 6 | module Pio 7 | module Type 8 | # IP address 9 | class IpAddress < BinData::Primitive 10 | array :octets, type: :uint8, initial_length: 4 11 | 12 | def set(value) 13 | self.octets = IPv4Address.new(value).to_a 14 | end 15 | 16 | def get 17 | IPv4Address.new(octets.map { |each| format('%d', each) }.join('.')) 18 | end 19 | 20 | def >>(other) 21 | get.to_i >> other 22 | end 23 | 24 | def &(other) 25 | get.to_i & other 26 | end 27 | 28 | def ==(other) 29 | get == other 30 | end 31 | 32 | def to_bytes 33 | octets.map(&:to_hex).join(', ') 34 | end 35 | 36 | def inspect 37 | %("#{get}") 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_destination_ip_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_destination_ip_address' 4 | 5 | describe Pio::OpenFlow10::SetDestinationIpAddress do 6 | describe '.new' do 7 | context "with '1.2.3.4'" do 8 | When(:set_destination_ip_addr) do 9 | Pio::OpenFlow10::SetDestinationIpAddress.new('1.2.3.4') 10 | end 11 | 12 | describe '#ip_address' do 13 | Then { set_destination_ip_addr.ip_address == '1.2.3.4' } 14 | end 15 | 16 | describe '#action_type' do 17 | Then { set_destination_ip_addr.action_type == 7 } 18 | end 19 | 20 | describe '#action_length' do 21 | Then { set_destination_ip_addr.action_length == 8 } 22 | end 23 | 24 | describe '#to_binary' do 25 | Then { set_destination_ip_addr.to_binary.length == 8 } 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/flow_mod.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow' 4 | require 'pio/open_flow10/actions' 5 | require 'pio/open_flow10/flow_mod/command' 6 | require 'pio/open_flow10/match10' 7 | 8 | module Pio 9 | module OpenFlow10 10 | # OpenFlow 1.0 Flow Mod message. 11 | class FlowMod < OpenFlow::Message 12 | open_flow_header version: 1, type: 14, 13 | length: -> { 72 + actions.binary.length } 14 | 15 | match10 :match 16 | uint64 :cookie 17 | command :command 18 | uint16 :idle_timeout 19 | uint16 :hard_timeout 20 | uint16 :priority 21 | uint32 :buffer_id 22 | uint16 :out_port 23 | flags_16bit :flags, 24 | %i[send_flow_rem 25 | check_overwrap 26 | emerg] 27 | actions10 :actions, length: -> { length - 72 } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/set_tos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/monkey_patch/integer' 4 | require 'pio/open_flow/action' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # An action to modify the IP ToS/DSCP field of a packet. 9 | class SetTos < OpenFlow::Action 10 | action_header action_type: 8, action_length: 8 11 | uint8 :type_of_service 12 | string :padding, length: 3 13 | hide :padding 14 | 15 | def initialize(type_of_service) 16 | # tos (IP ToS) value consists of 8 bits, of which only the 17 | # 6 high-order bits belong to DSCP, the 2 low-order bits must 18 | # be zero. 19 | unless type_of_service.unsigned_8bit? && (type_of_service % 4).zero? 20 | raise ArgumentError, 'Invalid type_of_service (ToS) value.' 21 | end 22 | super(type_of_service: type_of_service) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /features/open_flow10/description_stats_reply.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: DescriptionStats::Reply 3 | 4 | Information about the switch manufacturer, hardware revision, 5 | software revision, serial number, and a description field is 6 | available from a Description Stats Reply. 7 | 8 | Scenario: read 9 | When I parse a file named "open_flow10/description_stats_reply.raw" with "DescriptionStats::Reply" class 10 | Then the message has the following fields and values: 11 | | field | value | 12 | | version | 1 | 13 | | transaction_id | 12 | 14 | | xid | 12 | 15 | | stats_type | :description | 16 | | manufacturer | Nicira, Inc. | 17 | | hardware | | 18 | | software | | 19 | | serial_number | | 20 | | datapath | | 21 | -------------------------------------------------------------------------------- /features/.nav: -------------------------------------------------------------------------------- 1 | - ethernet_header.feature 2 | - ipv4_header.feature 3 | - arp: 4 | - arp.feature 5 | - arp_request.feature 6 | - arp_reply.feature 7 | - icmp: 8 | - icmp.feature 9 | - icmp_request.feature 10 | - icmp_reply.feature 11 | - lldp.feature 12 | - dhcp.feature 13 | - udp.feature 14 | - open_flow10: 15 | - hello.feature 16 | - hello_failed.feature 17 | - bad_request.feature 18 | - echo_request.feature 19 | - echo_reply.feature 20 | - features_request.feature 21 | - features_reply.feature 22 | - packet_in.feature 23 | - flow_removed.feature 24 | - port_status.feature 25 | - packet_out.feature 26 | - flow_mod.feature 27 | - exact_match.feature 28 | - open_flow13: 29 | - hello.feature 30 | - hello_failed.feature 31 | - bad_request.feature 32 | - echo_request.feature 33 | - echo_reply.feature 34 | - features_request.feature 35 | - features_reply.feature 36 | - match.feature 37 | -------------------------------------------------------------------------------- /lib/pio/icmp/message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/class_inspector' 4 | require 'pio/icmp/format' 5 | require 'pio/instance_inspector' 6 | require 'pio/message' 7 | require 'pio/ruby_dumper' 8 | 9 | module Pio 10 | class Icmp 11 | # Base class of Icmp::Request and Icmp::Reply. 12 | class Message < Pio::Message 13 | extend ClassInspector 14 | include InstanceInspector 15 | 16 | def self.fields 17 | Icmp::Format.fields 18 | end 19 | 20 | def self.create(format) 21 | allocate.tap do |message| 22 | message.instance_variable_set :@format, format 23 | end 24 | end 25 | 26 | def initialize(user_options) 27 | @format = Icmp::Format.new(parse_options(user_options)) 28 | end 29 | 30 | def method_missing(method, *args) 31 | @format.__send__(method, *args) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pio/lldp/port_id_tlv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | class Lldp 7 | # Port ID TLV 8 | class PortIdTlv < BinData::Primitive 9 | endian :big 10 | 11 | bit7 :tlv_type, value: 2 12 | bit9 :tlv_info_length, initial_value: -> { port_id.num_bytes + 1 } 13 | uint8 :subtype, initial_value: 7 14 | string :port_id, read_length: -> { tlv_info_length - 1 } 15 | 16 | def get 17 | tmp_id = port_id 18 | 19 | if subtype == 7 20 | BinData::Uint32be.read tmp_id 21 | else 22 | tmp_id 23 | end 24 | end 25 | 26 | def set(value) 27 | self.port_id = if subtype == 7 28 | BinData::Uint32be.new(value).to_binary_s 29 | else 30 | value 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /fixtures/icmp/icmp_reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac 5 | 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac 6 | 0x08, 0x00, # ether_type 7 | 0b0100_0101, # ip_version, ip_header_length 8 | 0x00, # ip_type_of_service 9 | 0x00, 0x36, # ip_total_length 10 | 0x00, 0x00, # ip_identifier 11 | 0b000_0000000000000, # ip_flag, ip_fragment 12 | 0x80, # ip_ttl 13 | 0x01, # ip_protocol 14 | 0x12, 0x75, # ip_header_checksum 15 | 0xc0, 0xa8, 0x53, 0xfe, # source_ip_address 16 | 0xc0, 0xa8, 0x53, 0x03, # destination_ip_address 17 | 0x00, # icmp_type 18 | 0x00, # icmp_code 19 | 0x6f, 0xf5, # icmp_checksum 20 | 0x01, 0x00, # icmp_identifier 21 | 0x00, 0x6f, # icmp_sequence_number 22 | 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, # echo_data 23 | ].pack('C20nC46') 24 | -------------------------------------------------------------------------------- /fixtures/icmp/icmp_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | [ 4 | 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac 5 | 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac 6 | 0x08, 0x00, # ether_type 7 | 0b0100_0101, # ip_version, ip_header_length 8 | 0x00, # ip_type_of_service 9 | 0x00, 0x36, # ip_total_length 10 | 0x00, 0x00, # ip_identifier 11 | 0b000_0000000000000, # ip_flag, ip_fragment 12 | 0x80, # ip_ttl 13 | 0x01, # ip_protocol 14 | 0x12, 0x75, # ip_header_checksum 15 | 0xc0, 0xa8, 0x53, 0x03, # source_ip_address 16 | 0xc0, 0xa8, 0x53, 0xfe, # destination_ip_address 17 | 0x08, # icmp_type 18 | 0x00, # icmp_code 19 | 0x67, 0xf5, # icmp_checksum 20 | 0x01, 0x00, # icmp_identifier 21 | 0x00, 0x6f, # icmp_sequence_number 22 | 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, # echo_data 23 | ].pack('C20nC46') 24 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/stats_reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/aggregate_stats/reply' 5 | require 'pio/open_flow10/description_stats/reply' 6 | require 'pio/open_flow10/flow_stats/reply' 7 | 8 | module Pio 9 | module OpenFlow10 10 | class Stats 11 | # Stats reply parser. 12 | class Reply < OpenFlow::Message 13 | open_flow_header version: 1, type: 17, length: 10 14 | stats_type :stats_type 15 | 16 | TYPE = { 17 | description: OpenFlow10::DescriptionStats::Reply, 18 | flow: OpenFlow10::FlowStats::Reply, 19 | aggregate: OpenFlow10::AggregateStats::Reply 20 | }.freeze 21 | 22 | def self.read(binary) 23 | TYPE.fetch(Format.read(binary).stats_type.to_sym).read(binary) 24 | rescue KeyError 25 | raise "Unknown stats type: #{stats_type}" 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/stats_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow' 4 | 5 | module Pio 6 | module OpenFlow13 7 | # OpenFlow 1.3 Stats Request message 8 | class StatsRequest < OpenFlow::Message 9 | # Request type of Stats Request message 10 | class StatsType < BinData::Primitive 11 | TYPES = %i[description 12 | flow 13 | aggregate 14 | table 15 | port 16 | queue 17 | vendor].freeze 18 | 19 | endian :big 20 | 21 | uint16 :stats_type 22 | 23 | def set(value) 24 | self.stats_type = TYPES.find_index(value) 25 | end 26 | 27 | def get 28 | TYPES[stats_type] 29 | end 30 | end 31 | 32 | open_flow_header version: 4, type: 16, length: 12 33 | stats_type :stats_type 34 | uint16 :stats_flags 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/open_flow13/hello.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: Hello 3 | Scenario: new 4 | When I create an OpenFlow message with: 5 | """ 6 | Pio::Hello.new 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | version | 4 | 11 | | transaction_id | 0 | 12 | | xid | 0 | 13 | | supported_versions | [:open_flow13] | 14 | 15 | Scenario: new(transaction_id: 123) 16 | When I create an OpenFlow message with: 17 | """ 18 | Pio::Hello.new(transaction_id: 123) 19 | """ 20 | Then the message has the following fields and values: 21 | | field | value | 22 | | version | 4 | 23 | | transaction_id | 123 | 24 | | xid | 123 | 25 | | supported_versions | [:open_flow13] | 26 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/error/hello_failed_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/error/hello_failed' 4 | 5 | describe Pio::OpenFlow10::Error::HelloFailed do 6 | it_should_behave_like('an OpenFlow message', 7 | Pio::OpenFlow10::Error::HelloFailed) 8 | 9 | describe '.new' do 10 | When(:hello_failed) { Pio::OpenFlow10::Error::HelloFailed.new(options) } 11 | 12 | context 'with {}' do 13 | Given(:options) { {} } 14 | 15 | Then { hello_failed.length == 12 } 16 | Then { hello_failed.error_type == :hello_failed } 17 | Then { hello_failed.error_code == :incompatible } 18 | Then { hello_failed.description == '' } 19 | end 20 | 21 | context "with description: 'error description'" do 22 | Given(:options) { { description: 'error description' } } 23 | 24 | Then { hello_failed.length == 29 } 25 | Then { hello_failed.description == 'error description' } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/error/hello_failed_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/error/hello_failed' 4 | 5 | describe Pio::OpenFlow13::Error::HelloFailed do 6 | it_should_behave_like('an OpenFlow message', 7 | Pio::OpenFlow13::Error::HelloFailed) 8 | 9 | describe '.new' do 10 | When(:hello_failed) { Pio::OpenFlow13::Error::HelloFailed.new(options) } 11 | 12 | context 'with {}' do 13 | Given(:options) { {} } 14 | 15 | Then { hello_failed.length == 12 } 16 | Then { hello_failed.error_type == :hello_failed } 17 | Then { hello_failed.error_code == :incompatible } 18 | Then { hello_failed.description == '' } 19 | end 20 | 21 | context "with description: 'error description'" do 22 | Given(:options) { { description: 'error description' } } 23 | 24 | Then { hello_failed.length == 29 } 25 | Then { hello_failed.description == 'error description' } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /features/open_flow10/hello.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Hello 3 | 4 | Hello messages are exchanged between the switch and controller upon 5 | connection startup. Hello messages have the version field set to the 6 | highest OpenFlow protocol version supported by the sender. 7 | 8 | Scenario: new 9 | When I create an OpenFlow message with: 10 | """ 11 | Pio::Hello.new 12 | """ 13 | Then the message has the following fields and values: 14 | | field | value | 15 | | version | 1 | 16 | | transaction_id | 0 | 17 | | xid | 0 | 18 | 19 | Scenario: new(transaction_id: 123) 20 | When I create an OpenFlow message with: 21 | """ 22 | Pio::Hello.new(transaction_id: 123) 23 | """ 24 | Then the message has the following fields and values: 25 | | field | value | 26 | | version | 1 | 27 | | transaction_id | 123 | 28 | | xid | 123 | 29 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/error/bad_request/bad_request_code.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | 5 | module Pio 6 | module OpenFlow10 7 | module Error 8 | class BadRequest < OpenFlow::Message 9 | # enum ofp_bad_request_code 10 | class BadRequestCode < BinData::Primitive 11 | ERROR_CODES = { 12 | bad_version: 0, 13 | bad_type: 1, 14 | bad_stats: 2, 15 | bad_vendor: 3, 16 | bad_subtype: 4, 17 | permissions_error: 5, 18 | bad_length: 6, 19 | buffer_empty: 7, 20 | buffer_unknown: 8 21 | }.freeze 22 | 23 | endian :big 24 | uint16 :error_code 25 | 26 | def get 27 | ERROR_CODES.invert.fetch(error_code) 28 | end 29 | 30 | def set(value) 31 | self.error_code = ERROR_CODES.fetch(value) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/open_flow10/table_stats_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: TableStats::Request 3 | 4 | Information about tables is requested with a Table Stats Request 5 | message. 6 | 7 | Scenario: new 8 | When I create an OpenFlow message with: 9 | """ 10 | Pio::TableStats::Request.new 11 | """ 12 | Then the message has the following fields and values: 13 | | field | value | 14 | | version | 1 | 15 | | transaction_id | 0 | 16 | | xid | 0 | 17 | | stats_type | :table | 18 | 19 | Scenario: new(transaction_id: 123) 20 | When I create an OpenFlow message with: 21 | """ 22 | Pio::TableStats::Request.new(transaction_id: 123) 23 | """ 24 | Then the message has the following fields and values: 25 | | field | value | 26 | | version | 1 | 27 | | transaction_id | 123 | 28 | | xid | 123 | 29 | | stats_type | :table | 30 | -------------------------------------------------------------------------------- /features/open_flow10/port_stats_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: PortStats::Request 3 | Scenario: new(:none) 4 | When I create an OpenFlow message with: 5 | """ 6 | Pio::OpenFlow10::PortStats::Request.new(:none) 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | version | 1 | 11 | | transaction_id | 0 | 12 | | xid | 0 | 13 | | stats_type | :port | 14 | | port | :none | 15 | 16 | Scenario: new(:none, transaction_id: 123) 17 | When I create an OpenFlow message with: 18 | """ 19 | Pio::OpenFlow10::PortStats::Request.new(:none, transaction_id: 123) 20 | """ 21 | Then the message has the following fields and values: 22 | | field | value | 23 | | version | 1 | 24 | | transaction_id | 123 | 25 | | xid | 123 | 26 | | stats_type | :port | 27 | | port | :none | 28 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/vendor_action.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'forwardable' 5 | 6 | module Pio 7 | module OpenFlow10 8 | # Vendor defined action 9 | class VendorAction 10 | # OpenFlow 1.0 OFPAT_VENDOR action format. 11 | class Format < BinData::Record 12 | endian :big 13 | 14 | uint16 :action_type, value: 0xffff 15 | uint16 :action_length, value: 8 16 | uint32 :vendor 17 | end 18 | 19 | def self.read(raw_data) 20 | allocate.tap do |strip_vlan| 21 | strip_vlan.instance_variable_set :@format, Format.read(raw_data) 22 | end 23 | end 24 | 25 | extend Forwardable 26 | 27 | def_delegators :@format, :action_type 28 | def_delegator :@format, :action_length, :length 29 | def_delegators :@format, :vendor 30 | def_delegator :@format, :to_binary_s, :to_binary 31 | 32 | def initialize(vendor) 33 | @format = Format.new(vendor: vendor) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/open_flow13/nicira_send_out_port.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: NiciraSendOutPort 3 | 4 | Outputs to the OpenFlow port number written to source[offset:offset+n_bits] 5 | 6 | Scenario: new(:reg0) 7 | When I create an OpenFlow action with: 8 | """ 9 | Pio::NiciraSendOutPort.new(:reg0) 10 | """ 11 | Then the action has the following fields and values: 12 | | field | value | 13 | | offset | 0 | 14 | | n_bits | 32 | 15 | | source | :reg0 | 16 | | max_length.to_hex | 0xffff | 17 | 18 | Scenario: new(:reg0, offset: 16, n_bits: 16, max_length: 256) 19 | When I create an OpenFlow action with: 20 | """ 21 | Pio::NiciraSendOutPort.new(:reg0, offset: 16, n_bits: 16, max_length: 256) 22 | """ 23 | Then the action has the following fields and values: 24 | | field | value | 25 | | offset | 16 | 26 | | n_bits | 16 | 27 | | source | :reg0 | 28 | | max_length | 256 | 29 | -------------------------------------------------------------------------------- /spec/pio/open_flow_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow' 4 | 5 | describe Pio::OpenFlow do 6 | describe 'switch_version' do 7 | When(:result) { Pio::OpenFlow.version = version } 8 | 9 | context 'with :OpenFlow10' do 10 | Given(:version) { :OpenFlow10 } 11 | Then { Pio::OpenFlow.version == :OpenFlow10 } 12 | end 13 | 14 | context 'with "OpenFlow10"' do 15 | Given(:version) { 'OpenFlow10' } 16 | Then { Pio::OpenFlow.version == :OpenFlow10 } 17 | end 18 | 19 | context 'with :OpenFlow13' do 20 | Given(:version) { :OpenFlow13 } 21 | Then { Pio::OpenFlow.version == :OpenFlow13 } 22 | end 23 | 24 | context 'with "OpenFlow13"' do 25 | Given(:version) { 'OpenFlow13' } 26 | Then { Pio::OpenFlow.version == :OpenFlow13 } 27 | end 28 | 29 | context 'with :OpenFlow100' do 30 | Given(:version) { :OpenFlow100 } 31 | Then do 32 | result == Failure(RuntimeError, 'OpenFlow100 is not supported yet.') 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/features/reply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/datapath_id' 4 | require 'pio/open_flow/message' 5 | 6 | module Pio 7 | module OpenFlow13 8 | # Features Request and Reply message. 9 | class Features 10 | # Features Reply message. 11 | class Reply < OpenFlow::Message 12 | open_flow_header version: 4, type: 6, length: 32 13 | datapath_id :datapath_id 14 | alias dpid datapath_id 15 | uint32 :n_buffers 16 | uint8 :n_tables 17 | uint8 :auxiliary_id 18 | uint16 :padding 19 | hide :padding 20 | flags_32bit(:capabilities, 21 | %i[flow_stats 22 | table_stats 23 | port_stats 24 | group_stats 25 | NOT_USED 26 | ip_reasm 27 | queue_stats 28 | NOT_USED 29 | port_blocked]) 30 | uint32 :reserved 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_source_mac_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_source_mac_address' 4 | 5 | describe Pio::OpenFlow10::SetSourceMacAddress do 6 | describe '.new' do 7 | Given(:set_source_mac_address) do 8 | Pio::OpenFlow10::SetSourceMacAddress.new(mac_address) 9 | end 10 | 11 | context "with '11:22:33:44:55:66'" do 12 | When(:mac_address) { '11:22:33:44:55:66' } 13 | Then { set_source_mac_address.mac_address == '11:22:33:44:55:66' } 14 | 15 | describe '#to_binary' do 16 | Then { set_source_mac_address.to_binary.length == 16 } 17 | end 18 | end 19 | 20 | context 'with 0x112233445566' do 21 | When(:mac_address) { 0x112233445566 } 22 | Then { set_source_mac_address.mac_address == '11:22:33:44:55:66' } 23 | end 24 | 25 | context "with Pio::Mac.new('11:22:33:44:55:66')" do 26 | When(:mac_address) { Pio::Mac.new('11:22:33:44:55:66') } 27 | Then { set_source_mac_address.mac_address == '11:22:33:44:55:66' } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /fixtures/open_flow10/description_stats_reply.raw: -------------------------------------------------------------------------------- 1 | , Nicira, Inc.Open vSwitch2.0.1NoneNone -------------------------------------------------------------------------------- /lib/pio/dhcp/frame.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/dhcp/dhcp_field' 4 | require 'pio/dhcp/field_util' 5 | require 'pio/ipv4_header' 6 | require 'pio/ethernet_header' 7 | require 'pio/udp_header' 8 | 9 | module Pio 10 | class Dhcp 11 | # Dhcp frame parser. 12 | class Frame < BinData::Record 13 | include FieldUtil 14 | 15 | OPTION_FIELD_LENGTH = 60 16 | 17 | include Ethernet 18 | include IPv4 19 | include UdpHeader 20 | 21 | endian :big 22 | ethernet_header ether_type: Ethernet::Type::IPV4 23 | ipv4_header ip_protocol: ProtocolNumber::UDP 24 | udp_header 25 | dhcp_field :dhcp 26 | string :padding, read_length: 0, initial_value: :ff_and_padding 27 | 28 | def to_binary 29 | to_binary_s 30 | end 31 | 32 | private 33 | 34 | def ff_and_padding 35 | padding_length = OPTION_FIELD_LENGTH - dhcp.optional_tlvs.num_bytes - 1 36 | [0xFF].pack('C') + (padding_length > 0 ? "\x00" * padding_length : '') 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/phy_port16_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio' 4 | 5 | describe Pio::OpenFlow10::PhyPort16 do 6 | describe '.new' do 7 | When(:phy_port) do 8 | Pio::OpenFlow10::PhyPort16.new(number: 1, 9 | mac_address: '11:22:33:44:55:66', 10 | name: 'port123', 11 | config: [:port_down], 12 | state: [:link_down], 13 | curr: %i[port_10gb_fd port_copper]) 14 | end 15 | 16 | Then { phy_port.number == 1 } 17 | Then { phy_port.mac_address == '11:22:33:44:55:66' } 18 | Then { phy_port.name == 'port123' } 19 | Then { phy_port.config == [:port_down] } 20 | Then { phy_port.state == [:link_down] } 21 | Then { phy_port.curr == %i[port_10gb_fd port_copper] } 22 | Then { phy_port.advertised.empty? } 23 | Then { phy_port.supported.empty? } 24 | Then { phy_port.peer.empty? } 25 | Then { !phy_port.to_binary_s.empty? } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/error/error_type13.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Pio 4 | module OpenFlow13 5 | module Error 6 | # enum ofp_error_type 7 | class ErrorType13 < BinData::Primitive 8 | ERROR_TYPES = { 9 | hello_failed: 0, 10 | bad_request: 1, 11 | bad_action: 2, 12 | bad_instruction: 3, 13 | bad_match: 4, 14 | flow_mod_failed: 5, 15 | group_mod_failed: 6, 16 | port_mod_failed: 7, 17 | table_mod_failed: 8, 18 | queue_operation_failed: 9, 19 | switch_config_failed: 10, 20 | role_request_failed: 11, 21 | meter_mod_failed: 12, 22 | table_features_failed: 13, 23 | experimenter: 0xffff 24 | }.freeze 25 | 26 | endian :big 27 | uint16 :error_type 28 | 29 | def get 30 | ERROR_TYPES.invert.fetch(error_type) 31 | end 32 | 33 | def set(value) 34 | self.error_type = ERROR_TYPES.fetch(value) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /pio.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'pio/version' 6 | 7 | Gem::Specification.new do |gem| 8 | gem.name = 'pio' 9 | gem.version = Pio::VERSION 10 | gem.summary = 'Packet parser and generator.' 11 | gem.description = 'Pure ruby packet parser and generator.' 12 | 13 | gem.licenses = %w[GPLv2 MIT] 14 | 15 | gem.authors = ['Yasuhito Takamiya'] 16 | gem.email = ['yasuhito@gmail.com'] 17 | gem.homepage = 'http://github.com/trema/pio' 18 | 19 | gem.files = %w[CONTRIBUTING.md Rakefile pio.gemspec] 20 | gem.files += Dir.glob('lib/**/*.rb') 21 | gem.files += Dir.glob('spec/**/*') 22 | 23 | gem.require_paths = ['lib'] 24 | 25 | gem.extra_rdoc_files = %w[README.md CHANGELOG.md CONTRIBUTING.md] 26 | gem.test_files = Dir.glob('spec/**/*') 27 | gem.test_files += Dir.glob('features/**/*') 28 | 29 | gem.required_ruby_version = '>= 2.3.0' 30 | 31 | gem.add_dependency 'bindata', '~> 2.1.0' 32 | gem.add_dependency 'activesupport', '~> 5.1.0' 33 | end 34 | -------------------------------------------------------------------------------- /lib/pio/open_flow/error_message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/module/attribute_accessors' 4 | require 'active_support/core_ext/string/inflections' 5 | 6 | module Pio 7 | module OpenFlow 8 | # Error message parser 9 | module ErrorMessage 10 | mattr_reader(:type) { 1 } 11 | 12 | # rubocop:disable AbcSize 13 | def read(binary) 14 | body = OpenFlow::Header.read(binary).body 15 | error = const_get(:BodyParser).read(body) 16 | klass = error_classes.find do |each| 17 | each.name.split('::').last.underscore == error.error_type.to_s 18 | end 19 | unless klass 20 | raise 'Unknown error message '\ 21 | "(type=#{error.error_type}, code=#{error.error_code})" 22 | end 23 | klass.read binary 24 | end 25 | # rubocop:enable AbcSize 26 | 27 | def error_classes 28 | OpenFlow::Message.descendants.select do |each| 29 | each.parents.include? parent.const_get(:Error) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/meter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'forwardable' 4 | require 'pio/open_flow/instruction' 5 | 6 | # Base module. 7 | module Pio 8 | module OpenFlow13 9 | # Apply meter (rate limiter) 10 | class Meter < OpenFlow::Instruction 11 | # OpenFlow 1.3.4 OFPIT_METER instruction format 12 | class Format < BinData::Record 13 | endian :big 14 | 15 | uint16 :instruction_type, value: 6 16 | uint16 :instruction_length, value: 8 17 | uint32 :meter_id 18 | end 19 | 20 | def self.read(raw_data) 21 | allocate.tap do |meter| 22 | meter.instance_variable_set :@format, Format.read(raw_data) 23 | end 24 | end 25 | 26 | extend Forwardable 27 | 28 | def_delegators :@format, :instruction_type 29 | def_delegators :@format, :instruction_length 30 | def_delegators :@format, :meter_id 31 | def_delegators :@format, :to_binary_s 32 | 33 | def initialize(meter_id) 34 | @format = Format.new(meter_id: meter_id) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/pio/lldp/options.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/options' 4 | 5 | module Pio 6 | class Lldp 7 | # User options for creating an LLDP frame. 8 | class Options < Pio::Options 9 | mandatory_option :dpid 10 | mandatory_option :port_number 11 | option :destination_mac 12 | option :source_mac 13 | 14 | DEFAULT_DESTINATION_MAC = '01:80:c2:00:00:0e' 15 | DEFAULT_SOURCE_MAC = '01:02:03:04:05:06' 16 | 17 | def initialize(options) 18 | validate options 19 | @dpid = options[:dpid].freeze 20 | @port_id = options[:port_number].freeze 21 | @destination_mac = 22 | Mac.new(options[:destination_mac] || DEFAULT_DESTINATION_MAC).freeze 23 | @source_mac = 24 | Mac.new(options[:source_mac] || DEFAULT_SOURCE_MAC).freeze 25 | end 26 | 27 | def to_hash 28 | { 29 | chassis_id: @dpid, 30 | port_id: @port_id, 31 | destination_mac: @destination_mac, 32 | source_mac: @source_mac 33 | }.freeze 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /features/open_flow10/barrier_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Barrier::Request 3 | 4 | When the controller wants to ensure message dependencies have been 5 | met or wants to receive notifications for completed operations, it 6 | may use an Barrier Request message. This message has no body. 7 | 8 | Scenario: new 9 | When I create an OpenFlow message with: 10 | """ 11 | Pio::Barrier::Request.new 12 | """ 13 | Then the message has the following fields and values: 14 | | field | value | 15 | | version | 1 | 16 | | transaction_id | 0 | 17 | | xid | 0 | 18 | | body | | 19 | 20 | Scenario: new(transaction_id: 123) 21 | When I create an OpenFlow message with: 22 | """ 23 | Pio::Barrier::Request.new(transaction_id: 123) 24 | """ 25 | Then the message has the following fields and values: 26 | | field | value | 27 | | version | 1 | 28 | | transaction_id | 123 | 29 | | xid | 123 | 30 | | body | | 31 | -------------------------------------------------------------------------------- /lib/pio/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/ethernet_frame' 4 | require 'pio/ethernet_header' 5 | require 'pio/ipv4_header' 6 | 7 | module Pio 8 | # Raw data parser. 9 | class Parser 10 | # IPv4 packet parser 11 | class IPv4Packet < BinData::Record 12 | include Ethernet 13 | include IPv4 14 | 15 | endian :big 16 | 17 | ethernet_header ether_type: Ethernet::Type::IPV4 18 | ipv4_header 19 | 20 | uint16 :transport_source_port 21 | uint16 :transport_destination_port 22 | rest :rest 23 | end 24 | 25 | # rubocop:disable MethodLength 26 | def self.read(raw_data) 27 | ethernet_frame = EthernetFrame.read(raw_data) 28 | case ethernet_frame.ether_type 29 | when Ethernet::Type::IPV4, Ethernet::Type::VLAN 30 | IPv4Packet.read raw_data 31 | when Ethernet::Type::ARP 32 | Pio::Arp.read raw_data 33 | when Ethernet::Type::LLDP 34 | Pio::Lldp.read raw_data 35 | else 36 | ethernet_frame 37 | end 38 | end 39 | # rubocop:enable MethodLength 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_destination_mac_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_destination_mac_address' 4 | 5 | describe Pio::OpenFlow10::SetDestinationMacAddress do 6 | describe '.new' do 7 | Given(:set_destination_mac_address) do 8 | Pio::OpenFlow10::SetDestinationMacAddress.new(mac_address) 9 | end 10 | 11 | context "with '11:22:33:44:55:66'" do 12 | When(:mac_address) { '11:22:33:44:55:66' } 13 | Then { set_destination_mac_address.mac_address == '11:22:33:44:55:66' } 14 | 15 | describe '#to_binary' do 16 | Then { set_destination_mac_address.to_binary.length == 16 } 17 | end 18 | end 19 | 20 | context 'with 0x112233445566' do 21 | When(:mac_address) { 0x112233445566 } 22 | Then { set_destination_mac_address.mac_address == '11:22:33:44:55:66' } 23 | end 24 | 25 | context "with Pio::Mac.new('11:22:33:44:55:66')" do 26 | When(:mac_address) { Pio::Mac.new('11:22:33:44:55:66') } 27 | Then { set_destination_mac_address.mac_address == '11:22:33:44:55:66' } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /features/open_flow13/meter.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: Meter 3 | Scenario: new(1) 4 | When I create an OpenFlow instruction with: 5 | """ 6 | Pio::OpenFlow13::Meter.new(1) 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | class | Pio::OpenFlow13::Meter | 11 | | instruction_type | 6 | 12 | | instruction_length | 8 | 13 | | to_binary_s.length | 8 | 14 | | meter_id | 1 | 15 | 16 | Scenario: read 17 | When I parse a file named "open_flow13/instruction_meter.raw" with "Pio::OpenFlow13::Meter" class 18 | Then the message has the following fields and values: 19 | | field | value | 20 | | class | Pio::OpenFlow13::Meter | 21 | | instruction_type | 6 | 22 | | instruction_length | 8 | 23 | | to_binary_s.length | 8 | 24 | | meter_id | 1 | 25 | -------------------------------------------------------------------------------- /features/open_flow10/port_status.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: PortStatus 3 | 4 | As physical ports are added, modified, and removed from the 5 | datapath, the controller needs to be informed with the PortStatus 6 | message. 7 | 8 | Scenario: read 9 | When I parse a file named "open_flow10/port_status.raw" with "PortStatus" class 10 | Then the message has the following fields and values: 11 | | field | value | 12 | | transaction_id | 4 | 13 | | xid | 4 | 14 | | reason | :delete | 15 | | number | 65533 | 16 | | mac_address | 01:02:03:04:05:06 | 17 | | name | foo | 18 | | config | [:no_flood] | 19 | | state | [:stp_forward, :stp_block] | 20 | | curr | [:port_10mb_hd] | 21 | | advertised | [:port_1gb_fd] | 22 | | supported | [:port_autoneg] | 23 | | peer | [:port_pause_asym] | 24 | 25 | -------------------------------------------------------------------------------- /spec/pio/open_flow13/nicira_send_out_port_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow13/nicira_send_out_port' 4 | 5 | describe Pio::OpenFlow13::NiciraSendOutPort do 6 | describe '.new' do 7 | When(:nicira_send_out_port) do 8 | Pio::OpenFlow13::NiciraSendOutPort.new(source) 9 | end 10 | 11 | context 'with :reg0' do 12 | Given(:source) { :reg0 } 13 | 14 | Invariant do 15 | nicira_send_out_port.n_bits == 16 | nicira_send_out_port._source[:oxm_length] * 8 17 | end 18 | 19 | Then { nicira_send_out_port.action_type == 0xffff } 20 | Then { nicira_send_out_port.action_length == 24 } 21 | Then { nicira_send_out_port.vendor == 0x2320 } 22 | Then { nicira_send_out_port.subtype == 15 } 23 | Then { nicira_send_out_port.offset.zero? } 24 | Then { nicira_send_out_port.n_bits == 32 } 25 | Then { nicira_send_out_port.source == :reg0 } 26 | Then { nicira_send_out_port._source[:oxm_class] == 1 } 27 | Then { nicira_send_out_port._source[:oxm_field].zero? } 28 | Then { nicira_send_out_port._source[:oxm_length] == 4 } 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /features/open_flow10/description_stats_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: DescriptionStats::Request 3 | 4 | Information about the switch manufacturer, hardware revision, 5 | software revision, serial number, and a description field is 6 | available by sending a Description Stats Request. 7 | 8 | Scenario: new 9 | When I create an OpenFlow message with: 10 | """ 11 | Pio::DescriptionStats::Request.new 12 | """ 13 | Then the message has the following fields and values: 14 | | field | value | 15 | | version | 1 | 16 | | transaction_id | 0 | 17 | | xid | 0 | 18 | | stats_type | :description | 19 | 20 | Scenario: new(transaction_id: 123) 21 | When I create an OpenFlow message with: 22 | """ 23 | Pio::DescriptionStats::Request.new(transaction_id: 123) 24 | """ 25 | Then the message has the following fields and values: 26 | | field | value | 27 | | version | 1 | 28 | | transaction_id | 123 | 29 | | xid | 123 | 30 | | stats_type | :description | 31 | -------------------------------------------------------------------------------- /features/open_flow10/barrier_reply.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: Barrier::Reply 3 | 4 | When the switch received a Barrier Request message, the switch must 5 | finish processing all previously-received messages before executing 6 | any messages beyond the Barrier Request. When such processing is 7 | complete, the switch must send an Barrier Reply message with the xid 8 | of the original request. 9 | 10 | Scenario: new 11 | When I create an OpenFlow message with: 12 | """ 13 | Pio::Barrier::Reply.new 14 | """ 15 | Then the message has the following fields and values: 16 | | field | value | 17 | | version | 1 | 18 | | transaction_id | 0 | 19 | | xid | 0 | 20 | | body | | 21 | 22 | Scenario: new(transaction_id: 123) 23 | When I create an OpenFlow message with: 24 | """ 25 | Pio::Barrier::Reply.new(transaction_id: 123) 26 | """ 27 | Then the message has the following fields and values: 28 | | field | value | 29 | | version | 1 | 30 | | transaction_id | 123 | 31 | | xid | 123 | 32 | | body | | 33 | -------------------------------------------------------------------------------- /features/open_flow13/goto_table.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: GotoTable 3 | Scenario: new(1) 4 | When I create an OpenFlow instruction with: 5 | """ 6 | Pio::OpenFlow13::GotoTable.new(1) 7 | """ 8 | Then the message has the following fields and values: 9 | | field | value | 10 | | class | Pio::OpenFlow13::GotoTable | 11 | | instruction_type | 1 | 12 | | instruction_length | 8 | 13 | | to_binary_s.length | 8 | 14 | | table_id | 1 | 15 | 16 | Scenario: read 17 | When I parse a file named "open_flow13/instruction_goto_table.raw" with "Pio::OpenFlow13::GotoTable" class 18 | Then the message has the following fields and values: 19 | | field | value | 20 | | class | Pio::OpenFlow13::GotoTable | 21 | | instruction_type | 1 | 22 | | instruction_length | 8 | 23 | | to_binary_s.length | 8 | 24 | | table_id | 1 | 25 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | RELISH_PROJECT = 'trema/pio' 6 | FLAY_THRESHOLD = 1446 7 | 8 | task default: :travis 9 | task test: %i[spec cucumber] 10 | task travis: %i[test quality] 11 | 12 | desc 'Check for code quality' 13 | task quality: %i[reek flog flay rubocop] 14 | 15 | Dir.glob('tasks/*.rake').each { |each| import each } 16 | 17 | require 'pio/pcap' 18 | 19 | def dump_in_hex(data) 20 | hexdump = data.unpack('C*').map do |each| 21 | format('0x%02x', each) 22 | end 23 | puts "[#{hexdump.join(', ')}]" 24 | end 25 | 26 | desc 'Dump packet data file in Array' 27 | task :dump do 28 | unless ENV['PACKET_FILE'] 29 | raise 'Usage: rake PACKET_FILE="foobar.{pcap,raw}" dump' 30 | end 31 | packet_file = 32 | File.join(File.dirname(__FILE__), 'features/', ENV['PACKET_FILE']) 33 | case File.extname(packet_file) 34 | when '.raw' 35 | dump_in_hex(IO.read(packet_file)) 36 | when '.pcap' 37 | File.open(packet_file) do |file| 38 | Pio::Pcap::Frame.read(file).records.each do |each| 39 | dump_in_hex(each.data) 40 | end 41 | end 42 | else 43 | raise "Unsupported file extension: #{ENV['PACKET_FILE']}" 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /features/open_flow13/bad_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow13 2 | Feature: Error::BadRequest 3 | 4 | Request was not understood error. 5 | 6 | Scenario: new (raw_data = Echo request 1.0) 7 | When I create an OpenFlow message with: 8 | """ 9 | Pio::Error::BadRequest.new(raw_data: Pio::OpenFlow10::Echo::Request.new.to_binary) 10 | """ 11 | Then the message has the following fields and values: 12 | | field | value | 13 | | version | 4 | 14 | | transaction_id | 0 | 15 | | xid | 0 | 16 | | error_type | :bad_request | 17 | | error_code | :bad_version | 18 | | raw_data.length | 8 | 19 | 20 | Scenario: read 21 | When I parse a file named "open_flow13/bad_request.raw" with "Pio::Error::BadRequest" class 22 | Then the message has the following fields and values: 23 | | field | value | 24 | | version | 4 | 25 | | transaction_id | 0 | 26 | | xid | 0 | 27 | | error_type | :bad_request | 28 | | error_code | :bad_version | 29 | | raw_data.length | 8 | 30 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_vlan_vid_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_vlan_vid' 4 | 5 | describe Pio::OpenFlow10::SetVlanVid do 6 | describe '.new' do 7 | When(:set_vlan_vid) { Pio::OpenFlow10::SetVlanVid.new(vlan_id) } 8 | 9 | context 'with 10' do 10 | When(:vlan_id) { 10 } 11 | 12 | describe '#vlan_id' do 13 | Then { set_vlan_vid.vlan_id == 10 } 14 | end 15 | 16 | describe '#action_type' do 17 | Then { set_vlan_vid.action_type == 1 } 18 | end 19 | 20 | describe '#action_length' do 21 | Then { set_vlan_vid.action_length == 8 } 22 | end 23 | 24 | describe '#to_binary' do 25 | Then { set_vlan_vid.to_binary.length == 8 } 26 | end 27 | end 28 | 29 | context 'with 0' do 30 | When(:vlan_id) { 0 } 31 | Then { set_vlan_vid == Failure(ArgumentError) } 32 | end 33 | 34 | context 'with 4906' do 35 | When(:vlan_id) { 4096 } 36 | Then { set_vlan_vid == Failure(ArgumentError) } 37 | end 38 | 39 | context 'with :INVALID_VLAN_ID' do 40 | When(:vlan_id) { :INVALID_VLAN_ID } 41 | Then { set_vlan_vid == Failure(TypeError) } 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/pio/open_flow13/goto_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bindata' 4 | require 'forwardable' 5 | require 'pio/open_flow/instruction' 6 | 7 | module Pio 8 | module OpenFlow13 9 | # Resubmit to the table_id 10 | class GotoTable < OpenFlow::Instruction 11 | # OpenFlow 1.3.4 OFPIT_GOTO_TABLE instruction format 12 | class Format < BinData::Record 13 | endian :big 14 | 15 | uint16 :instruction_type, value: 1 16 | uint16 :instruction_length, value: 8 17 | uint8 :table_id 18 | bit24 :padding 19 | hide :padding 20 | end 21 | 22 | def self.read(raw_data) 23 | allocate.tap do |goto_table| 24 | goto_table.instance_variable_set :@format, Format.read(raw_data) 25 | end 26 | end 27 | 28 | extend Forwardable 29 | 30 | def_delegators :@format, :instruction_type 31 | def_delegators :@format, :instruction_length 32 | def_delegators :@format, :table_id 33 | def_delegators :@format, :to_binary_s 34 | def_delegator :@format, :to_binary_s, :to_binary 35 | 36 | def initialize(table_id) 37 | @format = Format.new(table_id: table_id) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pio/open_flow10/stats_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow/message' 4 | require 'pio/open_flow10/aggregate_stats/request' 5 | require 'pio/open_flow10/flow_stats/request' 6 | require 'pio/open_flow10/port_stats/request' 7 | require 'pio/open_flow10/queue_stats/request' 8 | require 'pio/open_flow10/table_stats/request' 9 | 10 | module Pio 11 | module OpenFlow10 12 | class Stats 13 | # Stats request parser. 14 | class Request < OpenFlow::Message 15 | open_flow_header version: 1, type: 16, length: 10 16 | stats_type :stats_type 17 | 18 | TYPE = { 19 | aggregate: OpenFlow10::AggregateStats::Request, 20 | description: OpenFlow10::DescriptionStats::Request, 21 | flow: OpenFlow10::FlowStats::Request, 22 | port: OpenFlow10::PortStats::Request, 23 | queue: OpenFlow10::QueueStats::Request, 24 | table: OpenFlow10::TableStats::Request 25 | }.freeze 26 | 27 | def self.read(binary) 28 | TYPE.fetch(Format.read(binary).stats_type.to_sym).read(binary) 29 | rescue KeyError 30 | raise "Unknown stats type: #{stats_type}" 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /features/open_flow10/queue_stats_request.feature: -------------------------------------------------------------------------------- 1 | @open_flow10 2 | Feature: QueueStats::Request 3 | 4 | Information about queues is requested with a Queue Stats Request 5 | message. 6 | 7 | Scenario: new(port: 1, queue_id: 1) 8 | When I create an OpenFlow message with: 9 | """ 10 | Pio::OpenFlow10::QueueStats::Request.new(port: 1, queue_id: 1) 11 | """ 12 | Then the message has the following fields and values: 13 | | field | value | 14 | | version | 1 | 15 | | transaction_id | 0 | 16 | | xid | 0 | 17 | | stats_type | :queue | 18 | | port | 1 | 19 | | queue_id | 1 | 20 | 21 | Scenario: new(port: 1, queue_id: 1, transaction_id: 123) 22 | When I create an OpenFlow message with: 23 | """ 24 | Pio::OpenFlow10::QueueStats::Request.new(port: 1, queue_id: 1, transaction_id: 123) 25 | """ 26 | Then the message has the following fields and values: 27 | | field | value | 28 | | version | 1 | 29 | | transaction_id | 123 | 30 | | xid | 123 | 31 | | stats_type | :queue | 32 | | port | 1 | 33 | | queue_id | 1 | 34 | -------------------------------------------------------------------------------- /spec/pio/open_flow10/set_vlan_priority_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pio/open_flow10/set_vlan_priority' 4 | 5 | describe Pio::OpenFlow10::SetVlanPriority do 6 | describe '.new' do 7 | When(:set_vlan_priority) { Pio::OpenFlow10::SetVlanPriority.new(priority) } 8 | 9 | context 'with 3' do 10 | When(:priority) { 3 } 11 | 12 | describe '#vlan_priority' do 13 | Then { set_vlan_priority.vlan_priority == 3 } 14 | end 15 | 16 | describe '#action_type' do 17 | Then { set_vlan_priority.action_type == 2 } 18 | end 19 | 20 | describe '#action_length' do 21 | Then { set_vlan_priority.action_length == 8 } 22 | end 23 | 24 | describe '#to_binary' do 25 | Then { set_vlan_priority.to_binary.length == 8 } 26 | end 27 | end 28 | 29 | context 'with -1' do 30 | When(:priority) { -1 } 31 | Then { set_vlan_priority == Failure(ArgumentError) } 32 | end 33 | 34 | context 'with 8' do 35 | When(:priority) { 8 } 36 | Then { set_vlan_priority == Failure(ArgumentError) } 37 | end 38 | 39 | context 'with :INVALID_PRIORITY' do 40 | When(:priority) { :INVALID_PRIORITY } 41 | Then { set_vlan_priority == Failure(TypeError) } 42 | end 43 | end 44 | end 45 | --------------------------------------------------------------------------------