return CIPSO_V4_HDR_LEN + ret_val;
 }
 
+static int cipso_v4_get_actual_opt_len(const unsigned char *data, int len)
+{
+       int iter = 0, optlen = 0;
+
+       /* determining the new total option length is tricky because of
+        * the padding necessary, the only thing i can think to do at
+        * this point is walk the options one-by-one, skipping the
+        * padding at the end to determine the actual option size and
+        * from there we can determine the new total option length
+        */
+       while (iter < len) {
+               if (data[iter] == IPOPT_END) {
+                       break;
+               } else if (data[iter] == IPOPT_NOP) {
+                       iter++;
+               } else {
+                       iter += data[iter + 1];
+                       optlen = iter;
+               }
+       }
+       return optlen;
+}
+
 /**
  * cipso_v4_sock_setattr - Add a CIPSO option to a socket
  * @sk: the socket
                u8 cipso_len;
                u8 cipso_off;
                unsigned char *cipso_ptr;
-               int iter;
                int optlen_new;
 
                cipso_off = opt->opt.cipso - sizeof(struct iphdr);
                memmove(cipso_ptr, cipso_ptr + cipso_len,
                        opt->opt.optlen - cipso_off - cipso_len);
 
-               /* determining the new total option length is tricky because of
-                * the padding necessary, the only thing i can think to do at
-                * this point is walk the options one-by-one, skipping the
-                * padding at the end to determine the actual option size and
-                * from there we can determine the new total option length */
-               iter = 0;
-               optlen_new = 0;
-               while (iter < opt->opt.optlen) {
-                       if (opt->opt.__data[iter] == IPOPT_END) {
-                               break;
-                       } else if (opt->opt.__data[iter] == IPOPT_NOP) {
-                               iter++;
-                       } else {
-                               iter += opt->opt.__data[iter + 1];
-                               optlen_new = iter;
-                       }
-               }
+               optlen_new = cipso_v4_get_actual_opt_len(opt->opt.__data,
+                                                        opt->opt.optlen);
                hdr_delta = opt->opt.optlen;
                opt->opt.optlen = (optlen_new + 3) & ~3;
                hdr_delta -= opt->opt.optlen;
  */
 int cipso_v4_skbuff_delattr(struct sk_buff *skb)
 {
-       int ret_val;
+       int ret_val, cipso_len, hdr_len_actual, new_hdr_len_actual, new_hdr_len,
+           hdr_len_delta;
        struct iphdr *iph;
        struct ip_options *opt = &IPCB(skb)->opt;
        unsigned char *cipso_ptr;
        if (ret_val < 0)
                return ret_val;
 
-       /* the easiest thing to do is just replace the cipso option with noop
-        * options since we don't change the size of the packet, although we
-        * still need to recalculate the checksum */
-
        iph = ip_hdr(skb);
        cipso_ptr = (unsigned char *)iph + opt->cipso;
-       memset(cipso_ptr, IPOPT_NOOP, cipso_ptr[1]);
+       cipso_len = cipso_ptr[1];
+
+       hdr_len_actual = sizeof(struct iphdr) +
+                        cipso_v4_get_actual_opt_len((unsigned char *)(iph + 1),
+                                                    opt->optlen);
+       new_hdr_len_actual = hdr_len_actual - cipso_len;
+       new_hdr_len = (new_hdr_len_actual + 3) & ~3;
+       hdr_len_delta = (iph->ihl << 2) - new_hdr_len;
+
+       /* 1. shift any options after CIPSO to the left */
+       memmove(cipso_ptr, cipso_ptr + cipso_len,
+               new_hdr_len_actual - opt->cipso);
+       /* 2. move the whole IP header to its new place */
+       memmove((unsigned char *)iph + hdr_len_delta, iph, new_hdr_len_actual);
+       /* 3. adjust the skb layout */
+       skb_pull(skb, hdr_len_delta);
+       skb_reset_network_header(skb);
+       iph = ip_hdr(skb);
+       /* 4. re-fill new padding with IPOPT_END (may now be longer) */
+       memset((unsigned char *)iph + new_hdr_len_actual, IPOPT_END,
+              new_hdr_len - new_hdr_len_actual);
+
+       opt->optlen -= hdr_len_delta;
        opt->cipso = 0;
        opt->is_changed = 1;
-
+       if (hdr_len_delta != 0) {
+               iph->ihl = new_hdr_len >> 2;
+               iph_set_totlen(iph, skb->len);
+       }
        ip_send_check(iph);
 
        return 0;