ortnat_tpl[0],nat_tpl[1] = pkt[IP].dst,pkt [TCP]. dport = nat_tpl [2],nat_tpl[ for in fragment (pkt [IP], fragsize=MTU-ler n(Ether())) sendp (Ether ()/f, iface=LB.IFACE) def LoadBalancer () : sniff (iface=LB_IFACE,prn=pkt_callback,filter="tcp") _name main__" LoadBalancer () *varaiable format (cs-sc)_(f-b)(s-d)(i-p-tpl): sc-cs is direction between client and server, f-b is a side of frontend/backend, s-d is source/destination and i-p-tpl is IP, port or 4-tuple. Figure 3.6 Load balancer code and a slice (bold and bold italics). NF models. Figure 3.6 is its implementation based on the model in [65]. The high-level logic of the code is as follows. Inbound packets that have the IP addresses/ports of clients and the load balancer would be mapped to the IP address/ports of the load balancer and servers. If an inbound packet is a new flow never seen before, one of the backend servers is picked for the mapping and the mapping is stored; otherwise, 3.5 NF Modeling the mapping is read from the dictionary and used for the address/port translation. For the outbound packets, the packets of existing flows would have address/port translation in the same way, but outbound packets of a nonexisting flow would be dropped (i.e., only inbound packets can initiate address/port translation mapping). From reading this code, we gain important insights about NFs. First, different configurations can lead to different program behaviors. For example, the variable "mode" is used to configure how a backend server is selected for a new flow, and it can be either round-robin or random hash. Some existing NF models [65] fail to capture this detail. Second, state variables are dynamically updated as packets are processed. It causes the action on packets to be different at runtime. In this example, whether a flow's 4-tuple is stored in the dictionary is a state that causes the actions on the flow's first packet and that on the remaining packets to be different. Third, NF programs naturally have two key pieces of logic, that is, packet processing and