分类 技术杂记 下的文章

Warp+非常好用,速度快,又免费(名义上的收费,但事实上的免费),但是源IP默认为相邻的地区,在使用一些应用时会受目标站点的限制,比如访问: ChatGPT、Netflix等站点,现有终于有大神解决了该问题。

下载

项目源代码:

https://github.com/bepass-org/warp-plus

可执行文件下载地址:
warp-plus

使用

warp -b 0.0.0.0: 本地端口号 -e 优选 ip: 端口号 -k g8lG9B50-Ge346l2I-5FeYv182 -country 国家代码 -cfon
  • 本地端口号:本机Socks代理端口号
  • 优选 ip: 端口号:使用warp plus的优选Endpoint IP及端口,通过优选工具选取最优IP及端口
  • warp plus密钥:填写Warp+ key,参考get warp plus key
  • 国家代码:填写你想设定的国家地区代码,如下:

    Austria (AT)
    Belgium (BE)
    Bulgaria (BG)
    Brazil (BR)
    Canada (CA)
    Switzerland (CH)
    Czech Republic (CZ)
    Germany (DE)
    Denmark (DK)
    Estonia (EE)
    Spain (ES)
    Finland (FI)
    France (FR)
    United Kingdom (GB)
    Hungary (HU)
    Ireland (IE)
    India (IN)
    Italy (IT)
    Japan (JP)
    Latvia (LV)
    Netherlands (NL)
    Norway (NO)
    Poland (PL)
    Romania (RO)
    Serbia (RS)
    Sweden (SE)
    Singapore (SG)
    Slovakia (SK)
    Ukraine (UA)
    United States (US)

    应用示例:

    warp -b 0.0.0.0:12345 -e 162.159.192.100:987 -k g8lG9B50-Ge346l2I-5FeYv182 -country JP -cfon

    执行后,出现如下提示,说明建立成功:
    2024-04-15T06:13:54.png

保持该窗口不关闭,用代理软件或者浏览器访问本地socks代理127.0.0.1:12345即可上网,并实现IP固定为日本地区。

嵌入式Linux内核修改转发skb网络数据包

实际的项目软件开过程中,碰到一个需求,需要修改以太网接收到的数据包,主要有两点需求:

  • L2层特定自定义协议包修改目的MAC地址
  • 针对特定端口的UDP包,修改其目的IP后,再接交给上层(或在Bridge转发之前)处理

方案评估

第一点需求,修改L2层的MAC地址,可采用:

  • 采用ebtable的DNAT修改L2层MAC地址
  • 通过网络驱动传参修改

第二点需求,修改Bridge转发之前的特定端口包目的IP,可采用:

  • 用iptables的INT_NET_PREROUTING Hook,结合br_netfilter模块的bridge-nf-call-iptables功能实现

    /proc/sys/net/bridge/bridge-nf-call-iptables
  • 编写一个netfilter hook内核模块,通过bridge NF_BR_PRE_ROUTING hook点特殊处理

最终采用的方案

上述两点的两个方案,理论上都是第一条比较优雅,但是现实总是残酷的,在嵌入式系统上,芯片资源有限,CPU\RAM\Flash资源受限的情况下,无论是ebtables或是iptables都太重了,特别还要考虑转发执行效率尽量的减少CPU处理,最终都采用了第二种策略。

实现注意点

  • 网络驱动的代码netif_rx(skb);都处于中断的下半区,代码需要高效简短,否则严重影响CPU。
  • 编写netfilter hook模块时,挂载在NF_BR_PRE_ROUTING hook处理节点,处于L2层,收到的skb数据包,无论是网络层、还是传输层指针都是无效的,不能直接引用
  • 挂载点协议选择NFPROTO_BRIDGE

    static struct nf_hook_ops nfho = {
      .hook = hook_func,
      .hooknum = NF_BR_PRE_ROUTING,
      .pf = NFPROTO_BRIDGE,
      .priority = NF_BR_PRI_FIRST,
    };
  • 修改了UDP包的目的IP后,需要重新计算UDP及IP Header的checksum;此外,这里仅修改目的IP,不需要重新全部计算,可以使用inet_proto_csum_replace4只作替代更新,提高效率
  • 目的IP如果修改为组播或广播包,则需要对应的修改eth header的目的MAC地址,否则包不会经过Bridge广播,从L2层上看还是会传给NF_BR_LOCAL_IN

