linux 虚拟网卡 c语言,虚拟网卡程序设计

一、创建设备并拦截数据包

1、打开虚拟设备tun   (存在opentun文件夹下)

opentun.c

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

/*******************代码说明***********************

打开tun/tap设备  written by fys 2012/4/18

/*******************代码说明***********************

/*

所在头文件:自定义函数

函数原型:int tuntap_create (const char *dev);

函数功能:打开TUN/TAP设备,返回设备文件描述符号

入口参数:设备名字符串,"tun"或"tap"

出口参数:设备文件描述符号

*/

int tuntap_create (const char *dev)  //dev可以是字符串“tun”,也可以是“tap”

{

struct ifreq ifr;//ifreq存放接口信息

int fd;

char *device = "/dev/net/tun";

/*打开设备文件描述*/

if ((fd = open (device, O_RDWR)) < 0)

fprintf(stderr, "Cannot open TUN/TAP dev %s", device);

/*打开设备文件描述*/

memset (&ifr,0, sizeof (ifr));

ifr.ifr_flags = IFF_NO_PI;

/*判断参数dev是何种设备*/

if (!strncmp (dev, "tun", 3))//比较dev与tun的前三个字符

{

ifr.ifr_flags |= IFF_TUN;

}

else if (!strncmp (dev, "tap", 3))

{

ifr.ifr_flags |= IFF_TAP;

}

else

{

fprintf(stderr, "I don't recognize device %s as a TUN or TAP device",dev);

}

/*判断参数dev是何种设备*/

if (strlen (dev) > 3)/* unit number specified? */

strncpy (ifr.ifr_name, dev, IFNAMSIZ);

if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)//打开虚拟网卡

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

if(ioctl(fd, TUNSETNOCSUM, (void *)&ifr) < 0)//不校验和

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

fprintf(stderr, "TUN/TAP device %s opened\n", ifr.ifr_name);

return fd;//返回设备文件描述符号

}

int main()

{

tuntap_create("tun");//打开tun设备,返回tun文件描述符号

system("ifconfig tun0 up");

while(1);//使设备一直处于打开状态,死循环。

return 0;

}

opentun.sh

#!/bin/bash

#开启tun设备

gcc -o opentun.exe opentun.c

./opentun.exe

2、TUN/TAP设备结构 struct tun_struct { char name[8]; //设备名 unsigned long flags; //区分tun和tap设备 struct fasync_struct *fasync; //文件异步通知结构 wait_queue_head_t read_wait; //等待队列 struct net_device dev; //linux 抽象网络设备结构 struct sk_buff_head txq; //网络缓冲区队列 struct net_device_stats stats; //网卡状态信息结构 };

struct net_device结构是linux内核提供的统一网络设备结构,定义了系统统一的访问接口。

Tun/tap驱动中实现的网卡驱动的处理例程:

static int tun_net_open(struct net_device *dev);

static int tun_net_close(struct net_device *dev);

static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev);//数据包发送例程

static void tun_net_mclist(struct net_device *dev);//设置多点传输的地址链表

static struct net_device_stats *tun_net_stats(struct net_device *dev);//当一个应用程序需要知道网络接口的一些统计数据时,可调用该函数,如ifconfig、netstat等。

int tun_net_init(struct net_device *dev);//网络设备初始例程

字符设备部分:

在Linux中,字符设备和块设备统一以文件的方式访问,访问它们的接口是统一的,都是使用open()函数打开设备文件或普通文件,用read()和write()函数实现读写文件等等。Tun/tap驱动定义的字符设备的访问接口如下:

static struct file_operations tun_fops = {

owner: THIS_MODULE,

llseek: tun_chr_lseek,

read tun_chr_read,

write: tun_chr_write,

poll: tun_chr_poll,

ioctl: tun_chr_ioctl,

open: tun_chr_open,

release: tun_chr_close,

fasync: tun_chr_fasync

};

