├── Makefile ├── README.md ├── main.c ├── nat_type.c └── nat_type.h /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | 3 | CFLAGS = -g -Wall 4 | 5 | TARGET = nat_type 6 | 7 | all: $(TARGET) 8 | 9 | $(TARGET): *.c 10 | $(CC) $(CFLAGS) -o $(TARGET) *.c 11 | 12 | clean: 13 | $(RM) $(TARGET) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A C program to detect NAT type 2 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "nat_type.h" 6 | 7 | static char* STUN_SERVER = "stun.ideasip.com"; 8 | 9 | int main(int argc, char** argv) 10 | { 11 | char* stun_server = STUN_SERVER; 12 | char* local_host = "0.0.0.0"; 13 | uint16_t stun_port = DEFAULT_STUN_SERVER_PORT; 14 | uint16_t local_port = DEFAULT_LOCAL_PORT; 15 | 16 | static char* usage = "usage: [-h] [-H STUN_HOST] [-P STUN_PORT] [-i SOURCE_IP] [-p SOURCE_PORT]\n"; 17 | int opt; 18 | while ((opt = getopt (argc, argv, "H:h:P:p:i")) != -1) 19 | { 20 | switch (opt) 21 | { 22 | case 'h': 23 | printf("%s", usage); 24 | break; 25 | case 'H': 26 | stun_server = optarg; 27 | break; 28 | case 'P': 29 | stun_port = atoi(optarg); 30 | break; 31 | case 'i': 32 | local_host = optarg; 33 | break; 34 | case 'p': 35 | local_port = atoi(optarg); 36 | break; 37 | case '?': 38 | default: 39 | printf("invalid option: %c\n", opt); 40 | printf("%s", usage); 41 | 42 | return -1; 43 | } 44 | } 45 | 46 | char ext_ip[16] = {0}; 47 | uint16_t ext_port = 0; 48 | 49 | nat_type type = detect_nat_type(stun_server, stun_port, local_host, local_port, ext_ip, &ext_port); 50 | 51 | printf("NAT type: %s\n", get_nat_desc(type)); 52 | if (ext_port) { 53 | printf("external address: %s:%d\n", ext_ip, ext_port); 54 | } else { 55 | return -1; 56 | } 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /nat_type.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "nat_type.h" 13 | 14 | static const char* nat_types[] = { 15 | "blocked", 16 | "open internet", 17 | "full cone", 18 | "restricted NAT", 19 | "port-restricted cone", 20 | "symmetric NAT", 21 | "error" 22 | }; 23 | 24 | char* encode16(char* buf, uint16_t data) { 25 | uint16_t ndata = htons(data); 26 | memcpy(buf, (void*)(&ndata), sizeof(uint16_t)); 27 | return buf + sizeof(uint16_t); 28 | } 29 | 30 | char* encode32(char* buf, uint32_t data) { 31 | uint32_t ndata = htonl(data); 32 | memcpy(buf, (void*)(&ndata), sizeof(uint32_t)); 33 | 34 | return buf + sizeof(uint32_t); 35 | } 36 | 37 | char* encodeAtrUInt32(char* ptr, uint16_t type, uint32_t value) { 38 | ptr = encode16(ptr, type); 39 | ptr = encode16(ptr, 4); 40 | ptr = encode32(ptr, value); 41 | 42 | return ptr; 43 | } 44 | 45 | char* encode(char* buf, const char* data, unsigned int length) { 46 | memcpy(buf, data, length); 47 | return buf + length; 48 | } 49 | 50 | static int stun_parse_atr_addr( char* body, unsigned int hdrLen, StunAtrAddress* result ) { 51 | if (hdrLen == 8 /* ipv4 size */ || hdrLen == 20 /* ipv6 size */ ) { 52 | body++; // Skip pad 53 | result->family = *body++; 54 | 55 | uint16_t nport; 56 | memcpy(&nport, body, 2); 57 | body += 2; 58 | result->port = ntohs(nport); 59 | 60 | if (result->family == IPv4Family) { 61 | uint32_t naddr; 62 | memcpy(&naddr, body, sizeof(uint32_t)); body+=sizeof(uint32_t); 63 | result->addr.ipv4 = ntohl(naddr); 64 | // Note: addr.ipv4 is stored in host byte order 65 | return 0; 66 | } else if (result->family == IPv6Family) { 67 | printf("ipv6 is not implemented yet"); 68 | } 69 | } 70 | 71 | return -1; 72 | } 73 | 74 | static void gen_random_string(char *s, const int len) { 75 | static const char alphanum[] = 76 | "0123456789" 77 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 78 | "abcdefghijklmnopqrstuvwxyz"; 79 | 80 | int i = 0; 81 | for (; i < len; ++i) { 82 | s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; 83 | } 84 | } 85 | 86 | static int send_bind_request(int sock, const char* remote_host, uint16_t remote_port, uint32_t change_flag, StunAtrAddress* addr_array, char* pkt_dst) { 87 | char* buf = malloc(MAX_STUN_MESSAGE_LENGTH); 88 | char* ptr = buf; 89 | 90 | StunHeader h; 91 | h.msgType = BindRequest; 92 | 93 | gen_random_string((char*)&h.magicCookieAndTid, 16); 94 | 95 | ptr = encode16(ptr, h.msgType); 96 | char* lengthp = ptr; 97 | ptr = encode16(ptr, 0); 98 | ptr = encode(ptr, (const char*)&h.id, sizeof(h.id)); 99 | 100 | if (change_flag) { 101 | ptr = encodeAtrUInt32(ptr, ChangeRequest, change_flag); 102 | 103 | // length of stun body 104 | encode16(lengthp, ptr - buf - sizeof(StunHeader)); 105 | } 106 | 107 | struct hostent *server = gethostbyname(remote_host); 108 | if (server == NULL) { 109 | fprintf(stderr, "no such host, %s\n", remote_host); 110 | free(buf); 111 | 112 | return -1; 113 | } 114 | struct sockaddr_in remote_addr; 115 | 116 | remote_addr.sin_family = AF_INET; 117 | memcpy(&remote_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length); 118 | remote_addr.sin_port = htons(remote_port); 119 | 120 | if (-1 == sendto(sock, buf, ptr - buf, 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr))) { 121 | free(buf); 122 | return -1; 123 | } 124 | 125 | struct timeval tv = {5, 0}; 126 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); 127 | #ifdef __linux__ 128 | if (pkt_dst) { 129 | // IP_PKTINFO is Linux-specific, use this flag to get local address from ancillary message 130 | int opt = 1; 131 | setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)); 132 | char cmsg_buf[512]; 133 | struct sockaddr_in dst_addr; 134 | struct iovec iov; 135 | iov.iov_base = buf; 136 | iov.iov_len = 512; 137 | 138 | // msg_iov 139 | struct msghdr mh = { 140 | .msg_name = &dst_addr, 141 | .msg_namelen = sizeof(dst_addr), 142 | .msg_control = cmsg_buf, 143 | .msg_controllen = sizeof(cmsg_buf), 144 | .msg_iov = &iov, 145 | .msg_iovlen = 1, 146 | }; 147 | 148 | if (recvmsg(sock, &mh, 0) <=0) { 149 | free(buf); 150 | return -1; 151 | } 152 | 153 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh); 154 | for (; cmsg != NULL; cmsg = CMSG_NXTHDR(&mh, cmsg)) { 155 | if (cmsg->cmsg_level != IPPROTO_IP || cmsg->cmsg_type != IP_PKTINFO) { 156 | continue; 157 | } 158 | struct in_pktinfo *pi = CMSG_DATA(cmsg); 159 | strcpy(pkt_dst, inet_ntoa(pi->ipi_spec_dst)); 160 | } 161 | } else { 162 | socklen_t fromlen = sizeof remote_addr; 163 | if (recvfrom(sock, buf, 512, 0, (struct sockaddr *)&remote_addr, &fromlen) <= 0) { 164 | free(buf); 165 | return -1; 166 | } 167 | } 168 | #else 169 | socklen_t fromlen = sizeof remote_addr; 170 | if (recvfrom(sock, buf, 512, 0, (struct sockaddr *)&remote_addr, &fromlen) <= 0) { 171 | free(buf); 172 | return -1; 173 | } 174 | 175 | #endif 176 | StunHeader reply_header; 177 | memcpy(&reply_header, buf, sizeof(StunHeader)); 178 | 179 | uint16_t msg_type = ntohs(reply_header.msgType); 180 | 181 | if (msg_type == BindResponse) { 182 | char* body = buf + sizeof(StunHeader); 183 | uint16_t size = ntohs(reply_header.msgLength); 184 | 185 | StunAtrHdr* attr; 186 | unsigned int attrLen; 187 | unsigned int attrLenPad; 188 | int atrType; 189 | 190 | while (size > 0) { 191 | attr = (StunAtrHdr*)(body); 192 | 193 | attrLen = ntohs(attr->length); 194 | // attrLen may not be on 4 byte boundary, in which case we need to pad to 4 bytes when advancing to next attribute 195 | attrLenPad = attrLen % 4 == 0 ? 0 : 4 - (attrLen % 4); 196 | atrType = ntohs(attr->type); 197 | 198 | if ( attrLen + attrLenPad + 4 > size ) { 199 | free(buf); 200 | return -1; 201 | } 202 | 203 | body += 4; // skip the length and type in attribute header 204 | size -= 4; 205 | 206 | switch (atrType) { 207 | case MappedAddress: 208 | if (stun_parse_atr_addr(body, attrLen, addr_array)) { 209 | free(buf); 210 | return -1; 211 | } 212 | break; 213 | case ChangedAddress: 214 | if (stun_parse_atr_addr( body, attrLen, addr_array + 1)) { 215 | free(buf); 216 | return -1; 217 | } 218 | break; 219 | default: 220 | // ignore 221 | break; 222 | } 223 | body += attrLen + attrLenPad; 224 | size -= attrLen + attrLenPad; 225 | } 226 | } 227 | 228 | free(buf); 229 | return 0; 230 | } 231 | 232 | const char* get_nat_desc(nat_type type) { 233 | return nat_types[type]; 234 | } 235 | 236 | nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_host, uint16_t local_port, char* ext_ip, uint16_t* ext_port) { 237 | uint32_t mapped_ip = 0; 238 | uint16_t mapped_port = 0; 239 | int s; 240 | if((s = socket(AF_INET, SOCK_DGRAM, 0)) <= 0) { 241 | return Error; 242 | } 243 | nat_type nat_type; 244 | 245 | struct sockaddr_in local_addr; 246 | local_addr.sin_family = AF_INET; 247 | local_addr.sin_addr.s_addr = inet_addr(local_host); 248 | local_addr.sin_port = htons(local_port); 249 | 250 | if (bind(s, (struct sockaddr *)&local_addr, sizeof(local_addr))) { 251 | if (errno == EADDRINUSE) { 252 | printf("addr in use, try another port\n"); 253 | } 254 | 255 | nat_type = Error; 256 | goto cleanup_sock; 257 | } 258 | 259 | // 0 for mapped addr, 1 for changed addr 260 | StunAtrAddress bind_result[2]; 261 | memset(bind_result, 0, sizeof(StunAtrAddress) * 2); 262 | 263 | char pkt_dst[32] = {0}; 264 | if (send_bind_request(s, stun_host, stun_port, 0, bind_result, pkt_dst)) { 265 | nat_type = Blocked; 266 | goto cleanup_sock; 267 | } 268 | 269 | mapped_ip = bind_result[0].addr.ipv4; // in host byte order 270 | mapped_port = bind_result[0].port; 271 | uint32_t changed_ip = bind_result[1].addr.ipv4; 272 | uint16_t changed_port = bind_result[1].port; 273 | 274 | struct in_addr mapped_addr; 275 | mapped_addr.s_addr = htonl(mapped_ip); 276 | 277 | if (!strcmp(pkt_dst, inet_ntoa(mapped_addr))) { 278 | nat_type = OpenInternet; 279 | goto cleanup_sock; 280 | } else { 281 | if (changed_ip != 0 && changed_port != 0) { 282 | if (send_bind_request(s, stun_host, stun_port, ChangeIpFlag | ChangePortFlag, bind_result, NULL)) { 283 | struct in_addr addr = {htonl(changed_ip)}; 284 | char* alt_host = inet_ntoa(addr); 285 | 286 | memset(bind_result, 0, sizeof(StunAtrAddress) * 2); 287 | 288 | // changed port only 289 | if (send_bind_request(s, alt_host, changed_port, 0, bind_result, NULL)) { 290 | printf("failed to send request to alterative server\n"); 291 | nat_type = Error; 292 | goto cleanup_sock; 293 | } 294 | 295 | if (mapped_ip != bind_result[0].addr.ipv4 || mapped_port != bind_result[0].port) { 296 | nat_type = SymmetricNAT; 297 | goto cleanup_sock; 298 | } 299 | 300 | if (send_bind_request(s, alt_host, changed_port, ChangePortFlag, bind_result, NULL)) { 301 | nat_type = RestricPortNAT; 302 | goto cleanup_sock; 303 | } 304 | 305 | nat_type = RestricNAT; 306 | goto cleanup_sock; 307 | } else { 308 | nat_type = FullCone; 309 | goto cleanup_sock; 310 | } 311 | } else { 312 | printf("no alterative server, can't detect nat type\n"); 313 | nat_type = Error; 314 | goto cleanup_sock; 315 | } 316 | } 317 | cleanup_sock: 318 | close(s); 319 | struct in_addr ext_addr; 320 | ext_addr.s_addr = htonl(mapped_ip); 321 | strcpy(ext_ip, inet_ntoa(ext_addr)); 322 | *ext_port = mapped_port; 323 | 324 | return nat_type; 325 | } 326 | -------------------------------------------------------------------------------- /nat_type.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | Blocked, 5 | OpenInternet, 6 | FullCone, 7 | RestricNAT, 8 | RestricPortNAT, 9 | SymmetricNAT, 10 | Error, 11 | } nat_type; 12 | 13 | #define DEFAULT_STUN_SERVER_PORT 3478 14 | #define DEFAULT_LOCAL_PORT 34780 15 | #define MAX_STUN_MESSAGE_LENGTH 512 16 | 17 | // const static constants cannot be used in case label 18 | #define MappedAddress 0x0001 19 | #define SourceAddress 0x0004 20 | #define ChangedAddress 0x0005 21 | 22 | // define stun constants 23 | const static uint8_t IPv4Family = 0x01; 24 | const static uint8_t IPv6Family = 0x02; 25 | 26 | const static uint32_t ChangeIpFlag = 0x04; 27 | const static uint32_t ChangePortFlag = 0x02; 28 | 29 | const static uint16_t BindRequest = 0x0001; 30 | const static uint16_t BindResponse = 0x0101; 31 | 32 | const static uint16_t ResponseAddress = 0x0002; 33 | const static uint16_t ChangeRequest = 0x0003; /* removed from rfc 5389.*/ 34 | const static uint16_t MessageIntegrity = 0x0008; 35 | const static uint16_t ErrorCode = 0x0009; 36 | const static uint16_t UnknownAttribute = 0x000A; 37 | const static uint16_t XorMappedAddress = 0x0020; 38 | 39 | typedef struct { uint32_t longpart[4]; } UInt128; 40 | typedef struct { uint32_t longpart[3]; } UInt96; 41 | 42 | typedef struct 43 | { 44 | uint32_t magicCookie; // rfc 5389 45 | UInt96 tid; 46 | } Id; 47 | 48 | typedef struct 49 | { 50 | uint16_t msgType; 51 | uint16_t msgLength; // length of stun body 52 | union 53 | { 54 | UInt128 magicCookieAndTid; 55 | Id id; 56 | }; 57 | } StunHeader; 58 | 59 | typedef struct 60 | { 61 | uint16_t type; 62 | uint16_t length; 63 | } StunAtrHdr; 64 | 65 | typedef struct 66 | { 67 | uint8_t family; 68 | uint16_t port; 69 | union 70 | { 71 | uint32_t ipv4; // in host byte order 72 | UInt128 ipv6; // in network byte order 73 | } addr; 74 | } StunAtrAddress; 75 | 76 | nat_type detect_nat_type(const char* stun_host, uint16_t stun_port, const char* local_host, uint16_t local_port, char* ext_ip, uint16_t* ext_port); 77 | 78 | const char* get_nat_desc(nat_type type); 79 | --------------------------------------------------------------------------------