Skip to main content

OVS Phần 6: ovs-ofctl và OpenFlow Cơ Bản

Phần 6/21 trong loạt Open vSwitch (OVS) — nối tiếp Phần 5, nơi bạn đã học ovs-vsctl thao tác trên OVSDB: vòng đời bridge/port qua add-br/add-port/del-*, cờ --may-exist/--if-exists để script chạy lại được nhiều lần, ba loại interface system/internal/tap, gán VLAN bằng tag=/trunks=, so sánh ovs-vsctl show với ovsdb-client dump, và cách gộp nhiều lệnh vào một transaction atomic bằng --. Toàn bộ mục đó là cấu hình switch — OVSDB. Bài này chuyển hẳn sang hành vi forward packet — OpenFlow, việc ovs-ofctl làm — và đây là bài cuối Module 1 (Nền tảng). Đọc xong, Module 2 bắt đầu từ Bài 7 với OVS + KVM/libvirt, gắn VM thật vào bridge bạn đã dựng.

Mục tiêu bài học: sau bài này bạn phân biệt dứt khoát OVSDB (cấu hình, việc ovs-vsctl làm) với flow table OpenFlow (hành vi forward packet thật sự, việc ovs-ofctl làm), đọc được flow mặc định của một bridge mới bằng ovs-ofctl dump-flows, hiểu vì sao br0 ở Bài 4 đã forward được packet dù chưa ai cấu hình OpenFlow gì, tự tay thêm được một flow rule đơn giản bằng ovs-ofctl add-flow với đúng cú pháp match/priority/action, và biết OVS chọn flow nào khi nhiều flow cùng khớp một packet.

Ba câu hỏi mà bài này trả lời:

  1. OVSDB (Bài 5) và flow table OpenFlow khác nhau ở chỗ nào, và ovs-ofctl dump-flows đang đọc dữ liệu từ đâu?
  2. Vì sao một bridge mới tạo, chưa ai chạy một lệnh ovs-ofctl nào, vẫn switch traffic bình thường như Bài 4 bạn đã tự thấy?
  3. Cú pháp ovs-ofctl add-flow cơ bản là gì, và khi nhiều flow cùng khớp một packet, OVS chọn flow nào để áp dụng?

Mục lục


1. Nhắc lại: config state (OVSDB) và flow state (OpenFlow) khác nhau ở đâu?

💡 Hình dung như thế này: OVSDB giống running-config của một switch vật lý — bridge nào tồn tại, port nào gán VLAN gì, tất cả bền trên đĩa và mô tả "switch này được lắp ráp như thế nào". Flow table OpenFlow giống bảng forwarding phần cứng (CAM/TCAM) bên trong ASIC — nơi quyết định thật sự packet đi đâu, dựa trên các rule được cài vào đó. Trên switch vật lý bạn hiếm khi thấy trực tiếp TCAM; trên OVS, ovs-ofctl cho bạn nhìn thẳng vào nó.

Bài 5 đã khắc sâu một điều: mọi lệnh ovs-vsctl là một transaction ghi vào OVSDB, ovs-vswitchd đọc database đó rồi mới áp trạng thái xuống datapath. Nhưng OVSDB chỉ trả lời câu hỏi "switch này gồm những gì" — bridge nào, port nào, port đó thuộc VLAN gì. Nó không trả lời câu hỏi "một packet cụ thể, đi vào một port cụ thể, sẽ bị xử lý ra sao". Câu hỏi thứ hai đó là việc của một cơ chế hoàn toàn khác: OpenFlow flow table, và công cụ đọc/ghi nó là ovs-ofctl — không phải ovs-vsctl.

Đây chính là ranh giới quan trọng nhất của cả bài: ovs-vsctl set port veth0-ovs tag=10 (Bài 5) và ovs-ofctl add-flow br0 "priority=100,in_port=1,actions=drop" (bài này) trông có vẻ "cùng một họ lệnh OVS", nhưng chúng nói chuyện với hai hệ thống khác nhau hoàn toàn — một bên là OVSDB qua ovsdb-server, một bên là flow table qua kênh giao thức OpenFlow mà ovs-vswitchd cũng đóng vai trò switch lắng nghe. Gộp nhầm hai khái niệm này là lỗi tư duy phổ biến nhất khi mới học OVS, vì cả hai đều gõ qua dòng lệnh ovs-<gì đó> trông rất giống nhau.


2. Đọc flow table mặc định: ovs-ofctl dump-flows br0

