heat_template_version: 2015-04-30 description: | SFC example deployment The script deploys 2 Fedora VMs: * A VM with a UDP echo server, that listens on port 2345, and replies any any datagram it receives back to the sender. * A VM acting as a service function, that receives all port 2345 UDP packets originating from the first VM, and replaces all instances of sf_filter with sf_sub. How to deploy: $ openstack stack create -t doc/source/sfc-example/sfc-example.yaml stackname Wait a few minutes $ openstack stack show stackname Look for server_fip address e.g.: server_fip=$(openstack stack show -f yaml stackname | shyaml get-value outputs.0.output_value) $ echo dragonflow | nc -u $server_fip 2345 DRAGONFLOW The service function VM needs a few minutes to install dependencies. parameters: key_name: type: string label: Keypair name default: stack image_id: type: string label: Image ID default: Fedora-Cloud-Base-25-1.3.x86_64 provider_net: type: string label: Provider net to use default: public sf_filter: type: string label: Filter to look for in returned messages default: dragonflow sf_sub: type: string label: The text to plug instead of filtered messages default: DRAGONFLOW resources: flavor: type: OS::Nova::Flavor properties: name: sfc-test-flavor disk: 3 ram: 1024 vcpus: 1 private_net: type: OS::Neutron::Net properties: name: sfc-test-net private_subnet: type: OS::Neutron::Subnet properties: name: sfc-test-subnet network_id: { get_resource: private_net } cidr: 20.0.0.0/24 gateway_ip: 20.0.0.1 enable_dhcp: true allocation_pools: - start: 20.0.0.10 end: 20.0.0.100 router: type: OS::Neutron::Router properties: name: sfc-test-router external_gateway_info: network: { get_param: provider_net } router_interface: type: OS::Neutron::RouterInterface properties: router_id: { get_resource: router } subnet_id: { get_resource: private_subnet } sec_group: type: OS::Neutron::SecurityGroup properties: name: sfc-test-sg rules: - remote_ip_prefix: 0.0.0.0/0 protocol: tcp - remote_ip_prefix: 0.0.0.0/0 protocol: udp - remote_ip_prefix: 0.0.0.0/0 protocol: icmp source_vm_port: type: OS::Neutron::Port properties: name: sfc-test-src-vm-port network_id: { get_resource: private_net } fixed_ips: - subnet_id: { get_resource: private_subnet } security_groups: - { get_resource: sec_group } source_vm: type: OS::Nova::Server properties: name: sfc-test-src-vm admin_pass: test key_name: { get_param: key_name } flavor: { get_resource: flavor } image: { get_param: image_id } networks: - port: { get_resource: source_vm_port } user_data_format: RAW user_data: | #cloud-config write_files: - content: | import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('', 2345)) while True: data, address = sock.recvfrom(1024) sock.sendto(data, address) path: /tmp/echo.py runcmd: - python3 /tmp/echo.py source_fip: type: OS::Neutron::FloatingIP properties: floating_network: { get_param: provider_net } port_id: { get_resource: source_vm_port } sf_port_ctrl: type: OS::Neutron::Port properties: name: sfc-test-sf-port-ctrl network_id: { get_resource: private_net } fixed_ips: - subnet_id: { get_resource: private_subnet } security_groups: - { get_resource: sec_group } sf_port_ingress: type: OS::Neutron::Port properties: name: sfc-test-sf-port-ingress network_id: { get_resource: private_net } fixed_ips: - subnet_id: { get_resource: private_subnet } port_security_enabled: false sf_port_egress: type: OS::Neutron::Port properties: name: sfc-test-sf-port-egress network_id: { get_resource: private_net } fixed_ips: - subnet_id: { get_resource: private_subnet } port_security_enabled: false sf_vm: type: OS::Nova::Server properties: name: sfc-test-sf admin_pass: test key_name: { get_param: key_name } flavor: { get_resource: flavor } image: { get_param: image_id } networks: - port: { get_resource: sf_port_ctrl } - port: { get_resource: sf_port_ingress } - port: { get_resource: sf_port_egress } user_data_format: RAW user_data: str_replace: template: | #cloud-config write_files: - content: | import os from os_ken.base import app_manager from os_ken.controller import ofp_event from os_ken.controller.handler import CONFIG_DISPATCHER from os_ken.controller.handler import MAIN_DISPATCHER from os_ken.controller.handler import set_ev_cls from os_ken.lib.packet import packet from os_ken.lib.packet import ethernet from os_ken.lib.packet import ipv4 from os_ken.lib.packet import mpls from os_ken.lib.packet import udp from os_ken.ofproto import ofproto_v1_3 FILTER = os.environ.get('SF_FILTER') SUB = os.environ.get('SF_SUB') class SimpleServiceFunction(app_manager.OsKenApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): msg = ev.msg dp = msg.datapath ofp_parser = dp.ofproto_parser message = dp.ofproto_parser.OFPFlowMod( datapath=dp, table_id=0, command=dp.ofproto.OFPFC_ADD, priority=100, match=ofp_parser.OFPMatch(in_port=1, eth_type=0x8847), instructions=[ ofp_parser.OFPInstructionActions( dp.ofproto.OFPIT_APPLY_ACTIONS, [ ofp_parser.OFPActionOutput( ofproto_v1_3.OFPP_CONTROLLER, ofproto_v1_3.OFPCML_NO_BUFFER, ) ], ), ], ) dp.send_msg(message) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg = ev.msg dp = msg.datapath ofp_parser = dp.ofproto_parser pkt = packet.Packet(msg.data) payload = pkt.protocols[-1] if isinstance(payload, (bytes, bytearray)): new_payload = payload.decode( 'utf-8' ).replace( FILTER, SUB, ).encode('utf-8') new_pkt = packet.Packet() new_pkt.add_protocol(pkt.get_protocol(ethernet.ethernet)) new_pkt.add_protocol(pkt.get_protocol(mpls.mpls)) pkt_ip = pkt.get_protocol(ipv4.ipv4) pkt_ip.csum = 0 pkt_ip.total_length = 0 new_pkt.add_protocol(pkt_ip) pkt_udp = pkt.get_protocol(udp.udp) pkt_udp.csum = 0 new_pkt.add_protocol(pkt_udp) new_pkt.add_protocol(new_payload) new_pkt.serialize() pkt = new_pkt actions = [ofp_parser.OFPActionOutput(port=2)] out = ofp_parser.OFPPacketOut( datapath=dp, buffer_id=ofproto_v1_3.OFP_NO_BUFFER, in_port=ofproto_v1_3.OFPP_CONTROLLER, data=pkt.data, actions=actions, ) dp.send_msg(out) path: /tmp/controller.py - content: | #!/bin/bash dnf install -y openvswitch python3-ryu systemctl start openvswitch ovs-vsctl add-br br-sf ovs-vsctl set-controller br-sf tcp:127.0.0.1:6653 ovs-vsctl add-port br-sf eth1 ovs-vsctl add-port br-sf eth2 ovs-ofctl del-flows br-sf ip link set dev eth1 up ip link set dev eth2 up SF_FILTER=$filter SF_SUB=$sub ryu-manager-3 /tmp/controller.py path: /tmp/run.sh runcmd: - sudo bash -x /tmp/run.sh params: $filter: { get_param: sf_filter } $sub: { get_param: sf_sub } sf_fip: type: OS::Neutron::FloatingIP properties: floating_network: { get_param: provider_net } port_id: { get_resource: sf_port_ctrl } port_pair: type: OS::Neutron::PortPair properties: name: sfc-test-pp ingress: { get_resource: sf_port_ingress } egress: { get_resource: sf_port_egress } service_function_parameters: correlation: mpls depends_on: sf_vm port_pair_group: type: OS::Neutron::PortPairGroup properties: name: sfc-test-ppg port_pairs: - { get_resource: port_pair } flow_classifier: type: OS::Neutron::FlowClassifier properties: name: sfc-test-fc logical_source_port: { get_resource: source_vm_port } ethertype: IPv4 protocol: udp source_port_range_min: 2345 source_port_range_max: 2345 port_chain: type: OS::Neutron::PortChain properties: name: sfc-test-pc flow_classifiers: - { get_resource: flow_classifier } port_pair_groups: - { get_resource: port_pair_group } outputs: server_fip: description: Floating IP of the echo server value: { get_attr: [source_fip, floating_ip_address] }