最近在学零信任相关的知识,给记录下来方便以后查阅

Iptables原理

iptables就是linux中的防火墙工具,其中的四表五链比较重要,并且用了c的底层编写。

四表

  • raw 表的作用是将命中规则的包,跳过其它表的处理,它的优先级最高。
  • mangle 表的作用是根据规则修改数据包的一些标志位,比如 TTL
  • nat 表的作用是实现网络地址转换
  • filter 表的作用是过滤某些包,这是防火墙工作的基础

filter(过滤表)

包含三个规则链

  • INPUT 输入链,处理目标地址为本机 IP 地址的报文。
  • OUTPUT 输出链,处理本机 IP 地址产生的报文。
  • FORWARD 转发链,处理经过本机路由的报文。

经过本机转发的报文经过 FORWARD 链,不经过 IPPUT 链和 OUTPUT 链。

nat(网络地址转换表)

先科普一下网络地址转换是什么

NAT的主要功能是将源IP地址和/或目标IP地址在数据包经过网络边界设备(如路由器或防火墙)时进行转换。这种转换可以在不同层级上进行,包括网络层(IP地址转换)和传输层(端口转换)。

  1. 源地址转换(Source NAT,SNAT):将私有网络内部主机的源IP地址转换为公共网络上的一个IP地址。这允许私有网络中的主机通过共享单个公共IP地址来访问互联网。
  2. 目标地址转换(Destination NAT,DNAT):将公共网络上的目标IP地址转换为私有网络内部主机的一个IP地址。这允许从公共网络访问特定服务或应用程序时,将流量定向到私有网络中的特定主机或服务器。
  3. 端口地址转换(Port Address Translation,PAT):在NAT过程中同时转换IP地址和端口号。通过在传输层级别上对端口号进行转换,可以将多个私有网络主机共享单个公共IP地址。

nat 用来完成源/目的地址和端口的转换,当一个报文在创建一个新的连接时进入该表。它也有 3 个内置规则链。

  • PREROUTING:用于修改到来的报文,只用来做网络地址转换。
  • OUTPUT:用于修改本机产生的并且在路由处理之前的报文。
  • POSTROUTING:用于对转换后的数据包进行进一步处理,比如修改数据包的源地址、源端口。

mangle(修改表)

这个表主要用来进行报文修改,有 5 个内置规则链。

  • PREROUTING:针对到来的报文,在路由之前修改的地方。
  • INPUT:针对目的地址为网关本身的报文。
  • FORWARD:针对通过网关路由转发的报文。
  • POSTROUTING:将要发送出去的报文的地方。
  • OUTPUT:本机产生报文在路由之前修改的地方。

通常使用该表进行报文修改,以便进行 QoS 和策略路由。通常每一个报文都进入该表,但不使用它来做报文过滤。

raw(原始表)

这个表主要用于配置连接跟踪相关内容,在 ip_conntrack 之前调用。

  • PREPROUTING:到达本机的报文。
  • OUTPUT:本机进程产生的报文。

这里是能够在连接跟踪生效前处理报文的地方,你可以标记符合某种条件的报文不被连接跟踪处理。

security 表(新增)

这个表用于安全 Linux 的防火墙规则,是 iptables 最近的新增表,在实际项目中还很少用到。

五链

五链刚刚已经基本上都提到了,下面将根据信息在防火墙中的处理方式来进一步加深理解。

(1)当一个数据包进入网卡时,它首先进入PREROUTING链,内核根据数据包目的IP判断是否需要转送出去。

(2)如果数据包就是进入本机的,它就会沿着图向下移动,到达INPUT链。数据包到了INPUT链后,任何进程都会收到它。本机上运行的程序可以发送数据包,这些数据包会经过OUTPUT链,然后到达POSTROUTING链输出。

(3)如果数据包是要转发出去的,且内核允许转发,数据包就会如图所示向右移动,经过FORWARD链,然后到达POSTROUTING链输出。

数据接收处理流程:PREROUTING链 -> 路由判断(是本机)-> INPUT链 -> …

本机发送数据包流程:路由选择 -> OUTPUT链 -> POSTROUTING链 -> …

转发数据流程:PREROUTING链 -> 路由判断(不是本设备,找到下一跳) -> FORWARD链 -> POSTROUTING链 -> …

所以四表五链的关系如图所示

iptables_1