在内核中利用misc_register() 函数将该驱动注册为非标准字符设备驱动,提供字符设备具有的各种程序接口。代码摘自linux-2.4.20\linux-2.4.20\drivers\net\tun.c static struct miscdevice tun_miscdev= { TUN_MINOR, "net/tun", &tun_fops }; int __init tun_init(void) { … if (misc_register(&tun_miscdev)) { printk(KERN_ERR "tun: Can't register misc device %d\n", TUN_MINOR); return -EIO; } return 0; }

3、

截获发往某个地址的数据

OpenTunAndRead.c

#include <stdio.h>

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

/*******************代码说明***********************

打开tun/tap设备 读取数据  written by fys 2012/4/18

需要在另一个终端写入数据!否则本程序不能顺利执行!

/*******************代码说明***********************

/*

所在头文件:自定义函数

函数原型:int tuntap_create (const char *dev);

函数功能:打开TUN/TAP设备,返回设备文件描述符号

入口参数:设备名字符串,"tun"或"tap"

出口参数:设备文件描述符号

*/

int tuntap_create (const char *dev)  //dev可以是字符串“tun”,也可以是“tap”

{

struct ifreq ifr;//ifreq存放接口信息

int fd;

char *device = "/dev/net/tun";

/*以读写方式打开device设备,返回描述符*/

if ((fd = open (device, O_RDWR)) < 0)

fprintf(stderr, "Cannot open TUN/TAP dev %s", device);

/*以读写方式打开device设备,返回描述符*/

memset (&ifr,0, sizeof (ifr));

ifr.ifr_flags = IFF_NO_PI;

/*判断参数dev是何种设备*/

if (!strncmp (dev, "tun", 3))//比较dev与tun的前三个字符

{

ifr.ifr_flags |= IFF_TUN;

}

else if (!strncmp (dev, "tap", 3))

{

ifr.ifr_flags |= IFF_TAP;

}

else

{

fprintf(stderr, "I don't recognize device %s as a TUN or TAP device",dev);

}

/*判断参数dev是何种设备*/

if (strlen (dev) > 3)/* unit number specified? */

strncpy (ifr.ifr_name, dev, IFNAMSIZ);

/*

使用ioctl()函数操作字符设备文件描述符,将调用字符设备中tun_chr_ioctl 来设置已经open好的tun/tap设备,如果设置标志为TUNSETIFF,则调用tun_set_iff() 函数,此函数将完成很重要的一步操作,就是对网卡驱动进行注册register_netdev(&tun->dev),网卡驱动的各个处理例程的挂接在open操作时由tun_chr_open()函数初始化好了。

*/

if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)//打开虚拟网卡

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

if(ioctl(fd, TUNSETNOCSUM, (void *)&ifr) < 0)//不校验和

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

fprintf(stderr, "TUN/TAP device %s opened\n", ifr.ifr_name);

return fd;//返回设备文件描述符号

}

/*

所在头文件:自定义

函数原型:int WriteToTun(int tunfd,char *buffer);

函数功能:向tun设备文件中写数据

入口参数:tun设备文件描述符号,要写入tun的字符串指针buffer,写入tun的字节数SizeOfBuffer

出口参数:共写入tun的字节数

*/

int WriteToTun(int tunfd,char *buffer,int SizeOfBuffer)

{

int CountWriteToTun;

CountWriteToTun = write(tunfd,buffer,SizeOfBuffer);

printf("write %d bytes to tun\n\n",CountWriteToTun);

return CountWriteToTun;

}

int main()

{

int tuntapfd,CountReadFromTun;

int i;

char DataWriteToTun[]="hello world";//写入tun设备的数据

unsigned char DataReadFromTun[4096];

tuntapfd = tuntap_create("tun");//打开tun设备,返回tun文件描述符号

system("ifconfig tun0 up");

while(1)

{

CountReadFromTun = read(tuntapfd,DataReadFromTun,sizeof(DataReadFromTun));

for(i = 0 ; i < CountReadFromTun; i++)

{

if ( i % 16 == 0 ) printf("\n");

printf("%02x",DataReadFromTun[i]);//按16进制输出,长度不足2,补零

}

printf("\nCountReadFromTun:%dBytes\n",CountReadFromTun);

}

//while(1);//使设备一直处于打开状态,死循环。

return 0;

}