Man page chính thức mô tả dump-flows: "Prints to the console all flow entries in switch's tables that match flows." Nếu br0/ns1/ns2 từ Bài 4/5 vẫn còn (VLAN tag=10 gán ở Bài 5 không ảnh hưởng gì tới flow table — đó vẫn là OVSDB), chạy thẳng lệnh này ngay bây giờ, trước khi thêm bất kỳ flow nào:

sudo ovs-ofctl dump-flows br0
# Output minh họa
NXST_FLOW reply (xid=0x4):
cookie=0x0, duration=45.231s, table=0, n_packets=6, n_bytes=588, idle_age=2, priority=0 actions=NORMAL

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy.) Chưa ai chạy ovs-ofctl add-flow lần nào, nhưng flow table không rỗng — đã có sẵn đúng một flow. Đây là điều tài liệu tutorial chính thức của OVS xác nhận: nếu bridge không được đặt fail_mode=secure, "the flow table would start out with a single flow that executes the 'normal' action". Con số priority=0 cụ thể ở trên là quan sát nhất quán trong thực tế vận hành (giá trị thấp nhất trong thang priority 0–65535, đúng vai trò một rule "vét cạn" — catch-all) chứ man page không liệt kê tường minh con số này; tự chạy dump-flows trên máy bạn để xác nhận là cách chắc chắn nhất.

Đọc từng trường theo đúng định nghĩa man page: duration=secs"the time, in seconds, that the entry has been in the table"; n_packets/n_bytes là số packet/byte đã khớp entry này — đây chính là counter, tăng dần mỗi lần một packet thật đi qua flow, hoàn toàn khác OVSDB (OVSDB không đếm packet); idle_age=secs"the integer number of seconds that have passed without any packets passing through the flow".

Hai trường còn lại xuất hiện ở ngay đầu dòng — cookie=0x0table=0 — dễ bị lướt qua nhưng đáng dừng lại một chút. cookie không phải số OVS tự đếm, man page gọi nó là "an opaque identifier": "An opaque identifier called a cookie can be used as a handle to identify a set of flows", và giá trị này do chính người thêm flow tự gán khi add-flow"A cookie can be associated with a flow using the add-flow, add-flows, and mod-flows commands. value can be any 64-bit number and need not be unique among flows". Không khai báo cookie= khi add-flow thì mặc định là 0x0, đúng như flow NORMAL mặc định đang có; trên một bridge có nhiều controller/script cùng thêm flow, gán cookie= riêng cho từng nhóm là cách để sau này lọc đúng nhóm đó bằng dump-flows/del-flows, không cần match lại toàn bộ field. Còn table=0: OVS hỗ trợ nhiều flow table nối tiếp nhau thành một pipeline (tương tự nhiều tầng ACL xử lý tuần tự), nhưng bài này cố tình chỉ dùng đúng bảng table=0 — đi sâu vào cách packet nhảy qua nhiều table sẽ là chủ đề riêng của Module 3.


3. actions NORMAL nghĩa là gì, và vì sao Bài 4 đã ping thông dù chưa cấu hình OpenFlow?

Đây chính là câu hỏi bạn có thể đã tự thắc mắc từ Bài 4: dựng xong br0, gắn hai port, ping qua lại giữa ns1/ns2 là thông ngay — không ai gõ một lệnh ovs-ofctl nào cả. Câu trả lời nằm gọn trong flow duy nhất vừa đọc ở Mục 2: actions=NORMAL.

NORMAL là một action chuẩn của OpenFlow, định nghĩa chung: "Subjects the packet to the device's normal L2/L3 processing." Tài liệu cũng lưu ý ngay sau đó: "This action is not implemented by all OpenFlow switches, and each switch implements it differently" — nghĩa là bản thân đặc tả OpenFlow không bắt buộc NORMAL phải làm chính xác điều gì, để mỗi switch tự định nghĩa. Với OVS, NORMAL được triển khai đúng theo một pipeline riêng ("OVS Normal Pipeline") mô phỏng lại hành vi một switch Ethernet truyền thống: học địa chỉ MAC nguồn vào bảng MAC-learning nội bộ, xử lý VLAN tag theo cấu hình tag=/trunks= (đúng cột Bài 5 đã gán), rồi flood hoặc forward theo MAC đích đã học — chính xác quy trình một switch Layer 2 vật lý bạn đã quen thuộc, chỉ khác là toàn bộ logic đó chạy trong ovs-vswitchd thay vì trong ASIC.

Cơ chế đứng sau việc flow priority=0 actions=NORMAL tự động xuất hiện gọi là fail_mode, một cột trên bảng Bridge của OVSDB (đúng, đây lại là một cột OVSDB — nhưng nó điều khiển hành vi OpenFlow, một ví dụ cho thấy hai thế giới này vẫn giao nhau ở một vài điểm). Tài liệu schema xác nhận: "The default is standalone if the value is unset", và với standalone: "Open vSwitch causes the bridge to act like an ordinary MAC-learning switch" mỗi khi không có controller nào set up flow (kể cả trường hợp chưa từng cấu hình controller — đúng tình huống lab của bạn). Giá trị đối lập, secure, thì ngược lại hoàn toàn: "Open vSwitch will not set up flows on its own when the controller connection fails or when no controllers are defined" — một bridge secure mới tạo sẽ có flow table rỗng, không ping thông được gì cho tới khi bạn (hoặc một controller) tự thêm flow. Vì br0 ở Bài 4 dùng đúng giá trị mặc định (fail_mode để trống → standalone), nó có sẵn flow NORMAL và hoạt động như một switch Layer 2 bình thường ngay từ đầu — không có gì "kỳ diệu", chỉ là bạn chưa nhìn thấy flow table cho tới tận bây giờ.


4. Tự tay thêm một flow: cú pháp ovs-ofctl add-flow

💡 Hình dung như thế này: thêm một flow bằng add-flow giống viết một dòng access-list trên switch/router vật lý — bạn khai báo điều kiện match (giống permit/deny theo địa chỉ nguồn/đích/giao thức) và hành động áp dụng khi match. Khác biệt: ACL vật lý thường có "sequence number" xác định thứ tự xử lý cố định, còn OpenFlow dùng "priority" — flow nào priority cao hơn thắng, không quan tâm bạn gõ lệnh trước hay sau.

Man page mô tả cú pháp flow: "Such flow descriptions comprise a series of field=value assignments, separated by commas or white space", và với add-flow, actions=[action][,action...] "must be the final field specified". Trước khi thêm flow chặn theo in_port, cần biết veth0-ovs (nối ns1) đang là port OpenFlow số mấy — số này khác hoàn toàn tên interface, và do chính OVS gán (cột ofport trên bảng Interface, "OpenFlow port number for this interface. Open vSwitch sets this column's value, so other clients should treat it as read-only"):

sudo ovs-ofctl show br0
# Output minh họa
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000...
n_tables:254, n_buffers:256
1(veth0-ovs): addr:... config: 0 state: 0
2(veth1-ovs): addr:... config: 0 state: 0
LOCAL(br0): addr:... config: 0 state: 0

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy; số port thật tùy thứ tự bạn add-port ở Bài 4.) Cách khác để lấy đúng con số này: sudo ovs-vsctl get Interface veth0-ovs ofport. Giả sử veth0-ovs là port 1 — giờ thêm một flow chặn ICMP đi vào từ đúng port đó, dùng shorthand icmp (tài liệu ovs-fields(7) xác nhận icmp tương đương eth_type=0x0800,ip_proto=1, tức bắt buộc gói phải là IPv4 và giao thức tầng 4 là ICMP):

sudo ovs-ofctl add-flow br0 "priority=100,in_port=1,icmp,actions=drop"
sudo ovs-ofctl dump-flows br0
# Output minh họa
cookie=0x0, duration=4.017s, table=0, n_packets=0, n_bytes=0, idle_age=4, priority=100,icmp,in_port=1 actions=drop
cookie=0x0, duration=612.409s, table=0, n_packets=41, n_bytes=3948, idle_age=1, priority=0 actions=NORMAL

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy.) Bây giờ flow table có hai flow cùng tồn tại — flow bạn vừa thêm và flow NORMAL mặc định vẫn còn nguyên, add-flow không xóa flow cũ. Verify ngay bằng ping thật:

sudo ip netns exec ns1 ping -c 3 -W 1 10.0.0.2
# Output minh họa
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
--- 10.0.0.2 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2041ms

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy.) ICMP từ ns1 timeout, đúng như flow bạn vừa cài đặt: mọi packet ICMP đi vào br0 qua in_port=1 bị drop trước khi kịp tới flow NORMAL. Chạy lại dump-flows sẽ thấy n_packets của flow priority=100 tăng lên — bằng chứng trực tiếp packet ping thật đã đi qua đúng flow đó, không phải flow NORMAL. Muốn thông trở lại, xóa flow vừa thêm bằng del-flows — man page mô tả: "Deletes entries from switch's flow table":

