implement basic status checking
diff --git a/state/state.go b/state/state.go
index 5f7cc56..e9ccce6 100644
--- a/state/state.go
+++ b/state/state.go
@@ -1,7 +1,16 @@
 package state
 
 import (
+	"context"
+	"fmt"
+	"sync"
+
+	"google.golang.org/grpc"
+
+	cpb "code.hackerspace.pl/q3k/topo/proto/config"
 	pb "code.hackerspace.pl/q3k/topo/proto/control"
+
+	"code.hackerspace.pl/q3k/hspki"
 )
 
 type SwitchportState struct {
@@ -11,14 +20,57 @@
 type SwitchState struct {
 	Name  string
 	Ports []*SwitchportState
+	Stub  pb.SwitchControlClient
+}
+
+func (s *SwitchState) Fetch(ctx context.Context) error {
+	req := pb.GetPortsRequest{}
+	res, err := s.Stub.GetPorts(ctx, &req)
+	if err != nil {
+		return fmt.Errorf("GetPorts: %v", err)
+	}
+	s.Ports = make([]*SwitchportState, len(res.Ports))
+	for i, port := range res.Ports {
+		s.Ports[i] = &SwitchportState{port}
+	}
+	return nil
 }
 
 type StateManager struct {
 	Switches map[string]*SwitchState
+	Conns    map[string]*grpc.ClientConn
+	Mu       sync.RWMutex
 }
 
 func NewManager() *StateManager {
 	return &StateManager{
 		Switches: make(map[string]*SwitchState),
+		Conns:    make(map[string]*grpc.ClientConn),
 	}
 }
+
+func (s *StateManager) FetchState(ctx context.Context, conf *cpb.Config) error {
+	s.Mu.Lock()
+	defer s.Mu.Unlock()
+	for _, sw := range conf.Switch {
+		conn, ok := s.Conns[sw.ControlAddress]
+		if !ok {
+			var err error
+			conn, err = grpc.Dial(sw.ControlAddress, hspki.WithClientHSPKI())
+			if err != nil {
+				return fmt.Errorf("when connecting to switch %s: %v", sw.Name, err)
+			}
+			s.Conns[sw.ControlAddress] = conn
+		}
+
+		s.Switches[sw.Name] = &SwitchState{
+			Name: sw.Name,
+			Stub: pb.NewSwitchControlClient(conn),
+		}
+		err := s.Switches[sw.Name].Fetch(ctx)
+		if err != nil {
+			return fmt.Errorf("%q.Fetch: %v", sw.Name, err)
+		}
+	}
+	return nil
+}