OpenTunAndRead.sh

#!/bin/bash

gcc -o OpenTunAndRead.exe OpenTunAndRead.c

./OpenTunAndRead.exe

NetConfigAndPing.sh

#!/bin/bash

#配置虚拟网卡以及需要截获的IP

ifconfig tun0 10.10.10.1 up  #整个网络,路由表中无法查询的包都送到全零网络中

route add 10.10.10.2 dev tun0  #该route命令指明目标为10.10.10.2的包由网卡tun0路由出去

route add 119.75.217.56 dev tun0 #截获所有发向www.baidu.com的数据

route add 218.30.66.102 dev tun0 #截获所有发向www.sina.com的数据

#ping 10.10.10.2

#route add 219.245.89.102 dev tun0

操作:

1、一个终端执行 ./OpenTunAndRead.sh

2、另一个终端执行./NetConfigAndPing.sh

3、在网页中输入地址119.75.217.56则虚拟网卡会截获发往该地址的数据

(要截获的地址数据在NetConfigAndPing.sh中设置)

4500003c06a040004006038d0a0a0a01

da1e426694f200509af8a7e000000000

a00216d086cc0000020405b40402080a

007aa1400000000001030305

CountReadFromTun:60Bytes 0a0a0a01  10.10.10.1 虚拟网卡地址

da1e4266  218.30.66.102 www.sina.com地址

TUN/TAP 虚拟网络设备的原理比较简单,他在Linux内核中添加了一个TUN/TAP虚拟网络设备的驱动程序和一个与之相关连的字符设备 /dev/net/tun,字符设备tun作为用户空间和内核空间交换数据的接口。当内核将数据包发送到虚拟网络设备时,数据包被保存在设备相关的一个队 列中,直到用户空间程序通过打开的字符设备tun的描述符读取时,它才会被拷贝到用户空间的缓冲区中,其效果就相当于,数据包直接发送到了用户空间。通过 系统调用write发送数据包时其原理与此类似。

值得注意的是:一次read系统调用,有且只有一个数据包被传送到用户空间,并且当用户空间的缓冲区比较小时,数据包将被截断,剩余部分将永久地消失,write系统调用与read类似,每次只发送一个数据包。所以在编写此类程序的时候,请用足够大的缓冲区,直接调用系统调用read/write,避免采用C语言的带缓存的IO函数。

三、MN VINC拦截数据包并发送给HA,HA改变包地址后再将包发给MN

CaptureSendReceivePackets.c

/***********************程序说明***********************

打开tun设备,将虚拟设备文件/dev/tun中的数据显示在终端上;

以socket将虚拟设备文件中的数据传给HA,

测试:接收HA发来的数据并写入虚拟网卡。

written by fys 2012/5/8

************************程序说明***********************/

#include <stdio.h>

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define REMOTEPORT 4567          //REMOTEPORT即HA开放的接收MN数据的端口

#define REMOTEIP "219.245.89.102" //REMOTEIP即HA的IP,本机测试用127.0.0.1

/*

所在头文件:自定义函数

函数原型:int tuntap_create (const char *dev);

函数功能:打开TUN/TAP设备,返回设备文件描述符号

入口参数:设备名字符串,"tun"或"tap"

出口参数:设备文件描述符号

*/

int tuntap_create (const char *dev)  //dev可以是字符串“tun”,也可以是“tap”