解答:多个钩子可以处理不同的应用场景,数据接收,数据发送和数据转发需要不一样的策略。所以不能单单使用一个钩子解决,实际问题是复杂的而不是单一的。

Socket编程

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

Socket_1

后记

在防火墙中,锚点是一种机制,用于将一组规则绑定到特定的网络接口、IP地址、协议或服务等。锚点提供了一种有组织的方式来管理和应用规则集,以根据特定的条件对网络流量进行处理。

网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分,发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP协议规定,网络数据流应采用大端字节序,所以可能需要用到字节序转换

1
2
3
4
5
6
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong); //IP
uint16_t htons(uint16_t hostshort); //port
uint32_t ntohl(uint32_t netlong); //IP
uint16_t ntohs(uint16_t netshort); //port

h表示host,n表示network,l表示long,32位长整数,s表示short,16位短整数。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

IP地址转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
/*
作用:将本地字节序(string IP)转换成网络字节序
参数列表:
af:协议类型,AF_INET是ipv4,AF_INET是ipv6
src:传入参数,IP地址(点分十进制形式)
dst:传出参数,转换后的网络字节序IP地址
返回值:
成功:返回1
异常:返回0,src不是有效的IP地址
失败:返回-1
*/
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
/*
作用:将本地字节序(string IP)转换成网络字节序
参数列表:
af:协议类型,AF_INET是ipv4,AF_INET是ipv6
src:网络字节序IP地址
dst:本地字节序IP地址
size:dst
返回值:
成功:返回dst
失败:返回NULL
*/

支持IPv4和IPv6

可重入函数

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr。

因此函数接口是void *addrptr。

sockaddr结构

socket_2

