+ nh->ip_csum = recalc_csum32(nh->ip_csum, old_addr, new_addr);
+ put_16aligned_be32(addr, new_addr);
+}
+
+/* Returns true, if packet contains at least one routing header where
+ * segements_left > 0.
+ *
+ * This function assumes that L3 and L4 offsets are set in the packet. */
+static bool
+packet_rh_present(struct ofpbuf *packet)
+{
+ const struct ovs_16aligned_ip6_hdr *nh;
+ int nexthdr;
+ size_t len;
+ size_t remaining;
+ uint8_t *data = ofpbuf_l3(packet);
+
+ remaining = packet->l4_ofs - packet->l3_ofs;
+
+ if (remaining < sizeof *nh) {
+ return false;
+ }
+ nh = ALIGNED_CAST(struct ovs_16aligned_ip6_hdr *, data);
+ data += sizeof *nh;
+ remaining -= sizeof *nh;
+ nexthdr = nh->ip6_nxt;
+
+ while (1) {
+ if ((nexthdr != IPPROTO_HOPOPTS)
+ && (nexthdr != IPPROTO_ROUTING)
+ && (nexthdr != IPPROTO_DSTOPTS)
+ && (nexthdr != IPPROTO_AH)
+ && (nexthdr != IPPROTO_FRAGMENT)) {
+ /* It's either a terminal header (e.g., TCP, UDP) or one we
+ * don't understand. In either case, we're done with the
+ * packet, so use it to fill in 'nw_proto'. */
+ break;
+ }
+
+ /* We only verify that at least 8 bytes of the next header are
+ * available, but many of these headers are longer. Ensure that
+ * accesses within the extension header are within those first 8
+ * bytes. All extension headers are required to be at least 8
+ * bytes. */
+ if (remaining < 8) {
+ return false;
+ }
+
+ if (nexthdr == IPPROTO_AH) {
+ /* A standard AH definition isn't available, but the fields
+ * we care about are in the same location as the generic
+ * option header--only the header length is calculated
+ * differently. */
+ const struct ip6_ext *ext_hdr = (struct ip6_ext *)data;
+
+ nexthdr = ext_hdr->ip6e_nxt;
+ len = (ext_hdr->ip6e_len + 2) * 4;
+ } else if (nexthdr == IPPROTO_FRAGMENT) {
+ const struct ovs_16aligned_ip6_frag *frag_hdr
+ = ALIGNED_CAST(struct ovs_16aligned_ip6_frag *, data);
+
+ nexthdr = frag_hdr->ip6f_nxt;
+ len = sizeof *frag_hdr;
+ } else if (nexthdr == IPPROTO_ROUTING) {
+ const struct ip6_rthdr *rh = (struct ip6_rthdr *)data;
+
+ if (rh->ip6r_segleft > 0) {
+ return true;
+ }
+
+ nexthdr = rh->ip6r_nxt;
+ len = (rh->ip6r_len + 1) * 8;
+ } else {
+ const struct ip6_ext *ext_hdr = (struct ip6_ext *)data;
+
+ nexthdr = ext_hdr->ip6e_nxt;
+ len = (ext_hdr->ip6e_len + 1) * 8;
+ }
+
+ if (remaining < len) {
+ return false;
+ }
+ remaining -= len;
+ data += len;
+ }
+
+ return false;
+}
+
+static void
+packet_update_csum128(struct ofpbuf *packet, uint8_t proto,
+ ovs_16aligned_be32 addr[4], const ovs_be32 new_addr[4])
+{
+ size_t l4_size = ofpbuf_l4_size(packet);
+
+ if (proto == IPPROTO_TCP && l4_size >= TCP_HEADER_LEN) {
+ struct tcp_header *th = ofpbuf_l4(packet);
+
+ th->tcp_csum = recalc_csum128(th->tcp_csum, addr, new_addr);
+ } else if (proto == IPPROTO_UDP && l4_size >= UDP_HEADER_LEN) {
+ struct udp_header *uh = ofpbuf_l4(packet);
+
+ if (uh->udp_csum) {
+ uh->udp_csum = recalc_csum128(uh->udp_csum, addr, new_addr);
+ if (!uh->udp_csum) {
+ uh->udp_csum = htons(0xffff);
+ }
+ }
+ }
+}
+
+static void
+packet_set_ipv6_addr(struct ofpbuf *packet, uint8_t proto,
+ ovs_16aligned_be32 addr[4], const ovs_be32 new_addr[4],
+ bool recalculate_csum)
+{
+ if (recalculate_csum) {
+ packet_update_csum128(packet, proto, addr, new_addr);
+ }
+ memcpy(addr, new_addr, sizeof(ovs_be32[4]));
+}
+
+static void
+packet_set_ipv6_flow_label(ovs_16aligned_be32 *flow_label, ovs_be32 flow_key)
+{
+ ovs_be32 old_label = get_16aligned_be32(flow_label);
+ ovs_be32 new_label = (old_label & htonl(~IPV6_LABEL_MASK)) | flow_key;
+ put_16aligned_be32(flow_label, new_label);
+}
+
+static void
+packet_set_ipv6_tc(ovs_16aligned_be32 *flow_label, uint8_t tc)
+{
+ ovs_be32 old_label = get_16aligned_be32(flow_label);
+ ovs_be32 new_label = (old_label & htonl(0xF00FFFFF)) | htonl(tc << 20);
+ put_16aligned_be32(flow_label, new_label);