Skip to content

Commit 9d25438

Browse files
net: Dynamic NAT (WIP)
1 parent 51e6f61 commit 9d25438

7 files changed

Lines changed: 321 additions & 1 deletion

File tree

api/net/nat/napt.hpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// This file is a part of the IncludeOS unikernel - www.includeos.org
2+
//
3+
// Copyright 2017 Oslo and Akershus University College of Applied Sciences
4+
// and Alfred Bratterud
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#pragma once
19+
#ifndef NET_NAT_NAPT_HPP
20+
#define NET_NAT_NAPT_HPP
21+
22+
#include <map>
23+
#include <net/port_util.hpp>
24+
#include <net/inet>
25+
#include <net/tcp/tcp.hpp>
26+
27+
namespace net {
28+
namespace nat {
29+
30+
/**
31+
* @brief Network Address Port Translator
32+
*/
33+
class NAPT {
34+
public:
35+
using Stack = Inet<IP4>;
36+
using Translation_table = std::map<uint16_t, Socket>;
37+
38+
public:
39+
40+
// NAT
41+
IP4::IP_packet_ptr nat(IP4::IP_packet_ptr pkt, const Stack& inet);
42+
43+
// Replace source socket with external (this) address and random port
44+
IP4::IP_packet_ptr snat(IP4::IP_packet_ptr pkt, const Stack& inet);
45+
46+
// Replace source address with external address from table
47+
IP4::IP_packet_ptr dnat(IP4::IP_packet_ptr pkt, const Stack& inet);
48+
49+
void add_entry(uint16_t port, Socket sock)
50+
{
51+
// Bind the port
52+
tcp_ports.bind(port);
53+
// Add the entry
54+
tcp_trans.emplace(port, sock);
55+
56+
//printf("NAT entry: %s => %u\n", sock.to_string().c_str(), port);
57+
}
58+
59+
private:
60+
Port_util tcp_ports;
61+
Port_util udp_ports;
62+
63+
Translation_table tcp_trans;
64+
Translation_table udp_trans;
65+
66+
// Source NAT
67+
void snat(tcp::Packet& pkt, ip4::Addr src_ip);
68+
69+
// Destination NAT
70+
void dnat(tcp::Packet& pkt);
71+
72+
void recalculate_checksum(tcp::Packet& pkt) noexcept
73+
{
74+
pkt.set_checksum(0);
75+
pkt.set_ip_checksum();
76+
pkt.set_checksum(TCP::checksum(pkt));
77+
}
78+
79+
}; // < class NAPT
80+
81+
} // < namespace nat
82+
} // < namespace net
83+
84+
#endif

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ set(OS_OBJECTS
5959
net/http/mime_types.cpp net/http/cookie.cpp net/http/secure_server.cpp
6060
net/http/client_connection.cpp net/http/client.cpp
6161
net/http/server_connection.cpp net/http/server.cpp net/http/response_writer.cpp
62-
net/ws/websocket.cpp
62+
net/ws/websocket.cpp net/nat/napt.cpp
6363
fs/disk.cpp fs/filesystem.cpp fs/dirent.cpp fs/mbr.cpp fs/path.cpp
6464
fs/fat.cpp fs/fat_async.cpp fs/fat_sync.cpp fs/memdisk.cpp
6565
posix/fd.cpp posix/tcp_fd.cpp posix/udp_fd.cpp posix/unistd.cpp posix/fcntl.cpp posix/syslog.cpp

src/net/nat/napt.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// This file is a part of the IncludeOS unikernel - www.includeos.org
2+
//
3+
// Copyright 2017 Oslo and Akershus University College of Applied Sciences
4+
// and Alfred Bratterud
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#include <net/nat/napt.hpp>
19+
20+
namespace net {
21+
namespace nat {
22+
23+
IP4::IP_packet_ptr NAPT::nat(IP4::IP_packet_ptr pkt, const Stack& inet)
24+
{
25+
if(pkt->ip_dst() != inet.ip_addr())
26+
{
27+
pkt = snat(std::move(pkt), inet);
28+
}
29+
else
30+
{
31+
pkt = dnat(std::move(pkt), inet);
32+
}
33+
34+
return pkt;
35+
}
36+
37+
// Replace source socket with external (this) address and random port
38+
IP4::IP_packet_ptr NAPT::snat(IP4::IP_packet_ptr pkt, const Stack& inet)
39+
{
40+
// Early return if the packet is for me
41+
if(UNLIKELY(pkt->ip_dst() == inet.ip_addr()))
42+
return pkt;
43+
44+
if(pkt->ip_protocol() == Protocol::TCP)
45+
{
46+
snat(*static_cast<tcp::Packet*>(pkt.get()), inet.ip_addr());
47+
}
48+
49+
return pkt;
50+
}
51+
52+
// Replace source address with external address from table
53+
IP4::IP_packet_ptr NAPT::dnat(IP4::IP_packet_ptr pkt, const Stack& inet)
54+
{
55+
// Early return if the packet isn't for me
56+
if(UNLIKELY(pkt->ip_dst() != inet.ip_addr()))
57+
return pkt;
58+
59+
if(pkt->ip_protocol() == Protocol::TCP)
60+
{
61+
dnat(*static_cast<tcp::Packet*>(pkt.get()));
62+
}
63+
64+
return pkt;
65+
}
66+
67+
void NAPT::snat(tcp::Packet& pkt, ip4::Addr src_ip)
68+
{
69+
// Get the Socket
70+
Socket socket = pkt.source();
71+
72+
// Is there an entry?
73+
auto it = std::find_if(tcp_trans.begin(), tcp_trans.end(),
74+
[socket] (auto& ent) {
75+
return ent.second == socket;
76+
});
77+
78+
// If there already is an entry
79+
if(it != tcp_trans.end())
80+
{
81+
// Replace the source port with the already translated one
82+
pkt.set_src_port(it->first);
83+
}
84+
// If not
85+
else
86+
{
87+
// Generate a new port
88+
auto port = tcp_ports.get_next_ephemeral();
89+
90+
// Replace the source port
91+
pkt.set_src_port(port);
92+
93+
add_entry(port, socket);
94+
}
95+
96+
// At last, replace the source address
97+
pkt.set_ip_src(src_ip);
98+
99+
printf("SNAT %s => %s\n", socket.to_string().c_str(), pkt.source().to_string().c_str());
100+
101+
// Recalculate checksum
102+
recalculate_checksum(pkt);
103+
}
104+
105+
void NAPT::dnat(tcp::Packet& pkt)
106+
{
107+
auto dst_port = pkt.dst_port();
108+
109+
// Is there an entry?
110+
auto it = tcp_trans.find(dst_port);
111+
112+
// If there already is an entry
113+
if(it != tcp_trans.end())
114+
{
115+
// Get the Socket
116+
auto socket = it->second;
117+
printf("DNAT %s => %s\n", pkt.destination().to_string().c_str(), socket.to_string().c_str());
118+
// Replace the destination port with the original one
119+
pkt.set_destination(socket);
120+
121+
// Recalculate checksum
122+
recalculate_checksum(pkt);
123+
}
124+
}
125+
126+
}
127+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cmake_minimum_required(VERSION 2.8.9)
2+
3+
# IncludeOS install location
4+
if (NOT DEFINED ENV{INCLUDEOS_PREFIX})
5+
set(ENV{INCLUDEOS_PREFIX} /usr/local)
6+
endif()
7+
8+
include($ENV{INCLUDEOS_PREFIX}/includeos/pre.service.cmake)
9+
10+
project(test_nat)
11+
12+
# Human-readable name of your service
13+
set(SERVICE_NAME "IncludeOS NAT test")
14+
15+
# Name of your service binary
16+
set(BINARY "test_nat")
17+
18+
# Maximum memory can be hard-coded into the binary
19+
set(MAX_MEM 128)
20+
21+
# Source files to be linked with OS library parts to form bootable image
22+
set(SOURCES
23+
service.cpp
24+
)
25+
26+
# DRIVERS / PLUGINS:
27+
28+
set(DRIVERS
29+
virtionet
30+
)
31+
32+
# include service build script
33+
include($ENV{INCLUDEOS_PREFIX}/includeos/post.service.cmake)

test/net/integration/nat/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test NAT
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This file is a part of the IncludeOS unikernel - www.includeos.org
2+
//
3+
// Copyright 2016-2017 Oslo and Akershus University College of Applied Sciences
4+
// and Alfred Bratterud
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#include <service>
19+
#include <net/inet4>
20+
#include <net/nat/napt.hpp>
21+
22+
using namespace net;
23+
24+
25+
void ip_forward(Inet<IP4>& stack, IP4::IP_packet_ptr pckt) {
26+
stack.ip_obj().ship(std::move(pckt));
27+
}
28+
29+
void Service::start()
30+
{
31+
auto& router = Inet4::ifconfig<0>(
32+
{ 10, 0, 10, 1 }, { 255, 255, 0, 0 }, { 10, 0, 0, 1 });
33+
34+
auto& laptop1 = Inet4::ifconfig<1>(
35+
{ 10, 0, 10, 43 }, { 255, 255, 255, 0 }, router.ip_addr());
36+
37+
auto& internet_host = Inet4::ifconfig<2>(
38+
{ 10, 0, 1, 44 }, { 255, 255, 255, 0 }, router.ip_addr());
39+
40+
auto& internet_client = Inet4::ifconfig<3>(
41+
{ 10, 0, 1, 45 }, { 255, 255, 255, 0 }, router.ip_addr());
42+
43+
static nat::NAPT nat;
44+
45+
router.prerouting_chain().chain.push_back({nat, &nat::NAPT::nat});
46+
//router.postrouting_chain().chain.push_back({nat, &nat::NAPT::receive});
47+
48+
router.ip_obj().set_packet_forwarding(ip_forward);
49+
50+
internet_host.tcp().listen(80, [](auto conn) {
51+
printf("Internet page received a new connection! (%s)\n", conn->to_string().c_str());
52+
});
53+
54+
laptop1.tcp().connect({ internet_host.ip_addr(), 80 }, [](auto conn) {
55+
printf("Laptop1 connected to internet web page! (%s)\n", conn->to_string().c_str());
56+
});
57+
58+
nat.add_entry(8080, { laptop1.ip_addr(), 80 });
59+
60+
laptop1.tcp().listen(80, [](auto conn) {
61+
printf("Laptop1 received a new connection! (%s)\n", conn->to_string().c_str());
62+
});
63+
64+
internet_client.tcp().connect({ laptop1.ip_addr(), 80 }, [](auto conn) {
65+
printf("Bob (internet) connected to Laptop1! (%s)\n", conn->to_string().c_str());
66+
});
67+
}

test/net/integration/nat/vm.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"image" : "test_dhcp_server.img",
3+
"net" : [{"device" : "virtio", "mac" : "c0:01:0a:00:00:2a"},
4+
{"device" : "virtio", "mac" : "c0:01:0a:00:00:2f"},
5+
{"device" : "virtio", "mac" : "c0:01:0a:00:00:20"},
6+
{"device" : "virtio", "mac" : "c0:01:0a:00:00:23"}],
7+
"mem" : 128
8+
}

0 commit comments

Comments
 (0)