{

struct ifreq ifr;//ifreq存放接口信息

int fd;

char *device = "/dev/net/tun";

/*以读写方式打开device设备,返回描述符*/

if ((fd = open (device, O_RDWR)) < 0)

fprintf(stderr, "Cannot open TUN/TAP dev %s", device);

/*以读写方式打开device设备,返回描述符*/

memset (&ifr,0, sizeof (ifr));

ifr.ifr_flags = IFF_NO_PI;

/*判断参数dev是何种设备*/

if (!strncmp (dev, "tun", 3))//比较dev与tun的前三个字符

{

ifr.ifr_flags |= IFF_TUN;

}

else if (!strncmp (dev, "tap", 3))

{

ifr.ifr_flags |= IFF_TAP;

}

else

{

fprintf(stderr, "I don't recognize device %s as a TUN or TAP device",dev);

}

/*判断参数dev是何种设备*/

if (strlen (dev) > 3)/* unit number specified? */

strncpy (ifr.ifr_name, dev, IFNAMSIZ);

if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)//打开虚拟网卡

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

if(ioctl(fd, TUNSETNOCSUM, (void *)&ifr) < 0)//不校验和

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

fprintf(stderr, "TUN/TAP device %s opened\n", ifr.ifr_name);

return fd;//返回设备文件描述符号

}

/*

所在头文件:自定义

函数原型:int WriteToTun(int tunfd,char *buffer);

函数功能:向tun设备文件中写数据

入口参数:tun设备文件描述符号,要写入tun的字符串指针buffer,写入tun的字节数SizeOfBuffer

出口参数:共写入tun的字节数

*/

int WriteToTun(int tunfd,char *buffer,int SizeOfBuffer)

{

int CountWriteToTun;

CountWriteToTun = write(tunfd,buffer,SizeOfBuffer);

printf("write %d bytes to tun\n\n",CountWriteToTun);

return CountWriteToTun;

}

int main()

{

/*与tun相关的变量定义*/

int tuntapfd;               //打开的虚拟设备的文件描述符

int CountReadFromTun,CountWriteToTun,CountSendToHA,CountReceiveFromHA;

int tempi,tempj;

int IDReadFromTun = 0;

unsigned char DataReadFromTun[4096];

unsigned char DataReceiveFromHA[4096];

unsigned char DataSendToHA[4096];

/*与tun相关的变量定义*/

/*与socket相关的变量定义*/

int sockfd;                  //套接字描述符

struct sockaddr_in addr;  //套接字数据类型,用来存放HA目的套接字

int addr_len;             //变量addr的长度

/*与socket相关的变量定义*/

/*创建套接字*/

if(( sockfd = socket(AF_INET,SOCK_DGRAM,0))<0)

{

perror("error");

exit(1);

}

else

{

printf("socket created successfully!\n");

printf("socket id:%d\n",sockfd);

printf("remote ip:%s\n",REMOTEIP);

printf("remote port: %d\n\n",REMOTEPORT);

}

/*创建套接字*/

addr_len=sizeof(struct sockaddr_in);

bzero(&addr,sizeof(addr));

/*HA套接字地址*/

addr.sin_family=AF_INET;

addr.sin_port=htons(REMOTEPORT);

addr.sin_addr.s_addr=inet_addr(REMOTEIP);

/*HA套接字地址*/

tuntapfd = tuntap_create("tun");  //打开tun设备,返回tun文件描述符号

system("ifconfig tun0 up");       //使虚拟网卡工作

while(1)

{

//unsigned char MyPingIP[4];

/*从TUN中读取数据*/

CountReadFromTun = read(tuntapfd,DataReadFromTun,sizeof(DataReadFromTun));

if(CountReadFromTun<0)break;

/*从TUN中读取数据*/

/*将从TUN中读取的数据显示到终端*/

for(tempi = 0 ; tempi < CountReadFromTun; tempi++)

{

if ( tempi % 16 == 0 ) printf("\n");

printf("%02x",DataReadFromTun[tempi]);

//x表示无符号16进制数字并以小写表示,02表示长度不足2,前面补零

//例如:若DataReadFromTun[0]存储的数为00000110 ,则输出为 06

}

printf("\nAbove data is from tun of MN!\n");

printf("Count bytes read from tun of MN:%dBytes\n",CountReadFromTun);

/*将从TUN中读取的数据显示到终端*/

/*将TUN中的数据发送给HA*/

CountSendToHA = sendto(sockfd,DataReadFromTun,CountReadFromTun,0,(struct sockaddr *)&addr,addr_len);

printf("Count bytes send to HA:%dBytes\n",CountSendToHA);

/*将TUN中的数据发送给HA*/

/*接收从HA发来的数据*/

CountReceiveFromHA = recvfrom(sockfd,DataReceiveFromHA,sizeof(DataReceiveFromHA),0,(struct sockaddr *)&addr,&addr_len);

/*接收从HA发来的数据*/

/*将从HA接收的数据显示到终端*/

for(tempj = 0 ; tempj < CountReceiveFromHA; tempj++)

{

if ( tempj % 16 == 0 ) printf("\n");

printf("%02x",DataReceiveFromHA[tempj]);

}

printf("\nAbove data is from HA!\n");

printf("Count bytes receive from HA:%dBytes\n",CountReceiveFromHA);

/*将从HA接收的数据显示到终端*/

/*将从MN接收的数据写入虚拟网卡*/

CountWriteToTun = write(tuntapfd,DataReceiveFromHA,CountReceiveFromHA);

printf("Count bytes write to tun:%dbytes\n", CountWriteToTun);

/*将交换源目的地址后的数据写入TUN*/

printf("The serial number of the data:%d\n",IDReadFromTun);

IDReadFromTun++;

}//end while

return 0;

}

