| package main |
| |
| import ( |
| "context" |
| "encoding/hex" |
| "fmt" |
| "io" |
| |
| pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto" |
| "code.hackerspace.pl/hscloud/bgpwtf/cccampix/verifier/model" |
| "github.com/golang/glog" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| ) |
| |
| func (s *service) ProcessorStatus(ctx context.Context, req *pb.ProcessorStatusRequest) (*pb.ProcessorStatusResponse, error) { |
| s.processorsMu.RLock() |
| defer s.processorsMu.RUnlock() |
| |
| res := &pb.ProcessorStatusResponse{ |
| Processors: make([]*pb.ProcessorStatusResponse_Processor, len(s.processors)), |
| } |
| |
| i := 0 |
| for _, p := range s.processors { |
| res.Processors[i] = &pb.ProcessorStatusResponse_Processor{ |
| Name: p.name, |
| Status: pb.ProcessorStatusResponse_Processor_STATUS_OK, |
| LastRun: 0, |
| NextRun: 0, |
| } |
| |
| if p.lastRun != nil { |
| res.Processors[i].LastRun = p.lastRun.UnixNano() |
| res.Processors[i].NextRun = p.p.NextRun(*p.lastRun, p.lastErr != nil).UnixNano() |
| } |
| |
| if p.lastErr != nil { |
| res.Processors[i].Status = pb.ProcessorStatusResponse_Processor_STATUS_ERROR |
| } |
| |
| i += 1 |
| } |
| return res, nil |
| } |
| |
| func (s *service) PeerSummary(req *pb.PeerSummaryRequest, stream pb.Verifier_PeerSummaryServer) error { |
| peers, err := s.model.GetCheckablePeers(stream.Context()) |
| if err != nil { |
| glog.Errorf("model.GetCheckablePeers: %v", err) |
| return status.Error(codes.Unavailable, "model error") |
| } |
| |
| asns := make([]int64, len(peers)) |
| asnToRes := make(map[int64]*pb.PeerSummaryResponse) |
| |
| for i, peer := range peers { |
| routers := make([]*pb.PeeringDBMember_Router, len(peer.Routers)) |
| for i, router := range peer.Routers { |
| routers[i] = &pb.PeeringDBMember_Router{} |
| if router.V4 != nil { |
| routers[i].Ipv4 = router.V4.String() |
| } |
| if router.V6 != nil { |
| routers[i].Ipv6 = router.V6.String() |
| } |
| } |
| p := &pb.PeeringDBMember{ |
| Asn: peer.ASN, |
| Name: peer.Name, |
| Routers: routers, |
| } |
| res := &pb.PeerSummaryResponse{ |
| PeeringdbInfo: p, |
| CheckStatus: pb.PeerSummaryResponse_STATUS_OK, |
| } |
| asnToRes[peer.ASN] = res |
| asns[i] = peer.ASN |
| } |
| |
| checkres, err := s.model.GetPeerCheckResults(stream.Context(), asns) |
| if err != nil { |
| glog.Errorf("GetPeerCheckResults(%v): %v", asns, err) |
| for _, res := range asnToRes { |
| res.CheckStatus = pb.PeerSummaryResponse_STATUS_UNKNOWN |
| } |
| } else { |
| passedChecks := make(map[int64]map[string]bool) |
| for _, c := range checkres { |
| if _, ok := passedChecks[c.PeerASN]; !ok { |
| passedChecks[c.PeerASN] = make(map[string]bool) |
| } |
| passedChecks[c.PeerASN][c.CheckName] = c.Status == model.PeerCheckStatus_Okay |
| } |
| |
| for asn, checks := range passedChecks { |
| for _, required := range s.requiredChecks { |
| if !checks[required] { |
| asnToRes[asn].CheckStatus = pb.PeerSummaryResponse_STATUS_FAILED |
| break |
| } |
| } |
| } |
| } |
| |
| for _, res := range asnToRes { |
| if err := stream.Send(res); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (s *service) PeerDetails(ctx context.Context, req *pb.PeerDetailsRequest) (*pb.PeerDetailsResponse, error) { |
| if req.Asn <= 0 { |
| return nil, status.Error(codes.InvalidArgument, "asn must be set") |
| } |
| |
| res := &pb.PeerDetailsResponse{} |
| |
| peeringdb, err := s.model.GetPeeringDBPeer(ctx, req.Asn) |
| if err != nil { |
| glog.Errorf("GetPeeringDBPeer(%v): %v", req.Asn, err) |
| return nil, status.Error(codes.Unavailable, "could not get allowed prefixes") |
| } |
| |
| if peeringdb.Asn != req.Asn { |
| return nil, status.Error(codes.NotFound, "no such ASN") |
| } |
| |
| res.PeeringdbInfo = peeringdb |
| |
| checkres, err := s.model.GetPeerCheckResults(ctx, []int64{req.Asn}) |
| if err != nil { |
| glog.Errorf("GetPeerCheckResults(%v): %v", req.Asn, err) |
| return nil, status.Error(codes.Unavailable, "could not get check results") |
| } |
| |
| res.Checks = make([]*pb.PeerDetailsResponse_Check, len(checkres)) |
| for i, check := range checkres { |
| status := pb.PeerDetailsResponse_Check_STATUS_INVALID |
| switch check.Status { |
| case model.PeerCheckStatus_Okay: |
| status = pb.PeerDetailsResponse_Check_STATUS_OK |
| case model.PeerCheckStatus_SoftFailed: |
| status = pb.PeerDetailsResponse_Check_STATUS_OK |
| case model.PeerCheckStatus_Failed: |
| status = pb.PeerDetailsResponse_Check_STATUS_FAILED |
| } |
| res.Checks[i] = &pb.PeerDetailsResponse_Check{ |
| Name: check.CheckName, |
| Status: status, |
| Time: check.Time.UnixNano(), |
| Msg: check.Message, |
| } |
| } |
| |
| prefixes, err := s.model.GetAllowedPrefixes(ctx, req.Asn) |
| if err != nil { |
| glog.Errorf("GetAllowedPrefixes(%v): %v", req.Asn, err) |
| return nil, status.Error(codes.Unavailable, "could not get allowed prefixes") |
| } |
| |
| res.AllowedPrefixes = make([]*pb.PeerDetailsResponse_AllowedPrefix, len(prefixes)) |
| for i, prefix := range prefixes { |
| res.AllowedPrefixes[i] = &pb.PeerDetailsResponse_AllowedPrefix{ |
| Prefix: prefix.Prefix.String(), |
| MaxLength: prefix.MaxLength, |
| Ta: prefix.TA, |
| } |
| } |
| |
| return res, nil |
| } |
| |
| func (s *service) RouterHeartbeat(ctx context.Context, req *pb.RouterHeartbeatRequest) (*pb.RouterHeartbeatResponse, error) { |
| if req.Name == "" { |
| return nil, status.Error(codes.InvalidArgument, "name must be set") |
| } |
| |
| pcfgM := make(map[string]*model.PeerConfiguration) |
| pcfgs, err := s.model.GetPeerConfiguration(ctx) |
| if err != nil { |
| glog.Errorf("GetPeerConfiguration: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not get peer configs") |
| } |
| |
| for _, pcfg := range pcfgs { |
| ask := fmt.Sprintf("AS%d", pcfg.Peer.ASN) |
| pcfgM[ask] = pcfg |
| } |
| |
| peers, err := s.model.GetCheckablePeers(ctx) |
| if err != nil { |
| glog.Errorf("GetChecablePeers: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not get peers") |
| } |
| |
| asconfs := make(map[string]*pb.RouterHeartbeatResponse_ASConfig) |
| |
| for _, peer := range peers { |
| as := fmt.Sprintf("AS%d", peer.ASN) |
| |
| pcfg, ok := pcfgM[as] |
| if !ok { |
| continue |
| } |
| |
| asconfs[as] = &pb.RouterHeartbeatResponse_ASConfig{ |
| Asn: peer.ASN, |
| Routers: make([]*pb.RouterHeartbeatResponse_ASConfig_Router, len(pcfg.Peer.Routers)), |
| Prefixes: []*pb.RouterHeartbeatResponse_ASConfig_AllowedPrefix{}, |
| } |
| |
| glog.Infof("%+v", pcfg.Peer.Routers) |
| for i, r := range pcfg.Peer.Routers { |
| ipv6 := "" |
| if r.V6 != nil { |
| ipv6 = r.V6.String() |
| } |
| ipv4 := "" |
| if r.V4 != nil { |
| ipv4 = r.V4.String() |
| } |
| asconfs[as].Routers[i] = &pb.RouterHeartbeatResponse_ASConfig_Router{ |
| Ipv6: ipv6, |
| Ipv4: ipv4, |
| Password: r.Config.BGPSecret, |
| } |
| } |
| |
| prefixes, err := s.model.GetAllowedPrefixes(ctx, peer.ASN) |
| if err != nil { |
| glog.Errorf("GetAllowedPrefixes(_, %d): %v", peer.ASN, err) |
| return nil, status.Error(codes.Unavailable, "could not get peer prefixes") |
| } |
| |
| for _, prefix := range prefixes { |
| asconfs[as].Prefixes = append(asconfs[as].Prefixes, &pb.RouterHeartbeatResponse_ASConfig_AllowedPrefix{ |
| Prefix: prefix.Prefix.String(), |
| MaxLength: prefix.MaxLength, |
| }) |
| } |
| } |
| |
| res := &pb.RouterHeartbeatResponse{ |
| AsConfigs: make([]*pb.RouterHeartbeatResponse_ASConfig, len(asconfs)), |
| } |
| |
| i := 0 |
| for _, asconf := range asconfs { |
| res.AsConfigs[i] = asconf |
| i += 1 |
| } |
| |
| return res, nil |
| } |
| |
| func (s *service) PeerSecrets(ctx context.Context, req *pb.PeerSecretsRequest) (*pb.PeerSecretsResponse, error) { |
| if req.Asn <= 0 { |
| return nil, status.Error(codes.InvalidArgument, "asn must be set") |
| } |
| pcrs, err := s.model.GetPeerConfiguration(ctx) |
| if err != nil { |
| glog.Errorf("GetPeerConfiguration: %v", err) |
| return nil, status.Error(codes.Unavailable, "error when retrieving peer configs") |
| } |
| |
| var pcr *model.PeerConfiguration |
| for _, p := range pcrs { |
| if p.Peer.ASN == req.Asn { |
| pcr = p |
| break |
| } |
| } |
| |
| if pcr == nil { |
| return nil, status.Error(codes.NotFound, "no such ASN") |
| } |
| |
| plain := fmt.Sprintf(` |
| Hello AS %d! |
| |
| Here are your config settings: |
| `, req.Asn) |
| |
| for _, router := range pcr.Peer.Routers { |
| if router.V4 != nil { |
| plain += fmt.Sprintf(` |
| our addresses: 185.236.243.5 (rs1), 185.236.243.6 (rs2), 185.236.243.7 (upstream) |
| our asn: 208521 |
| your address: %s |
| your asn: %d |
| bgp secret: %s |
| `, router.V4.String(), req.Asn, router.Config.BGPSecret) |
| } |
| if router.V6 != nil { |
| plain += fmt.Sprintf(` |
| our addresses: 2a0d:eb02:4242:4242::5 (rs1), 2a0d:eb02:4242:4242::6 (rs2), 2a0d:eb02:4242:4242::7 (upstream) |
| our asn: 208521 |
| your address: %s |
| your asn: %d |
| bgp secret: %s |
| `, router.V6.String(), req.Asn, router.Config.BGPSecret) |
| } |
| } |
| |
| plain += ` |
| Happy exchanging! |
| bgp.wtf (DECT: 4735) |
| ` |
| |
| key, err := s.model.GetPeerPGPKey(ctx, req.Asn) |
| if err != nil { |
| glog.Errorf("GetPeerPGPKey: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not get pgp key") |
| } |
| |
| plainB := []byte(plain) |
| |
| stream, err := s.pgp.Encrypt(ctx) |
| if err != nil { |
| glog.Errorf("Encrypt: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not encrypt") |
| } |
| |
| fingerprint, err := hex.DecodeString(key.Fingerprint) |
| if err != nil { |
| glog.Errorf("Invalid fingerprint %q: %v", key.Fingerprint, err) |
| return nil, status.Error(codes.Unavailable, "could not encrypt") |
| } |
| |
| reqE := &pb.EncryptRequest{ |
| Data: plainB, |
| Info: pb.EncryptRequest_CHUNK_LAST, |
| Fingerprint: fingerprint, |
| } |
| |
| if err := stream.Send(reqE); err != nil { |
| glog.Errorf("Encrypt.Send: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not encrypt") |
| } |
| stream.CloseSend() |
| |
| cipher := []byte{} |
| for { |
| in, err := stream.Recv() |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| glog.Errorf("Encrypt.Recv: %v", err) |
| return nil, status.Error(codes.Unavailable, "could not encrypt") |
| } |
| cipher = append(cipher, in.Data...) |
| } |
| |
| return &pb.PeerSecretsResponse{ |
| PgpData: cipher, |
| }, nil |
| } |