参考代码及资料

  1. 参考代码1

    /*
     * Copyright (c) 2007-2012 Nicira, Inc.
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of version 2 of the GNU General Public
     * License as published by the Free Software Foundation.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
     * 02110-1301, USA
     */
    
    #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
    
    #include <linux/skbuff.h>
    #include <linux/in.h>
    #include <linux/ip.h>
    #include <linux/openvswitch.h>
    #include <linux/tcp.h>
    #include <linux/udp.h>
    #include <linux/in6.h>
    #include <linux/if_arp.h>
    #include <linux/if_vlan.h>
    #include <net/ip.h>
    #include <net/ipv6.h>
    #include <net/checksum.h>
    #include <net/dsfield.h>
    
    #include "checksum.h"
    #include "datapath.h"
    #include "vlan.h"
    #include "vport.h"
    
    static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
                   const struct nlattr *attr, int len,
                   struct ovs_key_ipv4_tunnel *tun_key, bool keep_skb);
    
    static int make_writable(struct sk_buff *skb, int write_len)
    {
     if (!skb_cloned(skb) || skb_clone_writable(skb, write_len))
         return 0;
    
     return pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
    }
    
    /* remove VLAN header from packet and update csum accordingly. */
    static int __pop_vlan_tci(struct sk_buff *skb, __be16 *current_tci)
    {
     struct vlan_hdr *vhdr;
     int err;
    
     err = make_writable(skb, VLAN_ETH_HLEN);
     if (unlikely(err))
         return err;
    
     if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
         skb->csum = csum_sub(skb->csum, csum_partial(skb->data
                     + ETH_HLEN, VLAN_HLEN, 0));
    
     vhdr = (struct vlan_hdr *)(skb->data + ETH_HLEN);
     *current_tci = vhdr->h_vlan_TCI;
    
     memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
     __skb_pull(skb, VLAN_HLEN);
    
     vlan_set_encap_proto(skb, vhdr);
     skb->mac_header += VLAN_HLEN;
     skb_reset_mac_len(skb);
    
     return 0;
    }
    
    static int pop_vlan(struct sk_buff *skb)
    {
     __be16 tci;
     int err;
    
     if (likely(vlan_tx_tag_present(skb))) {
         vlan_set_tci(skb, 0);
     } else {
         if (unlikely(skb->protocol != htons(ETH_P_8021Q) ||
                  skb->len < VLAN_ETH_HLEN))
             return 0;
    
         err = __pop_vlan_tci(skb, &tci);
         if (err)
             return err;
     }
     /* move next vlan tag to hw accel tag */
     if (likely(skb->protocol != htons(ETH_P_8021Q) ||
            skb->len < VLAN_ETH_HLEN))
         return 0;
    
     err = __pop_vlan_tci(skb, &tci);
     if (unlikely(err))
         return err;
    
     __vlan_hwaccel_put_tag(skb, ntohs(tci));
     return 0;
    }
    
    static int push_vlan(struct sk_buff *skb, const struct ovs_action_push_vlan *vlan)
    {
     if (unlikely(vlan_tx_tag_present(skb))) {
         u16 current_tag;
    
         /* push down current VLAN tag */
         current_tag = vlan_tx_tag_get(skb);
    
         if (!__vlan_put_tag(skb, current_tag))
             return -ENOMEM;
    
         if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
             skb->csum = csum_add(skb->csum, csum_partial(skb->data
                     + ETH_HLEN, VLAN_HLEN, 0));
    
     }
     __vlan_hwaccel_put_tag(skb, ntohs(vlan->vlan_tci) & ~VLAN_TAG_PRESENT);
     return 0;
    }
    
    static int set_eth_addr(struct sk_buff *skb,
             const struct ovs_key_ethernet *eth_key)
    {
     int err;
     err = make_writable(skb, ETH_HLEN);
     if (unlikely(err))
         return err;
    
     memcpy(eth_hdr(skb)->h_source, eth_key->eth_src, ETH_ALEN);
     memcpy(eth_hdr(skb)->h_dest, eth_key->eth_dst, ETH_ALEN);
    
     return 0;
    }
    
    static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
                 __be32 *addr, __be32 new_addr)
    {
     int transport_len = skb->len - skb_transport_offset(skb);
    
     if (nh->protocol == IPPROTO_TCP) {
         if (likely(transport_len >= sizeof(struct tcphdr)))
             inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb,
                          *addr, new_addr, 1);
     } else if (nh->protocol == IPPROTO_UDP) {
         if (likely(transport_len >= sizeof(struct udphdr))) {
             struct udphdr *uh = udp_hdr(skb);
    
             if (uh->check ||
                 get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
                 inet_proto_csum_replace4(&uh->check, skb,
                              *addr, new_addr, 1);
                 if (!uh->check)
                     uh->check = CSUM_MANGLED_0;
             }
         }
     }
    
     csum_replace4(&nh->check, *addr, new_addr);
     skb_clear_rxhash(skb);
     *addr = new_addr;
    }
    
    static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto,
                  __be32 addr[4], const __be32 new_addr[4])
    {
     int transport_len = skb->len - skb_transport_offset(skb);
    
     if (l4_proto == IPPROTO_TCP) {
         if (likely(transport_len >= sizeof(struct tcphdr)))
             inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
                           addr, new_addr, 1);
     } else if (l4_proto == IPPROTO_UDP) {
         if (likely(transport_len >= sizeof(struct udphdr))) {
             struct udphdr *uh = udp_hdr(skb);
    
             if (uh->check ||
                 get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
                 inet_proto_csum_replace16(&uh->check, skb,
                               addr, new_addr, 1);
                 if (!uh->check)
                     uh->check = CSUM_MANGLED_0;
             }
         }
     }
    }
    
    static void set_ipv6_addr(struct sk_buff *skb, u8 l4_proto,
               __be32 addr[4], const __be32 new_addr[4],
               bool recalculate_csum)
    {
     if (recalculate_csum)
         update_ipv6_checksum(skb, l4_proto, addr, new_addr);
    
     skb_clear_rxhash(skb);
     memcpy(addr, new_addr, sizeof(__be32[4]));
    }
    
    static void set_ipv6_tc(struct ipv6hdr *nh, u8 tc)
    {
     nh->priority = tc >> 4;
     nh->flow_lbl[0] = (nh->flow_lbl[0] & 0x0F) | ((tc & 0x0F) << 4);
    }
    
    static void set_ipv6_fl(struct ipv6hdr *nh, u32 fl)
    {
     nh->flow_lbl[0] = (nh->flow_lbl[0] & 0xF0) | (fl & 0x000F0000) >> 16;
     nh->flow_lbl[1] = (fl & 0x0000FF00) >> 8;
     nh->flow_lbl[2] = fl & 0x000000FF;
    }
    
    static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl)
    {
     csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8));
     nh->ttl = new_ttl;
    }
    
    static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key)
    {
     struct iphdr *nh;
     int err;
    
     err = make_writable(skb, skb_network_offset(skb) +
                  sizeof(struct iphdr));
     if (unlikely(err))
         return err;
    
     nh = ip_hdr(skb);
    
     if (ipv4_key->ipv4_src != nh->saddr)
         set_ip_addr(skb, nh, &nh->saddr, ipv4_key->ipv4_src);
    
     if (ipv4_key->ipv4_dst != nh->daddr)
         set_ip_addr(skb, nh, &nh->daddr, ipv4_key->ipv4_dst);
    
     if (ipv4_key->ipv4_tos != nh->tos)
         ipv4_change_dsfield(nh, 0, ipv4_key->ipv4_tos);
    
     if (ipv4_key->ipv4_ttl != nh->ttl)
         set_ip_ttl(skb, nh, ipv4_key->ipv4_ttl);
    
     return 0;
    }
    
    static int set_ipv6(struct sk_buff *skb, const struct ovs_key_ipv6 *ipv6_key)
    {
     struct ipv6hdr *nh;
     int err;
     __be32 *saddr;
     __be32 *daddr;
    
     err = make_writable(skb, skb_network_offset(skb) +
                 sizeof(struct ipv6hdr));
     if (unlikely(err))
         return err;
    
     nh = ipv6_hdr(skb);
     saddr = (__be32 *)&nh->saddr;
     daddr = (__be32 *)&nh->daddr;
    
     if (memcmp(ipv6_key->ipv6_src, saddr, sizeof(ipv6_key->ipv6_src)))
         set_ipv6_addr(skb, ipv6_key->ipv6_proto, saddr,
                   ipv6_key->ipv6_src, true);
    
     if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst))) {
         unsigned int offset = 0;
         int flags = OVS_IP6T_FH_F_SKIP_RH;
         bool recalc_csum = true;
    
         if (ipv6_ext_hdr(nh->nexthdr))
             recalc_csum = ipv6_find_hdr(skb, &offset,
                             NEXTHDR_ROUTING, NULL,
                             &flags) != NEXTHDR_ROUTING;
    
         set_ipv6_addr(skb, ipv6_key->ipv6_proto, daddr,
                   ipv6_key->ipv6_dst, recalc_csum);
     }
    
     set_ipv6_tc(nh, ipv6_key->ipv6_tclass);
     set_ipv6_fl(nh, ntohl(ipv6_key->ipv6_label));
     nh->hop_limit = ipv6_key->ipv6_hlimit;
    
     return 0;
    }
    
    /* Must follow make_writable() since that can move the skb data. */
    static void set_tp_port(struct sk_buff *skb, __be16 *port,
              __be16 new_port, __sum16 *check)
    {
     inet_proto_csum_replace2(check, skb, *port, new_port, 0);
     *port = new_port;
     skb_clear_rxhash(skb);
    }
    
    static void set_udp_port(struct sk_buff *skb, __be16 *port, __be16 new_port)
    {
     struct udphdr *uh = udp_hdr(skb);
    
     if (uh->check && get_ip_summed(skb) != OVS_CSUM_PARTIAL) {
         set_tp_port(skb, port, new_port, &uh->check);
    
         if (!uh->check)
             uh->check = CSUM_MANGLED_0;
     } else {
         *port = new_port;
         skb_clear_rxhash(skb);
     }
    }
    
    static int set_udp(struct sk_buff *skb, const struct ovs_key_udp *udp_port_key)
    {
     struct udphdr *uh;
     int err;
    
     err = make_writable(skb, skb_transport_offset(skb) +
                  sizeof(struct udphdr));
     if (unlikely(err))
         return err;
    
     uh = udp_hdr(skb);
     if (udp_port_key->udp_src != uh->source)
         set_udp_port(skb, &uh->source, udp_port_key->udp_src);
    
     if (udp_port_key->udp_dst != uh->dest)
         set_udp_port(skb, &uh->dest, udp_port_key->udp_dst);
    
     return 0;
    }
    
    static int set_tcp(struct sk_buff *skb, const struct ovs_key_tcp *tcp_port_key)
    {
     struct tcphdr *th;
     int err;
    
     err = make_writable(skb, skb_transport_offset(skb) +
                  sizeof(struct tcphdr));
     if (unlikely(err))
         return err;
    
     th = tcp_hdr(skb);
     if (tcp_port_key->tcp_src != th->source)
         set_tp_port(skb, &th->source, tcp_port_key->tcp_src, &th->check);
    
     if (tcp_port_key->tcp_dst != th->dest)
         set_tp_port(skb, &th->dest, tcp_port_key->tcp_dst, &th->check);
    
     return 0;
    }
    
    static int do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
    {
     struct vport *vport;
    
     if (unlikely(!skb))
         return -ENOMEM;
    
     vport = ovs_vport_rcu(dp, out_port);
     if (unlikely(!vport)) {
         kfree_skb(skb);
         return -ENODEV;
     }
    
     ovs_vport_send(vport, skb);
     return 0;
    }
    
    static int output_userspace(struct datapath *dp, struct sk_buff *skb,
                 const struct nlattr *attr)
    {
     struct dp_upcall_info upcall;
     const struct nlattr *a;
     int rem;
    
     upcall.cmd = OVS_PACKET_CMD_ACTION;
     upcall.key = &OVS_CB(skb)->flow->key;
     upcall.userdata = NULL;
     upcall.pid = 0;
    
     for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
          a = nla_next(a, &rem)) {
         switch (nla_type(a)) {
         case OVS_USERSPACE_ATTR_USERDATA:
             upcall.userdata = a;
             break;
    
         case OVS_USERSPACE_ATTR_PID:
             upcall.pid = nla_get_u32(a);
             break;
         }
     }
    
     return ovs_dp_upcall(dp, skb, &upcall);
    }
    
    static int sample(struct datapath *dp, struct sk_buff *skb,
           const struct nlattr *attr,
           struct ovs_key_ipv4_tunnel *tun_key)
    {
     const struct nlattr *acts_list = NULL;
     const struct nlattr *a;
     int rem;
    
     for (a = nla_data(attr), rem = nla_len(attr); rem > 0;
          a = nla_next(a, &rem)) {
         switch (nla_type(a)) {
         case OVS_SAMPLE_ATTR_PROBABILITY:
             if (net_random() >= nla_get_u32(a))
                 return 0;
             break;
    
         case OVS_SAMPLE_ATTR_ACTIONS:
             acts_list = a;
             break;
         }
     }
    
     return do_execute_actions(dp, skb, nla_data(acts_list),
                   nla_len(acts_list), tun_key, true);
    }
    
    static int execute_set_action(struct sk_buff *skb,
                  const struct nlattr *nested_attr,
                  struct ovs_key_ipv4_tunnel *tun_key)
    {
     int err = 0;
    
     switch (nla_type(nested_attr)) {
     case OVS_KEY_ATTR_PRIORITY:
         skb->priority = nla_get_u32(nested_attr);
         break;
    
     case OVS_KEY_ATTR_TUN_ID:
         /* If we're only using the TUN_ID action, store the value in a
          * temporary instance of struct ovs_key_ipv4_tunnel on the stack.
          * If both IPV4_TUNNEL and TUN_ID are being used together we
          * can't write into the IPV4_TUNNEL action, so make a copy and
          * write into that version.
          */
         if (!OVS_CB(skb)->tun_key)
             memset(tun_key, 0, sizeof(*tun_key));
         else if (OVS_CB(skb)->tun_key != tun_key)
             memcpy(tun_key, OVS_CB(skb)->tun_key, sizeof(*tun_key));
         OVS_CB(skb)->tun_key = tun_key;
    
         OVS_CB(skb)->tun_key->tun_id = nla_get_be64(nested_attr);
         break;
    
     case OVS_KEY_ATTR_IPV4_TUNNEL:
         OVS_CB(skb)->tun_key = nla_data(nested_attr);
         break;
    
     case OVS_KEY_ATTR_ETHERNET:
         err = set_eth_addr(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_IPV4:
         err = set_ipv4(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_IPV6:
         err = set_ipv6(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_TCP:
         err = set_tcp(skb, nla_data(nested_attr));
         break;
    
     case OVS_KEY_ATTR_UDP:
         err = set_udp(skb, nla_data(nested_attr));
         break;
     }
    
     return err;
    }
    
    /* Execute a list of actions against 'skb'. */
    static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
             const struct nlattr *attr, int len,
             struct ovs_key_ipv4_tunnel *tun_key, bool keep_skb)
    {
     /* Every output action needs a separate clone of 'skb', but the common
      * case is just a single output action, so that doing a clone and
      * then freeing the original skbuff is wasteful.  So the following code
      * is slightly obscure just to avoid that. */
     int prev_port = -1;
     const struct nlattr *a;
     int rem;
    
     for (a = attr, rem = len; rem > 0;
          a = nla_next(a, &rem)) {
         int err = 0;
    
         if (prev_port != -1) {
             do_output(dp, skb_clone(skb, GFP_ATOMIC), prev_port);
             prev_port = -1;
         }
    
         switch (nla_type(a)) {
         case OVS_ACTION_ATTR_OUTPUT:
             prev_port = nla_get_u32(a);
             break;
    
         case OVS_ACTION_ATTR_USERSPACE:
             output_userspace(dp, skb, a);
             break;
    
         case OVS_ACTION_ATTR_PUSH_VLAN:
             err = push_vlan(skb, nla_data(a));
             if (unlikely(err)) /* skb already freed. */
                 return err;
             break;
    
         case OVS_ACTION_ATTR_POP_VLAN:
             err = pop_vlan(skb);
             break;
    
         case OVS_ACTION_ATTR_SET:
             err = execute_set_action(skb, nla_data(a), tun_key);
             break;
    
         case OVS_ACTION_ATTR_SAMPLE:
             err = sample(dp, skb, a, tun_key);
             break;
         }
    
         if (unlikely(err)) {
             kfree_skb(skb);
             return err;
         }
     }
    
     if (prev_port != -1) {
         if (keep_skb)
             skb = skb_clone(skb, GFP_ATOMIC);
    
         do_output(dp, skb, prev_port);
     } else if (!keep_skb)
         consume_skb(skb);
    
     return 0;
    }
    
    /* We limit the number of times that we pass into execute_actions()
     * to avoid blowing out the stack in the event that we have a loop. */
    #define MAX_LOOPS 5
    
    struct loop_counter {
     u8 count;        /* Count. */
     bool looping;        /* Loop detected? */
    };
    
    static DEFINE_PER_CPU(struct loop_counter, loop_counters);
    
    static int loop_suppress(struct datapath *dp, struct sw_flow_actions *actions)
    {
     if (net_ratelimit())
         pr_warn("%s: flow looped %d times, dropping\n",
                 ovs_dp_name(dp), MAX_LOOPS);
     actions->actions_len = 0;
     return -ELOOP;
    }
    
    /* Execute a list of actions against 'skb'. */
    int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb)
    {
     struct sw_flow_actions *acts = rcu_dereference(OVS_CB(skb)->flow->sf_acts);
     struct loop_counter *loop;
     int error;
     struct ovs_key_ipv4_tunnel tun_key;
    
     /* Check whether we've looped too much. */
     loop = &__get_cpu_var(loop_counters);
     if (unlikely(++loop->count > MAX_LOOPS))
         loop->looping = true;
     if (unlikely(loop->looping)) {
         error = loop_suppress(dp, acts);
         kfree_skb(skb);
         goto out_loop;
     }
    
     OVS_CB(skb)->tun_key = NULL;
     error = do_execute_actions(dp, skb, acts->actions,
                      acts->actions_len, &tun_key, false);
    
     /* Check whether sub-actions looped too much. */
     if (unlikely(loop->looping))
         error = loop_suppress(dp, acts);
    
    out_loop:
     /* Decrement loop counter. */
     if (!--loop->count)
         loop->looping = false;
    
     return error;
    }
  2. 独立内核模块示例代码

    #include <linux/module.h>
    #include <linux/netfilter.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/ip.h>
    #include <linux/udp.h>
    #include <linux/skbuff.h>
    
    #define TARGET_PORT 12345  // 设置特定的目的端口
    #define MULTICAST_IP "224.0.0.1"  // 指定的组播IP地址
    
    // Netfilter钩子函数,用于处理入站的数据包
    static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
    {
     struct iphdr *ip_header;
     struct udphdr *udp_header;
    
     // 检查skb包是否为空
     if (!skb)
         return NF_ACCEPT;
    
     // 检查skb包是否包含IP头部
     if (!skb_network_header(skb))
         return NF_ACCEPT;
    
     // 获取IP头部和UDP头部
     ip_header = ip_hdr(skb);
     udp_header = (struct udphdr *)(skb_transport_header(skb) + ip_hdrlen(skb));
    
     // 检查是否为UDP协议和特定目的端口
     if (ip_header->protocol == IPPROTO_UDP && ntohs(udp_header->dest) == TARGET_PORT) {
         // 修改目的IP地址为指定的组播IP
         ip_header->daddr = in_aton(MULTICAST_IP);
    
         // 重新计算校验值
         udp_header->check = 0;
         udp_header->check = csum_tcpudp_magic(ip_header->saddr, ip_header->daddr,
                                                skb->len - ip_hdrlen(skb), IPPROTO_UDP, csum_partial(udp_header, skb->len - ip_hdrlen(skb), 0));
    
         // 更新校验和
         skb->ip_summed = CHECKSUM_NONE;
         skb->pkt_type = PACKET_MULTICAST;
    
         // 让tcp/ip协议栈处理skb包
         return NF_ACCEPT;
     }
    
     return NF_ACCEPT;
    }
    
    // 初始化和清理模块
    static struct nf_hook_ops nfho = {
     .hook = hook_func,
     .hooknum = NF_BR_PRE_ROUTING,
     .pf = NFPROTO_BRIDGE,
     .priority = NF_BR_PRI_FIRST,
    };
    
    static int __init my_module_init(void)
    {
     return nf_register_hook(&nfho);
    }
    
    static void __exit my_module_exit(void)
    {
     nf_unregister_hook(&nfho);
    }
    
    module_init(my_module_init);
    module_exit(my_module_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Kimson");
  3. linux内核中如何修改skb报文
  4. linux-2-6-network-stack-paper.pdf
  5. skbuff.pdf

因为历史原因,很多工具还需要32位库,Debian/Ubuntu默认未开启支持,直接安装会提示失败,如:
2023-05-30T02:39:18.png

开启i386库支持

sudo dpkg --add-architecture i386
sudo apt update

这样就可以愉快的安装了。

从搭建Blog开始

近期抽空注册了一个免费的VPS,本着不浪费的原则,考虑顺手搭建个Blog,虽然很老土,但还是有不少收获,至少可以解决几点关键需求:

  • 个人技术存档
  • 工具类后台需要
  • 练练手,动动脑

技术方案选型

考虑几点关键需求:

  • 支持HTTPS,证书注册及维护简单
  • 有管理后台,支持Markdown,方便编写文档
  • 小小简单,快速,对VPS要求低
  • 整个站点维护简单,移机轻松

综合上述需求,对应的技术方案:

  • Caddy 支持免费HTTPS证书自动注册及续期
  • typecho 小小简单,支持Markdown
  • Dockers+Docker-compose 实现快速部署,并解决后期的维护问题
    所以综合建立3个docker,分别是:

    Caddy2 + php8 + mysql8

VPS及域名

  • VPS
    VPS有太多的选择,个人注册了Oracle Cloud免费版,可以参考这篇文章,获取高性价比VPS:高性价比VPS整理
  • 域名
    域名注册推荐: Namesilo 或者 Freenom

Docker-compose

Docker-compose.yml:

version: '3'

services:

    caddy:
        image: caddy
        ports:
            - "80:80"
            - "443:443"
            - "443:443/udp"
        volumes:
            - /path_to_your/Caddyfile:/etc/caddy/Caddyfile
            - /path_to_your/caddy_data:/data
            - /path_to_your/caddy_config:/config
#Caddy requires write access to two locations: a data directory, and a configuration directory.
            - /path_to_your/site:/srv
        restart: unless-stopped
        networks:
            - frontend
    db:
        image: mysql
        volumes:
            - /path_to_your/database:/var/lib/mysql
            - /path_to_your/mysql_logs:/var/log/mysql
            - /path_to_your/mysql_conf:/etc/mysql/conf.d
        restart: always
        expose:
            - "3306"
        environment:
            - MYSQL_ROOT_PASSWORD=your_root_password
            - MYSQL_DATABASE=typecho
            - MYSQL_USER=typecho
            - MYSQL_PASSWORD=your_user_password
            - TZ=Asia/Shanghai
        networks:
            - frontend
    php:
      #image: php:fpm
        build: .
        volumes:
            - /path_to_your/site:/var/www/html
        restart: always
        expose:
            - "9000"
        environment:
          - TZ=Asia/Shanghai
        depends_on:
            - db
            - caddy
        networks:
            - frontend
networks:
    frontend:

Caddy配置

Caddyfile:

v365.life www.v365.life {
    encode gzip
    tls [email protected]
    php_fastcgi php:9000 {
        root /var/www/html
    }
    file_server
    log {
      output file /data/log/web.log
    }
}

PHP docker

dockerfile:

FROM php:fpm
RUN docker-php-ext-install mysqli pdo pdo_mysql && docker-php-ext-enable pdo_mysql

typecho

下载最新的typecho zip包,并解压到目录:/path_to_your/site下面

运行

执行如下命令,启动服务:

sudo docker-compose up -d

启动后根据typecho安装页面提示进行配置,在数据库界面选择pdo Mysql数据接口,需要根据配置填写:

  • 用户名: 根据配置MYSQL_USER=typecho
  • 密码: 根据配置MYSQL_PASSWORD=your_user_password
  • 数据库路径: db (默认的localhost需要修改)
  • 数据库名: typecho

注意点

  • Caddy V2与V1配置文件不兼容,有一些细节关键字段有差异,可以参考:Caddy2文档CaddyV2配置文件解析
  • PHP、Caddy及Mysql都在不同的Dockers内,不同于单机部署,需要通过bridge互相访问,即通过docker名+端口号形式,在配置文件时需要特别注意
  • Mysql /var/lib/mysql映射的数据库所在目录,第一次需确保里面为空,这样才能根据ENV变量自动创建MYSQL_DATABASE