xref: /dpdk/doc/guides/sample_app_ug/l3_forward.rst (revision a7c528e5d71ff3f569898d268f9de129fdfc152b)
15630257fSFerruh Yigit..  SPDX-License-Identifier: BSD-3-Clause
25630257fSFerruh Yigit    Copyright(c) 2010-2014 Intel Corporation.
3d0dff9baSBernard Iremonger
4d0dff9baSBernard IremongerL3 Forwarding Sample Application
5d0dff9baSBernard Iremonger================================
6d0dff9baSBernard Iremonger
7e0c7c473SSiobhan ButlerThe L3 Forwarding application is a simple example of packet processing using the DPDK.
8d0dff9baSBernard IremongerThe application performs L3 forwarding.
9d0dff9baSBernard Iremonger
10d0dff9baSBernard IremongerOverview
11d0dff9baSBernard Iremonger--------
12d0dff9baSBernard Iremonger
13e0c7c473SSiobhan ButlerThe application demonstrates the use of the hash and LPM libraries in the DPDK to implement packet forwarding.
14513b0723SMauricio Vasquez BThe initialization and run-time paths are very similar to those of the :doc:`l2_forward_real_virtual`.
15d0dff9baSBernard IremongerThe main difference from the L2 Forwarding sample application is that the forwarding decision
16d0dff9baSBernard Iremongeris made based on information read from the input packet.
17d0dff9baSBernard Iremonger
189a998f2dSReshma PattanThe lookup method is either hash-based or LPM-based and is selected at run time. When the selected lookup method is hash-based,
19d0dff9baSBernard Iremongera hash object is used to emulate the flow classification stage.
20d0dff9baSBernard IremongerThe hash object is used in correlation with a flow table to map each input packet to its flow at runtime.
21d0dff9baSBernard Iremonger
22d0dff9baSBernard IremongerThe hash lookup key is represented by a DiffServ 5-tuple composed of the following fields read from the input packet:
23d0dff9baSBernard IremongerSource IP Address, Destination IP Address, Protocol, Source Port and Destination Port.
24d0dff9baSBernard IremongerThe ID of the output interface for the input packet is read from the identified flow table entry.
25d0dff9baSBernard IremongerThe set of flows used by the application is statically configured and loaded into the hash at initialization time.
26d0dff9baSBernard IremongerWhen the selected lookup method is LPM based, an LPM object is used to emulate the forwarding stage for IPv4 packets.
27d0dff9baSBernard IremongerThe LPM object is used as the routing table to identify the next hop for each input packet at runtime.
28d0dff9baSBernard Iremonger
29d0dff9baSBernard IremongerThe LPM lookup key is represented by the Destination IP Address field read from the input packet.
30d0dff9baSBernard IremongerThe ID of the output interface for the input packet is the next hop returned by the LPM lookup.
31d0dff9baSBernard IremongerThe set of LPM rules used by the application is statically configured and loaded into the LPM object at initialization time.
32d0dff9baSBernard Iremonger
33d0dff9baSBernard IremongerIn the sample application, hash-based forwarding supports IPv4 and IPv6. LPM-based forwarding supports IPv4 only.
34d0dff9baSBernard Iremonger
35d0dff9baSBernard IremongerCompiling the Application
36d0dff9baSBernard Iremonger-------------------------
37d0dff9baSBernard Iremonger
387cacb056SHerakliusz LipiecTo compile the sample application see :doc:`compiling`.
39d0dff9baSBernard Iremonger
407cacb056SHerakliusz LipiecThe application is located in the ``l3fwd`` sub-directory.
41d0dff9baSBernard Iremonger
42d0dff9baSBernard IremongerRunning the Application
43d0dff9baSBernard Iremonger-----------------------
44d0dff9baSBernard Iremonger
4554659744SBeilei XingThe application has a number of command line options::
46d0dff9baSBernard Iremonger
4754659744SBeilei Xing    ./l3fwd [EAL options] -- -p PORTMASK
4854659744SBeilei Xing                             [-P]
4954659744SBeilei Xing                             [-E]
5054659744SBeilei Xing                             [-L]
5154659744SBeilei Xing                             --config(port,queue,lcore)[,(port,queue,lcore)]
5254659744SBeilei Xing                             [--eth-dest=X,MM:MM:MM:MM:MM:MM]
5354659744SBeilei Xing                             [--enable-jumbo [--max-pkt-len PKTLEN]]
5454659744SBeilei Xing                             [--no-numa]
5554659744SBeilei Xing                             [--hash-entry-num]
5654659744SBeilei Xing                             [--ipv6]
5754659744SBeilei Xing                             [--parse-ptype]
58f0a26885SShreyansh Jain                             [--per-port-pool]
59d0dff9baSBernard Iremonger
6054659744SBeilei XingWhere,
61d0dff9baSBernard Iremonger
6254659744SBeilei Xing* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
63d0dff9baSBernard Iremonger
6454659744SBeilei Xing* ``-P:`` Optional, sets all ports to promiscuous mode so that packets are accepted regardless of the packet's Ethernet MAC destination address.
65d0dff9baSBernard Iremonger  Without this option, only packets with the Ethernet MAC destination address set to the Ethernet address of the port are accepted.
66d0dff9baSBernard Iremonger
6754659744SBeilei Xing* ``-E:`` Optional, enable exact match.
68d0dff9baSBernard Iremonger
6954659744SBeilei Xing* ``-L:`` Optional, enable longest prefix match.
70d0dff9baSBernard Iremonger
7154659744SBeilei Xing* ``--config (port,queue,lcore)[,(port,queue,lcore)]:`` Determines which queues from which ports are mapped to which cores.
72d0dff9baSBernard Iremonger
7354659744SBeilei Xing* ``--eth-dest=X,MM:MM:MM:MM:MM:MM:`` Optional, ethernet destination for port X.
74d0dff9baSBernard Iremonger
7554659744SBeilei Xing* ``--enable-jumbo:`` Optional, enables jumbo frames.
76d0dff9baSBernard Iremonger
7754659744SBeilei Xing* ``--max-pkt-len:`` Optional, under the premise of enabling jumbo, maximum packet length in decimal (64-9600).
78d0dff9baSBernard Iremonger
7954659744SBeilei Xing* ``--no-numa:`` Optional, disables numa awareness.
8054659744SBeilei Xing
8154659744SBeilei Xing* ``--hash-entry-num:`` Optional, specifies the hash entry number in hexadecimal to be setup.
8254659744SBeilei Xing
8354659744SBeilei Xing* ``--ipv6:`` Optional, set if running ipv6 packets.
8454659744SBeilei Xing
8554659744SBeilei Xing* ``--parse-ptype:`` Optional, set to use software to analyze packet type. Without this option, hardware will check the packet type.
8671a7e242SJianfeng Tan
87f0a26885SShreyansh Jain* ``--per-port-pool:`` Optional, set to use independent buffer pools per port. Without this option, single buffer pool is used for all ports.
88f0a26885SShreyansh Jain
89e0ef2aecSPablo de LaraFor example, consider a dual processor socket platform with 8 physical cores, where cores 0-7 and 16-23 appear on socket 0,
90e0ef2aecSPablo de Larawhile cores 8-15 and 24-31 appear on socket 1.
91d0dff9baSBernard Iremonger
92e0ef2aecSPablo de LaraTo enable L3 forwarding between two ports, assuming that both ports are in the same socket, using two cores, cores 1 and 2,
93e0ef2aecSPablo de Lara(which are in the same socket too), use the following command:
94d0dff9baSBernard Iremonger
95d0dff9baSBernard Iremonger.. code-block:: console
96d0dff9baSBernard Iremonger
97e0ef2aecSPablo de Lara    ./build/l3fwd -l 1,2 -n 4 -- -p 0x3 --config="(0,0,1),(1,0,2)"
98d0dff9baSBernard Iremonger
99d0dff9baSBernard IremongerIn this command:
100d0dff9baSBernard Iremonger
101e0ef2aecSPablo de Lara*   The -l option enables cores 1, 2
102d0dff9baSBernard Iremonger
103d0dff9baSBernard Iremonger*   The -p option enables ports 0 and 1
104d0dff9baSBernard Iremonger
105e0ef2aecSPablo de Lara*   The --config option enables one queue on each port and maps each (port,queue) pair to a specific core.
106d0dff9baSBernard Iremonger    The following table shows the mapping in this example:
107d0dff9baSBernard Iremonger
108d0dff9baSBernard Iremonger+----------+-----------+-----------+-------------------------------------+
109d0dff9baSBernard Iremonger| **Port** | **Queue** | **lcore** | **Description**                     |
110d0dff9baSBernard Iremonger|          |           |           |                                     |
111d0dff9baSBernard Iremonger+----------+-----------+-----------+-------------------------------------+
112e0ef2aecSPablo de Lara| 0        | 0         | 1         | Map queue 0 from port 0 to lcore 1. |
113d0dff9baSBernard Iremonger|          |           |           |                                     |
114d0dff9baSBernard Iremonger+----------+-----------+-----------+-------------------------------------+
115e0ef2aecSPablo de Lara| 1        | 0         | 2         | Map queue 0 from port 1 to lcore 2. |
116d0dff9baSBernard Iremonger|          |           |           |                                     |
117d0dff9baSBernard Iremonger+----------+-----------+-----------+-------------------------------------+
118d0dff9baSBernard Iremonger
119e0c7c473SSiobhan ButlerRefer to the *DPDK Getting Started Guide* for general information on running applications and
120d0dff9baSBernard Iremongerthe Environment Abstraction Layer (EAL) options.
121d0dff9baSBernard Iremonger
122513b0723SMauricio Vasquez B.. _l3_fwd_explanation:
123513b0723SMauricio Vasquez B
124d0dff9baSBernard IremongerExplanation
125d0dff9baSBernard Iremonger-----------
126d0dff9baSBernard Iremonger
127d0dff9baSBernard IremongerThe following sections provide some explanation of the sample application code. As mentioned in the overview section,
128513b0723SMauricio Vasquez Bthe initialization and run-time paths are very similar to those of the :doc:`l2_forward_real_virtual`.
129d0dff9baSBernard IremongerThe following sections describe aspects that are specific to the L3 Forwarding sample application.
130d0dff9baSBernard Iremonger
131d0dff9baSBernard IremongerHash Initialization
132d0dff9baSBernard Iremonger~~~~~~~~~~~~~~~~~~~
133d0dff9baSBernard Iremonger
134d0dff9baSBernard IremongerThe hash object is created and loaded with the pre-configured entries read from a global array,
135d0dff9baSBernard Iremongerand then generate the expected 5-tuple as key to keep consistence with those of real flow
136d0dff9baSBernard Iremongerfor the convenience to execute hash performance test on 4M/8M/16M flows.
137d0dff9baSBernard Iremonger
138d0dff9baSBernard Iremonger.. note::
139d0dff9baSBernard Iremonger
140d0dff9baSBernard Iremonger    The Hash initialization will setup both ipv4 and ipv6 hash table,
141d0dff9baSBernard Iremonger    and populate the either table depending on the value of variable ipv6.
142d0dff9baSBernard Iremonger    To support the hash performance test with up to 8M single direction flows/16M bi-direction flows,
143d0dff9baSBernard Iremonger    populate_ipv4_many_flow_into_table() function will populate the hash table with specified hash table entry number(default 4M).
144d0dff9baSBernard Iremonger
145d0dff9baSBernard Iremonger.. note::
146d0dff9baSBernard Iremonger
147d0dff9baSBernard Iremonger    Value of global variable ipv6 can be specified with --ipv6 in the command line.
148d0dff9baSBernard Iremonger    Value of global variable hash_entry_number,
149d0dff9baSBernard Iremonger    which is used to specify the total hash entry number for all used ports in hash performance test,
150d0dff9baSBernard Iremonger    can be specified with --hash-entry-num VALUE in command line, being its default value 4.
151d0dff9baSBernard Iremonger
152d0dff9baSBernard Iremonger.. code-block:: c
153d0dff9baSBernard Iremonger
154d0dff9baSBernard Iremonger    #if (APP_LOOKUP_METHOD == APP_LOOKUP_EXACT_MATCH)
155d0dff9baSBernard Iremonger
156d0dff9baSBernard Iremonger        static void
157d0dff9baSBernard Iremonger        setup_hash(int socketid)
158d0dff9baSBernard Iremonger        {
159d0dff9baSBernard Iremonger            // ...
160d0dff9baSBernard Iremonger
161d0dff9baSBernard Iremonger            if (hash_entry_number != HASH_ENTRY_NUMBER_DEFAULT) {
162d0dff9baSBernard Iremonger                if (ipv6 == 0) {
163d0dff9baSBernard Iremonger                    /* populate the ipv4 hash */
164d0dff9baSBernard Iremonger                    populate_ipv4_many_flow_into_table(ipv4_l3fwd_lookup_struct[socketid], hash_entry_number);
165d0dff9baSBernard Iremonger                } else {
166d0dff9baSBernard Iremonger                    /* populate the ipv6 hash */
167d0dff9baSBernard Iremonger                    populate_ipv6_many_flow_into_table( ipv6_l3fwd_lookup_struct[socketid], hash_entry_number);
168d0dff9baSBernard Iremonger                }
169d0dff9baSBernard Iremonger            } else
170d0dff9baSBernard Iremonger                if (ipv6 == 0) {
171d0dff9baSBernard Iremonger                    /* populate the ipv4 hash */
172d0dff9baSBernard Iremonger                    populate_ipv4_few_flow_into_table(ipv4_l3fwd_lookup_struct[socketid]);
173d0dff9baSBernard Iremonger                } else {
174d0dff9baSBernard Iremonger                    /* populate the ipv6 hash */
175d0dff9baSBernard Iremonger                    populate_ipv6_few_flow_into_table(ipv6_l3fwd_lookup_struct[socketid]);
176d0dff9baSBernard Iremonger                }
177d0dff9baSBernard Iremonger            }
178d0dff9baSBernard Iremonger        }
179d0dff9baSBernard Iremonger    #endif
180d0dff9baSBernard Iremonger
181d0dff9baSBernard IremongerLPM Initialization
182d0dff9baSBernard Iremonger~~~~~~~~~~~~~~~~~~
183d0dff9baSBernard Iremonger
184d0dff9baSBernard IremongerThe LPM object is created and loaded with the pre-configured entries read from a global array.
185d0dff9baSBernard Iremonger
186d0dff9baSBernard Iremonger.. code-block:: c
187d0dff9baSBernard Iremonger
188d0dff9baSBernard Iremonger    #if (APP_LOOKUP_METHOD == APP_LOOKUP_LPM)
189d0dff9baSBernard Iremonger
190d0dff9baSBernard Iremonger    static void
191d0dff9baSBernard Iremonger    setup_lpm(int socketid)
192d0dff9baSBernard Iremonger    {
193d0dff9baSBernard Iremonger        unsigned i;
194d0dff9baSBernard Iremonger        int ret;
195d0dff9baSBernard Iremonger        char s[64];
196d0dff9baSBernard Iremonger
197d0dff9baSBernard Iremonger        /* create the LPM table */
198d0dff9baSBernard Iremonger
199a5cf3924SThomas Monjalon        snprintf(s, sizeof(s), "IPV4_L3FWD_LPM_%d", socketid);
200d0dff9baSBernard Iremonger
201d0dff9baSBernard Iremonger        ipv4_l3fwd_lookup_struct[socketid] = rte_lpm_create(s, socketid, IPV4_L3FWD_LPM_MAX_RULES, 0);
202d0dff9baSBernard Iremonger
203d0dff9baSBernard Iremonger        if (ipv4_l3fwd_lookup_struct[socketid] == NULL)
204d0dff9baSBernard Iremonger            rte_exit(EXIT_FAILURE, "Unable to create the l3fwd LPM table"
205d0dff9baSBernard Iremonger                " on socket %d\n", socketid);
206d0dff9baSBernard Iremonger
207d0dff9baSBernard Iremonger        /* populate the LPM table */
208d0dff9baSBernard Iremonger
209d0dff9baSBernard Iremonger        for (i = 0; i < IPV4_L3FWD_NUM_ROUTES; i++) {
210d0dff9baSBernard Iremonger            /* skip unused ports */
211d0dff9baSBernard Iremonger
212d0dff9baSBernard Iremonger            if ((1 << ipv4_l3fwd_route_array[i].if_out & enabled_port_mask) == 0)
213d0dff9baSBernard Iremonger                continue;
214d0dff9baSBernard Iremonger
215d0dff9baSBernard Iremonger            ret = rte_lpm_add(ipv4_l3fwd_lookup_struct[socketid], ipv4_l3fwd_route_array[i].ip,
216d0dff9baSBernard Iremonger           	                    ipv4_l3fwd_route_array[i].depth, ipv4_l3fwd_route_array[i].if_out);
217d0dff9baSBernard Iremonger
218d0dff9baSBernard Iremonger            if (ret < 0) {
219d0dff9baSBernard Iremonger                rte_exit(EXIT_FAILURE, "Unable to add entry %u to the "
220d0dff9baSBernard Iremonger                        "l3fwd LPM table on socket %d\n", i, socketid);
221d0dff9baSBernard Iremonger            }
222d0dff9baSBernard Iremonger
223d0dff9baSBernard Iremonger            printf("LPM: Adding route 0x%08x / %d (%d)\n",
224d0dff9baSBernard Iremonger                (unsigned)ipv4_l3fwd_route_array[i].ip, ipv4_l3fwd_route_array[i].depth, ipv4_l3fwd_route_array[i].if_out);
225d0dff9baSBernard Iremonger        }
226d0dff9baSBernard Iremonger    }
227d0dff9baSBernard Iremonger    #endif
228d0dff9baSBernard Iremonger
229d0dff9baSBernard IremongerPacket Forwarding for Hash-based Lookups
230d0dff9baSBernard Iremonger~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
231d0dff9baSBernard Iremonger
232d0dff9baSBernard IremongerFor each input packet, the packet forwarding operation is done by the l3fwd_simple_forward()
233d0dff9baSBernard Iremongeror simple_ipv4_fwd_4pkts() function for IPv4 packets or the simple_ipv6_fwd_4pkts() function for IPv6 packets.
234d0dff9baSBernard IremongerThe l3fwd_simple_forward() function provides the basic functionality for both IPv4 and IPv6 packet forwarding
235d0dff9baSBernard Iremongerfor any number of burst packets received,
236d0dff9baSBernard Iremongerand the packet forwarding decision (that is, the identification of the output interface for the packet)
237d0dff9baSBernard Iremongerfor hash-based lookups is done by the  get_ipv4_dst_port() or get_ipv6_dst_port() function.
238d0dff9baSBernard IremongerThe get_ipv4_dst_port() function is shown below:
239d0dff9baSBernard Iremonger
240d0dff9baSBernard Iremonger.. code-block:: c
241d0dff9baSBernard Iremonger
242d0dff9baSBernard Iremonger    static inline uint8_t
243c6d6982dSZhiyong Yang    get_ipv4_dst_port(void *ipv4_hdr, uint16_t portid, lookup_struct_t *ipv4_l3fwd_lookup_struct)
244d0dff9baSBernard Iremonger    {
245d0dff9baSBernard Iremonger        int ret = 0;
246d0dff9baSBernard Iremonger        union ipv4_5tuple_host key;
247d0dff9baSBernard Iremonger
248*a7c528e5SOlivier Matz        ipv4_hdr = (uint8_t *)ipv4_hdr + offsetof(struct rte_ipv4_hdr, time_to_live);
249d0dff9baSBernard Iremonger
250d0dff9baSBernard Iremonger        m128i data = _mm_loadu_si128(( m128i*)(ipv4_hdr));
251d0dff9baSBernard Iremonger
252d0dff9baSBernard Iremonger        /* Get 5 tuple: dst port, src port, dst IP address, src IP address and protocol */
253d0dff9baSBernard Iremonger
254d0dff9baSBernard Iremonger        key.xmm = _mm_and_si128(data, mask0);
255d0dff9baSBernard Iremonger
256d0dff9baSBernard Iremonger        /* Find destination port */
257d0dff9baSBernard Iremonger
258d0dff9baSBernard Iremonger        ret = rte_hash_lookup(ipv4_l3fwd_lookup_struct, (const void *)&key);
259d0dff9baSBernard Iremonger
260d0dff9baSBernard Iremonger        return (uint8_t)((ret < 0)? portid : ipv4_l3fwd_out_if[ret]);
261d0dff9baSBernard Iremonger    }
262d0dff9baSBernard Iremonger
263d0dff9baSBernard IremongerThe get_ipv6_dst_port() function is similar to the get_ipv4_dst_port() function.
264d0dff9baSBernard Iremonger
265d0dff9baSBernard IremongerThe simple_ipv4_fwd_4pkts() and simple_ipv6_fwd_4pkts() function are optimized for continuous 4 valid ipv4 and ipv6 packets,
266d0dff9baSBernard Iremongerthey leverage the multiple buffer optimization to boost the performance of forwarding packets with the exact match on hash table.
267d0dff9baSBernard IremongerThe key code snippet of simple_ipv4_fwd_4pkts() is shown below:
268d0dff9baSBernard Iremonger
269d0dff9baSBernard Iremonger.. code-block:: c
270d0dff9baSBernard Iremonger
271d0dff9baSBernard Iremonger    static inline void
272c6d6982dSZhiyong Yang    simple_ipv4_fwd_4pkts(struct rte_mbuf* m[4], uint16_t portid, struct lcore_conf *qconf)
273d0dff9baSBernard Iremonger    {
274d0dff9baSBernard Iremonger        // ...
275d0dff9baSBernard Iremonger
276*a7c528e5SOlivier Matz        data[0] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[0], unsigned char *) + sizeof(struct rte_ether_hdr) + offsetof(struct rte_ipv4_hdr, time_to_live)));
277*a7c528e5SOlivier Matz        data[1] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[1], unsigned char *) + sizeof(struct rte_ether_hdr) + offsetof(struct rte_ipv4_hdr, time_to_live)));
278*a7c528e5SOlivier Matz        data[2] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[2], unsigned char *) + sizeof(struct rte_ether_hdr) + offsetof(struct rte_ipv4_hdr, time_to_live)));
279*a7c528e5SOlivier Matz        data[3] = _mm_loadu_si128(( m128i*)(rte_pktmbuf_mtod(m[3], unsigned char *) + sizeof(struct rte_ether_hdr) + offsetof(struct rte_ipv4_hdr, time_to_live)));
280d0dff9baSBernard Iremonger
281d0dff9baSBernard Iremonger        key[0].xmm = _mm_and_si128(data[0], mask0);
282d0dff9baSBernard Iremonger        key[1].xmm = _mm_and_si128(data[1], mask0);
283d0dff9baSBernard Iremonger        key[2].xmm = _mm_and_si128(data[2], mask0);
284d0dff9baSBernard Iremonger        key[3].xmm = _mm_and_si128(data[3], mask0);
285d0dff9baSBernard Iremonger
286d0dff9baSBernard Iremonger        const void *key_array[4] = {&key[0], &key[1], &key[2],&key[3]};
287d0dff9baSBernard Iremonger
288656ecbe9SThomas Monjalon        rte_hash_lookup_bulk(qconf->ipv4_lookup_struct, &key_array[0], 4, ret);
289d0dff9baSBernard Iremonger
290d0dff9baSBernard Iremonger        dst_port[0] = (ret[0] < 0)? portid:ipv4_l3fwd_out_if[ret[0]];
291d0dff9baSBernard Iremonger        dst_port[1] = (ret[1] < 0)? portid:ipv4_l3fwd_out_if[ret[1]];
292d0dff9baSBernard Iremonger        dst_port[2] = (ret[2] < 0)? portid:ipv4_l3fwd_out_if[ret[2]];
293d0dff9baSBernard Iremonger        dst_port[3] = (ret[3] < 0)? portid:ipv4_l3fwd_out_if[ret[3]];
294d0dff9baSBernard Iremonger
295d0dff9baSBernard Iremonger        // ...
296d0dff9baSBernard Iremonger    }
297d0dff9baSBernard Iremonger
298d0dff9baSBernard IremongerThe simple_ipv6_fwd_4pkts() function is similar to the simple_ipv4_fwd_4pkts() function.
299d0dff9baSBernard Iremonger
30071a7e242SJianfeng TanKnown issue: IP packets with extensions or IP packets which are not TCP/UDP cannot work well at this mode.
30171a7e242SJianfeng Tan
302d0dff9baSBernard IremongerPacket Forwarding for LPM-based Lookups
303d0dff9baSBernard Iremonger~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304d0dff9baSBernard Iremonger
305d0dff9baSBernard IremongerFor each input packet, the packet forwarding operation is done by the l3fwd_simple_forward() function,
306d0dff9baSBernard Iremongerbut the packet forwarding decision (that is, the identification of the output interface for the packet)
307d0dff9baSBernard Iremongerfor LPM-based lookups is done by the get_ipv4_dst_port() function below:
308d0dff9baSBernard Iremonger
309d0dff9baSBernard Iremonger.. code-block:: c
310d0dff9baSBernard Iremonger
311c6d6982dSZhiyong Yang    static inline uint16_t
312*a7c528e5SOlivier Matz    get_ipv4_dst_port(struct rte_ipv4_hdr *ipv4_hdr, uint16_t portid, lookup_struct_t *ipv4_l3fwd_lookup_struct)
313d0dff9baSBernard Iremonger    {
314d0dff9baSBernard Iremonger        uint8_t next_hop;
315d0dff9baSBernard Iremonger
316c6d6982dSZhiyong Yang        return ((rte_lpm_lookup(ipv4_l3fwd_lookup_struct, rte_be_to_cpu_32(ipv4_hdr->dst_addr), &next_hop) == 0)? next_hop : portid);
317d0dff9baSBernard Iremonger    }
318