sudo ovs-ofctl del-flows br0 "in_port=1,icmp"

5. Priority và match field: OVS chọn flow nào khi nhiều flow cùng khớp?

Man page nêu rõ quy tắc cốt lõi: "priority=value... value is a number between 0 and 65535, inclusive. A higher value will match before a lower one." Đó là lý do flow priority=100 bạn vừa thêm ở Mục 4 thắng flow priority=0 actions=NORMAL mặc định — dù NORMAL được cài trước, priority cao hơn luôn được xét trước bất kể thứ tự thêm vào. Nếu bạn không khai báo priority= khi add-flow, man page xác nhận giá trị mặc định: "if the field is not specified, the flow's priority will default to 32768" — đúng bằng một nửa thang 65535, một lựa chọn "ở giữa" hợp lý để vừa có chỗ đặt flow ưu tiên cao hơn, vừa có chỗ đặt flow ưu tiên thấp hơn nó.

Một chi tiết dễ bỏ sót: match càng cụ thể (càng ít wildcard) không tự động thắng match ít cụ thể hơn — trừ một ngoại lệ duy nhất là exact-match (khớp chính xác toàn bộ trường, không còn trường nào để wildcard), man page ghi: "An exact-match entry will always have priority over an entry containing wildcards, so it has an implicit priority value of 65535" — cao hơn cả priority tối đa bạn có thể tự khai báo bằng tay. Ngoài trường hợp đặc biệt này, hai flow cùng match một packet mà bạn không chủ động phân biệt priority là tình huống nguy hiểm: "OpenFlow leaves behavior undefined when two or more flows with the same priority can match a single packet" — tức không có gì đảm bảo flow nào được chọn. Luôn đặt priority= tường minh khi bạn biết trước sẽ có nhiều flow overlap nhau, đừng trông chờ vào thứ tự thêm vào.

Về match field, các trường phổ biến nhất theo ovs-fields(7): in_port"the OpenFlow port on which the packet being processed arrived"; dl_src/dl_dst — địa chỉ MAC nguồn/đích, 48-bit; dl_type (hay viết eth_type) — Ethertype 16-bit, ví dụ 0x0800 cho IPv4; nw_src/nw_dst — địa chỉ IPv4 nguồn/đích, 32-bit; nw_proto (hay ip_proto) — giao thức tầng 4, ví dụ 1 cho ICMP, 6 cho TCP, 17 cho UDP. Đây đúng là bộ trường bạn đã quen dùng trong ACL trên switch/router vật lý — chỉ khác cách gọi tên và cách kết hợp thành một dòng field=value duy nhất thay vì cú pháp ACL riêng của từng vendor.

Cấu trúc một flow entry (ví dụ: chặn ICMP từ in_port=1)bốn phần OVS đọc theo đúng thứ tự khi một packet đi vào bridgematchin_port=1dl_type=0x0800nw_proto=1(shorthand: icmp)priority100mặc định là 32768nếu không khai báoactionsdrop(thay thế được bằngoutput:N hoặc NORMAL)countersn_packets=12n_bytes=1176idle_age=3snhiều flow cùng khớp một packet → OVS chọn flow priority cao nhấtexact-match (không còn trường nào wildcard) có priority ngầm định 65535flow priority=0 actions=NORMAL luôn có sẵn khi fail_mode=standalone (mặc định)— đóng vai trò catch-all cuối cùng khi không flow nào khác khớp(counters n_packets/n_bytes chỉ tăng khi packet THẬT đi qua flow — khác OVSDB, vốn không đếm packet)(sơ đồ minh họa cấu trúc flow entry — số liệu ví dụ, không phải giá trị cố định)

Nếu chỉ nhớ được một điều sau bài này, hãy nhớ: ovs-ofctl không nói chuyện với OVSDB — nó nói thẳng với flow table OpenFlow, nơi thật sự quyết định số phận từng packet, và mọi bridge OVS mới tạo đều đã có sẵn đúng một flow (priority=0 actions=NORMAL) khiến nó cư xử như một switch Layer 2 bình thường ngay cả khi chưa ai gõ một lệnh ovs-ofctl nào.