HA.c

/***********************程序说明***************************

这是HA的程序,接收CaptureAndSendPackets.c(即MN)发送的数据包,

显示在终端上,并写入虚拟网卡,测试:将改变源目的地址后发送给MN

written by fys 2012/5/8

************************程序说明***************************/

#include <stdio.h>

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define LOCALPORT 4567

int tuntap_create (const char *dev)  //dev可以是字符串“tun”,也可以是“tap”

{

struct ifreq ifr;//ifreq存放接口信息

int fd;

char *device = "/dev/net/tun";

/*以读写方式打开device设备,返回描述符*/

if ((fd = open (device, O_RDWR)) < 0)

fprintf(stderr, "Cannot open TUN/TAP dev %s", device);

/*以读写方式打开device设备,返回描述符*/

memset (&ifr,0, sizeof (ifr));

ifr.ifr_flags = IFF_NO_PI;

/*判断参数dev是何种设备*/

if (!strncmp (dev, "tun", 3))//比较dev与tun的前三个字符

{

ifr.ifr_flags |= IFF_TUN;

}

else if (!strncmp (dev, "tap", 3))

{

ifr.ifr_flags |= IFF_TAP;

}

else

{

fprintf(stderr, "I don't recognize device %s as a TUN or TAP device",dev);

}

/*判断参数dev是何种设备*/

if (strlen (dev) > 3)/* unit number specified? */

strncpy (ifr.ifr_name, dev, IFNAMSIZ);

if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)//打开虚拟网卡

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

if(ioctl(fd, TUNSETNOCSUM, (void *)&ifr) < 0)//不校验和

fprintf(stderr, "Cannot ioctl TUNSETIFF %s", dev);

fprintf(stderr, "TUN/TAP device %s opened\n", ifr.ifr_name);

return fd;//返回设备文件描述符号

}

int WriteToTun(int tunfd,char *buffer,int SizeOfBuffer)

{

int CountWriteToTun;

CountWriteToTun = write(tunfd,buffer,SizeOfBuffer);

printf("write %d bytes to tun\n\n",CountWriteToTun);

return CountWriteToTun;

}

int main(int argc,char *argv[])

{

int tuntapfd;

int sockfd;

int IDofDataFromMN=0; //从MN接收的组序号,即接收的第几组数据

int CountFromMN;      //从MN接收的一组数据的字节数

int CountSendToMN;

int CountWriteToTun;

int CountReadFromTun;

int tempi;

int tempj;

unsigned char DataFromMN[4096];//存放从MN发来的数据

unsigned char DataReadFromTun[4096];

unsigned char DataSendToMN[4096];

unsigned char tempIP[4];

struct sockaddr_in addr;

int addr_len;

/*创建本地套接字*/

if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)

