flow_compose_l4(struct dp_packet *p, const struct flow *flow,
                const void *l7, size_t l7_len)
{
    size_t orig_len = dp_packet_size(p);

    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 = dp_packet_put_zeros(p, sizeof *tcp);
            tcp->tcp_src = flow->tp_src;
            tcp->tcp_dst = flow->tp_dst;
            tcp->tcp_ctl = TCP_CTL(ntohs(flow->tcp_flags), 5);
            if (!(flow->tcp_flags & htons(TCP_SYN | TCP_FIN | TCP_RST))) {
                flow_compose_l7(p, l7, l7_len);
            }
        } else if (flow->nw_proto == IPPROTO_UDP) {
            struct udp_header *udp = dp_packet_put_zeros(p, sizeof *udp);
            udp->udp_src = flow->tp_src;
            udp->udp_dst = flow->tp_dst;
            udp->udp_len = htons(sizeof *udp + l7_len);
            flow_compose_l7(p, l7, l7_len);
        } else if (flow->nw_proto == IPPROTO_SCTP) {
            struct sctp_header *sctp = dp_packet_put_zeros(p, sizeof *sctp);
            sctp->sctp_src = flow->tp_src;
            sctp->sctp_dst = flow->tp_dst;
            /* XXX Someone should figure out what L7 data to include. */
        } else if (flow->nw_proto == IPPROTO_ICMP) {
            struct icmp_header *icmp = dp_packet_put_zeros(p, sizeof *icmp);
            icmp->icmp_type = ntohs(flow->tp_src);
            icmp->icmp_code = ntohs(flow->tp_dst);
            if ((icmp->icmp_type == ICMP4_ECHO_REQUEST ||
                 icmp->icmp_type == ICMP4_ECHO_REPLY)
                && icmp->icmp_code == 0) {
                flow_compose_l7(p, l7, l7_len);
            } else {
                /* XXX Add inner IP packet for e.g. destination unreachable? */
            }
        } else if (flow->nw_proto == IPPROTO_IGMP) {
            struct igmp_header *igmp = dp_packet_put_zeros(p, sizeof *igmp);
            igmp->igmp_type = ntohs(flow->tp_src);
            igmp->igmp_code = ntohs(flow->tp_dst);
            put_16aligned_be32(&igmp->group, flow->igmp_group_ip4);
        } else if (flow->nw_proto == IPPROTO_ICMPV6) {
            struct icmp6_data_header *icmp6;

            icmp6 = dp_packet_put_zeros(p, sizeof *icmp6);
            icmp6->icmp6_base.icmp6_type = ntohs(flow->tp_src);
            icmp6->icmp6_base.icmp6_code = ntohs(flow->tp_dst);
            put_16aligned_be32(icmp6->icmp6_data.be32, flow->igmp_group_ip4);

            if (icmp6->icmp6_base.icmp6_code == 0 &&
                (icmp6->icmp6_base.icmp6_type == ND_NEIGHBOR_SOLICIT ||
                 icmp6->icmp6_base.icmp6_type == ND_NEIGHBOR_ADVERT)) {
                struct in6_addr *nd_target;
                struct ovs_nd_lla_opt *lla_opt;

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

                if (!eth_addr_is_zero(flow->arp_sha)) {
                    lla_opt = dp_packet_put_zeros(p, 8);
                    lla_opt->len = 1;
                    lla_opt->type = ND_OPT_SOURCE_LINKADDR;
                    lla_opt->mac = flow->arp_sha;
                }
                if (!eth_addr_is_zero(flow->arp_tha)) {
                    lla_opt = dp_packet_put_zeros(p, 8);
                    lla_opt->len = 1;
                    lla_opt->type = ND_OPT_TARGET_LINKADDR;
                    lla_opt->mac = flow->arp_tha;
                }
            } else if (icmp6->icmp6_base.icmp6_code == 0 &&
                       (icmp6->icmp6_base.icmp6_type == ICMP6_ECHO_REQUEST ||
                        icmp6->icmp6_base.icmp6_type == ICMP6_ECHO_REPLY)) {
                flow_compose_l7(p, l7, l7_len);
            } else {
                /* XXX Add inner IP packet for e.g. destination unreachable? */
            }
        }
    }

    return dp_packet_size(p) - orig_len;
}