Bài tập cuối bài

  1. Trên br0 (lab Bài 4/5), xác định đúng số port OpenFlow của veth0-ovs bằng ovs-ofctl show br0 hoặc ovs-vsctl get Interface veth0-ovs ofport, sau đó tự viết một flow chặn ICMP đi vào từ đúng port đó (dùng priority=100,in_port=<n>,icmp,actions=drop). Verify bằng ping thật từ ns1 sang ns2 (phải timeout), rồi dump-flows xác nhận n_packets của flow bạn vừa thêm đã tăng. Cuối cùng del-flows để xóa flow đó và ping lại xác nhận thông trở lại.
  2. Thêm cùng lúc hai flow trên br0, cùng match in_port của veth0-ovs và cùng match icmp, nhưng một flow actions=NORMAL với priority=200 và một flow actions=drop với priority=100. Dùng dump-flows xác nhận cả hai flow cùng tồn tại, rồi ping thật để tự quan sát: traffic vẫn thông vì flow priority=200 thắng. Đổi ngược lại hai con số priority (flow drop lên 200, flow NORMAL xuống 100) và ping lại — quan sát kết quả đổi chiều, tự rút ra kết luận priority quyết định flow nào áp dụng, không phải thứ tự bạn gõ lệnh add-flow trước hay sau.

Nguồn tham khảo

  • dump-flows "Prints to the console all flow entries in switch's tables that match flows", add-flow "Add each flow entry to switch's tables", del-flows "Deletes entries from switch's flow table", cú pháp flow "a series of field=value assignments, separated by commas or white space" với actions=... "must be the final field specified", priority mặc định "the flow's priority will default to 32768", phạm vi priority "a number between 0 and 65535, inclusive. A higher value will match before a lower one", exact-match "will always have priority over an entry containing wildcards, so it has an implicit priority value of 65535", và cảnh báo "OpenFlow leaves behavior undefined when two or more flows with the same priority can match a single packet"ovs-ofctl(8), openvswitch.org.
  • Các trường counters trong dump-flows: duration=secs "the time... that the entry has been in the table", n_packets, n_bytes, idle_age=secs "seconds that have passed without any packets passing through the flow" — cùng nguồn ovs-ofctl(8) ở trên.
  • Trường cookie: "An opaque identifier called a cookie can be used as a handle to identify a set of flows", gán qua add-flow/add-flows/mod-flows "value can be any 64-bit number and need not be unique among flows"; trường table giới hạn/liệt kê flow theo từng bảng trong pipeline nhiều table — cùng nguồn ovs-ofctl(8) ở trên.
  • OpenFlow version mặc định của ovs-ofctl khi không dùng -O: "All current versions of ovs-ofctl enable only OpenFlow 1.0 by default. Use the -O option to enable support for later versions of OpenFlow in ovs-ofctl"Using OpenFlow FAQ, docs.openvswitch.org.
  • Định nghĩa action normal theo đặc tả chung: "Subjects the packet to the device's normal L2/L3 processing", kèm lưu ý "This action is not implemented by all OpenFlow switches, and each switch implements it differently"ovs-actions(7), openvswitch.org.
  • Cột fail_mode trên bảng Bridge: "The default is standalone if the value is unset"; standalone"Open vSwitch causes the bridge to act like an ordinary MAC-learning switch"; secure"Open vSwitch will not set up flows on its own when the controller connection fails or when no controllers are defined"ovs-vswitchd.conf.db(5), openvswitch.org.
  • Cột ofport trên bảng Interface: "OpenFlow port number for this interface. Open vSwitch sets this column's value, so other clients should treat it as read-only"ovs-vswitchd.conf.db(5), Ubuntu manpages mirror.
  • Một bridge mới không đặt fail-mode=secure "would start out with a single flow that executes the 'normal' action"Open vSwitch Advanced Features (tutorial), docs.openvswitch.org.
  • Định nghĩa các match field in_port "the OpenFlow port on which the packet being processed arrived", dl_src/dl_dst (Ethernet, 48-bit), dl_type/eth_type (16-bit, ví dụ 0x0800 cho IPv4), nw_src/nw_dst (IPv4, 32-bit), nw_proto/ip_proto (8-bit), và bảng shorthand giao thức (icmp = eth_type=0x0800,ip_proto=1, tcp = eth_type=0x0800,ip_proto=6, udp = eth_type=0x0800,ip_proto=17, arp = eth_type=0x0806) — ovs-fields(7), openvswitch.org.

Tiếp theo: Module 2, Bài 7 — OVS + KVM/libvirt: gắn VM vào OVS bridge thực tế.