On Tue, Sep 30, 2025 at 12:46:58PM -0400, Aaron Conole wrote:
> This commit wires up support for the socket() action, which will
> create a recirculation on the datapath to bypass as many extra
> recirculations as possible before the output. The rough idea is
> that a data packet can 'bypass' certain parts of the networking
> stack and experience a faster delivery. This makes some of the
> tracking features (for example, simple tcpdump type operations)
> much more complicated because the underlying datapath will not
> use the complete routing / sockets layer in datapath, but it
> instead directly will queue the data portion of the packet to
> the socket buffer. For non-data packets, the socket() action
> will hit the 'else' directive and take the recirc chain.
>
> One area that still needs enhancement is the ct() path. The
> conntrack portions are only doing the socket call after the
> recirculation chain. However, the proper way to do this is
> to rewrite the original recirculation state to hit the socket
> path first. That will require quite a bit more context and
> logic in the compose ct action, so it isn't done here, and
> it isn't recommended to use this with a really complex flow
> pipeline yet (since this is still a WIP). Future work will
> focus on this area so that an existing flow after freezing
> that looks like:
>
> recirc_id(0),eth(...),eth_type(...),ipv4(...),
> actions=ct(commit,nat(dst=1.2.3.4)),recirc(0x1)
>
> would be rewritten as:
>
> recirc_id(0),eth(...),eth_type(...),ipv4(...),...
> actions=socket(netns=...,inode=...,else(recirc(1))
> recirc_id(0x1),eth(...)...
> actions=ct(commit,nat(dst=1.2.3.4)),recirc(0x2)
>
> And then the corresponding output could get generated later.
>
> Signed-off-by: Aaron Conole <[email protected]>
> ---
> lib/dpif-netdev.c | 24 +++++-
> lib/netdev-dummy.c | 73 +++++++++++++++++
> ofproto/ofproto-dpif-rid.h | 1 +
> ofproto/ofproto-dpif-xlate.c | 153 +++++++++++++++++++++++++++++++++++
> ofproto/ofproto-dpif-xlate.h | 9 +++
> tests/ofproto-dpif.at | 81 +++++++++++++++++++
> 6 files changed, 340 insertions(+), 1 deletion(-)
>
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 1013d736eb..8c0c46c560 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -9186,6 +9186,29 @@ dp_execute_cb(void *aux_, struct dp_packet_batch
> *packets_,
> uint32_t packet_count, packets_dropped;
>
> switch ((enum ovs_action_attr)type) {
> + case OVS_ACTION_ATTR_SOCKET: {
> + /* Since socket isn't supported, but there's a fallback branch, we
> will
> + * execute the fallback side of the socket call. In the future, it
> + * may be feasible to implement via an AF_XDP type socket. */
> + static const struct nl_policy ovs_sock_act_policy[] = {
> + [OVS_SOCKET_ACTION_ATTR_NETNS_ID] = { .type = NL_A_U32 },
> + [OVS_SOCKET_ACTION_ATTR_INODE] = { .type = NL_A_U64 },
> + [OVS_SOCKET_ACTION_ATTR_ACTIONS] = { .type = NL_A_NESTED },
> + };
> + struct nlattr *sock_act[ARRAY_SIZE(ovs_sock_act_policy)];
> + if (!nl_parse_nested(a, ovs_sock_act_policy, sock_act,
> + ARRAY_SIZE(sock_act))) {
> + VLOG_ERR("Unable to parse socket type.");
> + return;
> + }
> +
> + struct nlattr *acts = sock_act[OVS_SOCKET_ACTION_ATTR_ACTIONS];
> + dp_netdev_execute_actions(aux->pmd, packets_, should_steal,
> + aux->flow, nl_attr_get(acts),
> + nl_attr_get_size(acts));
> + return;
> + }
> +
> case OVS_ACTION_ATTR_OUTPUT:
> dp_execute_output_action(pmd, packets_, should_steal,
> nl_attr_get_odp_port(a));
> @@ -9495,7 +9518,6 @@ dp_execute_cb(void *aux_, struct dp_packet_batch
> *packets_,
> case OVS_ACTION_ATTR_ADD_MPLS:
> case OVS_ACTION_ATTR_DEC_TTL:
> case OVS_ACTION_ATTR_PSAMPLE:
> - case OVS_ACTION_ATTR_SOCKET:
> case __OVS_ACTION_ATTR_MAX:
> OVS_NOT_REACHED();
> }
> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
> index b72820fcc5..244c58869f 100644
> --- a/lib/netdev-dummy.c
> +++ b/lib/netdev-dummy.c
> @@ -174,6 +174,9 @@ struct netdev_dummy {
>
> /* Set the segment size for netdev TSO support. */
> int ol_tso_segsz OVS_GUARDED;
> +
> + /* Socket lookup functionality state. */
> + bool socket_lookup_enabled OVS_GUARDED;
> };
>
> /* Max 'recv_queue_len' in struct netdev_dummy. */
> @@ -739,6 +742,7 @@ netdev_dummy_construct(struct netdev *netdev_)
> netdev->requested_n_rxq = netdev_->n_rxq;
> netdev->requested_n_txq = netdev_->n_txq;
> netdev->numa_id = 0;
> + netdev->socket_lookup_enabled = false;
>
> memset(&netdev->custom_stats, 0, sizeof(netdev->custom_stats));
>
> @@ -1796,6 +1800,71 @@ exit:
> return error ? -1 : 0;
> }
>
> +static int
> +netdev_dummy_get_target_ns(const struct netdev *netdev OVS_UNUSED,
> + int *target_ns)
> +{
> + /* For dummy devices, return a fixed hash value instead of real
> + * namespace ID. */
> + *target_ns = 0x12345678;
> + return 0;
> +}
> +
> +static int
> +netdev_dummy_set_socket_lookup_enabled(struct netdev *netdev_, bool enabled)
> +{
> + struct netdev_dummy *netdev = netdev_dummy_cast(netdev_);
> +
> + ovs_mutex_lock(&netdev->mutex);
> + netdev->socket_lookup_enabled = enabled;
> + ovs_mutex_unlock(&netdev->mutex);
> +
> + return 0;
> +}
> +
> +static bool
> +netdev_dummy_get_socket_lookup_enabled(const struct netdev *netdev_)
> +{
> + struct netdev_dummy *netdev = netdev_dummy_cast(netdev_);
> + bool enabled;
> +
> + ovs_mutex_lock(&netdev->mutex);
> + enabled = netdev->socket_lookup_enabled;
> + ovs_mutex_unlock(&netdev->mutex);
> +
> + return enabled;
> +}
> +
> +static int
> +netdev_dummy_get_socket_inode(const struct netdev *netdev OVS_UNUSED,
> + int proto, int af,
> + const void *src,
> + ovs_be16 sport,
> + const void *dst,
> + ovs_be16 dport,
> + uint64_t *inode_out, uint64_t *netns_out)
> +{
> + /* For dummy devices, return hashed values instead of real inode/netns */
> + if (proto != IPPROTO_TCP) {
> + return ENOENT;
> + }
> +
> + if (af == AF_INET) {
> + uint64_t inode_hash = hash_2words(*(uint32_t *) src,
> + *(uint32_t *) dst);
> + inode_hash = inode_hash << 32;
> + inode_hash |=
> + ((OVS_FORCE uint16_t) sport << 16) |
> + ((OVS_FORCE uint16_t) dport);
> +
> + *inode_out = inode_hash;
> + *netns_out = 0x12345678;
> + } else {
> + return ENOENT;
> + }
> + return 0;
> +}
> +
> #define NETDEV_DUMMY_CLASS_COMMON \
> .run = netdev_dummy_run, \
> .wait = netdev_dummy_wait, \
> @@ -1822,6 +1891,10 @@ exit:
> .dump_queue_stats = netdev_dummy_dump_queue_stats, \
> .get_addr_list = netdev_dummy_get_addr_list, \
> .update_flags = netdev_dummy_update_flags, \
> + .get_target_ns = netdev_dummy_get_target_ns, \
> + .set_socket_lookup_enabled = netdev_dummy_set_socket_lookup_enabled, \
> + .get_socket_lookup_enabled = netdev_dummy_get_socket_lookup_enabled, \
> + .get_socket_inode = netdev_dummy_get_socket_inode, \
> .rxq_alloc = netdev_dummy_rxq_alloc, \
> .rxq_construct = netdev_dummy_rxq_construct, \
> .rxq_destruct = netdev_dummy_rxq_destruct, \
> diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
> index 4df630c62b..f899a83260 100644
> --- a/ofproto/ofproto-dpif-rid.h
> +++ b/ofproto/ofproto-dpif-rid.h
> @@ -155,6 +155,7 @@ struct frozen_state {
> bool conntracked; /* Conntrack occurred prior to freeze. */
> bool was_mpls; /* MPLS packet */
> struct uuid xport_uuid; /* UUID of 1st port packet received on. */
> + bool socket_attempt; /* A socket output was already attempted.
> */
>
> /* Actions to be translated when thawing. */
> struct ofpact *ofpacts;
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index 2c8197fb73..2ac160507c 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -4456,6 +4456,146 @@ terminate_native_tunnel(struct xlate_ctx *ctx, const
> struct xport *xport,
> return *tnl_port != ODPP_NONE;
> }
>
> +static bool should_track_socket_flows(struct xlate_ctx *ctx,
> + const struct xport *xport)
> +{
> + struct flow *flow = &ctx->xin->flow;
> +
> + /* Only for valid ports (also need to check the addresses involved). */
> + return ((xport && xport->netdev &&
> + netdev_get_socket_lookup_enabled(xport->netdev)) &&
> + (flow->dl_type == htons(ETH_TYPE_IP) ||
> + flow->dl_type == htons(ETH_TYPE_IPV6)) &&
> + (flow->nw_proto == IPPROTO_TCP) &&
> + (flow->tp_src && flow->tp_dst));
> +}
> +
> +static void
> +unwildcard_socket_flow(struct xlate_ctx *ctx)
> +{
> + struct flow_wildcards *wc = ctx->wc;
> + struct flow *flow = &ctx->xin->flow;
> +
> + /* Protocol and TCP ports are common */
> + memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
> + memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
> + memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
> +
> + /* IP address fields */
> + if (flow->dl_type == htons(ETH_TYPE_IP)) {
> + /* IPv4 */
> + wc->masks.nw_src = OVS_BE32_MAX;
> + wc->masks.nw_dst = OVS_BE32_MAX;
> + } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
> + /* IPv6 */
> + memset(&wc->masks.ipv6_src, 0xff, sizeof wc->masks.ipv6_src);
> + memset(&wc->masks.ipv6_dst, 0xff, sizeof wc->masks.ipv6_dst);
> + }
> +
> + /* Eth fields. */
> + memset(&wc->masks.dl_type, 0xff, sizeof wc->masks.dl_type);
> + memset(&wc->masks.dl_src, 0xff, ETH_ADDR_LEN);
> + memset(&wc->masks.dl_dst, 0xff, ETH_ADDR_LEN);
> +}
> +
> +static void
> +compose_socket_action_with_fallback(struct ofpbuf *actions, uint32_t netns,
> + uint64_t inode, uint32_t recirc_id)
> +{
> + size_t socket_offset, actions_offset;
> +
> + socket_offset = nl_msg_start_nested(actions, OVS_ACTION_ATTR_SOCKET);
> + nl_msg_put_u32(actions, OVS_SOCKET_ACTION_ATTR_NETNS_ID, netns);
> + nl_msg_put_u64(actions, OVS_SOCKET_ACTION_ATTR_INODE, inode);
> + actions_offset = nl_msg_start_nested(actions,
> + OVS_SOCKET_ACTION_ATTR_ACTIONS);
> + nl_msg_put_u32(actions, OVS_ACTION_ATTR_RECIRC, recirc_id);
> + nl_msg_end_nested(actions, actions_offset);
> + nl_msg_end_nested(actions, socket_offset);
> +}
> +
> +static void
> +generate_and_compose_socket_action(struct xlate_ctx *ctx,
> + struct netdev *netdev, uint32_t recirc_id)
> +{
> + struct flow flow = ctx->base_flow; /* make a copy of the flow. */
> + int af = (flow.dl_type == htons(ETH_TYPE_IP)) ? AF_INET : AF_INET6;
> + uint64_t netns, inode;
> + int error;
> +
> + if (af == AF_INET) {
> + error = netdev_get_socket_inode(netdev, flow.nw_proto, af,
> + &flow.nw_src, flow.tp_src,
> + &flow.nw_dst, flow.tp_dst,
> + &inode, &netns);
> + } else {
> + error = netdev_get_socket_inode(netdev, flow.nw_proto, af,
> + &flow.ipv6_src, flow.tp_src,
> + &flow.ipv6_dst, flow.tp_dst,
> + &inode, &netns);
> + }
> +
> + if (error) {
> + VLOG_DBG("Socket lookup failed for flow: %s", ovs_strerror(error));
> + return;
> + }
This could be problematic. If we fail to find the socket maybe we should
go with the fallback (i.e: recirc) or even run the "output" normally.
Which makes me think: how would this work with non-ct pipelines? i.e: if
all TCP packets match the same openflow flows.
The first packet (SYN) will not find a valid socket and the subsequent will
match the already installed dp flows so we won't have a chance to make
the lookup again and install the right "socket" action.
Thanks.
Adrián
> +
> + if (ctx->conntracked) {
> + ctx->base_flow.tp_src = ctx->base_flow.ct_tp_src;
> + ctx->base_flow.tp_dst = ctx->base_flow.ct_tp_dst;
> +
> + if (af == AF_INET) {
> + ctx->base_flow.nw_src = ctx->base_flow.ct_nw_src;
> + ctx->base_flow.nw_dst = ctx->base_flow.ct_nw_dst;
> + } else {
> + memcpy(&ctx->base_flow.ipv6_src, &ctx->base_flow.ct_ipv6_src,
> + sizeof(ctx->base_flow.ipv6_src));
> + memcpy(&ctx->base_flow.ipv6_dst, &ctx->base_flow.ct_ipv6_dst,
> + sizeof(ctx->base_flow.ipv6_dst));
> + }
> + }
> +
> + /* Build flow key */
> + compose_socket_action_with_fallback(ctx->odp_actions, netns, inode,
> + recirc_id);
> +}
> +
> +static void
> +xlate_socket_action(struct xlate_ctx *ctx, const struct xport *xport,
> + bool is_last_action OVS_UNUSED)
> +{
> + struct frozen_state state = {
> + .table_id = ctx->table_id,
> + .ofproto_uuid = ctx->xbridge->ofproto->uuid,
> + .stack = ctx->stack.data,
> + .stack_size = ctx->stack.size,
> + .mirrors = ctx->mirrors,
> + .conntracked = ctx->conntracked,
> + .was_mpls = ctx->was_mpls,
> + .ofpacts = NULL,
> + .ofpacts_len = 0,
> + .action_set = NULL,
> + .action_set_len = 0,
> + .userdata = ctx->pause ? CONST_CAST(uint8_t *,ctx->pause->userdata)
> + : NULL,
> + .userdata_len = ctx->pause ? ctx->pause->userdata_len : 0,
> + };
> + frozen_metadata_from_flow(&state.metadata, &ctx->xin->flow);
> + state.socket_attempt = true;
> +
> + /* Allocate the fallback recirc ID, and update the state. */
> + uint32_t recirc_id = recirc_alloc_id_ctx(&state);
> + if (!recirc_id) {
> + xlate_report_error(ctx, "Failed to allocate recirculation id");
> + ctx->error = XLATE_NO_RECIRCULATION_CONTEXT;
> + return;
> + }
> + recirc_refs_add(&ctx->xout->recircs, recirc_id);
> +
> + unwildcard_socket_flow(ctx);
> + generate_and_compose_socket_action(ctx, xport->netdev, recirc_id);
> +}
> +
> static void
> compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
> const struct xlate_bond_recirc *xr, bool check_stp,
> @@ -4563,6 +4703,17 @@ compose_output_action__(struct xlate_ctx *ctx,
> ofp_port_t ofp_port,
> /* Commit accumulated flow updates before output. */
> xlate_commit_actions(ctx);
>
> + /* Check if we should enable socket flow tracking for this port */
> + if ((!ctx->xin->frozen_state ||
> + !ctx->xin->frozen_state->socket_attempt) &&
> + should_track_socket_flows(ctx, xport)) {
> + xlate_report(ctx, OFT_DETAIL,
> + "Socket flow tracking enabled for port %d",
> ofp_port);
> + /* Compose the action. */
> + xlate_socket_action(ctx, xport, is_last_action);
> + return;
> + }
> +
> if (xr && bond_use_lb_output_action(xport->xbundle->bond)) {
> /*
> * If bond mode is balance-tcp and optimize balance tcp is
> enabled
> @@ -8712,6 +8863,8 @@ xlate_resume(struct ofproto_dpif *ofproto,
> .mirrors = pin->mirrors,
> .conntracked = pin->conntracked,
> .xport_uuid = UUID_ZERO,
> + .socket_attempt = true, /* After a pin message ignore socket()
> + * calls (for now).*/
>
> /* When there are no actions, xlate_actions() will search the flow
> * table. We don't want it to do that (we want it to resume), so
> diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
> index d973a634ac..79ed645444 100644
> --- a/ofproto/ofproto-dpif-xlate.h
> +++ b/ofproto/ofproto-dpif-xlate.h
> @@ -65,6 +65,12 @@ struct xlate_out {
> /* Keep track of the last action whose purpose is purely observational.
> * e.g: IPFIX, sFlow, local sampling. */
> uint32_t last_observe_offset;
> +
> + /* Let's the upcall handler know that a socket action should be used
> + * along with any output action. */
> + bool use_socket_action;
> + const struct netdev *socket_outport;
> + uint32_t socket_recirc_id;
> };
>
> struct xlate_in {
> @@ -244,6 +250,9 @@ bool xlate_delete_static_mac_entry(const struct
> ofproto_dpif *,
> void xlate_set_support(const struct ofproto_dpif *,
> const struct dpif_backer_support *);
>
> +bool xlate_socket_flow_revalidation(const struct flow *,
> + const struct xlate_out *);
> +
> void xlate_txn_start(void);
> void xlate_txn_commit(void);
>
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index a0cd4a5cec..65ce440030 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -13324,3 +13324,84 @@ AT_CHECK([ovs-appctl coverage/read-counter
> revalidate_missing_dp_flow], [0],
>
> OVS_VSWITCHD_STOP(["/failed to flow_del (No such file or directory)/d"])
> AT_CLEANUP
> +
> +AT_SETUP([ofproto-dpif - socket action with TCP packets])
> +OVS_VSWITCHD_START
> +add_of_ports br0 1 2
> +
> +# Enable socket offload on port 1
> +AT_CHECK([ovs-vsctl set interface p2 other_config:socket-offload=true])
> +
> +# Add flow rule
> +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=output:2'])
> +
> +# Test established TCP connection (ACK flag)
> +m4_define([TCP_ACK_PKT], [m4_join([,],
> + [eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800)],
> + [ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6,tos=0,ttl=64,frag=no)],
> + [tcp(src=1234,dst=80),tcp_flags(ack)])])
> +
> +# Send TCP ACK packet and trace it
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'TCP_ACK_PKT'], [0], [stdout])
> +
> +# Check that socket action is generated for established connections too
> +AT_CHECK([ovs-appctl dpctl/dump-flows --names | strip_used | strip_stats |
> dnl
> + strip_duration | strip_dp_hash | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6,frag=no),tcp(src=1234,dst=80),
> packets:0, bytes:0, used:never,
> actions:socket(netns=305419896,inode=18446744072938082304,else(recirc(0x1)))
> +recirc_id(0x1),in_port(p1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no),
> packets:0, bytes:0, used:never, actions:p2
> +])
> +
> +# Test FIN packet
> +m4_define([TCP_FIN_PKT], [m4_join([,],
> + [eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800)],
> + [ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6,tos=0,ttl=64,frag=no)],
> + [tcp(src=1234,dst=80),tcp_flags(fin)])])
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'TCP_FIN_PKT'], [0], [stdout])
> +AT_CHECK([ovs-appctl dpctl/dump-flows --names | strip_used | strip_stats |
> dnl
> + strip_duration | strip_dp_hash | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6,frag=no),tcp(src=1234,dst=80),
> packets:0, bytes:0, used:0.0s, flags:F,
> actions:socket(netns=305419896,inode=18446744072938082304,else(recirc(0x1)))
> +recirc_id(0x1),in_port(p1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no),
> packets:0, bytes:0, used:0.0s, flags:F, actions:p2
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
> +AT_SETUP([ofproto-dpif - socket action after CT NAT])
> +OVS_VSWITCHD_START
> +
> +add_of_ports br0 1 2
> +
> +AT_DATA([flows.txt], [dnl
> +table=0,priority=10,ip,in_port=1,tcp,action=ct(commit,table=1,nat(dst=1.2.3.4))
> +table=1,priority=10,ip,in_port=1,tcp,action=2
> +])
> +AT_CHECK([ovs-ofctl --protocols=OpenFlow10 add-flows br0 flows.txt])
> +
> +# Enable socket offload on port 1
> +AT_CHECK([ovs-vsctl set interface p2 other_config:socket-offload=true])
> +
> +# Test established TCP connection (ACK flag)
> +m4_define([TCP_ACK_PKT], [m4_join([,],
> + [eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800)],
> + [ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6,tos=0,ttl=64,frag=no)],
> + [tcp(src=1234,dst=80),tcp_flags(ack)])])
> +
> +AT_CHECK([ovs-appctl vlog/set ofproto_dpif_xlate:dbg])
> +
> +# Send TCP SYN packet and trace it
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 'TCP_ACK_PKT'], [0], [stdout])
> +
> +# Check that socket action is generated for established connections too
> +AT_CHECK([ovs-appctl dpctl/dump-flows --names | strip_used | strip_stats |
> dnl
> + strip_duration | strip_dp_hash | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(proto=6,frag=no),
> packets:0, bytes:0, used:never,
> actions:ct(commit,nat(dst=1.2.3.4)),recirc(0x1)
> +recirc_id(0x1),in_port(p1),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=10.0.0.1,dst=1.2.3.4,proto=6,frag=no),tcp(src=1234,dst=80),
> packets:0, bytes:0, used:never,
> actions:socket(netns=305419896,inode=18446744072938082304,else(recirc(0x2)))
> +recirc_id(0x2),in_port(p1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(proto=6,frag=no),
> packets:0, bytes:0, used:never, actions:p2
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> --
> 2.51.0
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev