flow_compose_l4(struct dp_packet *p, const struct flow *flow)
{
    size_t l4_len = 0;

    if (!(flow->nw_frag & FLOW_NW_FRAG_ANY)
        || !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
        if (flow->nw_proto == IPPROTO_TCP) {
            struct tcp_header *tcp;

            l4_len = sizeof *tcp;
            tcp = dp_packet_put_zeros(p, l4_len);
            tcp->tcp_src = flow->tp_src;
            tcp->tcp_dst = flow->tp_dst;
            tcp->tcp_ctl = TCP_CTL(ntohs(flow->tcp_flags), 5);
        } else if (flow->nw_proto == IPPROTO_UDP) {
            struct udp_header *udp;

            l4_len = sizeof *udp;
            udp = dp_packet_put_zeros(p, l4_len);
            udp->udp_src = flow->tp_src;
            udp->udp_dst = flow->tp_dst;
        } else if (flow->nw_proto == IPPROTO_SCTP) {
            struct sctp_header *sctp;

            l4_len = sizeof *sctp;
            sctp = dp_packet_put_zeros(p, l4_len);
            sctp->sctp_src = flow->tp_src;
            sctp->sctp_dst = flow->tp_dst;
        } else if (flow->nw_proto == IPPROTO_ICMP) {
            struct icmp_header *icmp;

            l4_len = sizeof *icmp;
            icmp = dp_packet_put_zeros(p, l4_len);
            icmp->icmp_type = ntohs(flow->tp_src);
            icmp->icmp_code = ntohs(flow->tp_dst);
            icmp->icmp_csum = csum(icmp, ICMP_HEADER_LEN);
        } else if (flow->nw_proto == IPPROTO_IGMP) {
            struct igmp_header *igmp;

            l4_len = sizeof *igmp;
            igmp = dp_packet_put_zeros(p, l4_len);
            igmp->igmp_type = ntohs(flow->tp_src);
            igmp->igmp_code = ntohs(flow->tp_dst);
            put_16aligned_be32(&igmp->group, flow->igmp_group_ip4);
            igmp->igmp_csum = csum(igmp, IGMP_HEADER_LEN);
        } else if (flow->nw_proto == IPPROTO_ICMPV6) {
            struct icmp6_hdr *icmp;

            l4_len = sizeof *icmp;
            icmp = dp_packet_put_zeros(p, l4_len);
            icmp->icmp6_type = ntohs(flow->tp_src);
            icmp->icmp6_code = ntohs(flow->tp_dst);

            if (icmp->icmp6_code == 0 &&
                (icmp->icmp6_type == ND_NEIGHBOR_SOLICIT ||
                 icmp->icmp6_type == ND_NEIGHBOR_ADVERT)) {
                struct in6_addr *nd_target;
                struct ovs_nd_opt *nd_opt;

                l4_len += sizeof *nd_target;
                nd_target = dp_packet_put_zeros(p, sizeof *nd_target);
                *nd_target = flow->nd_target;

                if (!eth_addr_is_zero(flow->arp_sha)) {
                    l4_len += 8;
                    nd_opt = dp_packet_put_zeros(p, 8);
                    nd_opt->nd_opt_len = 1;
                    nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
                    nd_opt->nd_opt_mac = flow->arp_sha;
                }
                if (!eth_addr_is_zero(flow->arp_tha)) {
                    l4_len += 8;
                    nd_opt = dp_packet_put_zeros(p, 8);
                    nd_opt->nd_opt_len = 1;
                    nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
                    nd_opt->nd_opt_mac = flow->arp_tha;
                }
            }
            icmp->icmp6_cksum = (OVS_FORCE uint16_t)
                csum(icmp, (char *)dp_packet_tail(p) - (char *)icmp);
        }
    }
    return l4_len;
}