1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in {
sa_family_t sin_family; /* Address family */ 地址结构类型
in_port_t sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr { /* Internet address. */
__be32 s_addr;
};

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sockaddr_in addr;

addr.sin_family = AF_INET/AF_INET6

addr.sin_port = htons(9527);

int dst;

inet_pton(AF_INET, "192.157.22.45", (void *)&dst);

addr.sin_addr.s_addr = dst;

【*】addr.sin_addr.s_addr = htonl(INADDR_ANY); 取出系统中有效的任意IP地址。二进制类型。

bind(fd, (struct sockaddr *)&addr, size);

网络套接字函数

Socket_3

socket函数

1
2
3
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,domain参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可。

bind函数

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。

1
2
3
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1
2
3
4
5
6
7
8
9
sockfd:
socket文件描述符
addr:
构造出IP地址加端口号
(struct sockaddr *)&addr
addrlen:
sizeof(addr)长度
返回值:
成功返回0,失败返回-1, 设置errno

listen函数

listen函数用来设置和服务器连接的主机上限,并不是监听!

1
2
3
4
5
6
7
8
9
10
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd:
socket文件描述符
backlog:
连接上限,最大值128
返回值:
成功:返回0
失败:返回-1

accept函数

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h> 		/* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:
socket文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

accept函数使用示例:

1
2
3
4
5
6
7
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
......
close(connfd);
}

connect函数

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h> 					/* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
socket文件描述符
addr:
传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小
返回值:
成功返回0,失败返回-1,设置errno

server

下面通过最简单的客户端/服务器程序的实例来学习socket API。

server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);

printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
return 0;
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;

if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];

sockfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);

connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

write(sockfd, str, strlen(str));

n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n);
close(sockfd);

return 0;
}

TCP/IP

OSI各种设备和作用以及对应网络分层

TCP/IP_1

TCP/IP与OSI模型

TCP/IP_2

TCP/IP发送邮件时的分层处理

TCP/IP_3

包中的结构

TCP/IP_4

共享介质型网络

争用方式

TCP/IP_5

TCP/IP_6

疑问

要是一直有一个主机发送过程中一直与其他计算机冲突,该主机一直无法完整发送数据该怎么办?

令牌传递方式

TCP/IP_7

PPP

点对点协议。物理层也需要连接

数据帧格式

TCP/IP_8

IP协议

IP地址

分类:A、B、C、D类

fwknop项目

client分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
struct fko_context {
/** \name FKO SPA user-definable message data */

/*@{*/
char *rand_val; //随机值
char *username; //用户名
time_t timestamp; //时间戳
short message_type; //消息类型,表示message字段的内容类型。
char *message; //用户自定义的消息数据。
char *nat_access; //NAT(Network Address Translation)访问字符串,用于处理NAT穿透。
char *server_auth; //服务器认证信息
unsigned int client_timeout; //客户端超时时间,表示一次请求的有效期
/*@}*/
/** \name FKO SPA user-settable message encoding types */
/*@{*/
short digest_type; //摘要类型,用于消息摘要的算法类型。
short encryption_type; //加密类型,表示消息的加密算法类型。
int encryption_mode; //加密模式,表示加密算法的模式(例如,ECB,CBC等)
short hmac_type; //HMAC(Hash-based Message Authentication Code)类型,用于消息认证码的算法类型
/*@}*/
/** \name Computed or predefined data */
/*@{*/
char *version; //版本信息
char *digest; //消息摘要,用于数据完整性验证
int digest_len; //消息摘要的长度
/*@}*/
/** \name Digest of raw encrypted/base64 data
* This is used for replay attack detection
*/
/*@{*/
char *raw_digest; //原始加密/编码数据的摘要,用于防止重放攻击
short raw_digest_type; //原始加密/编码数据的摘要类型
int raw_digest_len; //原始加密/编码数据的摘要长度
/*@}*/
/** \name Computed processed data (encodings, etc.) */
/*@{*/
char *encoded_msg; //编码后的消息数据
int encoded_msg_len; //编码后的消息数据长度
char *encrypted_msg; //加密后的消息数据
int encrypted_msg_len; //加密后的消息数据长度
char *msg_hmac; //消息认证码
int msg_hmac_len; //消息认证码的长度
int added_salted_str; //标记是否添加了盐值字符串
int added_gpg_prefix; //标记是否添加了GPG(GNU Privacy Guard)前缀
/*@}*/
/** \name State info */
/*@{*/
unsigned int state; //状态信息,表示当前上下文的状态。
unsigned char initval; //初始化值。
/*@}*/
#if HAVE_LIBGPGME
/** \name For gpgme support */
/*@{*/
char *gpg_exe; //GPG执行程序路径
char *gpg_recipient; //GPG接收者(用于加密和解密)
char *gpg_signer; //GPG签名者(用于签名和验证)
char *gpg_home_dir; //GPG的主目录

unsigned char have_gpgme_context; //是否有GPGME(GnuPG Made Easy)上下文

gpgme_ctx_t gpg_ctx; //GPGME上下文
gpgme_key_t recipient_key; //GPG接收者密钥
gpgme_key_t signer_key; //GPG签名者密钥

unsigned char verify_gpg_sigs; //是否验证GPG签名
unsigned char ignore_gpg_sig_error; //是否忽略GPG签名错误

fko_gpg_sig_t gpg_sigs; //GPG签名信息

gpgme_error_t gpg_err; //GPG错误信息
/*@}*/
#endif /* HAVE_LIBGPGME */
};

问题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
int
fko_encode_spa_data(fko_ctx_t ctx)
{
int res, offset = 0;
char *tbuf;

#if HAVE_LIBFIU
fiu_return_on("fko_encode_spa_data_init", FKO_ERROR_CTX_NOT_INITIALIZED);
#endif
/* Must be initialized
*/
if(!CTX_INITIALIZED(ctx))
return(FKO_ERROR_CTX_NOT_INITIALIZED);

/* Check prerequisites.
* --DSS XXX: Needs review. Also, we could make this more robust (or
* (at leaset expand the error reporting for the missing
* data).
*/
#if HAVE_LIBFIU
fiu_return_on("fko_encode_spa_data_valid", FKO_ERROR_INCOMPLETE_SPA_DATA);
#endif
if( validate_username(ctx->username) != FKO_SUCCESS
|| ctx->version == NULL || strnlen(ctx->version, MAX_SPA_VERSION_SIZE) == 0
|| ctx->message == NULL || strnlen(ctx->message, MAX_SPA_MESSAGE_SIZE) == 0)
{
return(FKO_ERROR_INCOMPLETE_SPA_DATA);
}

if(ctx->message_type == FKO_NAT_ACCESS_MSG)
{
if(ctx->nat_access == NULL || strnlen(ctx->nat_access, MAX_SPA_MESSAGE_SIZE) == 0)
return(FKO_ERROR_INCOMPLETE_SPA_DATA);
}

#if HAVE_LIBFIU
fiu_return_on("fko_encode_spa_data_calloc", FKO_ERROR_MEMORY_ALLOCATION);
#endif
/* Allocate our initial tmp buffer.
*/
tbuf = calloc(1, FKO_ENCODE_TMP_BUF_SIZE);
if(tbuf == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);

/* Put it together a piece at a time, starting with the rand val.
*/
strlcpy(tbuf, ctx->rand_val, FKO_ENCODE_TMP_BUF_SIZE);

/* Add the base64-encoded username.
*/
/*-----------------------------------------------------------------*/
strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE); //只添加了一个冒号,为什么长度要是1024
if((res = append_b64(tbuf, ctx->username)) != FKO_SUCCESS)
{
free(tbuf);
return(res);
}

