Router (PfSense)¶
Tunables¶
Note: most of these are defaults but I can't remember whats default anymore so here's all of them.
System->Advanced->System Tunables !
net.inet.tcp.recvspace 2097152
net.inet.tcp.sendspace 2097152
net.raw.recvspace 262144
net.inet.raw.recvspace 262144
net.inet.raw.maxdgram 262144
net.raw.sendspace 262144
net.inet.ip.portrange.first 1024
net.inet.tcp.blackhole 2
net.inet.udp.blackhole 1
net.inet.ip.random_id 1
net.inet.tcp.drop_synfin 1
net.inet.ip.redirect 1
net.inet6.ip6.redirect 1
net.inet6.ip6.use_tempaddr 0
net.inet6.ip6.prefer_tempaddr 0
net.inet.tcp.syncookies 1
net.inet.tcp.delayed_ack 0
net.inet.udp.maxdgram 57344
net.link.bridge.pfil_onlyip 0
net.link.bridge.pfil_member 1
net.link.bridge.pfil_bridge 0
net.link.tap.user_open 1
net.link.vlan.mtag_pcp 1
kern.randompid 347
net.inet.ip.intr_queue_maxlen 1000
hw.syscons.kbd_reboot 0
net.inet.tcp.log_debug 0
net.inet.tcp.tso 1
net.inet.icmp.icmplim 0
vfs.read_max 32
kern.ipc.maxsockbuf 4262144
net.inet.ip.process_options 0
kern.random.harvest.mask 351
net.route.netisr_maxqlen 1024
net.inet.udp.checksum 1
net.inet.icmp.reply_from_interface 1
net.inet6.ip6.rfc6204w3 1
net.key.preferred_oldsa 0
net.inet.carp.senderr_demotion_factor 0
net.pfsync.carp_demotion_factor 0
kern.corefile /root/%N.core
General Setup¶
I know its a firewall not a router.
All interfaces have an MTU of 9000. All physical or virtualized adapters need to have this too. Right now I have every network port on the server running through the hypervisor on its own vswitch before it gets to pfsense. This is because I have the horsepower to do it without thinking and it doesn't impact my 10G networking performance appreciably. What I gain is flexibility with my physical port utilization and a degree of laziness. I also have the option to more directly connect a VM to a physical machine by wiring that VM directly to that port group.
- General setup:
- DNS 1.1.1.1 and 1.0.0.1
- theme pfsense-dark
- Interfaces:
- set MTU to 9000 on all.
- disable DHCP on all
- disable ipv6 on all
- add wireguard package
- add pfblockerng package
- IP->CIDR aggregation
- maybe check the latest optional lists to add restart pfsense
Wireguard¶
- Mullvad:
- add tunnel
- add peer
- allowed IPs should be 0.0.0.0/0
- go to system->routing
- apply to the mullvad interface
- add gateway (10.64.0.1 for some reason)
- check "Use non-local gateway through interface specific route."
- set ipv4 upstream gateway in mullvad interface
- restart wireguard service
- verify connection in wireguard->status
- Port Fowarding:
- no port 80
- port 443 TCP for Caddy HTTPS. dest "wan address". specific IP and add host like normal.
- port for the personal wireguard vpn, UDP, wan address
- Outbound NAT:
- manual mode, NEVER change it.
- my typical setup WAS a /26 to split a /24 given to the compute esxi box so some stuff is vpn and others not.
- now everything that needs isolation etc is just on an appropriate subnet/virtual adapter.
- clone the two rules for this /24.
- the /24 rules should go first and should have "interface" adjusted to mullvad's (wireguard tunnel) interface.
- /26 rules are the original rules, just edit the subnet to be /26
- remember to save and apply changes
- Firewall Rules:
- LAN: IPv4, source lan, destination *, default allow lan to any
- NAS: allow nas (source) to talk to stuff (destination)
- MEDIA: (media servers and servarrs): for each VM, allow (source) access to NAS (dest)
- allow /24 * (all) access to mullvad gateway, dest *
- personal WG: ipv4 * allow all.
- LAN (Nomicon)
- My primary LAN interface is used to connect me via my "main terminal" i.e. desktop, to pfsense and the internet.
- NAS
- NAS gets its own interface for security, reliability, and performance.
- MEDIA
- Everything
- VPN
- The interface we have connecting to a VPN provider. i.e. Mullvad
- We give this interface to virtual machines we want to only ever be able to see the internet through our VPN.
- WIREGUARD_CLIENTS
- Same for any new wireguard server instance because each new tunnel creates a new interface.
PfBlockerNG (Adblock)¶
Wireguard Server¶
Make a new tunnel, new interface. set interface to static ipv4 and give it a /24 subnet to control. Once you have your new tunnel, theres a download button in pfsense to download it, then run my little tool here and you should have a client config all ready to go. !
#!/usr/bin/env python3
"""
Generate WireGuard client configs from pfSense server config.
Generates new keypairs for each peer and outputs the public keys for pfSense update.
"""
import subprocess
import sys
def generate_keypair():
"""Generate a new WireGuard private/public keypair."""
try:
# Generate private key
privkey = subprocess.check_output(['wg', 'genkey'], text=True).strip()
# Derive public key
pubkey = subprocess.check_output(
['wg', 'pubkey'],
input=privkey,
text=True
).strip()
return privkey, pubkey
except FileNotFoundError:
print("Error: 'wg' command not found. Install wireguard-tools.", file=sys.stderr)
sys.exit(1)
def parse_server_config(config_path):
"""Parse pfSense WireGuard server config file."""
config = {}
peers = []
current_peer = None
with open(config_path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('[Interface]'):
current_peer = None
elif line.startswith('[Peer]'):
current_peer = {}
peers.append(current_peer)
elif '=' in line:
key, value = [x.strip() for x in line.split('=', 1)]
if current_peer is None:
# Server interface section
config[key] = value
else:
# Peer section
current_peer[key] = value
return config, peers
def generate_client_config(server_config, peer, server_endpoint, new_privkey):
"""Generate client config for a peer."""
# Extract server's public key
server_pubkey = server_config.get('PrivateKey', '')
if server_pubkey:
# Derive public key from server's private key
try:
server_pubkey = subprocess.check_output(
['wg', 'pubkey'],
input=server_pubkey,
text=True
).strip()
except:
server_pubkey = ''
# Get server's listen port
listen_port = server_config.get('ListenPort', '51820')
# Get peer's allowed IPs (this becomes the client's address)
allowed_ips = peer.get('AllowedIPs', '')
# Parse client IP from AllowedIPs (e.g., "10.0.0.2/32" -> "10.0.0.2/32")
client_address = allowed_ips if allowed_ips else '10.0.0.2/24'
# Build client config
config = f"""[Interface]
PrivateKey = {new_privkey}
Address = {client_address}
DNS = 1.1.1.1
[Peer]
PublicKey = {server_pubkey}
Endpoint = {server_endpoint}:{listen_port}
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25"""
return config
def main():
if len(sys.argv) < 3:
print("Usage: python script.py <server_config_file> <server_endpoint_ip>")
print("Example: python script.py wg0.conf 49.253.81.77")
sys.exit(1)
conf_file = sys.argv[1]
server_endpoint = sys.argv[2]
# for when I'm extra lazy
# server_endpoint = "ChangeMeToYourWANIP"
# for root, dirs, files in os.walk(os.getcwd()):
# for f in files:
# if f.endswith('.conf'):
# conf_files.append(os.path.join(root, f))
try:
with open(conf_file, "r"):
pass
except FileNotFoundError:
print(f"Error: Config file not found.", file=sys.stderr)
sys.exit(1)
# Parse server config
server_config, peers = parse_server_config(conf_file)
if not peers:
print("No peers found in config file.", file=sys.stderr)
sys.exit(1)
print(f"Found {len(peers)} peer(s) in config.\n")
# Generate configs for each peer
pfsense_updates = []
for i, peer in enumerate(peers, 1):
# Generate new keypair
new_privkey, new_pubkey = generate_keypair()
# Generate client config
client_config = generate_client_config(
server_config,
peer,
server_endpoint,
new_privkey
)
# Save client config
peer_id = peer.get('AllowedIPs', f'peer{i}').replace('/', '_')
output_file = f"client_{peer_id}.conf"
with open(output_file, 'w') as f:
f.write(client_config)
print(f"Generated: {output_file}")
print("=" * 70)
print(client_config)
print("=" * 70)
print()
# Store for pfSense update instructions
pfsense_updates.append({
'old_pubkey': peer.get('PublicKey', 'N/A'),
'new_pubkey': new_pubkey,
'allowed_ips': peer.get('AllowedIPs', 'N/A')
})
# Print pfSense update instructions
print("\n" + "=" * 70)
print("PFSENSE UPDATE INSTRUCTIONS")
print("=" * 70)
print("\nIn pfSense, go to VPN > WireGuard > Peers and update each peer:\n")
for i, update in enumerate(pfsense_updates, 1):
print(f"Peer {i} (Allowed IPs: {update['allowed_ips']}):")
print(f" Old Public Key: {update['old_pubkey']}")
print(f" New Public Key: {update['new_pubkey']}")
print()
if __name__ == '__main__':
main()