diff --git a/app/factory.py b/app/factory.py
index b608df8a3e76ff011993e76299d806c97ddb37bd..90740127e0c89aab961c465ad13f5f91658f461b 100644
--- a/app/factory.py
+++ b/app/factory.py
@@ -14,7 +14,7 @@ from flask import Flask
 from whitenoise import WhiteNoise
 from . import settings
 from .extensions import db, migrate, login_manager, ldap_manager, bootstrap, admin, mail, jwt, toolbar
-from .models import Action, Manufacturer, Model, Location, Status
+from .models import Action, Manufacturer, Model, Location, Status, Network, Host, Mac
 from .admin.views import AdminModelView, ItemAdmin, UserAdmin, GroupAdmin
 from .main.views import bp as main
 from .users.views import bp as users
@@ -101,6 +101,9 @@ def create_app(config=None):
     admin.add_view(AdminModelView(Location, db.session))
     admin.add_view(AdminModelView(Status, db.session))
     admin.add_view(ItemAdmin(db.session))
+    admin.add_view(AdminModelView(Network, db.session))
+    admin.add_view(AdminModelView(Host, db.session))
+    admin.add_view(AdminModelView(Mac, db.session))
 
     app.register_blueprint(main)
     app.register_blueprint(users)
diff --git a/app/models.py b/app/models.py
index 0d2f7b732106e1b61e08bf26c9457e27e173bdd1..40d439a615c86b218e3e58f19e9e2d63c8c175bf 100644
--- a/app/models.py
+++ b/app/models.py
@@ -9,9 +9,11 @@ This module implements the models used in the app.
 :license: BSD 2-Clause, see LICENSE for more details.
 
 """
+import ipaddress
 import re
 import qrcode
 import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
 from sqlalchemy.orm import validates
 from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy_continuum import make_versioned, version_class
@@ -185,6 +187,7 @@ class Model(QRCodeMixin, db.Model):
 
 class Location(QRCodeMixin, db.Model):
     items = db.relationship('Item', back_populates='location')
+    networks = db.relationship('Network', backref='location')
 
 
 class Status(QRCodeMixin, db.Model):
@@ -213,6 +216,7 @@ class Item(db.Model):
     location = db.relationship('Location', back_populates='items')
     status = db.relationship('Status', back_populates='items')
     children = db.relationship('Item', backref=db.backref('parent', remote_side=[id]))
+    macs = db.relationship('Mac', backref='item')
 
     def __init__(self, ics_id=None, serial_number=None, manufacturer=None, model=None, location=None, status=None):
         # All arguments must be optional for this class to work with flask-admin!
@@ -249,6 +253,7 @@ class Item(db.Model):
         }
         if long:
             d['children'] = [utils.format_field(child) for child in self.children]
+            d['macs'] = [mac.to_dict(long=True) for mac in self.macs]
             d['history'] = self.history()
         return d
 
@@ -272,6 +277,99 @@ class Item(db.Model):
         return versions
 
 
+class Network(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    label = db.Column(db.Text)
+    prefix = db.Column(postgresql.CIDR, nullable=False, unique=True)
+    first = db.Column(postgresql.INET, nullable=False, unique=True)
+    last = db.Column(postgresql.INET, nullable=False, unique=True)
+    gateway = db.Column(postgresql.INET)
+    vlanid = db.Column(db.Integer, unique=True)
+    location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
+
+    hosts = db.relationship('Host', backref='network')
+
+    __table_args__ = (
+        sa.CheckConstraint('first < last', name='first_less_than_last'),
+        sa.CheckConstraint('first << prefix', name='first_in_prefix'),
+        sa.CheckConstraint('last << prefix', name='last_in_prefix'),
+    )
+
+    def __str__(self):
+        return str(self.prefix)
+
+    @validates('hosts')
+    def validate_hosts(self, key, host):
+        """Ensure the host IP is in the network range"""
+        addr = ipaddress.ip_address(host.ip)
+        net = ipaddress.ip_network(self.prefix)
+        if addr not in net:
+            raise utils.CSEntryError(f'IP address shall be in network {self.prefix}', status_code=422)
+        if addr < ipaddress.ip_address(self.first) or addr > ipaddress.ip_address(self.last):
+            raise utils.CSEntryError(f'IP address shall be in range {self.first} - {self.last}', status_code=422)
+        return host
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'label': self.label,
+            'prefix': self.prefix,
+            'first': self.first,
+            'last': self.last,
+            'gateway': self.gateway,
+            'vlanid': self.vlanid,
+            'location': utils.format_field(self.location),
+        }
+
+
+class Host(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    network_id = db.Column(db.Integer, db.ForeignKey('network.id'), nullable=False)
+    ip = db.Column(postgresql.INET, nullable=False, unique=True)
+    name = db.Column(db.Text, unique=True)
+
+    mac = db.relationship('Mac', backref='host')
+
+    def __str__(self):
+        return str(self.ip)
+
+    def to_dict(self, long=False):
+        d = {
+            'id': self.id,
+            'ip': self.ip,
+            'name': self.name,
+            'network_id': self.network_id,
+        }
+        if long:
+            d['mac'] = getattr(self.mac, 'address', None)
+        return d
+
+
+class Mac(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    address = db.Column(postgresql.MACADDR, nullable=False, unique=True)
+    host_id = db.Column(db.Integer, db.ForeignKey('host.id'), unique=True)
+    item_id = db.Column(db.Integer, db.ForeignKey('item.id'), nullable=False)
+
+    def __str__(self):
+        return str(self.address)
+
+    def to_dict(self, long=False):
+        d = {
+            'id': self.id,
+            'address': self.address,
+            'host_id': self.host_id,
+            'item_id': self.item_id,
+        }
+        if long:
+            d['item_ics_id'] = self.item.ics_id
+            try:
+                d['host'] = self.host.to_dict()
+            except AttributeError:
+                d['host'] = None
+        return d
+
+
 # call configure_mappers after defining all the models
 # required by sqlalchemy_continuum
 sa.orm.configure_mappers()