/* Add the timestamp.
*/
offset = strlen(tbuf);
snprintf(((char*)tbuf+offset), FKO_ENCODE_TMP_BUF_SIZE - offset,
":%u:", (unsigned int) ctx->timestamp);

/* Add the version string.
*/
strlcat(tbuf, ctx->version, FKO_ENCODE_TMP_BUF_SIZE);

/* Before we add the message type value, we will once again
* check for whether or not a client_timeout was specified
* since the message_type was set. If this is the case, then
* we want to adjust the message_type first. The easy way
* to do this is simply call fko_set_spa_client_timeout and set
* it to its current value. This will force a re-check and
* possible reset of the message type.
*
*/
fko_set_spa_client_timeout(ctx, ctx->client_timeout);

/* Add the message type value.
*/
offset = strlen(tbuf);
snprintf(((char*)tbuf+offset), FKO_ENCODE_TMP_BUF_SIZE - offset,
":%i:", ctx->message_type);

/* Add the base64-encoded SPA message.
*/
if((res = append_b64(tbuf, ctx->message)) != FKO_SUCCESS)
{
free(tbuf);
return(res);
}

/* If a nat_access message was given, add it to the SPA
* message.
*/
if(ctx->nat_access != NULL)
{
strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE);
if((res = append_b64(tbuf, ctx->nat_access)) != FKO_SUCCESS)
{
free(tbuf);
return(res);
}
}

/* If we have a server_auth field set. Add it here.
*
*/
if(ctx->server_auth != NULL)
{
strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE);
if((res = append_b64(tbuf, ctx->server_auth)) != FKO_SUCCESS)
{
free(tbuf);
return(res);
}
}

/* If a client timeout is specified and we are not dealing with a
* SPA command message, add the timeout here.
*/
if(ctx->client_timeout > 0 && ctx->message_type != FKO_COMMAND_MSG)
{
offset = strlen(tbuf);
snprintf(((char*)tbuf+offset), FKO_ENCODE_TMP_BUF_SIZE - offset,
":%i", ctx->client_timeout);
}

/* If encoded_msg is not null, then we assume it needs to
* be freed before re-assignment.
*/
if(ctx->encoded_msg != NULL)
free(ctx->encoded_msg);

/* Copy our encoded data into the context.
*/
ctx->encoded_msg = strdup(tbuf);
free(tbuf);

if(ctx->encoded_msg == NULL)
return(FKO_ERROR_MEMORY_ALLOCATION);

ctx->encoded_msg_len = strnlen(ctx->encoded_msg, MAX_SPA_ENCODED_MSG_SIZE);

if(! is_valid_encoded_msg_len(ctx->encoded_msg_len))
return(FKO_ERROR_INVALID_DATA_ENCODE_MSGLEN_VALIDFAIL);

/* At this point we can compute the digest for this SPA data.
*/
if((res = fko_set_spa_digest(ctx)) != FKO_SUCCESS)
return(res);

/* Here we can clear the modified flags on the SPA data fields.
*/
FKO_CLEAR_SPA_DATA_MODIFIED(ctx);

return(FKO_SUCCESS);
}

问题2

为什么一会用strcat一会用snprintf的方式

1
strlcat(tbuf, ":", FKO_ENCODE_TMP_BUF_SIZE);
1
2
3
4
//获取当前的tbuf的长度
offset = strlen(tbuf);
snprintf(((char*)tbuf+offset), FKO_ENCODE_TMP_BUF_SIZE - offset,
":%u:", (unsigned int) ctx->timestamp);

问题3

这个是什么,为什么几乎每个函数前面都有这个

1
2
3
#if HAVE_LIBFIU
fiu_return_on("fko_set_spa_digest_init", FKO_ERROR_CTX_NOT_INITIALIZED);
#endif