{

perror("error");

exit(1);

}

else

{

printf("socket created successfully!\n");

printf("socked id:%d\n",sockfd);

}

/*创建本地套接字*/

addr_len = sizeof(struct sockaddr_in);

bzero(&addr,sizeof(addr));

/*本地套接字属性*/

addr.sin_family = AF_INET;

addr.sin_port = htons(LOCALPORT);

addr.sin_addr.s_addr = htonl(INADDR_ANY);

/*本地套接字属性*/

/*绑定本地套接字的端口和IP*/

if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0)

{

perror("error");

exit(1);

}

else

{

printf("bind successfully!\n");

printf("local port:%d \n\n",LOCALPORT);

}

/*绑定本地套接字的端口和IP*/

/*创建并打开虚拟网卡*/

tuntapfd = tuntap_create("tun");

system("ifconfig tun0 up");

/*创建并打开虚拟网卡*/

while(1)

{

bzero(DataFromMN,sizeof(DataFromMN));

CountFromMN = recvfrom(sockfd,DataFromMN,sizeof(DataFromMN),0,(struct sockaddr *)&addr,&addr_len);

/*将从MN接收的数据显示在终端上*/

for(tempi = 0 ; tempi < CountFromMN; tempi++)

{

if ( tempi % 16 == 0 ) printf("\n");

printf("%02x",DataFromMN[tempi]);

//x表示无符号16进制数字并以小写表示,02表示长度不足2,前面补零

//例如:若DataReadFromTun[0]存储的数为00000110 ,则输出为 06

}

printf("\nAbove data is from MN!\n");

printf("Received message length:%dBytes\n",CountFromMN);

printf("Received message from:%s\n",inet_ntoa(addr.sin_addr));

/*将从MN接收的数据显示在终端上*/

/*将从MN接收的数据写入虚拟网卡*/

CountWriteToTun = write(tuntapfd,DataFromMN,CountFromMN);

printf("Count bytes write to tun:%dbytes\n", CountWriteToTun);

/*将从MN接收的数据写入虚拟网卡*/

/*从TUN中读取CN传来的数据DataReadFromTun即CN传到HA的数据???*/

//这段语句块可能出错,上面的语句块向虚拟网卡写入数据,此处读出的数据是什么?(前面写入的orCN发来的?)

//CountReadFromTun = read(tuntapfd,DataReadFromTun,sizeof(DataReadFromTun));

//if(CountReadFromTun<0) break;

/*从TUN中读取CN传来的数据DataReadFromTun即CN传到HA的数据???*/

/*将从TUN中读取的包即CN发到HA的包??显示到终端上*/

//printf("Data read from tun:");

//for(tempj = 0 ; tempj < CountReadFromTun; tempj++)

//{

//    if ( tempj % 16 == 0 ) printf("\n" );

//    printf("%02x",DataReadFromTun[tempj]);

//}

/*将从TUN中读取的包即CN发到HA的包??显示到终端上*/

/*测试:改变接收到包源目的地址*/

memcpy(tempIP, &DataFromMN[12], 4);

memcpy(&DataFromMN[12], &DataFromMN[16], 4);

memcpy(&DataFromMN[16], tempIP, 4);

/*测试:改变接收到包源目的地址*/

/*实际发送给MN的数据改变此处的赋值*/

//测试时是直接将DataFromMN的源目的地址交换后给DataSendToMN的

DataSendToMN = DataFromMN;

/*实际发送给MN的数据改变此处的赋值*/

/*测试:将TUN中数据(这是测试语句块,还不是CN发来的)发送给MN*/

CountSendToMN = sendto(sockfd,DataSendToMN,sizeof(DataSendToMN),0,(struct sockaddr *)&addr,addr_len);

printf("Count send to MN from HA:%dBytes\n",CountSendToMN);

/*测试:将TUN中数据(这是测试语句块,还不是CN发来的)发送给MN*/

printf("The serial number of the data:%d\n\n",IDofDataFromMN);

IDofDataFromMN++;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值